A combination of cascada-mqtt and demiot. This one has the server. Its purpose is to create a general platform for iot on esp8266 over mqtt. It should be self sufficient using sbdev0's node_modules and everything should work. Currently 812K with no react code
[TOC]
[alt m] open in browser when you open the readme. Then, it should autoupdate on a save once you refresh the browser. This is because livereload is on in Windows with the C:/users/tim/appdata/local/temp/ directory added. There is also a Chrome livereload plugin installed. On sublime there is markdown-preview and markdownTOC installed
[alt-c] tools/MarkdownTOC/update
17/10/10
server either
webclient: wss://services.sitebuilt.net/geniot/ws/
main.ino: mqtt_server = "10.0.1.100";
mport = 1883;
or
webclient: wss://sslvh.tm:8443
main.ino: mqtt_server = "sitebuilt.net";
mport = 1883;
Actually the old react code wont run in the new versions so the current react app that uses geniot/esp8266 is in /c/wamp/www/sbdev0/cascada/client/react.Its repository is [email protected]:mckennatim/cascada-webpack.git. and it is online at http://cascada.sitebuilt.net/cascada/public/#/.
geniot seems pretty stable
Maybe try to build 3 simultaneous front-ends: vanilla,react,angular. What should they do?
Each device should have its own front-end: cascada, geniot, hrs, hvac. What so they have in common?
- some kind of routing
- a summary page of srstates
- a detail page for temp_t and timr_t types
- a program page
void Sched::ckRelays(){
//temp relays are checked in readTemps()
if(sr.timr1.state != digitalRead(po.timr1)){
digitalWrite(po.timr1, sr.timr1.state);
}
if(sr.timr2.state != digitalRead(po.timr2)){
digitalWrite(po.timr2, sr.timr2.state);
}
if(sr.timr3.state != digitalRead(po.timr3)){
digitalWrite(po.timr3, sr.timr3.state);
}
}
but relays on CYURDOO2 (cascada) are not working
TODO publish f.tIMElEFT and f.IStIMERoN on change
problem was using sizeof(f.tIMElEFT)
in updTimers() loop. sizeof reports 20 should be 5. Whenever that happens and you have an inaccurate sizeof in a for loop you are gonna just overwrite god knows what. In STATE.h is defined now
#define sizeOf(a) (sizeof(a) / sizeof(a[0]))
Revived a version that compiles. Sets the state on program change. TODO: that turn off alarm thing TODO: holds and boosts What does inserting into an existing program look like?
ALGO: boost has a time+setting and a duration ([12,30,68,66],90)
- calculate the time at the end of boost
- find
cur
for end of boost and replace its time with end boost time- for HOLDS replace current program with hold value [[0,0,56,54]] or [[0,0,1]] and then if nxt==curr don't set timer.
TODO: cmd and set
CYURD001/prg {id:0, insert new timr into existing program or a single event that takes replaces the program [[00,1]]} - changes
cur
settings [13,15,56,54]->[13,15,68,66], program will regain control atnxt
.Do it on the client. CYURD001/set {cREMENT: 6, aUTOMA: 0, fORCErESET: 0} You can't change: tIMElEFT since ending the timer is a function of ckAlarm() and its callbacks. But instead of sendingsrstate
everycREMENT
seconds you could just send thetIMElEFT[6]
array?
TODO: updTimers() Currently main.ino does this...
if(inow-schedcrement > f.cREMENT*1000){
schedcrement = inow;
if(IS_ON > 3){
sched.updateTmrs(tmr, client, po, ste, f);
publishTmr();
}
}
Maybe sched.updateTimrs
is too complicated, I mean, timers going to 0 just loosely coincides with with the Alarm callback anyway. If Sched::setTLeft()
did a bit more, say
if (s.state){ //if relay is on
setTleft(p, cur, nxt, tleft);
f.tIMElEFT[id]=tleft;
f.IStIMERoN = f.IStIMERoN | bit;
}
Then all updateTimers would have to do is deduct cREMENT for any IStIMERoN and set f.tIMElEFT[id]=0 when f.tIMElEFT[id]<=0 and turn off the f.IStIMERoN bit. The loop() check then becomes:
if(inow-schedcrement > f.cREMENT*1000){
schedcrement = inow;
if(f.IStIMERoN > 0){
sched.updTimers();f
sched.pubTleft();f
}
}
void SChed::updTimers(){
for(int i=0;i<sizeof(f.tIMElEFT);i++){
if(f.tIMElEFT[i]>0){
deductCrement(i);
}
}
}
void Sched::deductCrement(id){
int t = tIMElEFT[id];
t = t - f.cREMENT;
if(t<=0){
t=0;
f.IStIMERoN = f.IStIMERoN & (31-2^id) //11011
}
tIMElEFT[id] = t;
}
TODO: some looping mechanism through the set bits
doForSet(f.ckAlarms)
void doForSet(int bitmap) {//29
for (int i=0;i<f.nUMsr){
int bit = 2^i;
int allset = (2^f.nUMsr - 1);
int mask = allset -bit;
if((bitmap & bit) == bit){
if(temp_t){
ckTemp(i);
}
if(timr_t){
ckTimr(i);
}
}
}
}
Can you call access a struct element like this char* na = "temp1"; prg_t = prgs[na]; //same as = prgs.temp1;
Changed the start up procedure to prevent aUTOMA, CKaLARM and HAYsTATEcNG from getting set until the device gets the current time from the server. (PUZZLE is why sched.desiriTime() cant call actTime(), both have to be called separately from req.processInc().) Sending one prog
from client places the device in the correct program for the time and changes the state to reflect that.
Yes there is a way to have an initialized device customized mostly by changes to STATE.h even including arrays of strings that can be shared among classes and files. in STATE.h
struct labels_t {
static const char* scribedTo[];
static const int numcmds;
};
in STATE.cpp
const char* labels_t::scribedTo[] ={"devtime", "cmd", "prg", "req", "set", "progs"};
const int labels_t::numcmds =6;//
in .ino declare as
labels_t la;
and then wherever you want access to la.scribedTo[]
just
#include "STATE.h"
extern labels_t la;
test.ino + config, MQclient and Reqs
Serial.println(ipayload2);
if a external package has a callback signature builtin as a function, you cannot replace it with a class method. That is the case for PubSubClient client(espClient);
and Alarm.alarmOnce(hour(), minute()+1,9,bm2)
example state:
{id:0,darr:[temp,state,hilimit,lolimit]}
{id:2,darr:[state]}
example prg:
{id:0, id:255, ev:2, numdata:2, prg:[[0,0,72,70],[6,39,92,64], [8,45,87,87]]}
{id:2, id:255, ev:2, numdata:1, prg:[[0,0,0],[6,39,1], [8,45,0]]}
Require client to provide current defaults in the form of
{id:0, id:255, ev:3, numdata:2, prg:[[0,0,84,72], [6,39,92,64], [8,45,87,87]]}
{id:2, id:255, ev:3, numdata:1, prg:[[0,0,1], [6,39,1], [8,45,0]]}
void sched::ckAlarms(prgs_t& prgs, flags_t& f, state_t& state) {
if((f.CKaLARM & 4) == 4){
f.CKaLARM=f.CKaLARM & 27; //11011 turnoff CKaLARM for 4
prg_t& p = progs.timr2;
tmr_t& s = state.timr2;
int id =2;
int bit =4
int cur, nxt;
setCur(p.prg, cur, nxt);
int tleft=0;
//for timers
s.state = p[cur][2];
f.ISrELAYoN = f.ISrELAYoN | s.state;
if (s.state){ //if relay is on
setTleft(p, cur, nxt, tleft)
f.IStIMERoN = f.IStIMERoN | bit;
}
f.tIMElEFT[id]=tleft;
int asec = second()+1;
Alarm.alarmOnce(p[nxt][0],p[nxt][1], asec, bm8);
}
}
####### calculating tleft Is it from current time to next program if relay is on? If nxt=0 is it from current time til midnight?
void sched::setTleft(prg_t p, int cur, int nxt, int &tleft){
int hr = hour();
int min - minute();
if(nxt==0){
tleft = (23-hr)*60+(59-min) +1;
}else{
int nxthr = p.prg[nxt][0];
int nxtmin = p.prg[nxt][1];
if(nxtmin < min){//12:25 -> 14:05
nxtmin=nxtmin+60
nxthr--;
}
tleft= (nxthr-hr)*60 + (nxtmin - min)
}
void sched::setCur(prg_t& p, int &cur, int &nxt){
for(int j=0; j<p.ev;j++){
if (hour() == p.prg[j][0]){
if (minute() < p.prg[j][1]){
cur = j-1;
break;
}
}
if (hour() < p.prg[j][0]){
cur= j-1;
break;
}
cur =j;
}
nxt = cur+1;
if (nxt>=p.ev){
nxt=0;
}
}
Should there be defaults or yesterdays? {id:0,def:[hilimit, lolimit]}
, {id:2,[relay]}
? How would it work? Insert
[ [0,0,defhi,deflo], [6,39,92,64], [8,45,87,87], [23,59,defhi,deflo]]
functions used are ckAlarms(), pubFlags(), pubState(), updTimers(), desiriProg(), copyProg(), setCur(), pubPrg()
void sched::ckAlarms(prgs_t& prgs, flags_t& f, state_t& state) {
if((f.ckalarms & 4) == 4){
f.ckalarms=f.ckalarms & 27; //11011 turnoff ckalarms for 4
prg_t p = progs.timr2&;
tmr_t s = state.timr2&;
int id =2;
int bit =4
int arr[p.ev+2][p.numdata+1];
arr[0]=[0,0,s.def[0],s.def[1]];
for (int i=0; i<p.ev;i++){
arr[i+1]=p[i];
}
int cur, nxt;
setCur(arr, cur, nxt);
int tleft=0;
//for timers
s.state = p[cur][2];
if (s.state){ //if relay is on
setTleft(arr, cur, nxt, tleft)
f.IStIMERoN = f.IStIMERoN | bit;
}
f.tIMElEFT[id]=tleft;
int asec = second()+1;
Alarm.alarmOnce(p[nxt][0],p[nxt][1], asec, bm8);
}
}
esp8266 should be listening for
- cmd that change the state
- prg that changes a program
- set that changes machine settings
- req for info on ovstate, srstates, progs
- devtime to update time on device from server
subscriptions are set in MQclient.cpp
...
char req[25];
strcpy(req, cdevid);
strcat(req,"/req");
client.subscribe(cmd);
client.subscribe(devtime);
client.subscribe(progs);//deprecated
client.subscribe(prg);
client.subscribe(req);
req
{\"id\":0, \"req\":"srstates"}
{\"id\":1, \"req\":"progs"}
{\"id\":2, \"req\":"flags"}
esp8266 should be publishing
- srstates state of relays and sensors
- srtate state of one senrel
- progs all programs
- prog of one senrel
- ovstate general setting and state of flags
again, what is the interaction between cmd and prg? [considering timer vs on/off for cascada][OK so what about overrides]
OK so after a prog is writtens to progs, then what? How does it work now.
players
- flags: NEW_ALARM, and IS_ON (applies only to timers)
- methods: deseriProgs, actProgs2 ,bm callbacks, updateTmrs
Every time the programs are changed they are cleared with Alarm.clear().
So every time I change on senrels program, I have to clear the alarms and reset the progs of all the other senrels too.
NEW_ALARM = 31 tells asctProgs2 to intialize all 5 senrels. As each senrel is acted upon NEW_ALARM for that bit gets turned off. If an alarm has been set, its callback resets the NEW_ALARM (and IS_ON) bit so actProg2 can be called again. On every loop NEW_ALARM is checked.
Also on every 5 (tmr.crement) seconds in the loop IS_ON is checked to see if timers are running. If so then updateTmrs is run.
updateTimers is independent of actProgs2 and just takes 5 seconds off each running timr and sets a local var hi or low depending if timr still has time on it. If the relay is differrent from hl then it is switched.
after and anytime update timers is run then tmr is published ??? should IS_ON only be set for timrs or also for temp??? temp is more autonomous and passive. It adds nothing to report that 'yep 4 hrs to go til the tstat is set at 64' every 5 seconds.
Again actProgs run if NEW_ALARM bit flag is set for it and it is turned off right away. Then actProgs2
- checks the current time
- calls resetAlarm(int i, int &cur, int &nxt) with a weird address of int parameter. It sends back the index of which event is is cur in and which event is nxt(or nxt=0 if no more events).
- current settings are updated (things like himit, olimit, relay)
- alarm is set with next values for hr, min and settings
sd
struct flags_t{
bool aUTOMA;
bool fORCErESET;
int cREMENT;
int HAStIMR; //11100(28) 4,8, and 16 have timers not temp
int IStIMERoN;//11100 assume some time left, timers with tleft>0
int HAYpROG;// = senrels with events>1
int HAYpROGcNG;// 11111(31 force report) some prog change int or ext
int HAYsTATEcNG; 11111(31 force report) some state change int or ext
int CKaLARM; 11111 assume alarm is set at start
int ISrELAYoN;// = summary of relay states
int tIMElEFT[6];// =[0,0,56,0,0] timeleft in timrs
};
init {1,0,5,28,28,0,31,0,{0,0,0,0,0}}
time_t before = 0;
time_t schedcrement = 0;
time_t inow;
if (f.CKaLARM>0){
sched.ckAlarms(prgs,state,f); //whatever gets scheduled should publish its update
pubFlags();
pubPrg(f.CKaLARM);
f.CKaLARM=f.CKaLARM & 0; //11110 turnoff CKaLARM for 1
}
inow = millis();
if(inow-schedcrement > f.cREMENT*1000){
schedcrement = inow;
if(f.IStIMERoN > 0){
sched.updTimers();
pubFlags();
}
}
if (f.HAYsTATEcNG>0){
pubState(f.HAYsTATEcNG)
}
struct prg_t{
int id;
AlarmId aid;
int ev;
int numdata;
int prg[6][5];//max 6 events [hr,min,max 3 data]
};
struct prgs_t{
prg_t temp1;
prg_t temp2;
prg_t timr1;
prg_t timr2;
prg_t timr3;
};
prgs_t prgs;
flags_t f;
state_t state;
void initShit(){
prgs = {
{0,255,1,2,{{0,0,74,64}}},
{1,255,1,2,{{0,0,84,64}}},
{2,255,1,1,{{0,0,0}}},
{3,255,1,1,{{0,0,0}}},
{4,255,1,1,{{0,0,0}}}
};
f={1,0,5,28,28,0,31,31,31,0,{0,0,0,0,0}};
state = {{44,0,80,50},{33,0,90,60},{0},{0},{0}};
}
case 2://in prg
Serial.println(ipayload);
sched.deseriProg(prgs,ipayload);
NEW_MAIL =0;
break;
case
deseriProg might change prgs.timr2.prg to {2,255,2,1,{{12,57,1}, 14,20,0}}
and then set f.ckalarms=f.ckalarms | 4
on next loop sched.checkAlarms() get run
void sched::ckAlarms(prgs_t& prgs, flags_t& f) {
if((f.ckalarms & 4) == 4){
f.ckalarms=f.ckalarms & 27; //11011 turnoff ckalarms for 4
int cur, nxt;
setCur(prgs.timr2, cur, nxt);
if ((f.ISrELAYoN & 4) ==4){ //if relay is on
}
}
}
prgs = {
{0,255,0,3,{}},
{1,255,0,3,{}},
{2,255,2,1,{{12,57,1}, 14,20,0}},//2events turn on for some time then off
{3,255,0,1,{}},
{4,255,0,1,{}}};
}
How do you clear just the alarm for the senrel you are (re)programming? Include a AlarmId aid in prgs_t structure, store an AlarmId in progs.tmr1.aid, then free(progs.tmr1.aid) and prgs.tmr1.aid = dtINVALID_ALARM_ID; whenever we set it.
How does it work?
todo: add cmd to report out prog values
had some linking troubles that were actually about neglecting to put Sched::
in front of copyProg()
void Sched::copyProg(prg_t& t, JsonArray& ev){
for(int h=0;h<ev.size();h++){
JsonArray& aprg = ev[h];
aprg.printTo(Serial);
for(int j=0;j<t.numdata+2;j++){
t.prg[h][j] = aprg[j];
Serial.print(t.prg[h][j]);
}
}
}
void Sched::deseriProg(prgs_t& prgs, char* kstr){
StaticJsonBuffer<300> jsonBuffer;
JsonObject& rot = jsonBuffer.parseObject(kstr);
int id = rot["id"];
JsonArray& events = rot["pro"];
switch(id){
case 0:
copyProg(prgs.temp1, events);
break;
case 1:
copyProg(prgs.temp2, events);
break;
default:
Serial.println("in default");
}
}
Don't forget to define the function as sched::the function() in the .cpp file https://latedev.wordpress.com/2014/04/22/common-c-error-messages-2-unresolved-reference/
So there is a data structure for a prog getting sent into the device
{"id": 0, "pro":[[0,0,0,84,64],[6,30,1,84,70]]}
and there is a data structure of all the progs currently in the device
each day there is are new progs sent in
a flag is set if a senrel has a prog
progs comming in are deseralized and sent to the proper struct (cmd and status are doing this like so)
Pkt CYURD002/status {"id":0, "darr":[-196, 1, 1073681984, 1075879113]}
Pkt CYURD002/status {"id":1, "darr":[-196, 0, 90, 60]}
Pkt CYURD002/status {"id":2, "darr":[0]}
Pkt CYURD002/status {"id":3, "darr":[0]}
Pkt CYURD002/status {"id":4, "darr":[1]}
Pkt CYURD002/status {"id":5, "data":1}
Pkt CYURD002/status {"id":6, "data":0}
Pkt CYURD002/status {"id":7, "data":0}
so a prog looks like this
prgs_t prgs;
void initProgs(){
prgs = {
{0,0,3,{{0,0,0,84,64}}},
{0,0,3,{{0,0,0,84,64}}},
{2,0,1,{{0,0,0}}},
{3,0,1,{{0,0,0}}},
{4,0,1,{{0,0,0}}}};
}
struct prg_t{
int id;
int ev;
int numdat;
int[6][5] prg;//max 6 events [hr,min,max 3 data]
};
struct prgs_t{
prg_t temp1;
prg_t temp2;
prg_t timr1;
prg_t timr2;
prg_t timr3;
};
{"id": 0, "pro":[[0,0,0,84,64],[6,30,1,84,70]]}
{"id": 2, "pro":[[0,0,0],[6,30,1]]}
void Sched::deseriProg(prgs_t& prgs, char* kstr){
StaticJsonBuffer<300> jsonBuffer;
JsonObject& rot = jsonBuffer.parseObject(kstr);
int id =99;
id = rot["id"];
JsonArray& events = rot["pro"];
for(int h=0;h<events.size();h++){
JsonArray& aprg = events[h];
aprg.prettyPrintTo(Serial);
for(int j=0;j<prgs.temp1.numdata+2;j++){
}
}
void copyProg(prg_t& t, JsonArray& ev){
for(int h=0;h<ev.size();h++){
for(int j=0;j<t.numdata+2;j++){
JsonArray& aprg = ev[h];
t.prg[h,j] = aprg[j];
}
}
}
bool Sched::deseriProg(prgs_t& prgs, char* kstr){
StaticJsonBuffer<300> jsonBuffer;
JsonObject& rot = jsonBuffer.parseObject(kstr);
int id =99;
id = rot["id"];
JsonArray& events = rot["pro"];
switch(id){
case 0:
copyProgs(prgs.temp1, events);
break;
case 1:
copyProgs(prgs.temp2, events);
break;
case 2:
copyProgs(prgs.timr1, events);
break;
case 3:
copyProgs(prgs.timr2, events);
break;
case 4:
copyProgs(prgs.timr3, events);
break;
}
}
progs affect state and so do cmds parsing that would
- set f.isprog = f.isprog | 8
fix one command
-
a boost is OK, it just fits into the daily program
-
a hold sets a value and wipes out (1 or more) days program - justs sends a default prg
id:0,pro:[[0,0,75,72]]
a hold has to be controlled by the server since every day it gets its prgs from the server. -
bridge on wipes out the program and sets the relay on
id:2,pro:[[0,0,1]]
-
bridge timer on inserts itself into whatever program is running
-
bridge off wipes out he program and sets the relay off forever? for one day? OK so you set a watering schedule and turn on the taps. It runs every day taps on or taps off. But then you turn the taps off and want to just leave the relays on. Any time you go to use the water it goes on. Maybe it should never be off, only timed or on? Off just sets to off till the next program cycle, Off is a kind of unboost til next. If there is no progam, day to day it should remember the last state. So maybe a flag and a memory
{name: 'temp1', id:0, hayprog: 1, yesterday: [84,62]} {id:1, hayprog: 0, yesterday: [0]} {id:2, hayprog: 0, yesterday: [1]} {id:3, hayprog: 1, yesterday: [84,62]} {id:4, hayprog: 1, yesterday: [84,62]} or
[ [1,[84,62]], [0,62,32] [1, [1]]
orstruct overall_t{ bool AUTOMA; bool NEEDS_RESET;
int crement; int isprog = 10110 int isrunning = 00100 int relayis = 00011
int[5] tleft =[0,0,56,0,0]
int[5] pdata =[3,3,1,1,1]
}struct state_t{ temp_t temp1; temp_t temp2; timr_t timr1; timr_t timr2; timr_t timr3;
};
struct temp_t { int temp; bool state; int hilimit; int lolimit; int[2]: yesterday };struct timr_t{ bool state; int[1]: yesterday; };
If there are no programs it should run and be OK. If there are it should deal. any time a program comes in it should be stored, published and actedON.
ActingOn a program sets a timer if need be????
- there is always a 0:0, or is there. How should you know? If there is an array it could have zeros or could be full of crap. perhaps a flag should be set whenever there is a program {1,0,1,1,0}
- you can replace a program or delete it or create one
- it could be a simple timer or a 6 part schedule
- there may not be a next. If there is no next don't do anything
ActOn finds out where in the daily schedule you are(either at the next scheduled time or just jumped in at startup or when a new program is entered), sets the relays and setpoints that would be on then, and sets an alarm that will go off at the next scheduled time
Every time an alarm finishes there is a callback.
Setting a flag that get checked every second causes if progs[8][4][6] what does it do? I don't know what should it do?
- intialize a prog array to zero on startup
sending partial progs doesn't work. Intializing the sched::progs array helped but the prog not sent still has garbaage in minutes and value
12:59
countdown tmr3 is OFF
next countdown timr3 is set for: 0:1074796819->8704
updateTimers changes IS_ON
switch(job){
var newstate = Object.assign({}, this.state)
case "status":
switch(plo.id){
case 3:
console.log('state of relay3: ' + plo.darr[0]);
could be on, off or timed(on)
if on
if timr>0 then timer
else on
else off
- is mirrored on sitebuilt.net /var/www/geniot/server/lib
forever start ./appsServers2start.json
{
//geniot exp:3332, ws:3333, mqtt:1883
"uid": "geniot",
"append": true,
"watch": true,
"script": "index.js",
"sourceDir": "/var/www/geniot/server/lib"
}
logs at tail -f /root/.forever/geniot.log
Just copy lib directory to update