/* ------------------------------------------------------------------------ * Lndk_Avra08.ino * Interface Arduino pour regulateur solaire EP-SOLAR Tracer 2210RN * * Copyright (c) 2013-2015, Marc Dilasser, Le Net du Kermeur * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1 Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2 Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * 3 Neither the name of the copyright holders nor the names of * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * * History : * Original : Marc Dilasser, Le Net du Kermeur, Mars 2013 * * Version 0.4 * Moyenne des dix dernieres mesures * RESET, DUMP * Version 0.5 * TIME, UPTIME, PORT, LOAD, prise en compte 1 seul acces si multiple * Version 0.6 * Client Dhcp * Version 0.7 * Integration constantes en PROGMEM * Commande EEPROM * Version 0.8 * Optimisation messages en PROGMEM * Version 0.8a * Correctifs sur version 0.8 (Decembre 2013) * Version 0.8b * * Version 0.8c (decembre 2014) * Correction formatage debug serial en 02X * * Version 0.8d (janvier 2015) * Correction bug DELESTAGE avec P1= valeur non definie * et compte rendu OK ou ERR * * Version 0.8e (novembre 2016) * Commande RESET */ // Commandes du Tracer // 0x0A : read real time collected datas // 0xAA : manual control command // 0xAD : send control datas (temp. compensation, battery type, load control mode 1 and 2 // // Modifications dans la librairie EtherShield // http://blog.derouineau.fr/2011/07/putting-enc28j60-ethernet-controler-in-sleep-mode/ #include #include #include #include #include #include #include #include // #include "EtherShield.h" #include #include #include #include "Lndk_Avra08.h" #define LNDK_VERSION "0.8e" #define MYWWWPORT 80 #define ETH_SIZE 704 #define MAX_EEPROM_READ 128 #define SER_SIZE 96 #define MSG_SIZE 256 #define SRC_IP_POSITION 26 #define DAT01012013 1356994800 #define NORMAL 0x00 #define MESURE 0x01 #define PREDELESTAGE1 0x02 #define PREDELESTAGE2 0x03 #define DELESTAGE 0x04 #define ETHERNET_DOWN 0x05 #define WAITING_SERIAL 0x06 #define MAINTENANCE 0x07 #define BUTTON 0x02 #ifdef SEND_TRACER_COMMAND #define TRACER_DEVICE 0x01 #endif // Adresses des configurations en Eeprom #define MACADDRESS 0 #define ADDRESS 8 #define MASK 16 #define GATEWAY 24 #define WWWPORT 32 #define MASK_ACCESS_1 38 #define MASK_ACCESS_2 47 #define BASEVIN 56 #define STBY_VHIGH 62 #define STBY_VLOW 68 #define STBY_SOCH 74 #define STBY_SOCL 80 // Tension maximale VIN mesurable // Pont diviseur 4.7k et 18K // #define TENSION_MAX 15938 // Pont diviseur 4.7k et 22K #define TENSION_MAX 20250 #define STANDBY_VHIGH 13200 #define STANDBY_VLOW 13000 #define STANDBY_SOCH 64 #define STANDBY_SOCL 58 // les modes ECOnomie #define ECOSTOP 0 #define ECOSTANDBY 1 #define ECOSEARCH 2 #define ECORUNNING 3 // Passer les noms de commande en Progmem pour gagner de la place en datas // Les noms doivent obligatoirement apparaitre dans l'ordre alphabetique // pour permettre la recherche dychotomique const char st00[] PROGMEM = "ACC"; // Commande 32 const char st01[] PROGMEM = "ADDR"; // 33 const char st02[] PROGMEM = "CMD"; // 34 const char st03[] PROGMEM = "DELESTAGE"; // 35 const char st04[] PROGMEM = "DUMP"; // 36 const char st05[] PROGMEM = "EEPROM"; // 37 const char st06[] PROGMEM = "GATE"; // 38 const char st07[] PROGMEM = "HISTO"; // 39 const char st08[] PROGMEM = "LOAD"; // 40 const char st09[] PROGMEM = "MACA"; // 41 const char st10[] PROGMEM = "MASK"; // 42 const char st11[] PROGMEM = "MEMORY"; // 43 const char st12[] PROGMEM = "MESURES"; // 44 const char st13[] PROGMEM = "MODEECO"; // 45 const char st14[] PROGMEM = "PARAM"; // 46 const char st15[] PROGMEM = "PORT"; // 47 const char st16[] PROGMEM = "RESET"; // 48 const char st17[] PROGMEM = "SERIALDEBUG"; // 49 const char st18[] PROGMEM = "STANDBY"; // 50 const char st19[] PROGMEM = "TIME"; // 51 const char st20[] PROGMEM = "UPTIME"; // 52 const char st21[] PROGMEM = "VINMAX"; // 53 const char st22[] PROGMEM = "VOLTAGE"; // 54 const char st23[] PROGMEM = "WATCHDOG"; // 55 const char http01[] PROGMEM = "Lndk Tracer\n"; const char http02[] PROGMEM = "

Lndk Tracer version v" LNDK_VERSION "

\n"; const char http03[] PROGMEM = "\n\n\n"; const char htt200[] PROGMEM = "HTTP/1.0 200 OK\r\nContent-Type: text/html\r\nPragma: no-cache\r\n\r\n"; const char err401[] PROGMEM = "HTTP/1.0 401 Unauthorized\n" "Content-Type: text/html\n" "

401 Unauthorized

