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