SwitchMote BTN0 & BTN1 ON/OFF at the same times

Started by sparky, October 23, 2017, 11:40:05 AM

sparky

As with the previous version 8.9 I always had to stagger on/off commands by a couple seconds for both btn0 and btn1 to come on;

switchMote0ON_PM : { label:'SwitchMote0 ON at 9:00PM!', icon:'clock', descr:'Turn this switch ON every evening', nextSchedule:function(node) { return exports.timeoutOffset(21,00); }, scheduledExecute:function(node) { sendMessageToNode({nodeId:node._id, action:'BTN0:1'}); } },
  switchMote0OFF_AM : { label:'SwitchMote0 OFF at 6:00AM!', icon:'clock', descr:'Turn this switch OFF every morning', nextSchedule:function(node) { return exports.timeoutOffset(6,00); }, scheduledExecute:function(node) { sendMessageToNode({nodeId:node._id, action:'BTN0:0'}); } },
  switchMote1ON_PM : { label:'SwitchMote1 ON at 9:00:02PM!', icon:'clock', descr:'Turn this switch ON every evening', nextSchedule:function(node) { return exports.timeoutOffset(21,00,02); }, scheduledExecute:function(node) { sendMessageToNode({nodeId:node._id, action:'BTN1:1'}); } },
  switchMote1OFF_AM : { label:'SwitchMote1 OFF at 6:00:02AM!', icon:'clock', descr:'Turn this switch OFF every morning', nextSchedule:function(node) { return exports.timeoutOffset(6,00,02); }, scheduledExecute:function(node) { sendMessageToNode({nodeId:node._id, action:'BTN1:0'}); } },


If the on/off times in the events above were set to the same times it would only be btn1 that would turn on/off. (last one wins rule?)

So with the addition of suncalc in the new version I was not surprised to see the same results. 

Question: Is there a configuration in the rfm69 that needs to be set for both buttons to turn on at the same time?

Thanks!

sparky


Felix

#2
Sorry I saw it when you posted and forgot to reply or set it unread for review.

Currently the default SwitchMote sample sketch is just accepting 1 token at a time. If you need 2 buttons at the same time, an improvement could be done to support multiple tokens, for 2-3 buttons, the commands would fit in 1 packet.

I have 2 SwitchMotes with suncalc events to turn ON/OFF at dusk/dawn and they work just fine, very rarely I see the status not updated for one of them. I wanted to look into it and see where there is a race condition. Maybe the gateway should just implement a messaging queue of some sort, just haven't given it too much thought since I didn't see too much of this. Looks like you have lots more Smotes that do that?

BTW did you try suncalc?

sparky

Yes, I tried the suncalc which led me to try my original event with both on/off times set the same.  Both ways gave me the same results. 

I was having an issue with my BellMote mentioned here; https://lowpowerlab.com/forum/pi-gateway/gateway-v8-10-0-released!/msg21241/#msg21241 but the issues went away after deleting the bin files for it.

I just made a video which shows you better whats happening, see link below.