"; const char banner[] PROGMEM = "Lndk Tracer v" LNDK_VERSION ", (c)Le Net du Kermeur 2013-2016\n"; // Table des pointeurs sur les tokens const char *stokens[] PROGMEM = { st00, st01, st02, st03, st04, st05, st06, st07, st08, st09, st10, st11, st12, st13, st14, st15, st16, st17, st18, st19, st20, st21, st22, st23 }; #define NBTOKENS 24 #define OFFSET_TOKENS 32 // Numerotation des tokens a partir de 32 #define T_ACC 32 #define T_ADDR 33 #define T_CMD 34 #define T_DELESTAGE 35 #define T_DUMP 36 #define T_EEPROM 37 #define T_GATE 38 #define T_HISTO 39 #define T_LOAD 40 #define T_MACA 41 #define T_MASK 42 #define T_MEMORY 43 #define T_MESURES 44 #define T_MODEECO 45 #define T_PARAM 46 #define T_PORT 47 #define T_RESET 48 #define T_SERIALDEBUG 49 #define T_STANDBY 50 #define T_TIME 51 #define T_UPTIME 52 #define T_VINMAX 53 #define T_VOLTAGE 54 #define T_WATCHDOG 55 // Adresses MAC, IP, GATEWAY et PORT par defaut // Configurables en Eeprom par les commandes MACA, ADDR, GATE et PORT uint8_t mymac[6] = { 0x54,0x55,0x56,0x21,0x22,0x23}; uint8_t myip[4] = {192,168,1,25}; uint8_t gwip[4] = {192,168,1,1}; uint8_t mydnsip[4] = {192,168,1,1}; uint8_t dhcpsvrip[4] = {192,168,1,1}; uint8_t netmask[4] = {255,255,255,0}; uint8_t accmsk1[5] = {0, 0, 0, 0, 0}; uint8_t accmsk2[5] = {0, 0, 0, 0, 0}; uint8_t myport = MYWWWPORT ; // Base de mesure de la tension d'alimentation uint16_t baseVin = TENSION_MAX; // Parametres du mode STANDBY uint16_t stby_vhigh = STANDBY_VHIGH; uint16_t stby_vlow = STANDBY_VLOW; uint16_t stby_soch = STANDBY_SOCH; uint16_t stby_socl = STANDBY_SOCL; static uint8_t ethbuf[ETH_SIZE] = ""; static uint8_t serbuf[SER_SIZE] = ""; static uint8_t msgbuf[MSG_SIZE] = ""; static uint8_t tcmd[8]; // Stockage des mesures (6 * 10 * 2 + 9 * 10 = 210 octets) static uint16_t mbat[10], msol[10], mcon[10], modv[10], mbfv[10], mcur[10]; static uint8_t mlod[10], movl[10], mlsc[10], msoc[10], mbol[10], mbod[10], mful[10], mchg[10], mtmp[10]; int serind, msgind; #ifdef SERIALDEBUG int SerialDebug = 0; #endif int FlagMaintenance = LOW; unsigned long time, startTime, FinDelestage, DureeDelestage = 20000L; unsigned long t0State, t1State; unsigned long t0Mesure = 10000L, t1Mesure = 0; unsigned long t0Stanby = 0L; #ifdef ETHERNET_MODEECO unsigned long t0Eco, t1Eco, MoyennEco, DiffEco[4]; int State, LndkRunRun = 1, modeEco = ECOSEARCH; #else int State, LndkRunRun = 1; #endif static uint16_t ethind; int nbmes = 0; int VinOn = 0; // Detection alimentation par VIN // The ethernet shield EtherShield es=EtherShield(); /* -------------------------------------------------------- h t t p 2 0 0 o k */ uint16_t http200ok(void) { return(es.ES_fill_tcp_data_p(ethbuf, 0, htt200)); } /* -------------------------------------------------- h e a d _ w e b p a g e */ uint16_t head_webpage(uint16_t pos) { uint16_t plen; plen = es.ES_fill_tcp_data_p(ethbuf, pos, http01); plen = es.ES_fill_tcp_data_p(ethbuf, plen, http02); return plen; } /* ---------------------------------------------------- e n d _ w e b p a g e */ uint16_t end_webpage(uint16_t pos) { return (es.ES_fill_tcp_data_p(ethbuf, pos, http03)); } /* ------------------------------------------------ p r i n t _ w e b p a g e Prepare the webpage by writing the data to the tcp send buffer */ uint16_t print_webpage(uint8_t *buf) { uint16_t plen; plen = http200ok(); plen = head_webpage(plen); plen = es.ES_fill_tcp_data(ethbuf, plen, (char *) msgbuf); plen = end_webpage(plen); return(plen); } /* -------------------------------------------------- W r i t e E t h 2 H e x */ int WriteEth2Hex(int position, int nbval, int moyenne, uint8_t table[]) { int ij; byte inChar; unsigned long somme; somme = 0L; for (ij = 0; ij < nbval; ij ++) somme = somme + table[ij]; if (moyenne) somme /= nbval; inChar = (somme & 0x00F0) >> 4; msgbuf[position++] = (inChar > 9) ? (55 + inChar) : (48 + inChar); inChar = somme & 0x000F; msgbuf[position++] = (inChar > 9) ? (55 + inChar) : (48 + inChar); msgbuf[position++] = 32; return position; } /* -------------------------------------------------- W r i t e E t h 4 H e x */ int WriteEth4Hex(int position, int nbval, uint16_t table[]) { int ij; byte inChar; unsigned long somme; somme = 0L; for (ij = 0; ij < nbval; ij ++) somme = somme + table[ij]; somme = somme / nbval; inChar = (somme & 0xF000) >> 12; msgbuf[position++] = (inChar > 9) ? (55 + inChar) : (48 + inChar); inChar = (somme & 0x0F00) >> 8; msgbuf[position++] = (inChar > 9) ? (55 + inChar) : (48 + inChar); inChar = (somme & 0x00F0) >> 4; msgbuf[position++] = (inChar > 9) ? (55 + inChar) : (48 + inChar); inChar = somme & 0x000F; msgbuf[position++] = (inChar > 9) ? (55 + inChar) : (48 + inChar); msgbuf[position++] = 32; return position; } /* -------------------------------------------------- W r i t e E t h T i m e Ecriture d'un temps time_t en huit caracteres hexadecimaux */ int WriteEthTime(int position, time_t time) { int ij; ij = time >> 28; msgbuf[position++] = (ij > 9) ? (55 + ij) : (48 +ij); ij = (time & 0x0F000000) >> 24; msgbuf[position++] = (ij > 9) ? (55 + ij) : (48 +ij); ij = (time & 0x00F00000) >> 20; msgbuf[position++] = (ij > 9) ? (55 + ij) : (48 +ij); ij = (time & 0x000F0000) >> 16; msgbuf[position++] = (ij > 9) ? (55 + ij) : (48 +ij); ij = (time & 0x0000F000) >> 12; msgbuf[position++] = (ij > 9) ? (55 + ij) : (48 +ij); ij = (time & 0x00000F00) >> 8; msgbuf[position++] = (ij > 9) ? (55 + ij) : (48 +ij); ij = (time & 0x000000F0) >> 4; msgbuf[position++] = (ij > 9) ? (55 + ij) : (48 +ij); ij = time & 0x0F; msgbuf[position++] = (ij > 9) ? (55 + ij) : (48 +ij); msgbuf[position++] = ' '; return position; } /* ------------------------------------------ S t a t u s T i m e H e a d e r */ int StatusTimeHeader(void) { // Flag du programme pour info, par defaut 0000 msgbuf[0] = '0' + timeStatus(); if (nbmes > 15) nbmes = 15; msgbuf[1] = (nbmes > 9) ? (55 + nbmes) : (48 + nbmes); #ifdef ETHERNET_MODEECO msgbuf[2] = 48 + modeEco; #else msgbuf[2] = 48; #endif msgbuf[3] = 48 + State; msgbuf[4] = ' '; // Champ date en secondes depuis 1/1/1970, en hexadecimal return (WriteEthTime(5, now())); } /* ---------------------------------------------------------- S e n d T i m e */ uint16_t EthSendTime(void) { int ik; uint16_t lenstate; // Champ date en secondes depuis 1/1/1970, en hexadecimal ik = StatusTimeHeader(); // Uptime en secondes ik = WriteEthTime(ik, (now() - startTime)); msgind = ik; msgbuf[ik] = 0; lenstate = print_webpage(ethbuf); return (lenstate); } /* ------------------------------------------------ S e n d D e l e s t a g e */ uint16_t SendDelestage(uint16_t duree) { int ik; uint16_t lenstate; // Champ date en secondes depuis 1/1/1970, en hexadecimal ik = StatusTimeHeader(); // Uptime en secondes ik = WriteEthTime(ik, duree); if (duree >= 20) { msgbuf[ik++] = 'O'; msgbuf[ik++] = 'K'; } else { msgbuf[ik++] = 'E'; msgbuf[ik++] = 'R'; msgbuf[ik++] = 'R'; } msgbuf[ik] = 0; lenstate = print_webpage(ethbuf); return (lenstate); } /* ------------------------------------------------------------ S e n d M e m */ uint16_t SendMem(void) { int ik; uint16_t lenstate; int free; ik = 5; // Champ date en secondes depuis 1/1/1970, en hexadecimal ik = StatusTimeHeader(); msgbuf[ik++] = 'F'; msgbuf[ik++] = 'r'; msgbuf[ik++] = 'e'; msgbuf[ik++] = 'e'; msgbuf[ik++] = ':'; msgbuf[ik++] = ' '; free = freeRam(); if ((free < 0) || (free > 9999)) free = 0; if (free > 1000) msgbuf[ik++] = (free / 1000) + '0'; if (free > 100) msgbuf[ik++] = ((free % 1000) / 100) + '0'; if (free > 10) msgbuf[ik++] = ((free % 100) / 10) + '0'; msgbuf[ik++] = (free % 10) + '0'; // if (timeStatus() != timeNotSet) time = now(); // Champ date en secondes depuis 1/1/1970, en hexadecimal msgbuf[ik++] = ' '; msgbuf[ik++] = 'b'; msgbuf[ik++] = 'y'; msgbuf[ik++] = 't'; msgbuf[ik++] = 'e'; msgbuf[ik++] = 's'; msgbuf[ik] = 0; lenstate = print_webpage(ethbuf); return (lenstate); } /* ---------------------------------------------------------- S e n d V o l t */ uint16_t SendVolt(int Setting) { int ij, ik; uint16_t lenstate; time_t time; int sensorValue, mini, maxi; unsigned long tension; // if (timeStatus() != timeNotSet) time = now(); // Champ date en secondes depuis 1/1/1970, en hexadecimal ik = StatusTimeHeader(); tension = 0; mini = 1024; maxi = 0; analogRead(A7); // Faire 7 mesures, ne pas conserver mini et maxi consideres comme bruit for (ij = 0; ij < 7; ij ++ ) { delay(20); sensorValue = analogRead(A7); if (sensorValue < mini) mini = sensorValue; if (sensorValue > maxi) maxi = sensorValue; if ((! Setting) || (ij == 4)) { if (sensorValue > 1000) msgbuf[ik++] = (sensorValue / 1000) + '0'; if (sensorValue > 100) msgbuf[ik++] = ((sensorValue % 1000) / 100) + '0'; if (sensorValue > 10) msgbuf[ik++] = ((sensorValue % 100) / 10) + '0'; msgbuf[ik++] = (sensorValue % 10) + '0'; msgbuf[ik++] = ' '; } tension += sensorValue; } tension -= (mini + maxi); tension *= baseVin; tension /= (1024 * (ij - 2)); msgbuf[ik++] = '('; if (tension > 10000L) msgbuf[ik++] = (tension / 10000L) + '0'; if (tension > 1000L) msgbuf[ik++] = ((tension % 10000L) / 1000L) + '0'; if (tension > 100L) msgbuf[ik++] = ((tension % 1000L) / 100L) + '0'; if (tension > 10L) msgbuf[ik++] = ((tension % 100L) / 10L) + '0'; msgbuf[ik++] = (tension % 10L) + '0'; msgbuf[ik++] = ' '; msgbuf[ik++] = 'm'; msgbuf[ik++] = 'V'; msgbuf[ik++] = ')'; if (Setting) { msgbuf[ik++] = ' '; msgbuf[ik++] = 'M'; msgbuf[ik++] = 'a'; msgbuf[ik++] = 'x'; msgbuf[ik++] = ':'; if (baseVin > 10000L) msgbuf[ik++] = (baseVin / 10000L) + '0'; if (baseVin > 1000L) msgbuf[ik++] = ((baseVin % 10000L) / 1000L) + '0'; if (baseVin > 100L) msgbuf[ik++] = ((baseVin % 1000L) / 100L) + '0'; if (baseVin > 10L) msgbuf[ik++] = ((baseVin % 100L) / 10L) + '0'; msgbuf[ik++] = (baseVin % 10L) + '0'; } msgbuf[ik] = 0; lenstate = print_webpage(ethbuf); return (lenstate); } /* ---------------------------------------------- V a l e u r s _ E e p r o m */ int Valeurs_Eeprom(int page) { int ij, ik, il; uint16_t lenstate; uint8_t im, hh, hl; lenstate = http200ok(); lenstate = head_webpage(lenstate); ik = 0; il = page * 128; hh = (il & 0xF00) >> 8; msgbuf[ik++] = (hh > 9) ? (55 + hh) : 48 + hh; hh = (il & 0xF0) >> 4; msgbuf[ik++] = (hh > 9) ? (55 + hh) : 48 + hh; msgbuf[ik++] = '0'; msgbuf[ik++] = ':'; msgbuf[ik++] = ' '; // Limitation a 128 due a ETH_SIZE de ethbuf for (ij = il; ij < il + MAX_EEPROM_READ; ij ++) { if ((ij > il) && ((ij % 16) == 0)) { msgbuf[ik++] = 13; msgbuf[ik++] = 10; hh = (ij & 0xF00) >> 8; msgbuf[ik++] = (hh > 9) ? (55 + hh) : 48 + hh; hh = (ij & 0xF0) >> 4; msgbuf[ik++] = (hh > 9) ? (55 + hh) : 48 + hh; msgbuf[ik++] = '0'; msgbuf[ik++] = ':'; msgbuf[ik++] = ' '; if ((ij % 64) == 0) { msgbuf[ik] = 0; // Serial.println(ik, DEC); lenstate = es.ES_fill_tcp_data(ethbuf, lenstate, (char *) msgbuf); ik = 0; } } im = EEPROM.read(ij); hh = im >> 4; hl = im & 0x0F; msgbuf[ik ++] = (hh > 9) ? (55 + hh) : 48 + hh; msgbuf[ik ++] = (hl > 9) ? (55 + hl) : 48 + hl; msgbuf[ik ++] = ' '; } msgbuf[ik] = 0; lenstate = es.ES_fill_tcp_data(ethbuf, lenstate, (char *) msgbuf); lenstate = end_webpage(lenstate); // Serial.println(lenstate, DEC); // --> 603 pour 128 caracteres de l'Eeprom return (lenstate); } /* -------------------------------------------------------- S e n d S t a t e */ uint16_t SendState(int dump) { int ij, ik; uint16_t lenstate; time_t time; // serbuf[serind] = 0; time = now(); // Flag du programme pour info, par defaut 0000 msgbuf[0] = '0' + timeStatus(); if (nbmes > 15) nbmes = 15; msgbuf[1] = (nbmes > 9) ? (55 + nbmes) : (48 + nbmes); #ifdef ETHERNET_MODEECO msgbuf[2] = 48 + modeEco; #else msgbuf[2] = 48; #endif msgbuf[3] = 48 + State; msgbuf[4] = ' '; ik = 5; // if (timeStatus() != timeNotSet) time = now(); // Champ date en secondes depuis 1/1/1970, en hexadecimal ik = WriteEthTime(ik, now()); if (dump > 0) { // Donnees lues sur le port serie, en hexadecimal for (ij = 0; ij < min(40, SER_SIZE); ij++) { byte inChar; inChar = serbuf[ij] >> 4; msgbuf[ik++] = (inChar > 9) ? (55 + inChar) : (48 + inChar); inChar = serbuf[ij] & 0x0F; msgbuf[ik++] = (inChar > 9) ? (55 + inChar) : (48 + inChar); msgbuf[ik++] = 32; } } else { if (nbmes > 0) { int nbval; // Nombre de valeurs retenues nbval = min(10, nbmes); // Tension batterie ik = WriteEth4Hex(ik, nbval, mbat); // Tension panneau solaire ik = WriteEth4Hex(ik, nbval, msol); // Consommation ik = WriteEth4Hex(ik, nbval, mcon); // Over discharge voltage ik = WriteEth4Hex(ik, nbval, modv); // Batterie Full Voltage ik = WriteEth4Hex(ik, nbval, mbfv); // Courant de charge; ik = WriteEth4Hex(ik, nbval, mcur); // Load ON/OFF ik = WriteEth2Hex(ik, nbval, 0, mlod); // Indicateur Overload movl[10] ik = WriteEth2Hex(ik, nbval, 0, movl); // Indicateur Load short circuit, mlsc[10] ik = WriteEth2Hex(ik, nbval, 0, mlsc); // Indicateur SOC, faire la moyenne msoc[10] ik = WriteEth2Hex(ik, nbval, 1, msoc); // Indicateur Batterie overload mbol[10] ik = WriteEth2Hex(ik, nbval, 0, mbol); // Indicateur batterie over discharge mbod[10] ik = WriteEth2Hex(ik, nbval, 0, mbod); // Indicateur batterie full mful[10] ik = WriteEth2Hex(ik, nbval, 0, mful); // Indicateur de charge mchg[10] ik = WriteEth2Hex(ik, nbval, 0, mchg); // Temperature mtmp[10], faire la moyenne ik = WriteEth2Hex(ik, nbval, 1, mtmp); } } msgind = ik; msgbuf[ik] = 0; lenstate = print_webpage(ethbuf); #ifdef SERIALDEBUG if (SerialDebug > 0) { PrintSerialPgm(PSTR("Recu ")); Serial.print(serind); PrintSerialPgm(PSTR(" caracteres\n")); } #endif if (dump == 0) nbmes = 0; return (lenstate); } /* ---------------------------------------------------- D e u x H e x a E t h */ uint8_t DeuxHexaEth(int position) { uint8_t inChar; uint8_t value; value = 0; inChar = ethbuf[position]; if ((inChar >= '0') && (inChar <= '9')) { value = (inChar - 48); } else { if ((inChar >= 'A') && (inChar <= 'F')) { value = inChar - 55; } } value = value << 4; inChar = ethbuf[position + 1]; if ((inChar >= '0') && (inChar <= '9')) { value |= (inChar - 48); } else { if ((inChar >= 'A') && (inChar <= 'F')) { value |= inChar - 55; } else { value = 0; } } return(value); } /* ------------------------------------------------------------ p r i n t I p Output a ip address from buffer */ void printIP( uint8_t *buf ) { for ( int i = 0; i < 4; i++ ) { Serial.print( buf[i], DEC ); if( i < 3 ) Serial.print( "." ); } } /* -------------------------------------------------------- C o n f i g _ I p Ecriture en EEPROM de la configuration IP */ int Config_Ip(int config, int position) { uint8_t a1 = 0, a2 = 0, a3, a4, a5, a6; int ik = 0; // Configuration IPV4 if (strncmp("?P1=", (char *)&(ethbuf[position]), 4) == 0) { // Lecture des 4 a 10 caracteres hexa ik = position + 4; a1 = DeuxHexaEth(ik); ik += 2; a2 = DeuxHexaEth(ik); if (config != WWWPORT) { ik += 2; a3 = DeuxHexaEth(ik); ik += 2; a4 = DeuxHexaEth(ik); if ((config == MACADDRESS) || (config == MASK_ACCESS_1) || (config == MASK_ACCESS_2)) { ik += 2; a5 = DeuxHexaEth(ik); if (config == MACADDRESS) { ik += 2; a6 = DeuxHexaEth(ik); } else a6 = 0; } else a5 = a6 = 0; } else a3 = a4 = a5 = a6 = 0; #ifdef SERIALDEBUG if (SerialDebug) { Serial.print(a1); Serial.print('.'); Serial.print(a2); Serial.print('.'); Serial.print(a3); Serial.print('.'); Serial.print(a4); Serial.print('.'); Serial.print(a5); Serial.print('.'); Serial.println(a6); } #endif if ((config == ADDRESS) || ((a1 != 0) || (a2 != 0) || (a3 != 0) || (a4 != 0) || (a5 != 0) || (a6 != 0))) { ik = config; switch (config) { case MACADDRESS : EEPROM.write(ik++, 'M'); EEPROM.write(ik++, 'A'); break; case ADDRESS : EEPROM.write(ik++, 'A'); EEPROM.write(ik++, 'D'); EEPROM.write(ik++, 'D'); EEPROM.write(ik++, 'R'); break; case MASK : EEPROM.write(ik++, 'M'); EEPROM.write(ik++, 'A'); EEPROM.write(ik++, 'S'); EEPROM.write(ik++, 'K'); break; case GATEWAY : EEPROM.write(ik++, 'G'); EEPROM.write(ik++, 'A'); EEPROM.write(ik++, 'T'); EEPROM.write(ik++, 'E'); break; case WWWPORT : EEPROM.write(ik++, 'P'); EEPROM.write(ik++, 'O'); EEPROM.write(ik++, 'R'); EEPROM.write(ik++, 'T'); break; case MASK_ACCESS_1 : case MASK_ACCESS_2 : EEPROM.write(ik++, 'A'); EEPROM.write(ik++, 'C'); EEPROM.write(ik++, 'C'); if (config == MASK_ACCESS_1) EEPROM.write(ik++, '1'); else EEPROM.write(ik++, '2'); break; } EEPROM.write(ik++, a1); EEPROM.write(ik++, a2); if (config != WWWPORT) { EEPROM.write(ik++, a3); EEPROM.write(ik++, a4); if ((config == MACADDRESS) || (config == MASK_ACCESS_1) || (config == MASK_ACCESS_2)) { EEPROM.write(ik++, a5); if (config == MACADDRESS) EEPROM.write(ik++, a6); } } } } else { #ifdef SERIALDEBUG if (SerialDebug) { PrintSerialPgm(PSTR("Manque parametres\n")); } #endif } return (ik); } /* ------------------------------------------ S a v e C o n f i g V i n M a x */ void SaveConfigVinMax(int value) { int ik; uint8_t a1 = 0, a2 = 0; ik = BASEVIN; EEPROM.write(ik++, 'B'); EEPROM.write(ik++, 'V'); EEPROM.write(ik++, 'I'); EEPROM.write(ik++, 'N'); a1 = value >> 8; a2 = value & 0xFF; EEPROM.write(ik++, a1); EEPROM.write(ik++, a2); } /* ---------------------------------------------- S a v e C o n f i g S t b y */ void SaveConfigStby(uint16_t b1, uint16_t b2, uint16_t b3, int16_t b4 ) { int ik; uint8_t a1 = 0, a2 = 0; ik = STBY_VHIGH; EEPROM.write(ik++, 'S'); EEPROM.write(ik++, 'T'); EEPROM.write(ik++, 'B'); EEPROM.write(ik++, 'H'); a1 = b1 >> 8; a2 = b1 & 0xFF; EEPROM.write(ik++, a1); EEPROM.write(ik++, a2); ik = STBY_VLOW; EEPROM.write(ik++, 'S'); EEPROM.write(ik++, 'T'); EEPROM.write(ik++, 'B'); EEPROM.write(ik++, 'L'); a1 = b2 >> 8; a2 = b2 & 0xFF; EEPROM.write(ik++, a1); EEPROM.write(ik++, a2); if ((b3 > 0) && (b4 > 0) && (b3 > b4)) { ik = STBY_SOCH; EEPROM.write(ik++, 'S'); EEPROM.write(ik++, 'O'); EEPROM.write(ik++, 'C'); EEPROM.write(ik++, 'H'); a1 = b3 >> 8; a2 = b3 & 0xFF; EEPROM.write(ik++, a1); EEPROM.write(ik++, a2); ik = STBY_SOCL; EEPROM.write(ik++, 'S'); EEPROM.write(ik++, 'O'); EEPROM.write(ik++, 'C'); EEPROM.write(ik++, 'L'); a1 = b4 >> 8; a2 = b4 & 0xFF; EEPROM.write(ik++, a1); EEPROM.write(ik++, a2); } } /* -------------------------------------------------- L i r e _ C o n f i g s */ void Lire_Configs(void) { uint8_t a1, a2, a3, a4, a5, a6; uint8_t ik; for (ik = 0; ik < 64; ik ++) { serbuf[ik] = EEPROM.read(ik); } serbuf[ik] = 0x00; ik = MACADDRESS; // Lecture adresse MAC if ((serbuf[ik] == 'M') && (serbuf[ik + 1] == 'A')) { ik += 2; a1 = serbuf[ik++]; a2 = serbuf[ik++]; a3 = serbuf[ik++]; a4 = serbuf[ik++]; a5 = serbuf[ik++]; a6 = serbuf[ik++]; if ( ((a1 != 0) || (a2 != 0) || (a3 != 0) || (a4 != 0) || (a5 != 0) || (a6 != 0)) && ((a1 != 0xFF) || (a2 != 0xFF) || (a3 != 0xFF) || (a4 != 0xFF) || (a5 != 0xFF) || (a6 != 0xFF))) { mymac[0] = a1; mymac[1] = a2; mymac[2] = a3; mymac[3] = a4; mymac[4] = a5; mymac[5] = a6; } } // Lecture adresse IP ik = ADDRESS; if ((serbuf[ik] == 'A') && (serbuf[ik + 1] == 'D') && (serbuf[ik + 2] == 'D') && (serbuf[ik + 3] == 'R')) { ik += 4; a1 = serbuf[ik++]; a2 = serbuf[ik++]; a3 = serbuf[ik++]; a4 = serbuf[ik]; // L'adresse 0.0.0.0 est admise et interprete comme DHCP client if ((a1 != 0xFF) || (a2 != 0xFF) || (a3 != 0xFF) || (a4 != 0xFF)) { myip[0] = a1; myip[1] = a2; myip[2] = a3; myip[3] = a4; } } // Adresse passerelle ik = GATEWAY; if ((serbuf[ik] == 'G') && (serbuf[ik + 1] == 'A') && (serbuf[ik + 2] == 'T') && (serbuf[ik + 3] == 'E')) { ik += 4; a1 = serbuf[ik++]; a2 = serbuf[ik++]; a3 = serbuf[ik++]; a4 = serbuf[ik]; if ( ((a1 != 0) || (a2 != 0) || (a3 != 0) || (a4 != 0)) && ((a1 != 0xFF) || (a2 != 0xFF) || (a3 != 0xFF) || (a4 != 0xFF))) { gwip[0] = a1; gwip[1] = a2; gwip[2] = a3; gwip[3] = a4; } } // Port TCP ik = WWWPORT; if ((serbuf[ik] == 'P') && (serbuf[ik + 1] == 'O') && (serbuf[ik + 2] == 'R') && (serbuf[ik + 3] == 'T')) { ik += 4; a1 = serbuf[ik++]; a2 = serbuf[ik++]; if ((a1 != 0) || (a2 != 0)) { myport = a1 * 256 + a2; } } // Masque adresses clientes permises ik = MASK_ACCESS_1; if ((serbuf[ik] == 'A') && (serbuf[ik + 1] == 'C') && (serbuf[ik + 2] == 'C') && (serbuf[ik + 3] == '1')) { ik += 4; a1 = serbuf[ik++]; a2 = serbuf[ik++]; a3 = serbuf[ik++]; a4 = serbuf[ik++]; a5 = serbuf[ik]; if ((a5 > 0) && (a5 <= 32)) { if ( ((a1 != 0) || (a2 != 0) || (a3 != 0) || (a4 != 0)) && ((a1 != 0xFF) || (a2 != 0xFF) || (a3 != 0xFF) || (a4 != 0xFF))) { accmsk1[0] = a1; accmsk1[1] = a2; accmsk1[2] = a3; accmsk1[3] = a4; accmsk1[4] = a5; // Le second masque d'adresses ne peut être defini que si le premier // que si le premier est defini et valide ik = MASK_ACCESS_2; if ((serbuf[ik] == 'A') && (serbuf[ik + 1] == 'C') && (serbuf[ik + 2] == 'C') && (serbuf[ik + 3] == '2')) { ik += 4; a1 = serbuf[ik++]; a2 = serbuf[ik++]; a3 = serbuf[ik++]; a4 = serbuf[ik++]; a5 = serbuf[ik]; if ((a5 > 0) && (a5 <= 32)) { if ( ((a1 != 0) || (a2 != 0) || (a3 != 0) || (a4 != 0)) && ((a1 != 0xFF) || (a2 != 0xFF) || (a3 != 0xFF) || (a4 != 0xFF))) { accmsk2[0] = a1; accmsk2[1] = a2; accmsk2[2] = a3; accmsk2[3] = a4; accmsk2[4] = a5; } } // else netmask 2 invalide } // else pas d'enregistement ACC2 } } // else netmask 1 invalide } // else pas d'enregistement ACC1 // Echelle de mesure de la tension sur A7 ik = BASEVIN; if ((serbuf[ik] == 'B') && (serbuf[ik + 1] == 'V') && (serbuf[ik + 2] == 'I') && (serbuf[ik + 3] == 'N')) { ik += 4; a1 = serbuf[ik++]; a2 = serbuf[ik++]; if ((a1 != 0) || (a2 != 0)) { baseVin = a1 * 256 + a2; } } // Parametres du mode STANDBY ik = STBY_VHIGH; if ((serbuf[ik] == 'S') && (serbuf[ik + 1] == 'T') && (serbuf[ik + 2] == 'B') && (serbuf[ik + 3] == 'H')) { ik += 4; a1 = serbuf[ik++]; a2 = serbuf[ik++]; if ((a1 != 0) || (a2 != 0)) { stby_vhigh = a1 * 256 + a2; } } ik = STBY_VLOW; if ((serbuf[ik] == 'S') && (serbuf[ik + 1] == 'T') && (serbuf[ik + 2] == 'B') && (serbuf[ik + 3] == 'L')) { ik += 4; a1 = serbuf[ik++]; a2 = serbuf[ik++]; if ((a1 != 0) || (a2 != 0)) { stby_vlow = a1 * 256 + a2; } } ik = STBY_SOCH; if ((serbuf[ik] == 'S') && (serbuf[ik + 1] == 'O') && (serbuf[ik + 2] == 'C') && (serbuf[ik + 3] == 'H')) { ik += 4; a1 = serbuf[ik++]; a2 = serbuf[ik++]; if ((a1 != 0) || (a2 != 0)) { stby_soch = a1 * 256 + a2; } } ik = STBY_SOCL; if ((serbuf[ik] == 'S') && (serbuf[ik + 1] == 'O') && (serbuf[ik + 2] == 'C') && (serbuf[ik + 3] == 'L')) { ik += 4; a1 = serbuf[ik++]; a2 = serbuf[ik++]; if ((a1 != 0) || (a2 != 0)) { stby_socl = a1 * 256 + a2; } } } #ifdef ETHERNET_MODEECO /* ---------------------------------------- E c o R u n n i n g 2 S e a r c h Desactivation du mode ECO s'il etait actif */ void EcoRunning2Search() { if (modeEco == ECORUNNING) { modeEco = ECOSEARCH; #ifdef SERIALDEBUG if (SerialDebug) { PrintSerialPgm(PSTR("Mode ECO desactive\n")); } #endif } } #endif /* ------------------------------------ S e n d _ T r a c e r _ C o m m a n d */ #ifdef SEND_TRACER_COMMAND void Send_Tracer_Command(uint8_t command, uint8_t datalen) { int ij; uint16_t crc; msgbuf[0] = TRACER_DEVICE; // msgbuf[1] = command; // Commande msgbuf[2] = datalen; // Longueur de donnees for (ij = 0; ij < datalen; ij ++ ) { msgbuf[ij + 3] = tcmd[ij]; // Parametres s'il y a lieu } ij = datalen + 3; msgbuf[ij ++] = 0; msgbuf[ij ++] = 0; msgbuf[ij] = 0x7F; // Calcul et placement du checksum crc = Crc16(msgbuf, datalen + 5); msgbuf[datalen + 3] = (crc >> 8); msgbuf[datalen + 4] = (crc & 0xFF); for (ij = 0; ij < 3; ij ++) { #ifdef SERIALDEBUG if (VinOn && (SerialDebug == 0)) { #else if (VinOn) { #endif Serial.write(0xEB); Serial.write(0x90); } else { Serial.print(0xEB, HEX); Serial.print(' '); Serial.print(0x90, HEX); Serial.print(' '); } } for (ij = 0; ij < (datalen + 6); ij ++) { #ifdef SERIALDEBUG if (VinOn && (SerialDebug == 0)) #else if (VinOn) #endif Serial.write(msgbuf[ij]); else { // Forcer formatage hexa en 02X if (msgbuf[ij] < 16) Serial.print('0'); Serial.print(msgbuf[ij], HEX); Serial.write(' '); } } #ifdef SERIALDEBUG if ((! VinOn) || (SerialDebug > 0)) Serial.println(); #else if (! VinOn) Serial.println(); #endif } #else /* -------------------------------------------- W r i t e _ P r e a m b u l e Envoi de debut de commande */ void Write_Preambule(void) { int ij; for (ij = 0; ij < 3; ij ++) { Serial.write(0xEB); Serial.write(0x90); } } #endif /* -------------------------------------------------------- L o a d O n O f f */ void LoadOnOff(int on) { // Envoi de la commande de coupure de l'utilisation #ifdef SEND_TRACER_COMMAND tcmd[0] = (on) ? 1 : 0; tcmd[1] = 0; Send_Tracer_Command(0xAA, 0x01); #else Write_Preambule(); // Synchro Serial.write(0x00); // Device Serial.write(0xAA); // Commande Serial.write(0x01); // Longueur de donnees if (on) { Serial.write(0x01); // Donnee Serial.write(0x4D); Serial.write(0x9A); // Checksum } else { Serial.write(0x00); // Donnee Serial.write(0x5D); Serial.write(0xDB); // Checksum } Serial.write(0x7F); // Fin de commande #endif } #ifdef SEND_TRACER_COMMAND /* ------------------------------------------------------ Q u e r y P a r a m */ void QueryParam(void) { // Envoi de la commande de coupure de l'utilisation tcmd[0] = 0; Send_Tracer_Command(0xAD, 0x00); } #endif /* ---------------------------------------------- M e s u r e s _ T r a c e r Si une sequence de mesures du regulateur est reconnue, l'enregistrer */ void Mesures_Tracer(void) { int ij; uint8_t c1, c2; uint16_t crc; // Detection de mesure, si ok la sauvegarder // 5 10 15 20 25 30 36 // EB 90 EB 90 EB 90 00 A0 18 DB 04 0F 00 00 00 09 00 4B 04 C8 05 01 00 00 25 00 00 00 00 2B 00 00 00 92 74 7F if ( (serbuf[0] == 0xEB) && (serbuf[1] == 0x90) && (serbuf[2] == 0xEB) && (serbuf[3] == 0x90) && (serbuf[4] == 0xEB) && (serbuf[5] == 0x90)) { // Decaler anciennes mesures for (ij = 8; ij >= 0; ij --) { mbat[ij + 1] = mbat[ij]; msol[ij + 1] = msol[ij]; mcon[ij + 1] = mcon[ij]; modv[ij + 1] = modv[ij]; mbfv[ij + 1] = mbfv[ij]; mlod[ij + 1] = mlod[ij]; movl[ij + 1] = movl[ij]; mlsc[ij + 1] = mlsc[ij]; msoc[ij + 1] = msoc[ij]; mbol[ij + 1] = mbol[ij]; mbod[ij + 1] = mbod[ij]; mful[ij + 1] = mful[ij]; mchg[ij + 1] = mchg[ij]; mtmp[ij + 1] = mtmp[ij]; mcur[ij + 1] = mcur[ij]; } mbat[0] = (serbuf[10] << 8) | serbuf[9]; msol[0] = (serbuf[12] << 8) | serbuf[11]; mcon[0] = (serbuf[16] << 8) | serbuf[15]; modv[0] = (serbuf[18] << 8) | serbuf[17]; mbfv[0] = (serbuf[20] << 8) | serbuf[19]; mlod[0] = serbuf[21]; movl[0] = serbuf[22]; mlsc[0] = serbuf[23]; msoc[0] = serbuf[24]; mbol[0] = serbuf[25]; mbod[0] = serbuf[26]; mful[0] = serbuf[27]; mchg[0] = serbuf[28]; mtmp[0] = serbuf[29]; mcur[0] = (serbuf[31] << 8) | serbuf[30]; // serbuf[32] reserve // serbuf[33] checksum // serbuf[34] checksum // serbuf[35] 0x7F // Recalculer le checksum c1 = serbuf[33]; c2 = serbuf[34]; serbuf[33] = serbuf[34] = 0x00; crc = Crc16((uint8_t *)&(serbuf[6]), 29); // Retablir les valeurs originales serbuf[33] = c1; serbuf[34] = c2; // Deposer le CRC calcule a la suite, vus dans la commande DUMP serbuf[36] = (crc >> 8); serbuf[37] = (crc & 0xFF); nbmes++; serind=0; serbuf[SER_SIZE] = 0; #ifdef HISTORIQUE // Conservation a chaque changement d'heure #endif } } /* ---------------------------------------------------- T i m e C o m m a n d */ int TimeCommand(char *param, int setIt) { int ij; char a1; unsigned long t0, t1; if (setIt) { if (strncmp("?P1=", param, 4) == 0) { t0 = 0L; ij = 4; a1 = param[ij++]; while (((a1 >= '0') && (a1 <= '9')) && (ij < 15)) { t0 *= 10; t0 += (a1 - 48); a1 = param[ij++]; } t1 = 0L; if (strncmp("&P2=", ¶m[ij - 1], 4) == 0) { ij += 3; a1 = param[ij++]; while (((a1 >= '0') && (a1 <= '9')) && (ij < 25)) { t1 *= 10; t1 += (a1 - 48); a1 = param[ij++]; } if ((t1 < 120L) || (t1 > 7200L)) t1 = 1200L; setSyncInterval(t1); } // If time not allready set, set startTime if (timeStatus() == 0) { startTime = t0 - (millis() / 1000L); } setTime(t0); #ifdef SERIALDEBUG if (SerialDebug) { PrintSerialPgm(PSTR("Set Time ")); Serial.print(t0); Serial.print("("); Serial.print(t1); Serial.println(")"); } #endif } } return (EthSendTime()); } /* ------------------------------------------------------ S e t B a s e V i n */ int SetBaseVin(char *param) { int ij; char a1; unsigned long b1; // Serial.println(param); if (strncmp("?P1=", param, 4) == 0) { ij = 4; b1 = 0L; a1 = param[ij++]; while (((a1 >= '0') && (a1 <= '9')) && (ij <= 9)) { b1 *= 10; b1 += (a1 - 48); a1 = param[ij++]; } // Serial.println(b1); if ((b1 > 4000L) && (b1 <= 32000L)) { baseVin = b1; if (strncmp("&P2=1", ¶m[ij - 1], 5) == 0) { SaveConfigVinMax(baseVin); } } } return SendVolt(1); } /* -------------------------------------------- S e t S t a n d b y P a r a m */ uint16_t SetStandbyParam(char *param) { int ij, ik, ok, b3, b4; char a1; uint16_t lenstate; unsigned long b1, b2; ok = b3 = b4 = 0; if (strncmp("?P1=", param, 4) == 0) { ij = 4; b1 = 0L; a1 = param[ij++]; while (((a1 >= '0') && (a1 <= '9')) && (ij <= 9)) { b1 *= 10; b1 += (a1 - 48); a1 = param[ij++]; } ij --; if (strncmp("&P2=", ¶m[ij], 4) == 0) { ij += 4; b2 = 0L; a1 = param[ij++]; while (((a1 >= '0') && (a1 <= '9')) && (ij <= 20)) { b2 *= 10; b2 += (a1 - 48); a1 = param[ij++]; } // Parametres optionnels P3 et P4 : valeurs haute et basse du SOC ij --; if (strncmp("&P3=", ¶m[ij], 4) == 0) { ij += 4; ik = ij + 3; b3 = 0; a1 = param[ij++]; while (((a1 >= '0') && (a1 <= '9')) && (ij <= ik)) { b3 *= 10; b3 += (a1 - 48); a1 = param[ij++]; } ij --; if (strncmp("&P4=", ¶m[ij], 4) == 0) { ij += 4; ik = ij + 3; b4 = 0; a1 = param[ij++]; while (((a1 >= '0') && (a1 <= '9')) && (ij <= ik)) { b4 *= 10; b4 += (a1 - 48); a1 = param[ij++]; } } if ((b3 < 100) && (b4 < b3) && (b3 > 50) && (b4 > 40)) { stby_soch = b3; stby_socl = b4; } else { b3 = b4 = 0; } } // Prendre en compte systeme 12 et 24V if ((b1 > b2) && (b1 >= 12600L) && (b1 <= 27200L) && (b2 >= 12400L) && (b2 <= 26000L)) { stby_vhigh = b1; stby_vlow = b2; SaveConfigStby(stby_vhigh, stby_vlow, b3, b4); ok = 1; } } } ij = 0; lenstate = http200ok(); lenstate = head_webpage(lenstate); if (ok) { msgbuf[ij++] = 'O'; msgbuf[ij++] = 'k'; } else { msgbuf[ij++] = 'E'; msgbuf[ij++] = 'r'; msgbuf[ij++] = 'r'; } msgbuf[ij++] = 10; msgbuf[ij] = 0; lenstate = es.ES_fill_tcp_data(ethbuf, lenstate, (char *) msgbuf); lenstate = end_webpage(lenstate); return (lenstate); } /* -------------------------------------------- C l i e n t I p A l l o w e d */ int ClientIpAllowed(int pos) { int ret = 0; uint8_t ij; uint8_t nm1, nm2, nm3, nm4; if ((accmsk1[4] != 0) && ((accmsk1[0] != 0) || (accmsk1[1] != 0) || (accmsk1[2] != 0) || (accmsk1[3] != 0))) { nm1 = nm2 = nm3 = nm4 = 0; for (ij = 0; ij < 8; ij ++) { nm1 = (nm1 << 1) | ((ij >= accmsk1[4]) ? 0 : 1); } for (ij = 8; ij < 16; ij ++) { nm2 = (nm2 << 1) | ((ij >= accmsk1[4]) ? 0 : 1); } for (ij = 16; ij < 24; ij ++) { nm3 = (nm3 << 1) | ((ij >= accmsk1[4]) ? 0 : 1); } for (ij = 24; ij < 32; ij ++) { nm4 = (nm4 << 1) | ((ij >= accmsk1[4]) ? 0 : 1); } if ( ((ethbuf[pos] & nm1) == accmsk1[0]) && ((ethbuf[pos+1] & nm2) == accmsk1[1]) && ((ethbuf[pos+2] & nm3) == accmsk1[2]) && ((ethbuf[pos+3] & nm4) == accmsk1[3])) { ret = 1; } // Ne satisfait pas le premier filtre, tester le second s'il existe else { if ((accmsk2[4] != 0) && ((accmsk2[0] != 0) || (accmsk2[1] != 0) || (accmsk2[2] != 0) || (accmsk2[3] != 0))) { nm1 = nm2 = nm3, nm4 = 0; for (ij = 0; ij < 8; ij ++) { nm1 = (nm1 << 1) | ((ij >= accmsk2[4]) ? 0 : 1); } for (ij = 8; ij < 16; ij ++) { nm2 = (nm2 << 1) | ((ij >= accmsk2[4]) ? 0 : 1); } for (ij = 16; ij < 24; ij ++) { nm3 = (nm3 << 1) | ((ij >= accmsk2[4]) ? 0 : 1); } for (ij = 24; ij < 32; ij ++) { nm4 = (nm4 << 1) | ((ij >= accmsk2[4]) ? 0 : 1); } if ( ((ethbuf[pos] & nm1) == accmsk2[0]) && ((ethbuf[pos+1] & nm2) == accmsk2[1]) && ((ethbuf[pos+2] & nm3) == accmsk2[2]) && ((ethbuf[pos+3] & nm4) == accmsk2[3])) { ret = 1; } } } } // Premier filtre non defini, acces libre else ret = 1; #ifdef SERIALDEBUG if (SerialDebug) { if (ret == 0) PrintSerialPgm(PSTR("Unauthorized access\n")); } #endif return ret; } /* ---------------------------------------------------------------- C r c 1 6 */ uint16_t Crc16(uint8_t *buff, uint8_t len) { uint8_t ij, ik, r1, r2, r3, r4; uint16_t result; r1 = *buff++; r2 = *buff++; for (ij = 0; ij < (len - 2); ij ++ ) { r3 = *buff++; for (ik = 0; ik < 8; ik ++ ) { r4 = r1; r1 = r1 << 1; if ((r2 & 0x80) != 0) r1++; r2 = r2 << 1; if ((r3 & 0x80) != 0) r2++; r3 = r3 << 1; if ((r4 & 0x80) != 0) { r1 = r1 ^ 0x10; r2 = r2 ^ 0x41; } } } result = r1; result = (result << 8) | r2; return result; } #ifdef ETHERNET_MODEECO /* ---------------------------------------------------- M o d e S t a n d b y Reevaluation du mode ECO */ int ModeStandby(void) { int ij, ik, im, nbval, ret; unsigned long somme; ret = modeEco; if (timeStatus() == timeSet) { // Recalcul du mode STANDBY tous les 1/4H if ((unsigned long) (millis() - t0Stanby) > 900000L) { if (modeEco >= ECOSTANDBY) { if (nbmes > 8) { t0Stanby = millis(); somme = 0L; ik = im = 0; nbval = min(nbmes, 10); // Tension batterie en 1/10e de Volt for (ij = 0; ij < nbval; ij ++) somme = somme + mbat[ij]; somme = (somme * 10) / nbval; // Indicateur de charge for (ij = 0; ij < nbval; ij ++) ik += mchg[ij]; // Indicateur SOC for (ij = 0; ij < nbval; ij ++) im = im + msoc[ij]; // Si batterie a plus de 13,2V et charge en cours et SOC > 64 // passer en mode ECOSTANDBY if ((somme >= stby_vhigh) && (ik == nbval) && (im >= (stby_soch * nbval))) { ret = ECOSTANDBY; } // Si batterie a moins 13V ou plus de charge en cours ou SOC < 58 // abandonner le mode ECOSTANDBY si on y etait if ((somme < stby_vlow) || (ik == 0) || (im < (stby_socl * nbval))) { if (modeEco == ECOSTANDBY) { ret = ECOSEARCH; // Forcer re-synchro DiffEco[0] = DiffEco[1] = DiffEco[2] = DiffEco[3] = 0L; } } } } } } // Garde fou : // Si on est en mode ECO active et qu'il y a un silence de plus de deux fois // la moyenne, on retourne en mode recherche de synchro. if (modeEco == ECORUNNING) { if (MoyennEco >= 120000L) { if ((unsigned long) (millis() - t0Eco) > (2 * MoyennEco)) { ret = ECOSEARCH; DiffEco[0] = DiffEco[1] = DiffEco[2] = DiffEco[3] = 0L; es.ES_enc28j60PowerUp(); t1Eco = millis(); State = NORMAL; } } } return ret; } #endif /* -------------------------------------------------- t o k e n C o m m a n d Recherche dychotomique du token de la commande */ int tokenCommand(char *buff) { static int ij, ik; int im, imin, imax, token, tokendone; // Si meme commande que la precedente, test rapide if (ik && (strncmp_P(buff, (char*)pgm_read_word(&(stokens[ij])), ik) == 0)) { token = ij + OFFSET_TOKENS; } else { imin = 0; imax = NBTOKENS - 1; tokendone = 0; token = 0; while ((tokendone == 0) && (imin <= imax)) { ij = (imin + imax) / 2; ik = strlen_P((char*)pgm_read_word(&(stokens[ij]))); // #ifdef SERIALDEBUG // Serial.print(ij); // Serial.print('('); // Serial.print(ik); // Serial.print(')'); // Serial.print(' '); // #endif im = strncmp_P(buff, (char*)pgm_read_word(&(stokens[ij])), ik); if (im == 0) { token = ij + OFFSET_TOKENS; tokendone = 1; } else { if (im > 0) imin = ij + 1; else imax = ij - 1; } } } return token; } /* ---------------------------------------------- P r i n t S e r i a l P g m */ void PrintSerialPgm(const char *cmsg) { byte b1; int ij = 0; while ((b1 = pgm_read_byte(&(cmsg[ij]))) != 0) { if (b1 == 10) Serial.println(); else Serial.write(b1); ij ++; }; } /* -------------------------------------------- T r a i t e m e n t _ h t t p */ int Traitement_http(void) { int done; uint16_t ij, ik; uint8_t a1; int token; // Passer toute la requete en majuscules for (ij = ethind; (ij < (ethind + 64)) && (ij <=ETH_SIZE); ij ++) { uint8_t ethChar; ethChar = ethbuf[ij]; if ((ethChar >= 'a') && (ethChar <= 'z')) { ethChar -= 32; ethbuf[ij] = ethChar; } } done = 0; if (strncmp("GET ",(char *)&(ethbuf[ethind]),4)!=0){ // head, post and other methods: ethind=http200ok(); ethind = es.ES_fill_tcp_data_p(ethbuf,ethind,PSTR("

