Still a fan of an SBC

. Besides fault tolerance, as I previously explained, one of the benefits of having what is essentially a full-fledged modern computer is that you can use a RNG of known high quality like
Mersenne Twister that you can seed with 48 bits of entropy or more (Arduino is limited to 32 bits).
If you're running this project for months on end, the chances of a power outage occurring are high. An SBC will reboot and recover its state. An MCU won't be able to do that. If you go with an MCU, a UPS may be a requirement. If you have to buy one, the MCU approach is going to be
more expensive than an SBC.
On the other hand, if you ignore reliability, you can write a darned simple MCU program. We've given hints how to go about that, but for grins, I went ahead and implemented it using
Wokwi:
diagram.json{
"version": 1,
"author": "sparkydog",
"editor": "wokwi",
"parts": [ { "type": "wokwi-arduino-nano", "id": "nano", "top": 0, "left": 0, "attrs": {} } ],
"connections": [],
"dependencies": {}
}
random-event.ino// How often should an event happen (in seconds)?
const long interval_secs = 30; // 30 * 60 * 60 * 24
// How long should an event last (in seconds / milliseconds)?
const long event_secs = 5;
const long event_millis = 1000 * event_secs;
// How long (max) should each delay() be (in seconds / milliseconds)?
const long delay_secs = 10;
const long delay_millis = 1000 * delay_secs;
// Initial random seed
const unsigned long seed = 0x66554c8e;
#define LED_PIN 13
void setup() {
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, LOW);
randomSeed(seed);
}
void sleep_for(long t_s, long t_ms) {
while (t_s > delay_secs) {
t_s -= delay_secs;
delay(delay_millis);
}
delay((1000 * t_s) + t_ms);
}
void loop() {
long when = random(interval_secs - event_secs - 1);
long when_fine = random(1000);
sleep_for(when, when_fine);
digitalWrite(LED_PIN, HIGH);
delay(event_millis);
digitalWrite(LED_PIN, LOW);
sleep_for(interval_secs - event_secs - when - 1, 1000 - when_fine);
}
This just turns on the on-board LED (which was convenient for simulation), but can be trivially tweaked to twiddle any set of GPIO pins. For testing, the interval in which an event can occur is specified as 30 seconds rather than 30 days, but all the parameters are controlled by constants and are easily tunable. Because 30 days is more milliseconds than fit in a 32-bit number, I pick two random numbers to determine when to fire the event, one that has only seconds granularity, and another to select a specific millisecond. If you don't need sub-second level of randomization, you could simplify things a bit by dropping that code. Due to the same 32-bit limitation, I implement a simple counting loop to delay for the requested interval. For testing, I limit each
delay() to 10s, but for your final application, it might make sense to increase
delay_secs, but it has to be less than ~24 days. (Personally, I would keep it to 1 week or less, but... 🤷) Also note that, assuming the board's oscillator is perfect (it isn't), this is going to systemically drift towards an interval slightly longer than 30 days because no effort is made to compensate for how long it takes to execute any of the code. However, since you indicated that exact accuracy isn't needed, this is probably a non-issue.
You should probably look into how to generate an initial random seed using available hardware. You might also want to "salt" whatever you get with some bits of known quality entropy. On the other hand, if you're only ever going to run this once (or don't mind updating the seed manually every time), you can use a predetermined seed; just be aware that doing so means you'll get the exact same sequence every time you restart the MCU. (The one shown was generated with
dd if=/dev/random bs=4 count=1 | od -tx.)
p.s. Please test this yourself before using! It seems to work, but I only spent about a minute on testing.
p.p.s. I don't know what environment Swake is using; Arduino's
random(), at least the C version, doesn't take floating point numbers...