Here is part of the log that shows the event which looks normal to me;
[10-26-17_10:54:34.699] [LOG]   **** RUNNING SCHEDULED EVENT - nodeId:99 event:thermostatPoll...
[10-26-17_10:54:34.701] [LOG]   **** SCHEDULING EVENT - nodeId:99 event:thermostatPoll to run in ~30s
[10-26-17_10:54:34.727] [LOG]      [99] DB-Updates:1
[10-26-17_10:54:47.867] [LOG]   >: [99] F:7200 TARGET:72 HOLD:OFF TSTATE:OFF FSTATE:AUTO MODE:HEAT
[10-26-17_10:54:47.871] [LOG]   post: /home/pi/gateway/data/db/0099_F.bin[1509029687,72]
[10-26-17_10:54:47.887] [LOG]      [99] DB-Updates:1
[10-26-17_10:55:00.014] [LOG]   **** RUNNING SCHEDULED EVENT - nodeId:5 event:switchMote0ON_PM...
[10-26-17_10:55:00.023] [LOG]   NODEACTION: {"nodeId":5,"action":"BTN0:1"}
[10-26-17_10:55:00.025] [LOG]   **** SCHEDULING EVENT - nodeId:5 event:switchMote0ON_PM to run in ~23h59m
[10-26-17_10:55:00.027] [LOG]   **** RUNNING SCHEDULED EVENT - nodeId:5 event:switchMote1ON_PM...
[10-26-17_10:55:00.030] [LOG]   NODEACTION: {"nodeId":5,"action":"BTN1:1"}
[10-26-17_10:55:00.031] [LOG]   **** SCHEDULING EVENT - nodeId:5 event:switchMote1ON_PM to run in ~23h59m
[10-26-17_10:55:00.050] [LOG]   >: ACK:OK
[10-26-17_10:55:00.059] [LOG]      [5] DB-Updates:1
[10-26-17_10:55:00.067] [LOG]      [5] DB-Updates:1
[10-26-17_10:55:00.180] [LOG]   >: ACK:NOK
[10-26-17_10:55:01.197] [LOG]   >: [5] BTN0:1   [RSSI:-72][ACK-sent]
[10-26-17_10:55:01.205] [LOG]   post: /home/pi/gateway/data/db/0005_RSSI.bin[1509029701,-72]
[10-26-17_10:55:01.233] [LOG]      [5] DB-Updates:1
[10-26-17_10:55:04.706] [LOG]   **** RUNNING SCHEDULED EVENT - nodeId:99 event:thermostatPoll...
[10-26-17_10:55:04.708] [LOG]   **** SCHEDULING EVENT - nodeId:99 event:thermostatPoll to run in ~30s
[1


and here the switch metric located in the userMetrics folder;
// 
var request = require('request');
var config = require('nconf');
var JSON5 = require('json5');
config.argv().file({ file: require('path').resolve(__dirname, 'settings.json5'), format: JSON5 });
var settings = config.get('settings'); //these are local to avoid runtime errors but in events they will reference the global settings declared in gateway.js

exports.metrics = {
  //SwitchMote buttons
  SMB0_OFF : { name:'B0', regexp:/BTN0\:0/i, value:'OFF'},
  SMB0_ON  : { name:'B0', regexp:/BTN0\:1/i, value:'ON'},
  SMB1_OFF : { name:'B1', regexp:/(BTN1|SSR|RLY)\:0/i, value:'OFF', pin:1, graph:1, logValue:0, graphOptions:{ yaxis: {ticks:0, min:0, autoscaleMargin:0.5 }, colors:['#4a0']}},
  SMB1_ON  : { name:'B1', regexp:/(BTN1|SSR|RLY)\:1/i, value:'ON', pin:1, graph:1, logValue:1, graphOptions: { /* already defined above for 'B1', no need to repeat */ }},
  SMB2_OFF : { name:'B2', regexp:/BTN2\:0/i, value:'OFF'},
  SMB2_ON  : { name:'B2', regexp:/BTN2\:1/i, value:'ON'},
};

exports.events = {
  switchMote0ON_PM : { label:'SwitchMote0 ON at 10:45AM!', icon:'clock', descr:'Turn this switch ON every evening', nextSchedule:function(node) { return exports.timeoutOffset(10,55); }, scheduledExecute:function(node) { sendMessageToNode({nodeId:node._id, action:'BTN0:1'}); } },
  switchMote0OFF_AM : { label:'SwitchMote0 OFF at 11:45AM!', icon:'clock', descr:'Turn this switch OFF every morning', nextSchedule:function(node) { return exports.timeoutOffset(11,45); }, scheduledExecute:function(node) { sendMessageToNode({nodeId:node._id, action:'BTN0:0'}); } },
  switchMote1ON_PM : { label:'SwitchMote1 ON at 10:45AM!', icon:'clock', descr:'Turn this switch ON every evening', nextSchedule:function(node) { return exports.timeoutOffset(10,55); }, scheduledExecute:function(node) { sendMessageToNode({nodeId:node._id, action:'BTN1:1'}); } },
  switchMote1OFF_AM : { label:'SwitchMote1 OFF at 11:45AM!', icon:'clock', descr:'Turn this switch OFF every morning', nextSchedule:function(node) { return exports.timeoutOffset(11,45); }, scheduledExecute:function(node) { sendMessageToNode({nodeId:node._id, action:'BTN1:0'}); } },
  
  //switchMoteONBUZZ : { label:'SwitchMote ON Buzzer beep!', icon:'clock', descr:'Buzz gateway when switchmote is ON',  serverExecute:function(node) { if (node.metrics['B1'] && node.metrics['B1'].value == 'ON' && (Date.now() - new Date(node.metrics['B1'].updated).getTime() < 2000)) { setTimeout(function() { sendMessageToGateway('BEEP'); }, 5); } }},
  //motionLightON23 : { label:'Motion: SM23 ON!', icon:'action', descr:'Turn SwitchMote:23 ON when MOTION is detected', serverExecute:function(node) { if (node.metrics['M'] && node.metrics['M'].value == 'MOTION' && (Date.now() - new Date(node.metrics['M'].updated).getTime() < 2000)) { sendMessageToNode({nodeId:23, action:'MOT:1'}); }; } },
};

exports.motes = {
  SwitchMote: {
    label   : 'Light Switch',
    icon : 'icon_switchmote.png',
    controls : { B0 : { states: [{ label:'B0 (off)', action:'BTN0:1', css:'background-color:#FF9B9B;', icon:'power', condition:''+function(node) { return node.metrics['B0'] ? node.metrics['B0'].value == 'OFF' : false; }},  //http://api.jquerymobile.com/icons/
                                { label:'B0 (on)',  action:'BTN0:0', css:'background-color:#9BFFBE;color:#000000', icon:'power', condition:''+function(node) { return node.metrics['B0'] ? node.metrics['B0'].value == 'ON' : false; }}],
                       showCondition:''+function(node) { return (node.metrics && $.inArray('B0', Object.keys(node.metrics))>-1);}},
                B1 : { states: [{ label:'B1 (off)', action:'BTN1:1', css:'background-color:#FF9B9B;', icon:'power', condition:''+function(node) { return node.metrics['B1'] ? node.metrics['B1'].value == 'OFF' : false; }},
                                { label:'B1 (on)',  action:'BTN1:0', css:'background-color:#9BFFBE;color:#000000', icon:'power', condition:''+function(node) { return node.metrics['B1'] ? node.metrics['B1'].value == 'ON' : false; }}]},
                B2 : { states: [{ label:'B2 (off)', action:'BTN2:1', css:'background-color:#FF9B9B;', icon:'power', condition:''+function(node) { return node.metrics['B2'] ? node.metrics['B2'].value == 'OFF' : false; }},
                                { label:'B2 (on)',  action:'BTN2:0', css:'background-color:#9BFFBE;color:#000000', icon:'power', condition:''+function(node) { return node.metrics['B2'] ? node.metrics['B2'].value == 'ON' : false; }}],
                       showCondition:''+function(node) { return (node.metrics && $.inArray('B2', Object.keys(node.metrics))>-1);}},
               },
  },
 }


exports.ONEDAY = 86400000;
exports.isNumeric =  function(n) {
  return !isNaN(parseFloat(n)) && isFinite(n); //http://stackoverflow.com/questions/18082/validate-decimal-numbers-in-javascript-isnumeric/1830844#1830844
}

//extracts the value of a given metric based on the regular expression and any valuation function defined for that metric
exports.determineValue = function(matchingMetric, matchingToken) {
  var actualValueToProcess = matchingToken[1] || matchingToken[0]; //attempt to get first captured group if any, else fall back to entire match
  var result;
  if (matchingMetric.valuation != undefined)
  {
    //console.log('Valuating: ' + actualValueToProcess);
    result = matchingMetric.valuation(actualValueToProcess);
  }
  else result = matchingMetric.value || actualValueToProcess;
  if (exports.isNumeric(result))
    return Number(result);
  else return result;
};

//extracts the value of a given metric based on the regular expression
exports.determineGraphValue = function(matchingMetric, matchingToken) {
  var actualValueToProcess = matchingToken[2] || matchingToken[1] || matchingToken[0]; //attempt to get second captured group if any, else first group if any, else fall back to entire match
  var result;
  if (matchingMetric.valuation != undefined)
  {
    //console.log('Valuating: ' + actualValueToProcess);
    result = matchingMetric.valuation(actualValueToProcess);
  }
  else result = matchingMetric.value || actualValueToProcess;
  if (exports.isNumeric(result))
    return Number(result);
  else return result;
}

//calculates the milliseconds timeout remaining until a given time of the day (if it's 8AM now and time given was 3AM, it will calculate to the next day 3AM)
//offset can be used to add more time to the calculated timeout, for instance to delay by one day: pass offset=86400000
exports.timeoutOffset = function(hour, minute, second, offset) {
  var result = new Date().setHours(hour,minute,second || 0, 0);
  result = result < new Date().getTime() ? (result + exports.ONEDAY) : result;
  result -= new Date().getTime();
  if (exports.isNumeric(offset)) result += offset;
  return result;
}


and here is a link to the video;
https://www.dropbox.com/s/8q20flk3ovvovww/20171026_105335.mp4?dl=0

Thank you!

Felix

#4
So it's almost like the event does run but it won't update the UI?
What happens if you refresh the UI?

The timestamp in the UI remains the same and basically once it "crosses over" the javascript that updates the label will just calculate the absolute value to the time remaining  from NOW to the timestamp (actually elapsed in this case). Normally once the event runs, the gateway should update the UI with a new timestamp (when the event is resheduled next).

sparky

#5
Felix,

Unlike the GarageMote, the SwitchMote doesn't have a refresh.  Are you wanting me to try and refresh the URL?

Thanks

Edit:
Refreshing UI URL doesn't change anything.

Felix

So the colored event timestamp stays the same? And it increases in time rather than decrease?


Felix

This is going to be more tricky to solve, a hand from someone that's javascript fluent would be great :)
Like I said, I am aware of this but I don't really know exactly how to fix it. It's a bit elusive in my mind because nodeJS sends asynchronous requests to the serial port which does send the messages out according to the logs (and the switchMotes turn ON) so I think the race condition is somewhere past that point in the RF layer.

In your case, does it happen all the time or just now and then?
How many separate units do you turn ON at the "same" time? Two?

sparky

Happens all the time if;
       I use suncalc (sunset/sunrise) for both btn0 and btn1.
or    I set events to turn on btn0 and btn1 on/off at the same times.

My SwitchMote is the only node I'm trying to turn 2 events on/off at the same times.

I did try turning other nodes on/off at the same time and got similar results.  Example; BellMote and SwitchMote.

I've been getting around the issue by staggering the events by a couple seconds but I would rather use the new suncalc option for my SwitchMote.  (Great addition Felix)

Felix

I agree it should just work. It might have to require a messaging queue. OR maybe the easier route is just to make SwitchMote accept multiple commands in 1 packet. Is that something you could attempt?

sparky

I've learned a lot since coming to your site but I'm still a noob and would not have a clue where to start, sorry

It's all your fault for coming out with suncalc  ;D  I can see wanting to use that a lot in the future but maybe that won't be possible.