/* * Modifier pour le projet de serre */ // ads1115b.c read pot on ANC1 // output value in volts exit program. // uses one-shot mode // pull up resistors in module caused problems // used level translator - operated ADS1115 at 5V // by Lewis Loflin lewis@bvu.net // www.bristolwatch.com // http://www.bristolwatch.com/rpi/ads1115.html #include #include #include #include // open #include // open #include #include #include // open #include // read/write usleep #include // exit #include #include #include #include // uint8_t, etc #include // I2C bus definitions #include #include #include #include #include #include #include #include #include "coinapi.h" #include using namespace std; const int NB_SENSEURS=4; // Nombre d'entrées sur le ads1115 const int MAX_TEMPS=20; float readtemp (int fd, unsigned dev) { if (fd == -1) return 0; const float VPS = 4.096 / 32768.0; //volts per step static unsigned tbdev[]={0b01000000,0b01010000,0b01100000,0b01110000}; uint8_t writeBuf[3]; // set config register and start conversion // ANC1 and GND, 4.096v, 128s/s writeBuf[0] = 1; // config register is 1 writeBuf[1] = 0b10000011 | tbdev[dev]; // bit 15-8 0xD3 // bit 15 flag bit for single shot // Bits 14-12 input selection: // 100 ANC0; 101 ANC1; 110 ANC2; 111 ANC3 // Bits 11-9 Amp gain. Default to 010 here 001 P19 // Bit 8 Operational mode of the ADS1115. // 0 : Continuous conversion mode // 1 : Power-down single-shot mode (default) writeBuf[2] = 0b10000101; // bits 7-0 0x85 // Bits 7-5 data rate default to 100 for 128SPS // Bits 4-0 comparator functions see spec sheet. // begin conversion if (write(fd, writeBuf, 3) != 3) { perror("Write to register 1"); exit(-1); } // wait for conversion complete // checking bit 15 do { if (read(fd, writeBuf, 2) != 2) { perror("Read conversion"); exit(-1); } }while ((writeBuf[0] & 0x80) == 0); // read conversion register // write register pointer first uint8_t readBuf[2]; readBuf[0] = 0; // conversion register is 0 if (write(fd, readBuf, 1) != 1) { perror("Write register select"); exit(-1); } // read 2 bytes if (read(fd, readBuf, 2) != 2) { perror("Read conversion"); exit(-1); } // convert display results int16_t val = readBuf[0] << 8 | readBuf[1]; if(val < 0) val = 0; double myfloat = val * VPS; // convert to voltage float res = (3.3/myfloat-1)*12000; const double A= 1.279336835E-3; const double B= 2.045395490E-4; const double C= 2.537322864E-7; float temp = 1.0/(A+B*log(res)+C*powf(log(res),3))-273.15; //printf ("log %lf\n",3600*log(res)); debug_printf("Values: HEX 0x%02x DEC %d reading %4.3f volts. resistance %4.3f ohms temp %4.3f\n", val, val, myfloat,res,temp); return temp; } /* The resolution of the ADC in single ended mode we have 15 bit rather than 16 bit resolution, the 16th bit being the sign of the differential reading. */ static int ads_open() { // open device on /dev/i2c-1 // the default on Raspberry Pi B int fd; if ((fd = open("/dev/i2c-1", O_RDWR)) < 0) { tlmp_error ("Error: Couldn't open ADC device! %d (%s)\n", fd,strerror(errno)); }else{ // Note PCF8591 defaults to 0x48! int ads_address = 0x48; // connect to ads1115 as i2c slave if (ioctl(fd, I2C_SLAVE, ads_address) < 0) { tlmp_error ("Error: Couldn't find device on address!\n"); close (fd); fd = -1; } } return fd; } #include "pid.h" static int ads_gettemps(const char *port, vector &temps) { glocal temps; ("unix:",port,5); send ("gettemps\n"); glocal.temps.push_back(RECORD(line)); return temps.size() > 0 ? 0 : -1; } static int ads_savetemp(const vector &temps, const char *tempfile) { glocal temps; int ret = (tempfile,false); for (auto &t:glocal.temps){ fprintf (fout,"%lu",t.date); for (auto &v:t.temp) fprintf (fout," %lf",v); fputs ("\n",fout); } return 0; return ret; } // Ouvre le port de communication avec arduino static int ads_openarduino() { int fd = open ("/dev/ttyUSB0",O_RDWR|O_NOCTTY); if (fd == -1){ tlmp_error ("Ne peut ouvrir le fichier /dev/ttyUSB0 pour arduino (%s)\n",strerror(errno)); }else{ struct termios options; if (tcgetattr(fd,&options) < 0) { tlmp_error ("ioctl TCGETS2\n"); close (fd); fd = -1; }else{ cfsetispeed(&options, B9600); cfsetospeed(&options, B9600); // Enable the receiver and set local mode... options.c_cflag |= (CLOCAL | CREAD); options.c_cflag &= ~CSIZE; /* Mask the character size bits */ options.c_cflag |= CS8; /* Select 8 data bits */ options.c_cflag &= ~PARENB; options.c_cflag &= ~CSTOPB; options.c_cflag &= ~CRTSCTS; options.c_lflag &= ~(ICANON | ECHO | ECHOE | ECHONL | ISIG); options.c_oflag &= ~OPOST; options.c_iflag = 0; tcsetattr(fd, TCSANOW, &options); sleep(2); } } return fd; } static string ads_formatdate (time_t t) { struct tm *tt = localtime(&t); return string_f ("%04d/%02d/%02d-%02d:%02d:%02d" ,tt->tm_year+1900,tt->tm_mon+1,tt->tm_mday,tt->tm_hour,tt->tm_min,tt->tm_sec); } static string ads_getnow() { time_t t = time(nullptr); return ads_formatdate(t); } void showdate() { printf ("%s\n",ads_getnow().c_str()); } static vector ads_gettemp(int fd, const vector &arduinovals, const vector &espvals) { vector vals; for (unsigned dev=0; dev < NB_SENSEURS; dev++){ double temp = readtemp (fd,dev); vals.push_back(temp); } for (auto a:arduinovals) vals.push_back(a); for (auto a:espvals) vals.push_back(a); return vals; } static unsigned keeplogs = 20; // conserve 20 lignes /* Execute une commande et retourne la valeur produite */ static int ads_exec (vector &log, const char *command, PARAM_STRING opt) { glocal command; glocal opt; glocal int ret = -1; (command,opt,10); glocal.ret = atoi(line); return 0; while (log.size() > keeplogs) log.erase(log.begin()); log.push_back(string_f("%s: %s %s -> %d",ads_getnow().c_str(),command,opt.ptr,glocal.ret)); ("/var/log/adsexec.log",true); fprintf (fout,"%s ret=%d %s %s\n",ads_getnow().c_str(),glocal.ret,glocal.command,glocal.opt.ptr); return 0; return glocal.ret; } static vector esp_read (PARAM_STRING addr, PARAM_STRING cmd) { glocal int ret = -1; glocal vector tb; glocal cmd; (addr,"23",3); sendf ("%s\n",glocal.cmd.ptr); tlmp_error ("Delai écoulé pour parler au esp %s\n",info.host); end = true; if (strcmp(line,"fin")==0){ glocal.ret = 0; end = true; }else if (strncmp(line,"temp=",5)==0){ glocal.tb.push_back(atof(line+5)); } if (glocal.ret == -1) tlmp_error ("Communication incomplète\n"); return glocal.tb; } static const char *modenonactif = "Mode surveillance non activé"; static int ads_send2server (const char *port, PARAM_STRING line) { glocal line; glocal int ret = 0; ("unix:",port,5); sendf ("%s\n",glocal.line.ptr); tlmp_error ("ads_send2server: ne peut connecter au serveur %s port %s\n",info.host,info.port); glocal.ret = -1; printf ("%s\n",line); return glocal.ret; } static bool ads_gpio_getstate(int pin) { glocal bool actif = false; (string_f("/sys/class/gpio/gpio%d/value",pin),true); if (is_eq(line,"0")) glocal.actif = true; return 0; return glocal.actif; } static void ads_gpio_setstate (int pin, int value) { glocal pin; glocal value; string vfile = string_f("/sys/class/gpio/gpio%d/value",pin); if (!file_exist(vfile.c_str())){ tlmp_warning ("Programme le gpio %d\n",pin); ("/sys/class/gpio/export",false); fprintf (fout,"%d\n",glocal.pin); return 0; (string_f("/sys/class/gpio/gpio%d/direction",pin),false); fprintf (fout,"out\n"); return 0; } tlmp_warning ("Configure le gpio %d à %d\n",pin,value); (vfile,false); fprintf (fout,"%d",glocal.value); return 0; } /* Tâches diverses activées par la tampérature. Cette fonction est spécifique à chaque serre et devrait être soit dans un module chargé dynamiquement ou simplement dans un autre fichier C++. Mais comme on a juste une serre.... Pour l'instant, cela sert à démarrer le ventilateur l'été quand il fait trop chaud et ça démarre la lumière d'appoint l'hiver (même prise de courant) */ static struct { // Heure de démarrage et fin de l'éclairage d'appoint int debut = 16; int fin = 21; }lumiere; static void ads_tasks_init() { ("/etc/serres/adstasks.conf",true); // Le fichier est optionel if (!is_any_of(line[0],'\0','#')){ int debut,fin; if (splitline(line,match("lumiere"),limits(debut,0,24),limits(fin,0,24))){ if (debut < fin){ lumiere.debut = debut; lumiere.fin = fin; }else{ tlmp_error ("ads_tasks_init: lumiere: début(%d) doit être plus petit que fin(%d)\n",debut,fin); } }else{ tlmp_error ("ads_tasks_init: ligne invalide: %s\n",line); } } return 0; } static string ads_tasks_status() { return string_f("debut=%d fin=%d",lumiere.debut,lumiere.fin); } static void ads_tasks(const vector &temps, const vector &monitors) { { const int lumio = 15; bool actif = ads_gpio_getstate(lumio); time_t t = time(nullptr); struct tm *tt = localtime(&t); bool exist = file_exist("/tmp/lumiere"); if (exist || (tt->tm_hour >= lumiere.debut && tt->tm_hour < lumiere.fin)){ if (!actif) ads_gpio_setstate(lumio,0); }else{ if (actif) ads_gpio_setstate(lumio,1); } } if (monitors.size() == 1){ const int ventio = 14; bool actif = ads_gpio_getstate(ventio); auto monitor = monitors[0]; if ((unsigned)monitor >= temps.size()){ static bool done = false; if (!done){ done = true; tlmp_error ("ads_task: index de température invalide. monitor=%d temps.size()=%zu\n",monitor,temps.size()); } }else{ auto temp = temps[monitor]; //tlmp_warning ("monitor %d = %lf actif = %d\n",monitor,temp,actif); if (actif && temp < 18){ ads_gpio_setstate (ventio,1); }else if (!actif && temp > 23){ ads_gpio_setstate (ventio,0); } } }else if (monitors.size() > 1){ static bool done = false; if (!done){ done = true; tlmp_error ("ads_task: nombre invalide de températures à valider -> %zu\n",monitors.size()); } } } static int ads_main (int argc, char *argv[]) { glocal int ret = -1; glocal bool scan = false; glocal bool html = false; glocal bool server = false; glocal const char *tcpport = "43"; glocal const char *port = "/var/run/ads.sock"; glocal bool daemon = false; glocal const char *user = "root"; glocal const char *pidfile = "/var/run/ads.pid"; glocal bool gettemp = false; glocal bool checklowtemp = false; glocal bool gettemps = false; glocal bool quit = false; glocal bool status = false; glocal const char *tempfile = "/var/log/adstemp.save"; glocal unsigned keeptemps = 60*24*3; // On garde 4 jours de températures glocal vector mesures; glocal const char *title = "Senseurs température"; glocal bool svg = false; glocal bool save = false; glocal int monitor = -1; glocal int opertemp = 50; glocal int lowtemp = 35; glocal bool pidcontrol = false; glocal double targettemp = 53; // Température cible pour le mode PID glocal double pidparmprop = -0.5; glocal double pidparmdiff = 1; glocal double pidparmintg = -0.1; glocal unsigned minercuttemp = 0; glocal unsigned setminercuttemp = 0; glocal vector probtemps = {32,35}; glocal const char *command = nullptr; glocal const char *arduinocmd = "readTTTTTT"; // Commande envoyée au Arduino glocal vector esps; // Cartes esp32 a lire glocal vector espcmds; // Commandes a envoyer aux esp32 (readTT ...) glocal const char *settemps = nullptr; glocal const char *setpidparms = nullptr; glocal const char *setpidcontrol = nullptr; glocal const char *minerfile = "/etc/serres/mineurs.lst"; glocal const char *offlinefile = "/etc/serres/offline.lst"; glocal bool noads = false; glocal bool evalpid = false; glocal bool lock = false; glocal bool unlock = false; glocal bool piddoit = false; glocal bool reconarduino = false; glocal vector taskmonitors; for (unsigned i=0; i(argc,argv); setproginfo ("ads","0.0","Lit la tempéture avec une carte ads1115a\n"); setarg ('s',"scan","Lit la température sur les 4 entrées",glocal.scan,false); setarg ('H',"html","Affiche la température en HTML",glocal.html,false); setarg ('G',"graph","Produit un graphique SVG",glocal.svg,false); setarg ('S',"save","Sauve les mesures dans un fichier",glocal.save,false); setgrouparg ("Options serveur"); setarg (' ',"server","Démarre en mode serveur",glocal.server,false); setarg (' ' ,"port","Unix socket du serveur",glocal.port,false); setarg (' ',"daemon","Exécute en arrière plan",glocal.daemon,false); setarg (' ',"pidfile","Fichier PID",glocal.pidfile,false); setarg (' ',"tempfile","Fichier contenant les dernieres températures",glocal.tempfile,false); setarg (' ',"keeptemps","Conserver ce nombre de températures en mémoire",glocal.keeptemps,false); setarg (' ',"arduinocmd","Commande de lecture envoyée au Arduino aux 5 secondes (doit commencer par 'read')",glocal.arduinocmd,false); setarg (' ',"esp32","Liste des cartes esp32 a interroger",glocal.esps,false); setarg (' ',"espcmd","Liste des commandes a transmettre aux esp32",glocal.espcmds,false); setarg (' ',"minerfile","Fichier contenant la liste des mineurs",glocal.minerfile,false); setarg (' ',"offlinefile","Fichier contenant la liste des mineurs hors service",glocal.offlinefile,false); setarg (' ',"tcpport","Écoute sur le port TCP",glocal.tcpport,false); setarg (' ',"noads","Fonctionne même s'il n'y a pas de carte ads1115",glocal.noads,false); setgrouparg("Surveillance"); setarg (' ',"monitor","Numéro de la sonde de température à surveiller",glocal.monitor,false); setarg (' ',"opertemp","Température à laquelle tout devrait fonctionner",glocal.opertemp,false); setarg (' ',"lowtemp","Température trop basse",glocal.lowtemp,false); setarg (' ',"probtemp","Températures qui déclenche des actions",glocal.probtemps,false); setarg (' ',"command","Commande à exécuter pour corriger une situation",glocal.command,false); setarg (' ',"keeplogs","Conserve N lignes de journal",keeplogs,false); setarg (' ',"minercuttemp","Température maximum des mineurs qui déclenche un arrêt (0 = désactivé)",glocal.minercuttemp,false); setarg (' ',"taskmonitor","Index des températures à surveiller pour les tâches extra",glocal.taskmonitors,false); setgrouparg("Contrôle de la température PID"); setarg (' ',"pidcontrol","Utilise l'algorithme PID pour controler les mineurs",glocal.pidcontrol,false); setarg (' ',"targettemp","Température cible",glocal.targettemp,false); setarg (' ',"pidparmprop","Paramètre pour la partie proportionnelle",glocal.pidparmprop,false); setarg (' ',"pidparmintg","Paramètre pour la partie intégrale",glocal.pidparmintg,false); setarg (' ',"pidparmdiff","Paramètre pour la partie différentielle",glocal.pidparmdiff,false); setgrouparg("Options client"); setarg ('g',"gettemp","Obtient la température courante",glocal.gettemp,false); setarg (' ',"gettemps","Obtient les températures",glocal.gettemps,false); setarg (' ',"quit","Demande au serveur de terminer",glocal.quit,false); setarg (' ',"status","Obtient le statut du serveur",glocal.status,false); setarg (' ',"setmonitortemps","Change les températures de contrôle",glocal.settemps,false); setarg (' ',"setminercuttemp","Configure la température maximum des mineurs (coupure)",glocal.setminercuttemp,false); setarg (' ',"setpidparms","Configure les paramètre PID (targettemp pidprop pidintg piddiff)",glocal.setpidparms,false); setarg (' ',"setpidcontrol","Configure le mode d'opération PID (0 ou 1)",glocal.setpidcontrol,false); setarg (' ',"checklowtemp","Vérifie si la température surveillée est trop basse",glocal.checklowtemp,false); setarg (' ',"evalpid","Exécute la fonction PID et affiche le résultat",glocal.evalpid,false); setarg (' ',"lock","Empêche le contrôle des mineurs pour une minute",glocal.lock,false); setarg (' ',"unlock","Permet à nouveau le contrôle des mineurs",glocal.unlock,false); setarg (' ',"pid_doit","Force l'évaluation de l'algorithme PID",glocal.piddoit,false); setarg (' ',"reconarduino","Force une reconnexion du module Arduino",glocal.reconarduino,false); setgrouparg ("Options HTML"); setarg (' ',"nom_temp","Nom d'une mesure",glocal.mesures,false); setarg (' ',"titre","Titre de la page",glocal.title,false); if (glocal.daemon){ syslog (LOG_ERR,"%s",msg); }else{ fprintf (stderr,"%s",msg); } int ret = -1; while (glocal.mesures.size() < MAX_TEMPS){ glocal.mesures.push_back(string_f("Température dev=%zu",glocal.mesures.size())); } if (glocal.scan){ int fd = ads_open(); if (fd != -1){ for (unsigned dev=0; dev < NB_SENSEURS; dev++){ double temp = readtemp (fd,dev); printf ("%s %lf\n",glocal.mesures[dev].c_str(),temp); } ret = 0; close (fd); } }else if (glocal.server){ glocal int fd = glocal.noads ? -1 : ads_open(); ads_tasks_init(); if (glocal.fd != -1 || glocal.noads){ glocal time_t lockuntil = (time_t)0; glocal set tcpclients; glocal int fdarduino = -1; glocal time_t arduinostarted = time(nullptr); // Début de la communication avec le arduino glocal long arduinorecon = 0; // Nombre de reconnexions glocal long arduinonbcom = 0; // Nombre de communications réussies glocal bool arduinoidle = true; // En train de recevoir glocal bool arduinoonce = false; // On a reçu un premier jeu de donnée au complet // du arduino glocal vector arduinovals; glocal vector espvals; glocal unsigned arduinocount = 0; // Position d'écriture dans arduinovals glocal unsigned record_count=0; // Sauve les données de temps en temps glocal vector temps; glocal vector monitorlog; glocal vector minermaxtemps; glocal unsigned mineurs_actif=0; // Nombre de mineurs disponible glocal unsigned mineurs_running=0; // Nombre de mineurs en exécution glocal time_t lastpidchange = time(nullptr); // Dernière fois qu'on a fait un changement PID glocal string pidaction; // Message expliquant le changement glocal function&)> fchecklowtemp; glocal function fstatus; glocal function&, _F_TCPSERVER_V1 *)> fpid; glocal function freconarduino; glocal TCPSERVER_V1 *o = nullptr; // On initialise ce pointeur plus tard. glocal.fchecklowtemp = [monitor=glocal.monitor,lowtemp=glocal.lowtemp](_F_TCPSERVER_V1 *c, const vector &temps){ if (monitor >= (int)temps.size()){ tlmp_error ("La température %u à surveiller n'est pas disponible\n",monitor); c->sendf ("unknown\n"); }else{ double curtemp = temps[monitor]; double diff = curtemp - lowtemp; if (curtemp < lowtemp){ c->sendf ("low %6.2lf\n",diff); }else{ c->sendf ("ok %6.2lf\n",diff); } } }; glocal.fstatus = [&](_F_TCPSERVER_V1 *c){ c->sendf ("Température maximum (coupure) des mineurs: %s\n" ,glocal.minercuttemp==0 ? modenonactif : string_f("%u",glocal.minercuttemp).c_str()); if (glocal.monitor != -1){ c->sendf("Évolution température par minute: %lf\n",ads_diff (glocal.temps,glocal.monitor,glocal.targettemp)); } c->sendf ("Statut des mineurs: actif=%u en exécution=%u\n",glocal.mineurs_actif,glocal.mineurs_running); string temps; for (auto &mx:glocal.minermaxtemps) temps += string_f(" %s: %.2lf",mx.miner.c_str(),mx.maxtemp); c->sendf ("température courante maximum des mineurs: %s\n",temps.c_str()); }; // Calcul le facteur PID // Produit du debug optionnellement si le paramètre c est non null glocal.fpid = [&](const vector &temps, _F_TCPSERVER_V1 *c){ double curtemp = temps[glocal.monitor]; double prop = curtemp - glocal.targettemp; double intg = ads_intg (glocal.temps,glocal.monitor,glocal.targettemp); double diff = ads_diff (glocal.temps,glocal.monitor,glocal.targettemp); double res = glocal.pidparmprop*prop + glocal.pidparmintg*intg + glocal.pidparmdiff*diff; if (c != nullptr) c->sendf ("currtemp=%5.2f prop=%5.2f intg=%5.2f diff=%5.2f\n",curtemp,prop,intg,diff); return res; }; // Connecte ou reconnecte le arduino glocal.freconarduino = [&](){ if (glocal.fdarduino != -1){ glocal.o->closeclient (glocal.fdarduino); sleep(2); // Cela devrait permettre au arduino de faire son reset } glocal.fdarduino = ads_openarduino(); if (glocal.fdarduino != -1) glocal.o->inject(glocal.fdarduino); glocal.arduinorecon++; }; miner_getmaxtemp (glocal.minerfile,glocal.offlinefile,glocal.minermaxtemps,glocal.mineurs_actif,glocal.mineurs_running); (glocal.tempfile,true); while (glocal.temps.size() >= glocal.keeptemps) glocal.temps.erase(glocal.temps.begin()); glocal.temps.push_back(RECORD (line)); return 0; // On ajoute des valeurs 0 pour les échantillons qui manquent if (glocal.temps.size() > 0){ time_t now_60 = time(nullptr)-60; RECORD rec0; int nb = NB_SENSEURS + 5; // 5 pour arduino for (int i=0; i= glocal.keeptemps) glocal.temps.erase(glocal.temps.begin()); rec0.date = t.date+60; glocal.temps.push_back(rec0); }else{ break; } } } (string_f("unix:%s",glocal.port),5); if (strcmp(info.port,glocal.tcpport)==0){ glocal.tcpclients.insert(no); } glocal.tcpclients.erase(no); string command; double value1,value2,value3,value4; unsigned value; if (no == glocal.fdarduino){ if (strcmp(line,"fin")==0){ glocal.arduinoonce = true; glocal.arduinoidle = true; glocal.arduinonbcom++; }else{ const char *pt = strchr(line,'='); if (pt == nullptr){ tlmp_error ("Réponse invalide du arduino\n"); }else{ while (glocal.arduinovals.size() <= glocal.arduinocount) glocal.arduinovals.push_back(0); glocal.arduinovals[glocal.arduinocount] = atof(pt+1); glocal.arduinocount++; } } }else if (glocal.tcpclients.count(no) > 0){ auto tb = str_splitline(line); if (tb.size()==1 && tb[0] == "gettemp"){ auto temps = ads_gettemp(glocal.fd,glocal.arduinovals,glocal.espvals); for (auto t:temps) sendf ("%5.2lf\n",t); }else if (tb.size()==1 && tb[0] == "checklowtemp"){ auto temps = ads_gettemp(glocal.fd,glocal.arduinovals,glocal.espvals); glocal.fchecklowtemp(this,temps); }else if (tb.size()==1 && tb[0] == "status"){ sendf ("nombre de clients tcp: %zu\n",glocal.tcpclients.size()); glocal.fstatus(this); }else if (tb.size()==1 && tb[0] == "gettemps"){ for (auto &t:glocal.temps){ sendf ("%lu ",t.date); for (auto &v:t.temp) sendf (" %5.2f",v); send ("\n"); } }else{ send ("Commande invalide\n"); } send ("fin\n"); endclient = true; }else if (is_eq(line,"gettemp")){ auto temps = ads_gettemp(glocal.fd,glocal.arduinovals,glocal.espvals); for (auto t:temps) sendf ("%5.2lf\n",t); endclient = true; }else if (is_eq(line,"checklowtemp")){ auto temps = ads_gettemp(glocal.fd,glocal.arduinovals,glocal.espvals); glocal.fchecklowtemp(this,temps); endclient = true; }else if (is_eq(line,"gettemps")){ for (auto &t:glocal.temps){ sendf ("%lu ",t.date); for (auto &v:t.temp) sendf (" %5.2f",v); send ("\n"); } endclient = true; }else if (is_eq(line,"quit")){ ads_savetemp(glocal.temps,glocal.tempfile); sendf ("Le serveur termine, %zu températures sauvées dans le fichier %s\n",glocal.temps.size(),glocal.tempfile); endserver = true; }else if (is_eq(line,"status")){ sendf ("lock: %s\n",glocal.lockuntil != 0 ? "actif" : "non actif"); sendf ("tempfile=%s\nkeeptemps=%u\n%zu températures accumulées\n" ,glocal.tempfile,glocal.keeptemps,glocal.temps.size()); double curtemp = 0; if (glocal.fdarduino == -1 || glocal.arduinoonce){ auto temps = ads_gettemp(glocal.fd,glocal.arduinovals,glocal.espvals); if ((int)temps.size() > glocal.monitor) curtemp = temps[glocal.monitor]; } sendf ("arduino %s nbcom %lu last=%ld count=%u recon=%ld\n",glocal.fdarduino != -1 ? "actif" : "inactif",glocal.arduinonbcom ,time(nullptr)-glocal.arduinostarted,glocal.arduinocount,glocal.arduinorecon); if (glocal.monitor == -1){ sendf ("%s\n",modenonactif); }else{ sendf ("Température surveillée=%d (%5.2lf)\n",glocal.monitor,curtemp); // Gestion de la température par PID sendf ("pidcontrol %s\n",glocal.pidcontrol ? "actif" : "inactif"); sendf ("Température cible=%lf\n",glocal.targettemp); sendf ("Paramètres PID: Prop=%lf Intg=%lf Diff=%lf\n",glocal.pidparmprop,glocal.pidparmintg,glocal.pidparmdiff); sendf ("lastpidchange: %s\n",ads_formatdate(glocal.lastpidchange).c_str()); sendf ("pidaction=%s\n",glocal.pidaction.c_str()); // Gestion de la température de base sendf ("Température normale < %u\n",glocal.opertemp); send ("Températures problème:"); for (auto t:glocal.probtemps) sendf (" %u",t); send ("\n"); sendf ("Température trop basse < %u\n",glocal.lowtemp); } sendf ("paramètres de tâches: %s\n",ads_tasks_status().c_str()); sendf ("Commande de contrôle=%s\n",glocal.command); glocal.fstatus(this); for (auto &l:glocal.monitorlog) sendf ("log: %s\n",l.c_str()); endclient = true; }else if (is_eq(line,"save")){ int ret = ads_savetemp(glocal.temps,glocal.tempfile); if (ret == -1){ sendf ("ERREUR: écriture dans le fichier %s (%s)\n",glocal.tempfile,strerror(errno)); }else{ sendf ("%zu mesures sauvées dans le fichier %s\n",glocal.temps.size(),glocal.tempfile); } endclient = true; }else if (is_start_any_of(line,NONEED,"setmonitortemps ")){ auto tb = str_splitline(line+16); if (tb.size() >= 2){ glocal.opertemp = atoi(tb[0].c_str()); glocal.probtemps.clear(); for (unsigned i=1; i glocal.o = &o; if (strncmp(glocal.arduinocmd,"read",4)==0){ glocal.freconarduino(); } if (o.is_ok()){ int tcphandle = o.listen(glocal.tcpport); if (tcphandle == -1){ tlmp_error ("Ne peut écouter sur le port %s (%s)\n",glocal.tcpport,strerror(errno)); exit (-1); } chmod (glocal.port,0666); if (glocal.daemon) daemon_init (glocal.pidfile,glocal.user); (5); // We receive this message every 5 seconds // We retrieve info from the arduino and the esp32s each time. // We retrieve info from sensors every minute // And we save every 10 minutes time_t now = time(nullptr); if (glocal.fdarduino != -1){ if (now-glocal.arduinostarted > 10) glocal.freconarduino(); if (glocal.arduinoidle){ // Unlike the ads1115, talking to the arduino takes time // so we do it only here. write (glocal.fdarduino,glocal.arduinocmd,strlen(glocal.arduinocmd)); write (glocal.fdarduino,"\n",1); glocal.arduinoidle = false; glocal.arduinocount = 0; glocal.arduinostarted = now; } if (now - glocal.arduinostarted > 20){ tlmp_error ("Arduino ne répond plus\n"); } } glocal.espvals.clear(); for (unsigned i=0; i 10*12){ glocal.record_count=1; // Do a save once in a while. int ret = ads_savetemp(glocal.temps,glocal.tempfile); if (ret == -1){ tlmp_error ("Erreur d'écriture dans le fichier %s (%s)\n",glocal.tempfile,strerror(errno)); } } if (glocal.record_count % 2 == 0){ // On obtient la température maximum des mineurs à toutes 10 secondes miner_getmaxtemp (glocal.minerfile,glocal.offlinefile,glocal.minermaxtemps,glocal.mineurs_actif,glocal.mineurs_running); if (glocal.minercuttemp > 0){ bool reload = false; for (auto &mx:glocal.minermaxtemps){ if (mx.maxtemp > glocal.minercuttemp){ // On éteint ce mineur ads_exec(glocal.monitorlog,glocal.command,string_f("stop %s",mx.miner.c_str())); reload=true; } } if (reload){ miner_getmaxtemp (glocal.minerfile,glocal.offlinefile,glocal.minermaxtemps,glocal.mineurs_actif,glocal.mineurs_running); } } } if (glocal.record_count % 12 == 0){ // Nous ajoutons un échantillon au tableau des températures à toutes les minutes while (glocal.temps.size() >= glocal.keeptemps) glocal.temps.erase(glocal.temps.begin()); RECORD temp; temp.date = time(nullptr); for (unsigned i=0; i glocal.lockuntil && glocal.monitor != -1 && arduino_ok){ glocal.lockuntil = (time_t)0; if (glocal.monitor >= (int)temps.size()){ tlmp_error ("La température %d à surveiller n'est pas disponible\n",glocal.monitor); }else if (glocal.pidcontrol){ // algorithme de type PID. On essai de viser une température cible (glocal.targettemp); double res = glocal.fpid(temps,nullptr); time_t now = time(nullptr); // On fait une correction à toutes les minutes si res dépasse 0.5 if (fabsl(res) > 0.5 && (now - glocal.lastpidchange) > 60){ unsigned nbrunning = glocal.mineurs_running; if (res < 0 && nbrunning > 0){ nbrunning--; }else if (res > 0 && nbrunning < glocal.mineurs_actif){ nbrunning++; } if (nbrunning != glocal.mineurs_running){ glocal.pidaction = string_f("nbrunning: %u -> %u res=%5.2lf",glocal.mineurs_running,nbrunning,res); glocal.lastpidchange = now; ads_exec (glocal.monitorlog,glocal.command,string_f("%d",nbrunning)); glocal.mineurs_running = nbrunning; } } }else{ // Algorithme primitif pour controler les mineurs // On part tous les mineurs lorsque la température descend sous glocal.opertemp // On arrête un mineur pour chaque température dans probtemps qu'on dépasse. double curtemp = temps[glocal.monitor]; if (curtemp <= glocal.opertemp){ if (glocal.mineurs_running != glocal.mineurs_actif){ ads_exec (glocal.monitorlog,glocal.command,string_f("%u",glocal.mineurs_actif)); glocal.mineurs_running = glocal.mineurs_actif; } }else{ // On enlève un mineurs à chaque fois // qu'on dépasse une température problème unsigned cut = glocal.mineurs_actif; for (auto p:glocal.probtemps){ if (cut == 0) break; if (curtemp >= p){ cut--; }else{ break; } } if (cut < glocal.mineurs_running){ ads_exec (glocal.monitorlog,glocal.command,string_f("%d",cut)); glocal.mineurs_running = cut; } } } } netevent_loop (o,idle); ret = 0; } if (glocal.fd != -1) close (glocal.fd); } }else if (glocal.save){ ret = ads_send2server (glocal.port,"save"); }else if (glocal.gettemp){ ret = ads_send2server (glocal.port,"gettemp"); }else if (glocal.checklowtemp){ ret = ads_send2server (glocal.port,"checklowtemp"); }else if (glocal.gettemps){ vector temps; if (ads_gettemps(glocal.port,temps)!=-1){ for (auto &t:temps){ struct tm *tt = localtime(&t.date); printf ("%04d/%02d/%02d-%02d:%02d:%02d" ,tt->tm_year+1900,tt->tm_mon+1,tt->tm_mday,tt->tm_hour,tt->tm_min,tt->tm_sec); for (auto &v:t.temp) printf (" %5.2lf",v); printf ("\n"); } } }else if (glocal.quit){ ret = ads_send2server (glocal.port,"quit"); }else if (glocal.status){ ret = ads_send2server (glocal.port,"status"); }else if (glocal.settemps != nullptr){ ret = ads_send2server (glocal.port,string_f("setmonitortemps %s",glocal.settemps)); }else if (glocal.setpidparms != nullptr){ ret = ads_send2server (glocal.port,string_f("setpidparms %s",glocal.setpidparms)); }else if (glocal.setpidcontrol != nullptr){ ret = ads_send2server (glocal.port,string_f("setpidcontrol %s",glocal.setpidcontrol)); }else if (glocal.setminercuttemp > 0){ ret = ads_send2server (glocal.port,string_f("setminercuttemp %u\n",glocal.setminercuttemp)); }else if (glocal.evalpid){ ret = ads_send2server (glocal.port,"evalpid\n"); }else if (glocal.lock){ ret = ads_send2server (glocal.port,"lock\n"); }else if (glocal.unlock){ ret = ads_send2server (glocal.port,"unlock\n"); }else if (glocal.piddoit){ ret = ads_send2server (glocal.port,"piddoit\n"); }else if (glocal.reconarduino){ ret = ads_send2server (glocal.port,"reconarduino\n"); }else if (glocal.html){ printf ("\n"); printf ("\n"); printf ("%s\n",glocal.title); printf ("\n"); printf ("\n"); printf ("\n"); printf ("Maison\n"); printf ("
\n"); printf ("