200 OK

")); done = 1; } else { // just one web page in the "root directory" of the web server if (strncmp("/ ",(char *)&(ethbuf[ethind+4]),2)==0){ ethind=print_webpage(ethbuf); done = 1; } else { #ifdef SERIALDEBUG if (SerialDebug) PrintSerialPgm(PSTR("Command : ")); #endif token = tokenCommand((char *)&(ethbuf[ethind + 5])); #ifdef SERIALDEBUG if (SerialDebug) Serial.println(token); #endif switch (token) { #ifdef SERIALDEBUG case T_SERIALDEBUG : SerialDebug = (SerialDebug != 0) ? 0 : 1; if (SerialDebug) { PrintSerialPgm(PSTR("Mode debug\n")); } ethind = print_webpage(ethbuf); done = 1; break; #endif case T_MACA : // Modification et enregistrement de la configuration IP Config_Ip(MACADDRESS, ethind + 9); done = 1; break; case T_ADDR : Config_Ip(ADDRESS, ethind + 9); done = 1; break; case T_MASK : Config_Ip(MASK, ethind + 9); done = 1; break; case T_GATE : Config_Ip(GATEWAY, ethind + 9); done = 1; break; case T_PORT : Config_Ip(WWWPORT, ethind + 9); done = 1; break; case T_ACC : a1 = ethbuf[ethind + 8]; if (a1 == '1') Config_Ip(MASK_ACCESS_1, ethind + 9); if (a1 == '2') Config_Ip(MASK_ACCESS_2, ethind + 9); done = 1; break; case T_RESET : ethind = SendState(0); LndkRunRun = 0; done = 1; break; case T_LOAD : if (strncmp("?P1=", (char *)&(ethbuf[ethind + 9]), 4) == 0) { a1 = ethbuf[ethind + 13]; if (a1 == '0') LoadOnOff(0); if (a1 == '1') LoadOnOff(1); ethind = SendState(1); done = 1; } break; #ifdef ETHERNET_MODEECO case T_MODEECO : if (strncmp("?P1=", (char *)&(ethbuf[ethind + 12]), 4) == 0) { a1 = ethbuf[ethind + 16]; if (a1 == '0') modeEco = ECOSTOP; if (a1 == '1') modeEco = ECOSEARCH; if (a1 == '2') modeEco = ECOSEARCH; } ethind = SendState(1); done = 1; break; #endif case T_DUMP : ethind = SendState(1); done = 1; t0State = t1State = millis(); break; case T_MESURES : ethind = SendState(0); done = 1; t0State = t1State = millis(); break; case T_DELESTAGE : // Tester si parametre de duree if (strncmp("?P1=", (char *)&(ethbuf[ethind + 14]), 4) == 0) { ij=18; t0State = 0; // Cinq chiffres au maximum, maxi autorise 86400 a1 = ethbuf[ethind + ij++]; while (((a1 >= '0') && (a1 <= '9')) && (ij <= 23)) { t0State *= 10; t0State += (a1 - 48); a1 = (ij < 23) ? ethbuf[ethind + ij++] : 0; } } else { // Commande sans parametre if (ethbuf[ethind + 14] == ' ') t0State = 20L; } if ((t0State >= 20L) && (t0State <= 86400L)) { FinDelestage = now() + t0State; State = PREDELESTAGE1; } else t0State = 0L; ethind = SendDelestage(t0State); t0State = millis(); // Autres initialisations a effectuer, passer en mode ECO done = 1; break; case T_TIME : ethind = TimeCommand((char *) &(ethbuf[ethind + 9]), 1); done = 1; break; case T_UPTIME : ethind = TimeCommand((char *) &(ethbuf[ethind + 9]), 0); done = 1; break; case T_MEMORY : ethind = SendMem(); done = 1; break; case T_VOLTAGE : ethind = SendVolt(0); done = 1; break; case T_VINMAX : ethind = SetBaseVin((char *) &(ethbuf[ethind + 11])); done = 1; break; case T_EEPROM : // Lecture de l'Eeprom par page de 128 octets ik = 0; if (strncmp("?P1=", (char *)&(ethbuf[ethind + 11]), 4) == 0) { ij = 15; a1 = ethbuf[ethind + ij++]; while (((a1 >= '0') && (a1 <= '9')) && (ij <= 17)) { ik = (ik * 10) + (a1 - 48); a1 = ethbuf[ethind + ij++]; } if ((ik < 0) || (ik > 15)) ik = 0; } ethind = Valeurs_Eeprom(ik); done = 1; break; #ifdef SEND_TRACER_COMMAND case T_PARAM : // QueryParam(); // Commande pourrie State = WAITING_SERIAL; break; #endif case T_STANDBY : ethind = SetStandbyParam((char *)&(ethbuf[ethind + 12])); done = 1; break; // Les commandes qui restent a implementer case T_HISTO : case T_CMD : case T_WATCHDOG : done = 1; break; } } } return done; } /* ------------------------------------------------------------ f r e e R a m */ int freeRam () { extern int __heap_start, *__brkval; int v; return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval); } /* ------------------------------------------------------------ r e a d V c c */ long readVcc() { long result; // Read 1.1V reference against AVcc ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1); delay(2); // Wait for Vref to settle ADCSRA |= _BV(ADSC); // Convert while (bit_is_set(ADCSRA,ADSC)); result = ADCL; result |= ADCH<<8; result = 1126400L / result; // Back-calculate AVcc in mV return result; } /* ---------------------------------------------------------------- s e t u p */ void setup() { uint8_t ij; int sensorValue = 0; // variable to store the value coming from the sensor unsigned long tension; // Bouton mode maintenance pinMode(BUTTON, INPUT); pinMode(9, OUTPUT); // Lecture configuration IPV4 en EEPROM desactivee si bouton BUT enfonce if ((digitalRead(BUTTON)) == HIGH) Lire_Configs(); // Detection du mode d'alimentation // Le pont diviseur 4,7k + 22k sur A7 est lu 5 fois sensorValue = 0; analogRead(A7); for (ij = 0; ij < 5; ij ++) { delay(10); sensorValue += analogRead(A7); } tension = sensorValue; tension *= baseVin; tension /= (1024 * ij); // Si la tension est superieure a ~2V, le montage est alimente par Vin // et corollaire le port USB n'est pas branche, pas sur ... if ((sensorValue / ij) > 100) VinOn = 1; // Initialisation du port serie Serial.begin(9600); while (!Serial) { ; // wait for serial port to connect. Needed for Leonardo only } // Banner PrintSerialPgm(banner); // Initialise SPI interface es.ES_enc28j60SpiInit(); #ifdef ETHERNET_MODEECO // Forcer mode up, au cas ou il serait down es.ES_enc28j60PowerUp(); #endif // initialize enc28j60 #ifdef SERIALDEBUG PrintSerialPgm(PSTR("Init ENC28J60 ...")); #endif es.ES_enc28j60Init(mymac); #ifdef SERIALDEBUG PrintSerialPgm(PSTR("done\n")); #endif // initialize enc28j60 // es.ES_enc28j60Init(mymac); #ifdef LNDK_DHCP_CLIENT #ifdef SERIALDEBUG PrintSerialPgm(PSTR("ENC28J60 version ")); Serial.println( es.ES_enc28j60Revision(), HEX); if( es.ES_enc28j60Revision() <= 0 ) PrintSerialPgm(PSTR("Failed to access ENC28J60\n")); #endif // Si client DHCP if ((myip[0] == 0x00) && (myip[1] == 0x00) && (myip[2] == 0x00) && (myip[3] == 0x00)) { if (! VinOn) PrintSerialPgm(PSTR("Requesting IP Address ... ")); // Get IP Address details if ( es.allocateIPAddress(ethbuf, ETH_SIZE, mymac, 80, myip, netmask, gwip, dhcpsvrip, mydnsip ) > 0 ) { if (! VinOn) PrintSerialPgm(PSTR("done\n")); } else { if (! VinOn) PrintSerialPgm(PSTR("back to 192.168.1.25\n")); myip[0] = 192; myip[1] = 168; myip[2] = 1; myip[3] = 25; gwip[0] = 192; gwip[1] = 168; gwip[2] = 1; gwip[3] = 1; } } #endif es.ES_init_ip_arp_udp_tcp(mymac,myip, myport); es.ES_client_set_gwip(gwip); PrintSerialPgm(PSTR("Free memory: ")); Serial.print(freeRam()); PrintSerialPgm(PSTR(" bytes, Vcc: ")); Serial.print(readVcc()); PrintSerialPgm(PSTR(" mV, Vin: ")); Serial.print( tension, DEC ); PrintSerialPgm(PSTR(" mV\n")); if (! VinOn) { PrintSerialPgm(PSTR("IP address: ")); printIP( myip ); PrintSerialPgm(PSTR(", Netmask: ")); printIP( netmask ); PrintSerialPgm(PSTR(", Gateway: ")); printIP( gwip ); Serial.println(); } // Serial.println(millis()); msgind = 0; serind = 0; #ifdef ETHERNET_MODEECO DiffEco[0] = DiffEco[1] = DiffEco[2] = DiffEco[3] = 0L; t0Eco = t1Eco = millis(); #endif // Forcer mise en ligne de la charge sur un RESET LoadOnOff(1); State = NORMAL; // Sur un RESET, reinitialiser les buffers et arreter le mode DEBUG if (! LndkRunRun) { for (ij = 0; ij < 10; ij ++) { mbat[ij] = msol[ij] = mcon[ij] = modv[ij] = mbfv[ij] = mcur[ij] = 0; mlod[ij] = movl[ij] = mlsc[ij] = msoc[ij] = mbol[ij] = 0; mbod[ij] = mful[ij] = mchg[ij] = mtmp[ij] = 0; } nbmes = 0; SerialDebug = 0; } LndkRunRun = 1; } /* ------------------------------------------------------------------ l o o p */ void loop() { static uint16_t ij; static int done; static unsigned long t1 = 0L, t2 = 0L; while (LndkRunRun) { switch (State) { case NORMAL : // read packet, handle ping and wait for a tcp packet: ethind=es.ES_packetloop_icmp_tcp(ethbuf,es.ES_enc28j60PacketReceive(ETH_SIZE, ethbuf)); /* ethind will be unequal to zero if there is a valid http get */ if (ethind != 0) { #ifdef SERIALDEBUG if (SerialDebug) { PrintSerialPgm(PSTR("Src address : ")); for (ij = SRC_IP_POSITION; ij < (SRC_IP_POSITION + 4); ij ++) { Serial.print(ethbuf[ij]); // if (ij < 29) Serial.print("."); else Serial.println(); if (ij < 29) Serial.print("."); else Serial.print(", "); } } #endif if (ClientIpAllowed(SRC_IP_POSITION)) { done = Traitement_http(); if ((done == 0) && (State == 0)) { ethind=es.ES_fill_tcp_data_p(ethbuf, 0, err401); done = 1; } } } #ifdef ETHERNET_MODEECO else { // Silence pas de paquet recu if (modeEco == ECORUNNING) { unsigned long deltaEco, relanceEco; // Silence de plus de 5% de l'intervalle entre chaque paquet deltaEco = max(10000L, (MoyennEco / 20L)); relanceEco = MoyennEco / 12L; // t0Eco : date envoi dernier paquet // t1Eco : date reveil interface if (((unsigned long)(millis() - t0Eco) > deltaEco) && ((unsigned long)(millis() - t1Eco) > relanceEco)) { // Si une reponse de faite depuis le dernier reveil de l'interface if ((t0Eco - t1Eco) > 0L) { t0State = millis(); es.ES_enc28j60PowerDown(); DureeDelestage = MoyennEco - (2 * deltaEco); State = ETHERNET_DOWN; } // Sinon reactiver le mode recherche else modeEco = ECOSEARCH; } } } #endif break; case WAITING_SERIAL : t1State = millis(); // Pour une demande de mesure, on attend pendant 250ms la reponse if ((t1State - t0State) > 150) { ethind = SendState(1); done = 1; State = NORMAL; } break; case PREDELESTAGE1 : // Pour une demande de mesure, on attend pendant 250ms la reponse if ((unsigned long)(millis() - t0State) >= 250L) { ethind = SendState(1); done = 1; State = PREDELESTAGE2; } break; case PREDELESTAGE2 : // Laisser le temps pour la reponse ethernet if ((unsigned long)(millis() - t0State) >= 500L) { #ifdef ETHERNET_MODEECO es.ES_enc28j60PowerDown(); if (modeEco != ECOSTOP) modeEco = ECOSEARCH; #endif State = DELESTAGE; // Envoi de la commande de coupure de l'utilisation LoadOnOff(0); #ifdef SERIALDEBUG if (SerialDebug > 0) PrintSerialPgm(PSTR("Mode delestage\n")); #endif } break; case DELESTAGE : // Mise a jour historique en Eeprom toutes les heures if (now() > FinDelestage) { // Fin de delestage, envoyer la commande de retablissement de la charge LoadOnOff(1); #ifdef ETHERNET_MODEECO es.ES_enc28j60PowerUp(); t1Eco = millis(); #endif #ifdef SERIALDEBUG if (SerialDebug > 0) PrintSerialPgm(PSTR("Fin delestage\n")); #endif State = NORMAL; } break; case ETHERNET_DOWN : if ((unsigned long)(millis() - t0State) >= DureeDelestage) { // Faire une verification du LOAD ON avant de declarer le State a 0 ? #ifdef ETHERNET_MODEECO es.ES_enc28j60PowerUp(); t1Eco = millis(); #endif State = NORMAL; } break; } // End switch State // Toutes les 30 secondes, commande d'interrogation des valeurs du regulateur if ((unsigned long)(millis() - t0Mesure) > 30000L) { if (State != WAITING_SERIAL) { // Envoi de la commande d'interrogation des registres au regulateur #ifdef SEND_TRACER_COMMAND Send_Tracer_Command(0xA0, 0x00); #else Write_Preambule(); Serial.write(0x01); // Adresse Serial.write(0xA0); // Commande Serial.write(0x00); // Longueur de donnees Serial.write(0x6F); // Check sum Serial.write(0x52); // Check sum Serial.write(0x7F); #endif serind = 0; t0Mesure = millis() / 30000L; t0Mesure *= 30000L; } } // --------------------------------------------------------------- // Reponse HTTP a faire // --------------------------------------------------------------- if (done == 1) { #ifdef ETHERNET_MODEECO // Memoriser la duree des intervalles des cinq derniers acces // Plusieurs requetes à se suivre ne comptent que pour une if ((unsigned long) (millis() - t0Eco) > 10000L) { for (ij = 3; ij > 0; ij --) DiffEco[ij] = DiffEco[ij - 1]; DiffEco[0] = (millis() - t0Eco); } t0Eco = millis(); if (modeEco > ECOSTANDBY) { if ((DiffEco[0] != 0L) && (DiffEco[1] != 0L) && (DiffEco[2] != 0L) && (DiffEco[3] != 0L)) { unsigned long minEco, maxEco, deltaEco; MoyennEco = (DiffEco[0] + DiffEco[1] + DiffEco[2] + DiffEco[3]) / 4L; // Si intervalle moyen compris entre 2mn et 2H (Cacti : 5mn) if ((MoyennEco >= 120000L) && (MoyennEco <= 7200000L)) { deltaEco = MoyennEco / 20L; minEco = MoyennEco - deltaEco; maxEco = MoyennEco + deltaEco; // Si les ecarts sont stables, on active le mode ECO if ( (DiffEco[0] >= minEco) && (DiffEco[0] <= maxEco) && (DiffEco[1] >= minEco) && (DiffEco[1] <= maxEco) && (DiffEco[2] >= minEco) && (DiffEco[2] <= maxEco) && (DiffEco[3] >= minEco) && (DiffEco[3] <= maxEco)) { if (modeEco == ECOSEARCH) { modeEco = ECORUNNING; #ifdef SERIALDEBUG if (SerialDebug) { PrintSerialPgm(PSTR("Mode ECO ")); Serial.print(MoyennEco); PrintSerialPgm(PSTR(" ms\n")); } #endif } } else EcoRunning2Search(); } else EcoRunning2Search(); } } #endif es.ES_www_server_reply(ethbuf,ethind); // send web page data for (ij = 0; ij < ETH_SIZE; ij ++) ethbuf[ij] = 0; done = 0; } #ifdef ETHERNET_MODEECO modeEco = ModeStandby(); #endif // --------------------------------------------------------------- // Traitement du bouton BUT, mise en mode maintenance // --------------------------------------------------------------- ij = digitalRead(BUTTON); if (ij == LOW) { if (t1 == 0) { t1 = millis(); } else { t2 = millis(); } } else { // Tester si relachement du bouton if ((t1 > 0) && (t2 > 0)) { if (((t2 - t1) >= 750L) && ((t2 - t1) <= 2000L)) { FlagMaintenance = (FlagMaintenance == LOW) ? HIGH : LOW; digitalWrite(9, FlagMaintenance); if (FlagMaintenance == HIGH) { if (! VinOn) PrintSerialPgm(PSTR("Maintenance\n")); State = MAINTENANCE; #ifdef ETHERNET_MODEECO es.ES_enc28j60PowerDown(); // Avant sleep mode, 8mA de consommation #endif } else { if (! VinOn) PrintSerialPgm(PSTR("Wake up\n")); // Annulation d'un delestage eventuel LoadOnOff(1); #ifdef ETHERNET_MODEECO es.ES_enc28j60PowerUp(); #endif State = NORMAL; } } } t1 = t2 = 0; } // --------------------------------------------------------------- // Traitement des caracteres recus sur le port serie // --------------------------------------------------------------- while (Serial.available() > 0) { // get the new byte: char *ptr; ptr = (char *) &serbuf; ptr = ptr + serind; if (Serial.readBytes(ptr, 1) == 1) { serind++; } if ((serind == 36) && (serbuf[35] == 0x7F)) Mesures_Tracer(); if (serind >= SER_SIZE) serind = 0; } } // Reinitialisation par la commande RESET setup(); } /* -------- End of Lndk_Avra08.ino ------ That's All, Folks -------- */