With the help of a couple of Particle community forum topics, I've been able to piece together a simple way to keep state using the bits (0, 1) of an integer value. If you aren't familiar with binary bits in a byte (or an integer), you can read up in the Binary number Wikipedia article. However, I'll do a little explaining here, too! Be prepared, this is a little lengthy.
For example, let's say we have a relay control board that can control 4 power outlets. Ideally, we'd like to keep track of the state of each of those outlets. Fortunately, an outlet is (or should be) either ON or OFF (true or false, 1 or 0). Got that? Now, one way to keep track of those states is to use 4 different variables, one for each state like so:
bool relay1 = true; // On
bool relay2 = false; // Off
bool relay3 = false; // Off
bool relay4 = true; // On
It's pretty simple and intuitive. However, if you want to poll those states using the Spark Cloud, you'd have to expose all 4 variables and poll each one separately.
// https://api.particle.io/v1/your_device_id/relay1
Particle.variable("relay1", &relay1, BOOLEAN);
// https://api.particle.io/v1/your_device_id/relay2
Particle.variable("relay2", &relay2, BOOLEAN);
// https://api.particle.io/v1/your_device_id/relay3
Particle.variable("relay3", &relay3, BOOLEAN);
// https://api.particle.io/v1/your_device_id/relay4
Particle.variable("relay4", &relay4, BOOLEAN);
That's a lot of lines of code (well, not really), but it's a lot of polling. If it takes 1 second to poll each variable, that's 4 seconds. That doesn't sound too long of a time until you realize that it feels like a lot of time to most internet users. Don't believe me? Read this article that Akamai published a few years ago. Yeah, you've lost your users. Ouch.
What if you could send the states for all 4 relays in a single request? Yeah, you could build a string to do that. But, if you can package those states into an integer, you could even send back those states when you call a function declared in Particle.function()
. Now that is fancy!
Confused yet? Don't worry, it's not terrible. Consider your 4 relay states as 1's and 0's. Now, visualize them in a row, side-by-side. We'll use the example above where relays 1 and 4 are on while the other two are off. Just look at them from 1 to 4: 1 0 0 1
. Wait.. That looks like the binary for the number 9! How about 1 and 4 are off and 2 and 3 are on: 0 1 1 0
= 6. No relays on: 0 0 0 0
= 0. All relays on: 1 1 1 1
= 15. All those relay states are stored inside a regular-looking integer that can be polled with Particle.variable()
or returned inParticle.function()
. Keep in mind, though, that binary bits go from right to left. The smallest value is always on the right.
But how do you implement such magic! Here's a sample firmware to illustrate. I have omitted some of the setup for setting pin modes and digital writes to make sure everything is off.
// Some early function definitions. These are what make the magic happen.
#define bitRead(value, bit) (((value) >> (bit)) & 0x01)
#define bitSet(value, bit) ((value) |= (1UL << (bit)))
#define bitClear(value, bit) ((value) &= ~(1UL << (bit)))
#define bitWrite(value, bit, bitvalue) (bitvalue ? bitSet(value, bit) : bitClear(value, bit))
// Variable to hold the state of all 4 relays
// We will assume they are all initially off.
uint8_t relayStates = 0;
void setup() {
// The function that will turn our relays on and off
Particle.function("toggleRelay", toggleRelay);
// Expose the relayStates variable for independent polling without having to call a function
Particle.variable("relayStates", &relayStates, INT);
// Set your pin modes, digital writes, and whatnot here
}
void loop() {
// Intentionally empty
}
void toggleRelay(String command) {
// Clean up our incoming command a little
command.trim();
command.toUpperCase();
// All commands are "RELAY1ON", "RELAY1OFF", etc
// These are set explicitly so there is no doubt as to whether
// a relay will be on or off after you "toggle" it.
// Relay 1 on
if(command.equals("RELAY1ON")) {
bitWrite(relayStates, 0, 1); // Write a binary 1 to the 0th (first) place in the relayStates integer
// Relay 1 off
} else if(command.equals("RELAY1OFF")) {
bitWrite(relayStates, 0, 0); // Write a binary 0 to the 0th (first) place in the relayStates integer
// Relay 2 on
} else if(command.equals("RELAY2ON")) {
bitWrite(relayStates, 1, 1); // Write a binary 1 to the 1st (second) place in the relayStates integer
// Relay 2 off
} else if(command.equals("RELAY2OFF")) {
bitWrite(relayStates, 1, 0); // Write a binary 0 to the 1st (second) place in the relayStates integer
// Repeat the same pattern for relays 3 and 4
// If we did not like the command, return a "-1" indicating failure to the function caller
} else
return -1;
// Now we loop through 4 bits in our integer to read back the states and tell digitalWrite() what to do
// Please note that my relay is a little backwards, so I have to use a "!" before bitRead() to
// set the proper state.
for(uint8_t i=0; i<4; i++)
digitalWrite(i, !bitRead(relayStates, i)); // Read the ith bit
// Once everything is done, return the states of all 4 relays in a single integer!
return relayStates;
}
Wasn't that exciting?!
I know you're thinking "Great, but how the heck do I manipulate it in my browser JavaScript?" We're in luck, because JavaScript has a method that can take that integer and convert it into a string representation of the binary value. Once you make your Cloud call to toggleRelay()
, take the integer returned (we'll say it's inresponse.return_value
) and convert it using (response.return_value).toString(2)
. You can refer to the JavaScript number .toString() documentation for more information.
Here's some sample JavaScript for retrieving and posting those (adapted from my spark-deck-lights repo).
<script type="text/javascript">
var ACCESS_TOKEN = 'FFF00FF00F000000000FF00FF000F00F000000F0';
var CORE_ID = '00FF00000000000000000000';
// Interval between relay state check updates
var getRelayStates_interval = 18000;
// Timer for relay states
var getRelayStates_timer;
// Function to toggle relays via Particle Cloud
function toggleRelay(i) {
// Show a loading spinner
show_load();
// Assume we want to turn it on
var relayState = 'on';
// If it's already on, we will want to turn it off
if(isRelayOn(i)===true)
relayState = 'off';
// Clear the getRelayStates_timer
window.clearTimeout(getRelayStates_timer);
// Make the call to the Particle Cloud
$.post('https://api.particle.io/v1/devices/'+CORE_ID+'/toggleRelay?access_token='+ACCESS_TOKEN, {'args':'relay'+i+relayState}, function(resp) {
// Parse the response
parseStates(resp.return_value);
// Restart the getRelayStates_timer
getRelayStates_timer = window.setTimeout(getRelayStates, getRelayStates_interval);
// Hide the loading spinner
hide_load();
});
}
// Function to parse the integer response from the Spark Cloud into individual relay states
function parseStates(i) {
// Convert the integer into a binary string representation
var states = (i).toString(2);
// Pad leading 0s
while(states.length<4)
states = "0"+states;
// Loop through the relay states
for(var j=0; j<4; j++)
// If the relay is on, make the button green
if(states[j]==1)
$('#outlet'+(j+1)).removeClass('ui-btn-bg-red').addClass('ui-btn-bg-green');
// If the relay is off, make the button red
else
$('#outlet'+(j+1)).removeClass('ui-btn-bg-green').addClass('ui-btn-bg-red');
}
// Get the relay states via the relayStates variable
function getRelayStates() {
// Clear the timer
window.clearTimeout(getRelayStates_timer);
$.get('https://api.particle.io/v1/devices/'+CORE_ID+'/relayStates?access_token='+ACCESS_TOKEN, function(resp) {
// Parse the response
parseStates(resp.result);
// Restart the relay states timer
getRelayStates_timer = window.setTimeout(getRelayStates, getRelayStates_interval);
});
}
// Simple check to see if (we think) the relay is on based on whether the button is green or not
function isRelayOn(i) {
return $('#outlet'+i).hasClass('ui-btn-bg-green');
}
// Get relay states and kick off timers
getRelayStates();
</script>
Now, you can iterate through your string and toggle whatever buttons, icons, etc!
Comments