%s


\n",glocal.title); showdate(); printf ("

\n"); printf ("\n"); glocal int dev = 0; ("unix:",glocal.port,5); send ("gettemp\n"); auto &nom = glocal.mesures[glocal.dev]; if (nom != "skip"){ printf ("
%s   %s\n",nom.c_str(),line); } glocal.dev++; printf ("
\n"); printf ("

\n"); printf ("\n"); printf (" temps; if (ads_gettemps(glocal.port,temps)!=-1){ printf ("\n"); printf ("\n"); printf ("%s\n",glocal.title); printf ("\n"); printf ("\n"); printf ("Maison\n"); printf ("
\n"); printf ("

%s


\n",glocal.title); showdate(); printf ("

\n"); double mintemp = -10; double maxtemp = 30; unsigned nbvalues = 0; bool valids[MAX_TEMPS]; for (auto &v:valids) v = true; unsigned color_index[MAX_TEMPS]; unsigned color_pick = 0; for (unsigned i=0; i tt) mintemp = tt; if (maxtemp < tt) maxtemp = tt; if (i >= nbvalues) nbvalues = i+1; } } } // La lumière est présentée de 0 à 25 if (mintemp > 0 ) mintemp = 0; if (maxtemp < 25) maxtemp = 25; double difftemp = maxtemp - mintemp; const unsigned height=500; const unsigned width=1500; const unsigned offx = 50; const unsigned offy = 50; printf ("\n",width+2*offx,height+offy,temps.size()+2*offx,height+offy); // Horizontal haut printf ("\n" ,offx,offx+width); // Vertical gauche printf ("\n" ,offx,offx,height); // Vertical droit printf ("\n" ,offx+width-1,offx+width-1,height); // Horizontal bas printf ("\n" ,offx,height-1,offx+width,height-1); static const char *colors[]={ "red","blue","green","black","yellow","orange","pink","darkgreen","gray","lightgray", // On ajoute 10 couleur pour atteindre MAX_TEMPS, au cas. "black","black","black","black","black","black","black","black","black","black" }; // Échelle de température, à chaques extrémités. { int mint = (int)mintemp; int maxt = (int)maxtemp; int start = mint/5*5; if (start < mint) start += 5; for (; start < maxt; start+= 5){ unsigned y = height-(start-mintemp)/difftemp*height; // À gauche printf ("\n" ,offx/2,y,offx,y); // À droite unsigned endx = offx+width; printf ("\n" ,endx,y,endx+offx/2,y); // ligne pointillée printf ("\n" ,offx,y,offx+width,y); // À gauche printf ("%2d\n" ,0,y,start); // À droite printf ("%2d\n" ,endx+offx/2,y,start); } } // Échelle de temps { time_t start = temps[0].date; time_t end = temps[temps.size()-1].date; unsigned diff = end - start; time_t hour = start / 3600*3600; time_t hourend = end / 3600*3600; for (; hour <= hourend; hour += 3600){ double x = (hour-start)*(double)width/diff+offx; if ((unsigned)x >= offx){ printf ("\n" ,x,height,x,height+offy/2); struct tm *tt = localtime(&hour); printf ("%2d\n" ,x,height+offy,tt->tm_hour); } } } double lastx = 0; double mins[nbvalues]; for (auto &m:mins) m = 1000; double maxs[nbvalues]; for (auto &m:maxs) m = -1000; double totals[nbvalues]; for (auto &t:totals) t = 0;; unsigned lasty[nbvalues]; for (auto &l:lasty) l = 0;; bool lumieres[nbvalues]; for (auto &l:lumieres) l = false; { // Analyse arduinocmd et trouve les L size_t len_arduinocmd = strlen(glocal.arduinocmd); for (unsigned i=4; i\n" ,lastx+offx,lasty[i],x+offx,y,colors[color_index[i]]); } if (mins[i] > t.temp[i]) mins[i] = t.temp[i]; if (maxs[i] < t.temp[i]) maxs[i] = t.temp[i]; totals[i] += t.temp[i]; } lasty[i] = y; } lastx = x; notemp++; } printf ("\n"); printf ("

\n"); // Affiche la charte des couleurs et la température courante { auto &t = temps[temps.size()-1]; printf ("\n"); printf ("
PrésentMinimumMaximumMoyenne\n"); for (unsigned i=0; i%s           " "  %5.2lf  %5.2lf  %5.2lf  %5.2lf\n" ,glocal.mesures[i].c_str(),colors[color_index[i]],t.temp[i],mins[i],maxs[i],totals[i]/temps.size()); } } printf ("
\n"); } printf ("

\n"); printf ("\n"); printf (" int ret = -1; usage(); return ret; return glocal.ret; }
int coinapi_main (int argc, char *argv[]); int main (int argc, char *argv[]) { int ret = -1; const char *argv0 = argv[0]; const char *pt = strrchr(argv0,'/'); if (pt != nullptr) argv0 = pt+1; if (strcmp(argv0,"ads")==0){ ret = ads_main (argc,argv); }else if (strcmp(argv0,"coinapi")==0){ ret = coinapi_main(argc,argv); }else{ fprintf (stderr,"Le programme doit être appelé soit ads ou coinapi\n"); } return ret; }