Skip to content

mckennatim/geniot

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

13 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

geniot

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]

markdown reminders

[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

tags

13_esp8266-hp-pi-sb_client-raw-Hp-Sb

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;

12-cascada-client-react

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

11.1-geniot-complete-esp8266

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);
  }
}

11-geniot-complete

but relays on CYURDOO2 (cascada) are not working

10-reporting-countdown-to-index2

09-now-w-updTimers-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]))

08-almost-wo-updTimers

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 at nxt.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 sending srstate every cREMENT seconds you could just send the tIMElEFT[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;

07-prgs-srstate-interaction

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.

06-static-const-char-scribedTo

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;

05-incrbuild

test.ino + config, MQclient and Reqs

callback limitations

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)

04-ckAlarms-pubProg-in-test.ino

03-refactoring-continued

on the interaction between progs and srstate

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]]} 
option-require prg:[0,0,def[0],def[1]]

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]]} 
ckAlarms()(3)
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;
  }        
}
option-patch in for 00:00

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()

ckAlarms()(2)
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]

02-desiriProgs_copyProg

OK so after a prog is writtens to progs, then what? How does it work now.

current prog operation in general

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.

internals of actProgs2,resetAlarm

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

new version

sd

flags
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}}
loop
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)
}    
init
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}};
}     
processIncoming prg
    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

sched::ckAlarms(prgs_t& prgs, flags_t& f)
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");
  }
}

how to pass a JsonArray& to a function

ans

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/

initial thoughts on design of deseriProg() and copyProg()

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]]}
proof of concept step1
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

01-initial_commit

OK so what about overrides bud

  • 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]]
    or

    struct 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; };

take 1: random thoughts

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  

Server

  • 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

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Packages

No packages published