A Better Arduino MicroFogger Controller
Posted: October 29, 2023 | Categories: Arduino, Internet of Things (IoT)
In my previous post, I demonstrated how to control a Vosentech MicroFogger 5 Pro from an Arduino sketch. The example I provided there was simple and just showed how to control the smoke generator, turning on and off smoke generation for random periods of time.
Note: I published an even better (more better) version of this post's code in A Better Arduino MicroFogger Controller Redux.
That example has an issue, a big one, the sketch can't do anything while the sketch waits to turn off or turn on the smoke. I suggested at the end of the article that you could write the sketch in such a way as to let the sketch do other things. This post describes that alternate version. I'll show the complete code at the end of this article, but you can also access it on GitHub at microfogger-random-smoke-better.
This version of the sketch starts mostly the same as the other version, with a few additions.
#define SMOKE_PIN A0
#define MIN_TIME 1000
#define MAX_TIME 5000
bool isSmoking = false;
int timerDuration = 0;
unsigned long startTime;
void setup() {
// configure the output pin to control the smoke generator
pinMode(SMOKE_PIN, OUTPUT);
// Enable the Arduino device's onboard LED
pinMode(LED_BUILTIN, OUTPUT);
// start making smoke. You can do this later based
// on some other trigger if you want
startSmoke();
}
I added some variables that weren't in the other version:
- The boolean
isSmoking
allows the sketch to understand whether it's generating smoke or waiting before generating smoke again. - The integer
timerDuration
keeps track of how long the sketch generates smoke or waits between smoke generation. It tracks the time in milliseconds (1,000 milliseconds equals one second). Basically, the sketch resets this value with a randomly generated number of milliseconds every time it interacts with the smoke generator. In the previous example, the number of milliseconds was passed to thedelay()
function, so the sketch 'used' it only when starting a wait. In this example, we need to be able to track the wait duration external to any call todelay()
mostly because this version of the sketch doesn't usedelay()
. - The long integer
startTime
tracks the current time, in milliseconds, as tracked by the Arduino. Basically, the Arduino platform has a timer chip and starts counting milliseconds from the moment the Arduino powered on (resetting at about 50 days). This provides a sketch a very rough way to track time and calculate time between events or activities. The sketch uses this to help calculate how long since an event (starting smoke generation or waiting between generation events) started so it can determine, usingtimerDuration
whether it's time to end the event.
During setup()
, the sketch also calls startSmoke()
to begin smoke generation; I'll explain how that works a little later.
The loop()
function in this example changed dramatically. Previously, the loop did all of our smoke generation and waiting to generate smoke again. In this version, the loop simply makes a check to see if its time to end an event and start the next one (checkSmokeTimer
). This gives the sketch time to do other activities, as you can see from the comments in the code:
void loop() {
// ===
// do some stuff
// ===
checkSmokeTimer();
// ===
// do some other stuff
// ===
}
As long as the code the sketch executes before and/or after the timer check doesn't take too long, the sketch will accurately track the time assigned for each smoke event (generating and waiting). If your sketch's other code executes long-running actions or uses delay()
with more than 500 milliseconds or so, then this breaks down. For other long-running events, your sketch must use a similar timer approach (described below) to ensure every part of the sketch gets time to execute and share processor cycles with the rest of the sketch.
Alright, lets get into the meat of the sketch.
The startSmoke()
function turns on the smoke generator and onboard LED just like the previous example (uses the same code), it also sets those three new variables I described at the beginning of the post. It sets the isSmoking
boolean to true
(you know, because we're generating smoke at this time). In the startDelay
function I set it to false
and that's how the sketch knows which 'state' its in.
Next, it assigns the current time to startTime
(remember, in milliseconds) and assigns a random number of milliseconds between the values in MIN_TIME
and MAX_TIME
to timerDuration
. Simple subtraction between the two values performed later tells the sketch whether it's time to stop generating smoke, I'll explain this later.
void startSmoke() {
// turn the smoke machine and onboard LED on
digitalWrite(SMOKE_PIN, HIGH);
digitalWrite(LED_BUILTIN, HIGH);
isSmoking = true;
startTime = millis(); // capture when we started the timer
// figure out how long it generates smoke (randomly)
timerDuration = (int)random(MIN_TIME, MAX_TIME); // milliseconds
}
The startDelay()
function works exactly the same except for two differences:
- It turns smoke generation and the onboard LED off.
- It sets
isSMoking
to false because we're no longer generating smoke
Those two actions puts the sketch in delay mode, waiting for the next opportunity to generate smoke.
void startDelay() {
// turn the smoke machine off and onboard LED, then wait a while
digitalWrite(SMOKE_PIN, LOW);
digitalWrite(LED_BUILTIN, LOW);
isSmoking = false;
startTime = millis(); // capture when we started the timer
// figure out how long it waits between generating smoke (random duration)
timerDuration = (int)random(MIN_TIME, MAX_TIME); // milliseconds
}
Finally, and this is where all the magic happens, the checkSmokeTimer()
manages smoke generation state and kicks off smoke generation or waiting based on that state.
The first thing the sketch does is check to see if the timerDuration
contains a value greater than zero. If for any reason you want to stop this process all together, some other part of the sketch can change the value assigned to timerDuration
to zero or any negative integer and this check for zero causes checkSmokeTimer()
to skip execution all together. To restart the process, make a call to startSmoke()
;
void checkSmokeTimer() {
// Set `timerDuration` to 0 to stop/disable this process
if (timerDuration > 0) {
// check to see if enough time's passed since the timer started
if ((millis() - startTime) > timerDuration) {
// timer expired, so time to switch modes
if (!isSmoking) {
// we're not smoking, so start the smoke
startSmoke();
} else {
// we're smoking, so switch to delay
startDelay();
}
}
}
}
Next, the function subtracts the start time from the current time (millis()-startTime
) and compares it to timerDuration
. If the elapsed time is less than timerDuration
the function exits because there's still time left before changing state. If the value is greater than timerDuration
then the timer expired and the function calls startSmoke()
or startDelay()
depending on whether the sketch is in delay or smoke generation mode.
That's it, that's all there is to switching a sketch from using delay()
to control how long it waits to using a timer loop like this with the ability for other 'stuff' to happen during the wait period.
Here's the complete sketch:
#define SMOKE_PIN A0
#define MIN_TIME 1000
#define MAX_TIME 5000
bool isSmoking = false;
int timerDuration = 0;
unsigned long startTime;
void setup() {
// configure the output pin to control the smoke generator
pinMode(SMOKE_PIN, OUTPUT);
// Enable the Arduino device's onboard LED
pinMode(LED_BUILTIN, OUTPUT);
// start making smoke. You can do this later based
// on some other trigger if you want
startSmoke();
}
void loop() {
// ===
// do some stuff
// ===
checkSmokeTimer();
// ===
// do some other stuff
// ===
}
void startSmoke() {
// turn the smoke machine and onboard LED on
digitalWrite(SMOKE_PIN, HIGH);
digitalWrite(LED_BUILTIN, HIGH);
isSmoking = true;
startTime = millis(); // capture when we started the timer
// figure out how long it generates smoke (randomly)
timerDuration = (int)random(MIN_TIME, MAX_TIME); // milliseconds
}
void startDelay() {
// turn the smoke machine off and onboard LED, then wait a while
digitalWrite(SMOKE_PIN, LOW);
digitalWrite(LED_BUILTIN, LOW);
isSmoking = false;
startTime = millis(); // capture when we started the timer
// figure out how long it waits between generating smoke (random duration)
timerDuration = (int)random(MIN_TIME, MAX_TIME); // milliseconds
}
void checkSmokeTimer() {
// Set `timerDuration` to 0 to stop/disable this process
if (timerDuration > 0) {
// check to see if enough time's passed since the timer started
if ((millis() - startTime) > timerDuration) {
// timer expired, so time to switch modes
if (!isSmoking) {
// we're not smoking, so start the smoke
startSmoke();
} else {
// we're smoking, so switch to delay
startDelay();
}
}
}
}
Don't forget that you can access the full source on GitHub at microfogger-random-smoke-better.
Normally I'd record a video of this in action, but even though the code is dramatically different, the sketch still delivers the exact same functionality. If you want to see the video again though, here it is:
Next Post: A Better Arduino MicroFogger Controller Redux
Previous Post: Controlling the Vosentech MicroFogger From An Arduino
If this content helps you in some way, please consider buying me a coffee.
Header image: Photo by Robin Glauser on Unsplash.