/* tool to read power production data for SMA solar power convertors Copyright Wim Hofman 2010 Copyright Stephen Collier 2010,2011 This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ /* compile gcc -lbluetooth -lcurl -lmysqlclient -g -o smatool smatool.c */ #define _XOPEN_SOURCE /* glibc needs this */ #include #include #include #include #include #include #include #include #include #include #include #include #include "repost.h" #include "sma_mysql.h" /* * u16 represents an unsigned 16-bit number. Adjust the typedef for * your hardware. */ typedef u_int16_t u16; #define PPPINITFCS16 0xffff /* Initial FCS value */ #define PPPGOODFCS16 0xf0b8 /* Good final FCS value */ #define ASSERT(x) assert(x) #define SCHEMA "2" /* Current database schema */ #define _XOPEN_SOURCE /* glibc2 needs this */ typedef struct{ unsigned int key1; unsigned int key2; char description[20]; char units[20]; float divisor; } ReturnType; char *accepted_strings[] = { "$END", "$ADDR", "$TIME", "$SER", "$CRC", "$POW", "$DTOT", "$ADD2", "$CHAN", "$ITIME", "$TMMI", "$TMPL", "$TIMESTRING", "$TIMEFROM1", "$TIMETO1", "$TIMEFROM2", "$TIMETO2", "$TESTDATA", "$ARCHIVEDATA1", "$PASSWORD", "$SIGNAL", "$UNKNOWN", "$INVCODE", "$ARCHCODE", "$INVERTERDATA", "$CNT", /*Counter of sent packets*/ "$TIMEZONE", /*Timezone seconds +1 from GMT*/ "$TIMESET" /*Unknown string involved in time setting*/ }; int cc,debug = 0,verbose=0; unsigned char fl[1024] = { 0 }; static u16 fcstab[256] = { 0x0000, 0x1189, 0x2312, 0x329b, 0x4624, 0x57ad, 0x6536, 0x74bf, 0x8c48, 0x9dc1, 0xaf5a, 0xbed3, 0xca6c, 0xdbe5, 0xe97e, 0xf8f7, 0x1081, 0x0108, 0x3393, 0x221a, 0x56a5, 0x472c, 0x75b7, 0x643e, 0x9cc9, 0x8d40, 0xbfdb, 0xae52, 0xdaed, 0xcb64, 0xf9ff, 0xe876, 0x2102, 0x308b, 0x0210, 0x1399, 0x6726, 0x76af, 0x4434, 0x55bd, 0xad4a, 0xbcc3, 0x8e58, 0x9fd1, 0xeb6e, 0xfae7, 0xc87c, 0xd9f5, 0x3183, 0x200a, 0x1291, 0x0318, 0x77a7, 0x662e, 0x54b5, 0x453c, 0xbdcb, 0xac42, 0x9ed9, 0x8f50, 0xfbef, 0xea66, 0xd8fd, 0xc974, 0x4204, 0x538d, 0x6116, 0x709f, 0x0420, 0x15a9, 0x2732, 0x36bb, 0xce4c, 0xdfc5, 0xed5e, 0xfcd7, 0x8868, 0x99e1, 0xab7a, 0xbaf3, 0x5285, 0x430c, 0x7197, 0x601e, 0x14a1, 0x0528, 0x37b3, 0x263a, 0xdecd, 0xcf44, 0xfddf, 0xec56, 0x98e9, 0x8960, 0xbbfb, 0xaa72, 0x6306, 0x728f, 0x4014, 0x519d, 0x2522, 0x34ab, 0x0630, 0x17b9, 0xef4e, 0xfec7, 0xcc5c, 0xddd5, 0xa96a, 0xb8e3, 0x8a78, 0x9bf1, 0x7387, 0x620e, 0x5095, 0x411c, 0x35a3, 0x242a, 0x16b1, 0x0738, 0xffcf, 0xee46, 0xdcdd, 0xcd54, 0xb9eb, 0xa862, 0x9af9, 0x8b70, 0x8408, 0x9581, 0xa71a, 0xb693, 0xc22c, 0xd3a5, 0xe13e, 0xf0b7, 0x0840, 0x19c9, 0x2b52, 0x3adb, 0x4e64, 0x5fed, 0x6d76, 0x7cff, 0x9489, 0x8500, 0xb79b, 0xa612, 0xd2ad, 0xc324, 0xf1bf, 0xe036, 0x18c1, 0x0948, 0x3bd3, 0x2a5a, 0x5ee5, 0x4f6c, 0x7df7, 0x6c7e, 0xa50a, 0xb483, 0x8618, 0x9791, 0xe32e, 0xf2a7, 0xc03c, 0xd1b5, 0x2942, 0x38cb, 0x0a50, 0x1bd9, 0x6f66, 0x7eef, 0x4c74, 0x5dfd, 0xb58b, 0xa402, 0x9699, 0x8710, 0xf3af, 0xe226, 0xd0bd, 0xc134, 0x39c3, 0x284a, 0x1ad1, 0x0b58, 0x7fe7, 0x6e6e, 0x5cf5, 0x4d7c, 0xc60c, 0xd785, 0xe51e, 0xf497, 0x8028, 0x91a1, 0xa33a, 0xb2b3, 0x4a44, 0x5bcd, 0x6956, 0x78df, 0x0c60, 0x1de9, 0x2f72, 0x3efb, 0xd68d, 0xc704, 0xf59f, 0xe416, 0x90a9, 0x8120, 0xb3bb, 0xa232, 0x5ac5, 0x4b4c, 0x79d7, 0x685e, 0x1ce1, 0x0d68, 0x3ff3, 0x2e7a, 0xe70e, 0xf687, 0xc41c, 0xd595, 0xa12a, 0xb0a3, 0x8238, 0x93b1, 0x6b46, 0x7acf, 0x4854, 0x59dd, 0x2d62, 0x3ceb, 0x0e70, 0x1ff9, 0xf78f, 0xe606, 0xd49d, 0xc514, 0xb1ab, 0xa022, 0x92b9, 0x8330, 0x7bc7, 0x6a4e, 0x58d5, 0x495c, 0x3de3, 0x2c6a, 0x1ef1, 0x0f78 }; /* * Calculate a new fcs given the current fcs and the new data. */ u16 pppfcs16(u16 fcs, void *_cp, int len) { register unsigned char *cp = (unsigned char *)_cp; /* don't worry about the efficiency of these asserts here. gcc will * recognise that the asserted expressions are constant and remove them. * Whether they are usefull is another question. */ ASSERT(sizeof (u16) == 2); ASSERT(((u16) -1) > 0); while (len--) fcs = (fcs >> 8) ^ fcstab[(fcs ^ *cp++) & 0xff]; return (fcs); } /* * Strip escapes (7D) as they aren't includes in fcs */ void strip_escapes(unsigned char *cp, int *len) { int i,j; for( i=0; i<(*len); i++ ) { if( cp[i] == 0x7d ) { /*Found escape character. Need to convert*/ cp[i] = cp[i+1]^0x20; for( j=i+1; j<(*len)-1; j++ ) cp[j] = cp[j+1]; (*len)--; } } } /* * Add escapes (7D) as they are required */ void add_escapes(unsigned char *cp, int *len) { int i,j; for( i=19; i<(*len); i++ ) { switch( cp[i] ) { case 0x7d : case 0x7e : case 0x11 : case 0x12 : case 0x13 : for( j=(*len); j>i; j-- ) cp[j] = cp[j-1]; cp[i+1] = cp[i]^0x20; cp[i]=0x7d; (*len)++; } } } /* * Recalculate and update length to correct for escapes */ void fix_length_send(unsigned char *cp, int *len) { int delta=0; if( debug == 1 ) printf( "sum=%x\n", cp[1]+cp[3] ); if(( cp[1] != (*len)+1 )) { delta = (*len)+1 - cp[1]; if( debug == 1 ) { printf( " length change from %x to %x diff=%x \n", cp[1],(*len)+1,cp[1]+cp[3] ); } cp[3] = (cp[1]+cp[3])-((*len)+1); cp[1] =(*len)+1; switch( cp[1] ) { case 0x3a: cp[3]=0x44; break; case 0x3b: cp[3]=0x43; break; case 0x3c: cp[3]=0x42; break; case 0x3d: cp[3]=0x41; break; case 0x3e: cp[3]=0x40; break; case 0x3f: cp[3]=0x41; break; case 0x40: cp[3]=0x3e; break; case 0x41: cp[3]=0x3f; break; case 0x42: cp[3]=0x3c; break; case 0x52: cp[3]=0x2c; break; case 0x53: cp[3]=0x2b; break; case 0x54: cp[3]=0x2a; break; case 0x55: cp[3]=0x29; break; case 0x56: cp[3]=0x28; break; case 0x57: cp[3]=0x27; break; case 0x58: cp[3]=0x26; break; case 0x59: cp[3]=0x25; break; case 0x5a: cp[3]=0x24; break; case 0x5b: cp[3]=0x23; break; case 0x5c: cp[3]=0x22; break; case 0x5d: cp[3]=0x23; break; case 0x5e: cp[3]=0x20; break; case 0x5f: cp[3]=0x21; break; case 0x60: cp[3]=0x1e; break; case 0x61: cp[3]=0x1f; break; case 0x62: cp[3]=0x1e; break; default: printf( "NO CONVERSION!" );getchar();break; } if( debug == 1 ) printf( "new sum=%x\n", cp[1]+cp[3] ); } } /* * Recalculate and update length to correct for escapes */ void fix_length_received(unsigned char *received, int *len) { int delta=0; int sum; if( received[1] != (*len) ) { sum = received[1]+received[3]; if (debug == 1) printf( "sum=%x", sum ); delta = (*len) - received[1]; if (debug == 1) printf( "length change from %x to %x\n", received[1], (*len) ); if(( received[3] != 0x13 )&&( received[3] != 0x14 )) { received[1] = (*len); switch( received[1] ) { case 0x52: received[3]=0x2c; break; case 0x5a: received[3]=0x24; break; case 0x66: received[3]=0x1a; break; case 0x6a: received[3]=0x14; break; default: received[3]=sum-received[1]; break; } } } } /* * How to use the fcs */ void tryfcs16(unsigned char *cp, int len) { u16 trialfcs; unsigned int i; unsigned char stripped[1024] = { 0 }; memcpy( stripped, cp, len ); /* add on output */ if (debug ==2){ printf("String to calculate FCS\n"); for (i=0;i> 8) & 0x00ff); cc+=2; if (debug == 2 ){ printf("FCS = %x%x %x\n",(trialfcs & 0x00ff),((trialfcs >> 8) & 0x00ff), trialfcs); } } unsigned char conv(char *nn) { unsigned char tt=0,res=0; int i; for(i=0;i<2;i++){ switch(nn[i]){ case 65: /*A*/ case 97: /*a*/ tt = 10; break; case 66: /*B*/ case 98: /*b*/ tt = 11; break; case 67: /*C*/ case 99: /*c*/ tt = 12; break; case 68: /*D*/ case 100: /*d*/ tt = 13; break; case 69: /*E*/ case 101: /*e*/ tt = 14; break; case 70: /*F*/ case 102: /*f*/ tt = 15; break; default: tt = nn[i] - 48; } res = res + (tt * pow(16,1-i)); } return res; } int check_send_error( ConfType * conf, int *s, int *rr, unsigned char *received, int cc, unsigned char *last_sent, int *terminated, int *already_read ) { int bytes_read,i,j; unsigned char buf[1024]; /*read buffer*/ unsigned char header[3]; /*read buffer*/ struct timeval tv; fd_set readfds; tv.tv_sec = 0; // set timeout of reading tv.tv_usec = 5000; memset(buf,0,1024); FD_ZERO(&readfds); FD_SET((*s), &readfds); select((*s)+1, &readfds, NULL, NULL, &tv); (*terminated) = 0; // Tag to tell if string has 7e termination // first read the header to get the record length if (FD_ISSET((*s), &readfds)){ // did we receive anything within 5 seconds bytes_read = recv((*s), header, sizeof(header), 0); //Get length of string (*rr) = 0; for( i=0; i 0){ if (debug == 1) printf("\nReceiving\n"); if (debug == 1){ printf( " %08x: .. .. .. .. .. .. .. .. .. .. .. .. ", 0 ); j=12; for( i=0; ibt_timeout; // set timeout of reading tv.tv_usec = 0; memset(buf,0,1024); FD_ZERO(&readfds); FD_SET((*s), &readfds); select((*s)+1, &readfds, NULL, NULL, &tv); (*terminated) = 0; // Tag to tell if string has 7e termination // first read the header to get the record length if (FD_ISSET((*s), &readfds)){ // did we receive anything within 5 seconds bytes_read = recv((*s), header, sizeof(header), 0); //Get length of string (*rr) = 0; for( i=0; i 0){ if (debug == 1) printf("\nReceiving\n"); if (debug == 1){ printf( " %08x: .. .. .. .. .. .. .. .. .. .. .. .. ", 0 ); j=12; for( i=0; itm_mday; month = loctime->tm_mon +1; year = loctime->tm_year + 1900; hour = loctime->tm_hour; minute = loctime->tm_min; isdst = loctime->tm_isdst; utctime = gmtime(&curtime); if( debug == 1 ) printf( "utc=%04d-%02d-%02d %02d:%02d local=%04d-%02d-%02d %02d:%02d diff %d hours\n", utctime->tm_year+1900, utctime->tm_mon+1,utctime->tm_mday,utctime->tm_hour,utctime->tm_min, year, month, day, hour, minute, hour-utctime->tm_hour ); localOffset=(hour-utctime->tm_hour)+(minute-utctime->tm_min)/60; if( debug == 1 ) printf( "localOffset=%f\n", localOffset ); if(( year > utctime->tm_year+1900 )||( month > utctime->tm_mon+1 )||( day > utctime->tm_mday )) localOffset+=24; if(( year < utctime->tm_year+1900 )||( month < utctime->tm_mon+1 )||( day < utctime->tm_mday )) localOffset-=24; if( debug == 1 ) printf( "localOffset=%f isdst=%d\n", localOffset, isdst ); if( isdst > 0 ) localOffset=localOffset-1; tzsecs = (localOffset) * 3600 + 1; if( tzsecs < 0 ) tzsecs=65536+tzsecs; if( debug == 1 ) printf( "tzsecs=%x %d\n", tzsecs, tzsecs ); tzhex[1] = tzsecs/256; tzhex[0] = tzsecs -(tzsecs/256)*256; if( debug == 1 ) printf( "tzsecs=%02x %02x\n", tzhex[1], tzhex[0] ); return tzhex; } int auto_set_dates( ConfType * conf, int * daterange, int mysql, char * datefrom, char * dateto ) /* If there are no dates set - get last updated date and go from there to NOW */ { MYSQL_ROW row; char SQLQUERY[200]; time_t curtime; int day,month,year,hour,minute,second; struct tm *loctime; if( mysql == 1 ) { OpenMySqlDatabase( conf->MySqlHost, conf->MySqlUser, conf->MySqlPwd, conf->MySqlDatabase); // Gor - Remove illegal values first! sprintf(SQLQUERY,"DELETE FROM DayData WHERE CurrentPower < 0 or ETotalToday = 9999999.999" ); if (debug == 1) printf("%s\n",SQLQUERY); DoQuery(SQLQUERY); //Get last updated value sprintf(SQLQUERY,"SELECT DATE_FORMAT( DateTime, \"%%Y-%%m-%%d %%H:%%i:%%S\" ) FROM DayData ORDER BY DateTime DESC LIMIT 1" ); if (debug == 1) printf("%s\n",SQLQUERY); DoQuery(SQLQUERY); if ((row = mysql_fetch_row(res))) //if there is a result, update the row { strcpy( datefrom, row[0] ); } mysql_free_result( res ); mysql_close(conn); } if( strlen( datefrom ) == 0 ) strcpy( datefrom, "2000-01-01 00:00:00" ); curtime = time(NULL); //get time in seconds since epoch (1/1/1970) loctime = localtime(&curtime); day = loctime->tm_mday; month = loctime->tm_mon +1; year = loctime->tm_year + 1900; hour = loctime->tm_hour; minute = loctime->tm_min; second = loctime->tm_sec; sprintf( dateto, "%04d-%02d-%02d %02d:%02d:00", year, month, day, hour, minute ); (*daterange)=1; if( verbose == 1 ) printf( "Auto set dates from %s to %s\n", datefrom, dateto ); return 1; } int is_light( ConfType * conf ) /* Check if all data done and past sunset or before sunrise */ { int light=1; MYSQL_ROW row; char SQLQUERY[200]; OpenMySqlDatabase( conf->MySqlHost, conf->MySqlUser, conf->MySqlPwd, conf->MySqlDatabase); //Get Start of day value sprintf(SQLQUERY,"SELECT if(sunrise < NOW(),1,0) FROM Almanac WHERE date= DATE_FORMAT( NOW(), \"%%Y-%%m-%%d\" ) " ); if (debug == 1) printf("%s\n",SQLQUERY); DoQuery(SQLQUERY); if ((row = mysql_fetch_row(res))) //if there is a result, update the row { if( atoi( (char *)row[0] ) == 0 ) light=0; } if( light ) { sprintf(SQLQUERY,"SELECT if( dd.datetime > al.sunset,1,0) FROM DayData as dd left join Almanac as al on al.date=DATE(dd.datetime) and al.date=DATE(NOW()) WHERE 1 ORDER BY dd.datetime DESC LIMIT 1" ); if (debug == 1) printf("%s\n",SQLQUERY); DoQuery(SQLQUERY); if ((row = mysql_fetch_row(res))) //if there is a result, update the row { if( atoi( (char *)row[0] ) == 1 ) light=0; } } if (debug == 1) printf("Before close\n",SQLQUERY); mysql_close(conn); return light; } //Set a value depending on inverter void SetInverterType( ConfType * conf ) { if( strcmp(conf->Inverter, "1700TL") == 0 ) { conf->InverterCode[0] = 0x12; conf->InverterCode[1] = 0x1a; conf->InverterCode[2] = 0xd9; conf->InverterCode[3] = 0x38; conf->ArchiveCode = 0x63; } if( strcmp(conf->Inverter, "2100TL") == 0 ) { conf->InverterCode[0] = 0x17; conf->InverterCode[1] = 0x97; conf->InverterCode[2] = 0x51; conf->InverterCode[3] = 0x38; conf->ArchiveCode = 0x63; } if( strcmp(conf->Inverter, "3000TL") == 0 ) { conf->InverterCode[0] = 0x12; conf->InverterCode[1] = 0x1a; conf->InverterCode[2] = 0xd9; conf->InverterCode[3] = 0x38; conf->InverterCode[0] = 0x32; conf->InverterCode[1] = 0x42; conf->InverterCode[2] = 0x85; conf->InverterCode[3] = 0x38; conf->ArchiveCode = 0x71; } if( strcmp(conf->Inverter, "3000TLHF") == 0 ) { conf->InverterCode[0] = 0x1b; conf->InverterCode[1] = 0xb1; conf->InverterCode[2] = 0xa6; conf->InverterCode[3] = 0x38; conf->ArchiveCode = 0x83; } if( strcmp(conf->Inverter, "4000TL") == 0 ) { conf->InverterCode[0] = 0x78; conf->InverterCode[1] = 0x21; conf->InverterCode[2] = 0xbf; conf->InverterCode[3] = 0x3a; conf->ArchiveCode = 0x4e; } if( strcmp(conf->Inverter, "5000TL") == 0 ) { conf->InverterCode[0] = 0x3f; conf->InverterCode[1] = 0x10; conf->InverterCode[2] = 0xfb; conf->InverterCode[3] = 0x39; conf->ArchiveCode = 0x4e; } if( strcmp(conf->Inverter, "7000") == 0 ) { conf->InverterCode[0] = 0xcf; conf->InverterCode[1] = 0x84; conf->InverterCode[2] = 0x84; conf->InverterCode[3] = 0x3a; conf->ArchiveCode = 0x63; } if( strcmp(conf->Inverter, "10000TL") == 0 ) { conf->InverterCode[0] = 0x69; conf->InverterCode[1] = 0x45; conf->InverterCode[2] = 0x32; conf->InverterCode[3] = 0x39; conf->ArchiveCode = 0x80; } if( strcmp(conf->Inverter, "XXXXTL") == 0 ) { conf->InverterCode[0] = 0x99; conf->InverterCode[1] = 0x35; conf->InverterCode[2] = 0x40; conf->InverterCode[3] = 0x36; conf->ArchiveCode = 0x4e; } } //Convert a recieved string to a value long ConvertStreamtoLong( unsigned char * stream, int length, long unsigned int * value ) { int i, nullvalue; (*value) = 0; nullvalue = 1; for( i=0; i < length; i++ ) { if( stream[i] != 0xff ) //check if all ffs which is a null value nullvalue = 0; (*value) = (*value) + stream[i]*pow(256,i); } if( nullvalue == 1 ) (*value) = 0; //Asigning null to 0 at this stage unless it breaks something return (*value); } //Convert a recieved string to a value float ConvertStreamtoFloat( unsigned char * stream, int length, float * value ) { int i, nullvalue; (*value) = 0; nullvalue = 1; for( i=0; i < length; i++ ) { if( stream[i] != 0xff ) //check if all ffs which is a null value nullvalue = 0; (*value) = (*value) + stream[i]*pow(256,i); } if( nullvalue == 1 ) (*value) = 0; //Asigning null to 0 at this stage unless it breaks something return (*value); } //read return value data from init file ReturnType * InitReturnKeys( ConfType * conf, ReturnType * returnkeylist, int * num_return_keys ) { FILE *fp; char line[400]; ReturnType tmp; int i, j, reading, data_follows; data_follows = 0; fp=fopen(conf->File,"r"); while (!feof(fp)){ if (fgets(line,400,fp) != NULL){ //read line from smatool.conf if( line[0] != '#' ) { if( strncmp( line, ":unit conversions", 17 ) == 0 ) data_follows = 1; if( strncmp( line, ":end unit conversions", 21 ) == 0 ) data_follows = 0; if( data_follows == 1 ) { tmp.key1=0x0; tmp.key2=0x0; strcpy( tmp.description, "" ); //Null out value strcpy( tmp.units, "" ); //Null out value tmp.divisor=0; reading=0; if( sscanf( line, "%x %x", &tmp.key1, &tmp.key2 ) == 2 ) { j=0; for( i=6; line[i]!='\0'; i++ ) { if(( line[i] == '"' )&&(reading==1)) { tmp.description[j]='\0'; break; } if( reading == 1 ) { tmp.description[j] = line[i]; j++; } if(( line[i] == '"' )&&(reading==0)) reading = 1; } if( sscanf( line+i+1, "%s %f", tmp.units, &tmp.divisor ) == 2 ) { if( (*num_return_keys) == 0 ) returnkeylist=(ReturnType *)malloc(sizeof(ReturnType)); else returnkeylist=(ReturnType *)realloc(returnkeylist,sizeof(ReturnType)*((*num_return_keys)+1)); (returnkeylist+(*num_return_keys))->key1=tmp.key1; (returnkeylist+(*num_return_keys))->key2=tmp.key2; strcpy( (returnkeylist+(*num_return_keys))->description, tmp.description ); strcpy( (returnkeylist+(*num_return_keys))->units, tmp.units ); (returnkeylist+(*num_return_keys))->divisor = tmp.divisor; (*num_return_keys)++; } } } } } } fclose(fp); return returnkeylist; } //Convert a recieved string to a value int ConvertStreamtoInt( unsigned char * stream, int length, int * value ) { int i, nullvalue; (*value) = 0; nullvalue = 1; for( i=0; i < length; i++ ) { if( stream[i] != 0xff ) //check if all ffs which is a null value nullvalue = 0; (*value) = (*value) + stream[i]*pow(256,i); } if( nullvalue == 1 ) (*value) = 0; //Asigning null to 0 at this stage unless it breaks something return (*value); } //Convert a recieved string to a value time_t ConvertStreamtoTime( unsigned char * stream, int length, time_t * value ) { int i, nullvalue; (*value) = 0; nullvalue = 1; for( i=0; i < length; i++ ) { if( stream[i] != 0xff ) //check if all ffs which is a null value nullvalue = 0; (*value) = (*value) + stream[i]*pow(256,i); } if( nullvalue == 1 ) (*value) = 0; //Asigning null to 0 at this stage unless it breaks something return (*value); } // Set switches to save lots of strcmps void SetSwitches( ConfType *conf, char * datefrom, char * dateto, int *location, int *mysql, int *post, int *file, int *daterange, int *test ) { //Check if all location variables are set if(( conf->latitude_f <= 180 )&&( conf->longitude_f <= 180 )) (*location)=1; else (*location)=0; //Check if all Mysql variables are set if(( strlen(conf->MySqlUser) > 0 ) &&( strlen(conf->MySqlPwd) > 0 ) &&( strlen(conf->MySqlHost) > 0 ) &&( strlen(conf->MySqlDatabase) > 0 ) &&( (*test)==0 )) (*mysql)=1; else (*mysql)=0; //Check if all File variables are set if( strlen(conf->File) > 0 ) (*file)=1; else (*file)=0; //Check if all PVOutput variables are set if(( strlen(conf->PVOutputURL) > 0 ) &&( strlen(conf->PVOutputKey) > 0 ) &&( strlen(conf->PVOutputSid) > 0 )) (*post)=1; else (*post)=0; if(( strlen(datefrom) > 0 ) &&( strlen(dateto) > 0 )) (*daterange)=1; else (*daterange)=0; } unsigned char * ReadStream( ConfType * conf, int * s, unsigned char * stream, int * streamlen, unsigned char * datalist, int * datalen, unsigned char * last_sent, int cc, int * terminated, int * togo ) { int finished; int finished_record; int i, j=0; (*togo)=ConvertStreamtoInt( stream+43, 2, togo ); if( debug==1 ) printf( "togo=%d\n", (*togo) ); i=59; //Initial position of data stream (*datalen)=0; datalist=(unsigned char *)malloc(sizeof(char)); finished=0; finished_record=0; while( finished != 1 ) { datalist=(unsigned char *)realloc(datalist,sizeof(char)*((*datalen)+(*streamlen)-i)); while( finished_record != 1 ) { if( i> 500 ) break; //Somthing has gone wrong if(( i < (*streamlen) )&&(( (*terminated) != 1)||(i+3 < (*streamlen) ))) { datalist[j]=stream[i]; j++; (*datalen)=j; i++; } else finished_record = 1; } finished_record = 0; if( (*terminated) == 0 ) { read_bluetooth( conf, s, streamlen, stream, cc, last_sent, terminated ); i=18; } else finished = 1; } if( debug== 1 ) { printf( "len=%d data=", (*datalen) ); for( i=0; i< (*datalen); i++ ) printf( "%02x ", datalist[i] ); printf( "\n" ); } return datalist; } /* Init Config to default values */ void InitConfig( ConfType *conf, char * datefrom, char * dateto ) { strcpy( conf->Config,"./smatool.conf"); strcpy( conf->Setting,"./invcode.in"); strcpy( conf->Inverter, "" ); strcpy( conf->BTAddress, "" ); conf->bt_timeout = 30; strcpy( conf->Password, "0000" ); strcpy( conf->File, "sma.in.new" ); conf->latitude_f = 999 ; conf->longitude_f = 999 ; strcpy( conf->MySqlHost, "localhost" ); strcpy( conf->MySqlDatabase, "smatool" ); strcpy( conf->MySqlUser, "" ); strcpy( conf->MySqlPwd, "" ); strcpy( conf->PVOutputURL, "http://pvoutput.org/service/r2/addstatus.jsp" ); strcpy( conf->PVOutputKey, "" ); strcpy( conf->PVOutputSid, "" ); conf->InverterCode[0]=0; conf->InverterCode[1]=0; conf->InverterCode[2]=0; conf->InverterCode[3]=0; conf->ArchiveCode=0; strcpy( datefrom, "" ); strcpy( dateto, "" ); } /* read Config from file */ int GetConfig( ConfType *conf ) { FILE *fp; char line[400]; char variable[400]; char value[400]; if (strlen(conf->Config) > 0 ) { if(( fp=fopen(conf->Config,"r")) == (FILE *)NULL ) { printf( "Error! Could not open file %s\n", conf->Config ); return( -1 ); //Could not open file } } else { if(( fp=fopen("./smatool.conf","r")) == (FILE *)NULL ) { printf( "Error! Could not open file ./smatool.conf\n" ); return( -1 ); //Could not open file } } while (!feof(fp)){ if (fgets(line,400,fp) != NULL){ //read line from smatool.conf if( line[0] != '#' ) { strcpy( value, "" ); //Null out value sscanf( line, "%s %s", variable, value ); if( debug == 1 ) printf( "variable=%s value=%s\n", variable, value ); if( value[0] != '\0' ) { if( strcmp( variable, "Inverter" ) == 0 ) strcpy( conf->Inverter, value ); if( strcmp( variable, "BTAddress" ) == 0 ) strcpy( conf->BTAddress, value ); if( strcmp( variable, "BTTimeout" ) == 0 ) conf->bt_timeout = atoi(value); if( strcmp( variable, "Password" ) == 0 ) strcpy( conf->Password, value ); if( strcmp( variable, "File" ) == 0 ) strcpy( conf->File, value ); if( strcmp( variable, "Latitude" ) == 0 ) conf->latitude_f = atof(value) ; if( strcmp( variable, "Longitude" ) == 0 ) conf->longitude_f = atof(value) ; if( strcmp( variable, "MySqlHost" ) == 0 ) strcpy( conf->MySqlHost, value ); if( strcmp( variable, "MySqlDatabase" ) == 0 ) strcpy( conf->MySqlDatabase, value ); if( strcmp( variable, "MySqlUser" ) == 0 ) strcpy( conf->MySqlUser, value ); if( strcmp( variable, "MySqlPwd" ) == 0 ) strcpy( conf->MySqlPwd, value ); if( strcmp( variable, "PVOutputURL" ) == 0 ) strcpy( conf->PVOutputURL, value ); if( strcmp( variable, "PVOutputKey" ) == 0 ) strcpy( conf->PVOutputKey, value ); if( strcmp( variable, "PVOutputSid" ) == 0 ) strcpy( conf->PVOutputSid, value ); } } } } fclose( fp ); return( 0 ); } /* read Inverter Settings from file */ int GetInverterSetting( ConfType *conf ) { FILE *fp; char line[400]; char variable[400]; char value[400]; int found_inverter=0; if (strlen(conf->Setting) > 0 ) { if(( fp=fopen(conf->Setting,"r")) == (FILE *)NULL ) { printf( "Error! Could not open file %s\n", conf->Setting ); return( -1 ); //Could not open file } } else { if(( fp=fopen("./invcode.in","r")) == (FILE *)NULL ) { printf( "Error! Could not open file ./invcode.in\n" ); return( -1 ); //Could not open file } } while (!feof(fp)){ if (fgets(line,400,fp) != NULL){ //read line from smatool.conf if( line[0] != '#' ) { strcpy( value, "" ); //Null out value sscanf( line, "%s %s", variable, value ); if( debug == 1 ) printf( "variable=%s value=%s\n", variable, value ); if( value[0] != '\0' ) { if( strcmp( variable, "Inverter" ) == 0 ) { if( strcmp( value, conf->Inverter ) == 0 ) found_inverter = 1; else found_inverter = 0; } if(( strcmp( variable, "Code1" ) == 0 )&& found_inverter ) { sscanf( value, "%X", &conf->InverterCode[0] ); } if(( strcmp( variable, "Code2" ) == 0 )&& found_inverter ) sscanf( value, "%X", &conf->InverterCode[1] ); if(( strcmp( variable, "Code3" ) == 0 )&& found_inverter ) sscanf( value, "%X", &conf->InverterCode[2] ); if(( strcmp( variable, "Code4" ) == 0 )&& found_inverter ) sscanf( value, "%X", &conf->InverterCode[3] ); if(( strcmp( variable, "InvCode" ) == 0 )&& found_inverter ) sscanf( value, "%X", &conf->ArchiveCode ); } } } } fclose( fp ); if(( conf->InverterCode[0] == 0 ) || ( conf->InverterCode[1] == 0 ) || ( conf->InverterCode[2] == 0 ) || ( conf->InverterCode[3] == 0 ) || ( conf->ArchiveCode == 0 )) { printf( "\n Error ! not all codes set\n" ); fclose( fp ); return( -1 ); } return( 0 ); } /* Print a help message */ void PrintHelp() { printf( "Usage: smatool [OPTION]\n" ); printf( " -v, --verbose Give more verbose output\n" ); printf( " -d, --debug Show debug\n" ); printf( " -c, --config CONFIGFILE Set config file default smatool.conf\n" ); printf( " --test Run in test mode - don't update data\n" ); printf( "\n" ); printf( "Dates are no longer required - defaults to last update if using mysql\n" ); printf( "or 2000 to now if not using mysql\n" ); printf( " -from --datefrom YYYY-DD-MM HH:MM:00 Date range from date\n" ); printf( " -to --dateto YYYY-DD-MM HH:MM:00 Date range to date\n" ); printf( "\n" ); printf( "The following options are in config file but may be overridden\n" ); printf( " -i, --inverter INVERTER_MODEL inverter model\n" ); printf( " -a, --address INVERTER_ADDRESS inverter BT address\n" ); printf( " -t, --timeout TIMEOUT bluetooth timeout (secs) default 5\n" ); printf( " -p, --password PASSWORD inverter user password default 0000\n" ); printf( " -f, --file FILENAME command file default sma.in.new\n" ); printf( "Location Information to calculate sunset and sunrise so inverter is not\n" ); printf( "queried in the dark\n" ); printf( " -lat, --latitude LATITUDE location latitude -180 to 180 deg\n" ); printf( " -lon, --longitude LONGITUDE location longitude -90 to 90 deg\n" ); printf( "Mysql database information\n" ); printf( " -H, --mysqlhost MYSQLHOST mysql host default localhost\n"); printf( " -D, --mysqldb MYSQLDATBASE mysql database default smatool\n"); printf( " -U, --mysqluser MYSQLUSER mysql user\n"); printf( " -P, --mysqlpwd MYSQLPASSWORD mysql password\n"); printf( "Mysql tables can be installed using INSTALL you may have to use a higher \n" ); printf( "privelege user to allow the creation of databases and tables, use command line \n" ); printf( " --INSTALL install mysql data tables\n"); printf( " --UPDATE update mysql data tables\n"); printf( "PVOutput.org (A free solar information system) Configs\n" ); printf( " -url, --pvouturl PVOUTURL pvoutput.org live url\n"); printf( " -key, --pvoutkey PVOUTKEY pvoutput.org key\n"); printf( " -sid, --pvoutsid PVOUTSID pvoutput.org sid\n"); printf( " -repost verify and repost data if different\n"); printf( "\n\n" ); } /* Init Config to default values */ int ReadCommandConfig( ConfType *conf, int argc, char **argv, char * datefrom, char * dateto, int * verbose, int * debug, int * repost, int * test, int * install, int * update ) { int i; // these need validation checking at some stage TODO for (i=1;iConfig,argv[i]); } } else if (strcmp(argv[i],"--test")==0) (*test)=1; else if ((strcmp(argv[i],"-from")==0)||(strcmp(argv[i],"--datefrom")==0)){ i++; if(iInverter,argv[i]); } } else if ((strcmp(argv[i],"-a")==0)||(strcmp(argv[i],"--address")==0)){ i++; if (iBTAddress,argv[i]); } } else if ((strcmp(argv[i],"-t")==0)||(strcmp(argv[i],"--timeout")==0)){ i++; if (ibt_timeout = atoi(argv[i]); } } else if ((strcmp(argv[i],"-p")==0)||(strcmp(argv[i],"--password")==0)){ i++; if (iPassword,argv[i]); } } else if ((strcmp(argv[i],"-f")==0)||(strcmp(argv[i],"--file")==0)){ i++; if (iFile,argv[i]); } } else if ((strcmp(argv[i],"-lat")==0)||(strcmp(argv[i],"--latitude")==0)){ i++; if(ilatitude_f=atof(argv[i]); } } else if ((strcmp(argv[i],"-long")==0)||(strcmp(argv[i],"--longitude")==0)){ i++; if(ilongitude_f=atof(argv[i]); } } else if ((strcmp(argv[i],"-H")==0)||(strcmp(argv[i],"--mysqlhost")==0)){ i++; if (iMySqlHost,argv[i]); } } else if ((strcmp(argv[i],"-D")==0)||(strcmp(argv[i],"--mysqlcwdb")==0)){ i++; if (iMySqlDatabase,argv[i]); } } else if ((strcmp(argv[i],"-U")==0)||(strcmp(argv[i],"--mysqluser")==0)){ i++; if (iMySqlUser,argv[i]); } } else if ((strcmp(argv[i],"-P")==0)||(strcmp(argv[i],"--mysqlpwd")==0)){ i++; if (iMySqlPwd,argv[i]); } } else if ((strcmp(argv[i],"-url")==0)||(strcmp(argv[i],"--pvouturl")==0)){ i++; if(iPVOutputURL,argv[i]); } } else if ((strcmp(argv[i],"-key")==0)||(strcmp(argv[i],"--pvoutkey")==0)){ i++; if(iPVOutputKey,argv[i]); } } else if ((strcmp(argv[i],"-sid")==0)||(strcmp(argv[i],"--pvoutsid")==0)){ i++; if(iPVOutputSid,argv[i]); } } else if ((strcmp(argv[i],"-h")==0) || (strcmp(argv[i],"--help") == 0 )) { PrintHelp(); return( -1 ); } else if (strcmp(argv[i],"--INSTALL")==0) (*install)=1; else if (strcmp(argv[i],"--UPDATE")==0) (*update)=1; else { printf("Bad Syntax\n\n" ); for( i=0; i< argc; i++ ) printf( "%s ", argv[i] ); printf( "\n\n" ); PrintHelp(); return( -1 ); } } return( 0 ); } char * debugdate() { time_t curtime; struct tm *tm; static char result[20]; curtime = time(NULL); //get time in seconds since epoch (1/1/1970) tm = localtime(&curtime); sprintf( result, "%4d-%02d-%02d %02d:%02d:%02d", 1900+tm->tm_year, 1+tm->tm_mon, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec ); return result; } int main(int argc, char **argv) { FILE *fp; unsigned char * last_sent; ConfType conf; ReturnType *returnkeylist; int num_return_keys=0; struct sockaddr_rc addr = { 0 }; unsigned char received[1024]; unsigned char datarecord[1024]; unsigned char * data; unsigned char send_count = 0x0; int return_key; int gap=1; int datalen = 0; int archdatalen=0; int failedbluetooth=0; int terminated=0; int s,i,j,status,mysql=0,post=0,repost=0,test=0,file=0,daterange=0; int install=0, update=0, already_read=0; int location=0, error=0; int ret,found,crc_at_end, finished=0; int togo=0; int initstarted=0,setupstarted=0,rangedatastarted=0; long returnpos; int returnline; char compurl[400]; //seg error on curl fix 2012.01.14 char datefrom[100]; char dateto[100]; int pass_i; char line[400]; unsigned char address[6] = { 0 }; unsigned char address2[6] = { 0 }; unsigned char timestr[25] = { 0 }; unsigned char serial[4] = { 0 }; unsigned char tzhex[2] = { 0 }; unsigned char timeset[4] = { 0x30,0xfe,0x7e,0x00 }; int invcode; char *lineread; time_t curtime; time_t reporttime; time_t fromtime; time_t totime; time_t idate; time_t prev_idate; struct tm *loctime; struct tm tm; int day,month,year,hour,minute,second,datapoint; char tt[10] = {48,48,48,48,48,48,48,48,48,48}; char ti[3]; char chan[1]; float currentpower_total; int rr; int linenum = 0; float dtotal; float gtotal; float ptotal; float strength; MYSQL_ROW row, row1; char SQLQUERY[200]; struct archdata_type { time_t date; char inverter[20]; long unsigned int serial; float accum_value; float current_value; } *archdatalist; char sunrise_time[6],sunset_time[6]; CURL *curl; CURLcode result; memset(received,0,1024); last_sent = (unsigned char *)malloc( sizeof( unsigned char )); /* get the report time - used in various places */ reporttime = time(NULL); //get time in seconds since epoch (1/1/1970) // set config to defaults InitConfig( &conf, datefrom, dateto ); // read command arguments needed so can get config if( ReadCommandConfig( &conf, argc, argv, datefrom, dateto, &verbose, &debug, &repost, &test, &install, &update ) < 0 ) exit(0); // read Config file if( GetConfig( &conf ) < 0 ) exit(-1); // read command arguments again - they overide config if( ReadCommandConfig( &conf, argc, argv, datefrom, dateto, &verbose, &debug, &repost, &test, &install, &update ) < 0 ) exit(0); // read Inverter Setting file if( GetInverterSetting( &conf ) < 0 ) exit(-1); // set switches used through the program SetSwitches( &conf, datefrom, dateto, &location, &mysql, &post, &file, &daterange, &test ); if(( install==1 )&&( mysql==1 )) { install_mysql_tables( &conf, SCHEMA, debug ); exit(0); } if(( update==1 )&&( mysql==1 )) { update_mysql_tables( &conf, debug ); exit(0); } // Set value for inverter type //SetInverterType( &conf ); // Get Return Value lookup from file returnkeylist = InitReturnKeys( &conf, returnkeylist, &num_return_keys ); // Get Local Timezone offset in seconds get_timezone_in_seconds( tzhex ); // Location based information to avoid quering Inverter in the dark if((location==1)&&(mysql==1)) { if( debug == 1 ) printf( "Before todays Almanac\n" ); if( ! todays_almanac( &conf ) ) { sprintf( sunrise_time, "%s", sunrise(conf.latitude_f,conf.longitude_f )); sprintf( sunset_time, "%s", sunset(conf.latitude_f, conf.longitude_f )); if( verbose==1) printf( "sunrise=%s sunset=%s\n", sunrise_time, sunset_time ); update_almanac( &conf, sunrise_time, sunset_time ); } } if( mysql==1 ) if( debug == 1 ) printf( "Before Check Schema\n" ); if( check_schema( &conf, SCHEMA, debug ) != 1 ) exit(-1); if(daterange==0 ) { //auto set the dates if( debug == 1 ) printf( "auto_set_dates\n" ); auto_set_dates( &conf, &daterange, mysql, datefrom, dateto ); } else if( verbose == 1 ) printf( "QUERY RANGE from %s to %s\n", datefrom, dateto ); if(( daterange==1 )&&((location=0)||(mysql==0)||is_light( &conf ))) { if (debug ==1) printf("Address %s\n",conf.BTAddress); if (file ==1) fp=fopen(conf.File,"r"); else fp=fopen("/etc/sma.in","r"); for( i=1; i<20; i++ ){ // allocate a socket s = socket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM); // set the connection parameters (who to connect to) addr.rc_family = AF_BLUETOOTH; addr.rc_channel = (uint8_t) 1; str2ba( conf.BTAddress, &addr.rc_bdaddr ); // connect to server if( debug==1 ) { printf( "datefrom=%s dateto=%s\n", datefrom, dateto ); } status = connect(s, (struct sockaddr *)&addr, sizeof(addr)); if (status <0){ printf("Error connecting to %s\n",conf.BTAddress); close( s ); } else break; } if (status < 0 ) { printf("Bad Status\n"); return( -1 ); } // convert address address[5] = conv(strtok(conf.BTAddress,":")); address[4] = conv(strtok(NULL,":")); address[3] = conv(strtok(NULL,":")); address[2] = conv(strtok(NULL,":")); address[1] = conv(strtok(NULL,":")); address[0] = conv(strtok(NULL,":")); while (!feof(fp)){ start: if (fgets(line,400,fp) != NULL){ //read line from sma.in linenum++; lineread = strtok(line," ;"); if(!strcmp(lineread,"R")){ //See if line is something we need to receive if (debug == 1) printf("[%d] %s Waiting for string\n",linenum, debugdate() ); cc = 0; do{ lineread = strtok(NULL," ;"); switch(select_str(lineread)) { case 0: // $END //do nothing break; case 1: // $ADDR for (i=0;i<6;i++){ fl[cc] = address[i]; cc++; } break; case 3: // $SER for (i=0;i<4;i++){ fl[cc] = serial[i]; cc++; } break; case 7: // $ADD2 for (i=0;i<6;i++){ fl[cc] = address2[i]; cc++; } break; case 8: // $CHAN fl[cc] = chan[0]; cc++; break; default : fl[cc] = conv(lineread); cc++; } } while (strcmp(lineread,"$END")); if (debug == 1){ printf("[%d] %s waiting for: ", linenum, debugdate() ); for (i=0;i 0 ) free( archdatalist ); archdatalen=0; strcpy( lineread, "" ); sleep(10); failedbluetooth++; if( failedbluetooth > 3 ) exit(-1); goto start; } else { already_read=0; if (debug == 1){ printf( "[%d] %s looking for: ",linenum, debugdate()); for (i=0;i0;i=i-2){ //change order and convert to integer ti[1] = tt[i]; ti[0] = tt[i-1]; ti[2] = '\0'; fl[cc] = conv(ti); cc++; } break; case 11: // $TMPLUS // get report time and convert sprintf(tt,"%x",(int)reporttime+1); //convert to a hex in a string for (i=7;i>0;i=i-2){ //change order and convert to integer ti[1] = tt[i]; ti[0] = tt[i-1]; ti[2] = '\0'; fl[cc] = conv(ti); cc++; } break; case 10: // $TMMINUS // get report time and convert sprintf(tt,"%x",(int)reporttime-1); //convert to a hex in a string for (i=7;i>0;i=i-2){ //change order and convert to integer ti[1] = tt[i]; ti[0] = tt[i-1]; ti[2] = '\0'; fl[cc] = conv(ti); cc++; } break; case 4: //$crc tryfcs16(fl+19, cc -19); add_escapes(fl,&cc); fix_length_send(fl,&cc); break; case 8: // $CHAN fl[cc] = chan[0]; cc++; break; case 12: // $TIMESTRING for (i=0;i<25;i++){ fl[cc] = timestr[i]; cc++; } break; case 13: // $TIMEFROM1 // get report time and convert if( daterange == 1 ) { if( strptime( datefrom, "%Y-%m-%d %H:%M:%S", &tm) == 0 ) { if( debug==1 ) printf( "datefrom %s\n", datefrom ); printf( "Time Coversion Error\n" ); error=1; exit(-1); } tm.tm_isdst=-1; fromtime=mktime(&tm); if( fromtime == -1 ) { // Error we need to do something about it printf( "%03x",(int)fromtime ); getchar(); printf( "\n%03x", (int)fromtime ); getchar(); fromtime=0; printf( "bad from" ); getchar(); } } else { printf( "no from" ); getchar(); fromtime=0; } sprintf(tt,"%03x",(int)fromtime-300); //convert to a hex in a string and start 5 mins before for dummy read. for (i=7;i>0;i=i-2){ //change order and convert to integer ti[1] = tt[i]; ti[0] = tt[i-1]; ti[2] = '\0'; fl[cc] = conv(ti); cc++; } break; case 14: // $TIMETO1 if( daterange == 1 ) { if( strptime( dateto, "%Y-%m-%d %H:%M:%S", &tm) == 0 ) { if( debug==1 ) printf( "dateto %s\n", dateto ); printf( "Time Coversion Error\n" ); error=1; exit(-1); } tm.tm_isdst=-1; totime=mktime(&tm); if( totime == -1 ) { // Error we need to do something about it printf( "%03x",(int)totime ); getchar(); printf( "\n%03x", (int)totime ); getchar(); totime=0; printf( "bad to" ); getchar(); } } else totime=0; sprintf(tt,"%03x",(int)totime); //convert to a hex in a string // get report time and convert for (i=7;i>0;i=i-2){ //change order and convert to integer ti[1] = tt[i]; ti[0] = tt[i-1]; ti[2] = '\0'; fl[cc] = conv(ti); cc++; } break; case 15: // $TIMEFROM2 if( daterange == 1 ) { strptime( datefrom, "%Y-%m-%d %H:%M:%S", &tm); tm.tm_isdst=-1; fromtime=mktime(&tm)-86400; if( fromtime == -1 ) { // Error we need to do something about it printf( "%03x",(int)fromtime ); getchar(); printf( "\n%03x", (int)fromtime ); getchar(); fromtime=0; printf( "bad from" ); getchar(); } } else { printf( "no from" ); getchar(); fromtime=0; } sprintf(tt,"%03x",(int)fromtime); //convert to a hex in a string for (i=7;i>0;i=i-2){ //change order and convert to integer ti[1] = tt[i]; ti[0] = tt[i-1]; ti[2] = '\0'; fl[cc] = conv(ti); cc++; } break; case 16: // $TIMETO2 if( daterange == 1 ) { strptime( dateto, "%Y-%m-%d %H:%M:%S", &tm); tm.tm_isdst=-1; totime=mktime(&tm)-86400; if( totime == -1 ) { // Error we need to do something about it printf( "%03x",(int)totime ); getchar(); printf( "\n%03x", (int)totime ); getchar(); fromtime=0; printf( "bad from" ); getchar(); } } else totime=0; sprintf(tt,"%03x",(int)totime); //convert to a hex in a string for (i=7;i>0;i=i-2){ //change order and convert to integer ti[1] = tt[i]; ti[0] = tt[i-1]; ti[2] = '\0'; fl[cc] = conv(ti); cc++; } break; case 19: // $PASSWORD j=0; for(i=0;i<12;i++){ if( conf.Password[j] == '\0' ) fl[cc] = 0x88; else { pass_i = conf.Password[j]; fl[cc] = (( pass_i+0x88 )%0xff); j++; } cc++; } break; case 21: // $UNKNOWN for (i=0;i<4;i++){ fl[cc] = conf.InverterCode[i]; cc++; } break; case 22: // $INVCODE fl[cc] = invcode; cc++; break; case 23: // $ARCHCODE fl[cc] = conf.ArchiveCode; cc++; break; case 25: // $CNT send counter send_count++; fl[cc] = send_count; cc++; break; case 26: // $TIMEZONE timezone in seconds, reverse endian fl[cc] = tzhex[0]; fl[cc+1] = tzhex[1]; cc+=2; break; case 27: // $TIMESET unknown setting for( i=0; i<4; i++ ) { fl[cc] = timeset[i]; cc++; } break; default : fl[cc] = conv(lineread); cc++; } } while (strcmp(lineread,"$END")); if (debug == 1){ printf( "[%d] %s sending:\n",linenum, debugdate()); printf( " %08x: .. .. .. .. .. .. .. .. .. .. .. .. ", 0 ); j=12; for (i=0;itm_mday; month = loctime->tm_mon +1; year = loctime->tm_year + 1900; hour = loctime->tm_hour; minute = loctime->tm_min; second = loctime->tm_sec; printf("Date power = %d/%d/%4d %02d:%02d:%02d\n",day, month, year, hour, minute,second); //currentpower = (received[72] * 256) + received[71]; //printf("Current power = %i Watt\n",currentpower); break; case 5: // extract current power $POW data = ReadStream( &conf, &s, received, &rr, data, &datalen, last_sent, cc, &terminated, &togo ); if( (data+3)[0] == 0x08 ) gap = 40; if( (data+3)[0] == 0x10 ) gap = 40; if( (data+3)[0] == 0x40 ) gap = 28; if( (data+3)[0] == 0x00 ) gap = 28; for ( i = 0; itm_mday; month = loctime->tm_mon +1; year = loctime->tm_year + 1900; hour = loctime->tm_hour; minute = loctime->tm_min; second = loctime->tm_sec; ConvertStreamtoFloat( data+i+8, 3, ¤tpower_total ); return_key=-1; for( j=0; j= 0 ) printf("%d-%02d-%02d %02d:%02d:%02d %-20s = %.0f %-20s\n", year, month, day, hour, minute, second, returnkeylist[return_key].description, currentpower_total/returnkeylist[return_key].divisor, returnkeylist[return_key].units ); else printf("%d-%02d-%02d %02d:%02d:%02d NO DATA for %02x %02x = %.0f NO UNITS\n", year, month, day, hour, minute, second, (data+i+1)[0], (data+i+1)[1], currentpower_total ); } free( data ); break; case 6: // extract total energy collected today gtotal = (received[69] * 65536) + (received[68] * 256) + received[67]; gtotal = gtotal / 1000; printf("G total so far = %.2f Kwh\n",gtotal); dtotal = (received[84] * 256) + received[83]; dtotal = dtotal / 1000; printf("E total today = %.2f Kwh\n",dtotal); break; case 7: // extract 2nd address memcpy(address2,received+26,6); if (debug == 1) printf("address 2 \n"); break; case 8: // extract bluetooth channel memcpy(chan,received+22,1); if (debug == 1) printf("Bluetooth channel = %i\n",chan[0]); break; case 12: // extract time strings $TIMESTRING if(( received[60] == 0x6d )&&( received[61] == 0x23 )) { memcpy(timestr,received+63,24); if (debug == 1) printf("extracting timestring\n"); memcpy(timeset,received+79,4); idate=ConvertStreamtoTime( received+63,4, &idate ); /* Allow delay for inverter to be slow */ if( reporttime > idate ) { if( debug == 1 ) printf( "delay=%d\n", (int)(reporttime-idate) ); //sleep( reporttime - idate ); sleep(5); //was sleeping for > 1min excessive } } else { memcpy(timestr,received+63,24); if (debug == 1) printf("bad extracting timestring\n"); already_read=0; fseek( fp, returnpos, 0 ); linenum = returnline; found=0; if( archdatalen > 0 ) free( archdatalist ); archdatalen=0; strcpy( lineread, "" ); failedbluetooth++; if( failedbluetooth > 60 ) exit(-1); goto start; //exit(-1); } break; case 17: // Test data data = ReadStream( &conf, &s, received, &rr, data, &datalen, last_sent, cc, &terminated, &togo ); printf( "\n" ); free( data ); break; case 18: // $ARCHIVEDATA1 finished=0; ptotal=0; idate=0; printf( "\n" ); while( finished != 1 ) { data = ReadStream( &conf, &s, received, &rr, data, &datalen, last_sent, cc, &terminated, &togo ); j=0; for( i=0; i 11 ) { if( idate > 0 ) prev_idate=idate; else prev_idate=0; idate=ConvertStreamtoTime( datarecord, 4, &idate ); if( prev_idate == 0 ) prev_idate = idate-300; loctime = localtime(&idate); day = loctime->tm_mday; month = loctime->tm_mon +1; year = loctime->tm_year + 1900; hour = loctime->tm_hour; minute = loctime->tm_min; second = loctime->tm_sec; ConvertStreamtoFloat( datarecord+4, 8, >otal ); if(archdatalen == 0 ) ptotal = gtotal; printf("\n%d/%d/%4d %02d:%02d:%02d total=%.3f Kwh current=%.0f Watts togo=%d i=%d crc=%d", day, month, year, hour, minute,second, gtotal/1000, (gtotal-ptotal)*12, togo, i, crc_at_end); if( idate != prev_idate+300 ) { printf( "Date Error! prev=%d current=%d\n", (int)prev_idate, (int)idate ); // error=1; // break; } if( archdatalen == 0 ) archdatalist = (struct archdata_type *)malloc( sizeof( struct archdata_type ) ); else archdatalist = (struct archdata_type *)realloc( archdatalist, sizeof( struct archdata_type )*(archdatalen+1)); (archdatalist+archdatalen)->date=idate; strcpy((archdatalist+archdatalen)->inverter,conf.Inverter); ConvertStreamtoLong( serial, 4, &(archdatalist+archdatalen)->serial); (archdatalist+archdatalen)->accum_value=gtotal/1000; (archdatalist+archdatalen)->current_value=(gtotal-ptotal)*12; archdatalen++; ptotal=gtotal; j=0; //get ready for another record } } if( togo == 0 ) finished=1; else if( read_bluetooth( &conf, &s, &rr, received, cc, last_sent, &terminated ) != 0 ) { fseek( fp, returnpos, 0 ); linenum = returnline; found=0; if( archdatalen > 0 ) free( archdatalist ); archdatalen=0; strcpy( lineread, "" ); sleep(10); failedbluetooth++; if( failedbluetooth > 3 ) exit(-1); goto start; } } free( data ); printf( "\n" ); break; case 20: // SIGNAL signal strength strength = (received[22] * 100.0)/0xff; if (verbose == 1) { printf("bluetooth signal = %.0f%%\n",strength); } break; case 22: // extract time strings $INVCODE invcode=received[22]; if (debug == 1) printf("extracting invcode=%02x\n", invcode); break; case 24: // Inverter data $INVERTERDATA data = ReadStream( &conf, &s, received, &rr, data, &datalen, last_sent, cc, &terminated, &togo ); if( debug==1 ) printf( "data=%02x\n",(data+3)[0] ); if( (data+3)[0] == 0x08 ) gap = 40; if( (data+3)[0] == 0x10 ) gap = 40; if( (data+3)[0] == 0x40 ) gap = 28; if( (data+3)[0] == 0x00 ) gap = 28; for ( i = 0; itm_mday; month = loctime->tm_mon +1; year = loctime->tm_year + 1900; hour = loctime->tm_hour; minute = loctime->tm_min; second = loctime->tm_sec; ConvertStreamtoFloat( data+i+8, 3, ¤tpower_total ); return_key=-1; for( j=0; j= 0 ) { if( i==0 ) printf("%d-%02d-%02d %02d:%02d:%02d %s\n", year, month, day, hour, minute, second, (data+i+8) ); printf("%d-%02d-%02d %02d:%02d:%02d %-20s = %.0f %-20s\n", year, month, day, hour, minute, second, returnkeylist[return_key].description, currentpower_total/returnkeylist[return_key].divisor, returnkeylist[return_key].units ); } else printf("%d-%02d-%02d %02d:%02d:%02d NO DATA for %02x %02x = %.0f NO UNITS \n", year, month, day, hour, minute, second, (data+i+1)[0], (data+i+1)[0], currentpower_total ); } free( data ); break; } } while (strcmp(lineread,"$END")); } if(!strcmp(lineread,":init")){ //See if line is something we need to extract initstarted=1; returnpos=ftell(fp); returnline = linenum; } if(!strcmp(lineread,":setup")){ //See if line is something we need to extract setupstarted=1; returnpos=ftell(fp); returnline = linenum; } if(!strcmp(lineread,":startsetup")){ //See if line is something we need to extract sleep(1); } if(!strcmp(lineread,":setinverter1")){ //See if line is something we need to extract setupstarted=1; returnpos=ftell(fp); returnline = linenum; } if(!strcmp(lineread,":getrangedata")){ //See if line is something we need to extract rangedatastarted=1; returnpos=ftell(fp); returnline = linenum; } } } if ((mysql ==1)&&(error==0)){ /* Connect to database */ OpenMySqlDatabase( conf.MySqlHost, conf.MySqlUser, conf.MySqlPwd, conf.MySqlDatabase ); for( i=1; idate, (archdatalist+i)->inverter, (archdatalist+i)->serial, (archdatalist+i)->current_value, (archdatalist+i)->accum_value ); if (debug == 1) printf("%s\n",SQLQUERY); DoQuery(SQLQUERY); } mysql_close(conn); } curtime = time(NULL); //get time in seconds since epoch (1/1/1970) loctime = localtime(&curtime); day = loctime->tm_mday; month = loctime->tm_mon +1; year = loctime->tm_year + 1900; hour = loctime->tm_hour; minute = loctime->tm_min; datapoint = (int)(((hour * 60) + minute)) / 5; if ((post ==1)&&(mysql==1)&&(error==0)){ char batch_string[400]; int batch_count = 0; /* Connect to database */ OpenMySqlDatabase( conf.MySqlHost, conf.MySqlUser, conf.MySqlPwd, conf.MySqlDatabase ); /* //Get Start of day value sprintf(SQLQUERY,"SELECT EtotalToday FROM DayData WHERE DateTime=DATE_FORMAT( NOW(), \"%%Y%%m%%d000000\" ) " ); if (debug == 1) printf("%s\n",SQLQUERY); DoQuery(SQLQUERY); if (row = mysql_fetch_row(res)) //if there is a result, update the row { starttotal = atof( (char *)row[0] ); /* if( archdatalen < 3 ) //Use Batch mode if greater if ( 1 = 2 ) //Always use batch mode, r2 api is better and r1 may go away one day { for( i=1; icurrent_value > 0 ) { dtotal = (archdatalist+i)->accum_value*1000 - (starttotal*1000); idate = (archdatalist+i)->date; loctime = localtime(&(archdatalist+i)->date); day = loctime->tm_mday; month = loctime->tm_mon +1; year = loctime->tm_year + 1900; hour = loctime->tm_hour; minute = loctime->tm_min; second = loctime->tm_sec; ret=sprintf(compurl,"%s?d=%04i%02i%02i&t=%02i:%02i&v1=%f&v2=%f&key=%s&sid=%s",conf.PVOutputURL,year,month,day,hour,minute,dtotal,(archdatalist+i)->current_value,conf.PVOutputKey,conf.PVOutputSid); sprintf(SQLQUERY,"SELECT PVOutput FROM DayData WHERE DateTime=\"%i%02i%02i%02i%02i%02i\" and PVOutput IS NOT NULL", year, month, day, hour, minute, second ); if (debug == 1) printf("%s\n",SQLQUERY); DoQuery(SQLQUERY); if (debug == 1) printf("url = %s\n",compurl); if (row = mysql_fetch_row(res)) //if there is a result, already done { if (verbose == 1) printf("Already Updated\n"); } else { curl = curl_easy_init(); if (curl){ curl_easy_setopt(curl, CURLOPT_URL, compurl); curl_easy_setopt(curl, CURLOPT_FAILONERROR, compurl); result = curl_easy_perform(curl); if (debug == 1) printf("result = %d\n",result); curl_easy_cleanup(curl); if( result==0 ) { sprintf(SQLQUERY,"UPDATE DayData set PVOutput=NOW() WHERE DateTime=\"%i%02i%02i%02i%02i%02i\" ", year, month, day, hour, minute, second ); if (debug == 1) printf("%s\n",SQLQUERY); DoQuery(SQLQUERY); } else break; } } } } } else //Use batch mode 30 values at a time! */ // sprintf(SQLQUERY,"SELECT DATE_FORMAT(dd1.DateTime,\'%%Y%%m%%d\'), DATE_FORMAT(dd1.DateTime,\'%%H:%%i\'), ROUND((dd1.ETotalToday-dd2.EtotalToday)*1000), dd1.CurrentPower, dd1.DateTime FROM DayData as dd1 join DayData as dd2 on dd2.DateTime=DATE_FORMAT(dd1.DateTime,\'%%Y-%%m-%%d 00:00:00\') WHERE dd1.DateTime>=Date_Sub(CURDATE(),INTERVAL 29 DAY) and dd1.PVOutput IS NULL and dd1.CurrentPower>0 ORDER BY dd1.DateTime ASC" ); // YJB 060812 -Don't rely on midnight time stamp 00:00:00 sprintf(SQLQUERY,"SELECT DATE_FORMAT(t1.DateTime,\'%%Y%%m%%d\'), DATE_FORMAT(t1.DateTime,\'%%H:%%i\'), ROUND((t1.ETotalToday - d1.MinVal)*1000), t1.CurrentPower, t1.DateTime FROM DayData t1 INNER JOIN (SELECT DATE(DateTime) ts_date, MIN(ETotalToday) AS MinVal FROM DayData GROUP BY ts_date) d1 ON DATE(t1.DateTime) = d1.ts_date WHERE t1.DateTime>=Date_Sub(CURDATE(),INTERVAL 10 DAY) and t1.PVOutput IS NULL and t1.CurrentPower>0 ORDER BY t1.DateTime ASC" ); if (debug == 1) printf("%s\n",SQLQUERY); DoQuery(SQLQUERY); batch_count=0; if( mysql_num_rows(res) == 1 ) { if ((row = mysql_fetch_row(res))) //Need to update these { sprintf(compurl,"%s?d=%s&t=%s&v1=%s&v2=%s&key=%s&sid=%s",conf.PVOutputURL,row[0],row[1],row[2],row[3],conf.PVOutputKey,conf.PVOutputSid); if (debug == 1) printf("url = %s\n",compurl); { curl = curl_easy_init(); if (curl){ curl_easy_setopt(curl, CURLOPT_URL, compurl); curl_easy_setopt(curl, CURLOPT_FAILONERROR, compurl); result = curl_easy_perform(curl); if (debug == 1) printf("result = %d\n",result); curl_easy_cleanup(curl); if( result==0 ) { sprintf(SQLQUERY,"UPDATE DayData set PVOutput=NOW() WHERE DateTime=\"%s\" ", row[4] ); if (debug == 1) printf("%s\n",SQLQUERY); DoQuery(SQLQUERY); } } } } } else { while ((row = mysql_fetch_row(res))) //Need to update these { sleep(2); if( batch_count > 0 ) sprintf( batch_string,"%s;%s,%s,%s,%s", batch_string, row[0], row[1], row[2], row[3] ); else sprintf( batch_string,"%s,%s,%s,%s", row[0], row[1], row[2], row[3] ); batch_count++; if( batch_count == 30 ) { curl = curl_easy_init(); if (curl){ sprintf(compurl,"http://pvoutput.org/service/r2/addbatchstatus.jsp?data=%s&key=%s&sid=%s",batch_string,conf.PVOutputKey,conf.PVOutputSid); if (debug == 1) printf("url = %s\n",compurl); curl_easy_setopt(curl, CURLOPT_URL, compurl); curl_easy_setopt(curl, CURLOPT_FAILONERROR, compurl); result = curl_easy_perform(curl); sleep(1); if (debug == 1) printf("result = %d\n",result); curl_easy_cleanup(curl); if( result==0 ) { // sprintf(SQLQUERY,"SELECT DATE_FORMAT(dd1.DateTime,\'%%Y%%m%%d\'), DATE_FORMAT(dd1.DateTime,\'%%H:%%i\'), ROUND((dd1.ETotalToday-dd2.EtotalToday)*1000), dd1.CurrentPower, dd1.DateTime FROM DayData as dd1 join DayData as dd2 on dd2.DateTime=DATE_FORMAT(dd1.DateTime,\'%%Y-%%m-%%d 00:00:00\') WHERE dd1.DateTime>=Date_Sub(CURDATE(),INTERVAL 30 DAY) and dd1.PVOutput IS NULL and dd1.CurrentPower>0 ORDER BY dd1.DateTime ASC limit %d", batch_count ); // YJB 060812 -Don't rely on midnight time stamp 00:00:00 sprintf(SQLQUERY,"SELECT DATE_FORMAT(t1.DateTime,\'%%Y%%m%%d\'), DATE_FORMAT(t1.DateTime,\'%%H:%%i\'), ROUND((t1.ETotalToday - d1.MinVal)*1000), t1.CurrentPower, t1.DateTime FROM DayData t1 INNER JOIN (SELECT DATE(DateTime) ts_date, MIN(ETotalToday) AS MinVal FROM DayData GROUP BY ts_date) d1 ON DATE(t1.DateTime) = d1.ts_date WHERE t1.DateTime>=Date_Sub(CURDATE(),INTERVAL 10 DAY) and t1.PVOutput IS NULL and t1.CurrentPower>0 ORDER BY t1.DateTime ASC" ); if (debug == 1) printf("%s\n",SQLQUERY); DoQuery1(SQLQUERY); while ((row1 = mysql_fetch_row(res1))) //Need to update these { sprintf(SQLQUERY,"UPDATE DayData set PVOutput=NOW() WHERE DateTime=\"%s\" ", row1[4] ); if (debug == 1) printf("%s\n",SQLQUERY); DoQuery2(SQLQUERY); } mysql_free_result( res1 ); } else break; } batch_count = 0; strcpy( batch_string, "" ); //NULL the string } } if( batch_count > 0 ) { curl = curl_easy_init(); if (curl){ sprintf(compurl,"http://pvoutput.org/service/r2/addbatchstatus.jsp?data=%s&key=%s&sid=%s",batch_string,conf.PVOutputKey,conf.PVOutputSid); if (debug == 1) printf("url = %s\n",compurl); curl_easy_setopt(curl, CURLOPT_URL, compurl); curl_easy_setopt(curl, CURLOPT_FAILONERROR, compurl); result = curl_easy_perform(curl); sleep(1); if (debug == 1) printf("result = %d\n",result); curl_easy_cleanup(curl); if( result==0 ) { // sprintf(SQLQUERY,"SELECT DATE_FORMAT(dd1.DateTime,\'%%Y%%m%%d\'), DATE_FORMAT(dd1.DateTime,\'%%H:%%i\'), ROUND((dd1.ETotalToday-dd2.EtotalToday)*1000), dd1.CurrentPower, dd1.DateTime FROM DayData as dd1 join DayData as dd2 on dd2.DateTime=DATE_FORMAT(dd1.DateTime,\'%%Y-%%m-%%d 00:00:00\') WHERE dd1.DateTime>=Date_Sub(CURDATE(),INTERVAL 1 DAY) and dd1.PVOutput IS NULL and dd1.CurrentPower>0 ORDER BY dd1.DateTime ASC limit %d", batch_count ); // YJB 060812 -Don't rely on midnight time stamp 00:00:00 sprintf(SQLQUERY,"SELECT DATE_FORMAT(t1.DateTime,\'%%Y%%m%%d\'), DATE_FORMAT(t1.DateTime,\'%%H:%%i\'), ROUND((t1.ETotalToday - d1.MinVal)*1000), t1.CurrentPower, t1.DateTime FROM DayData t1 INNER JOIN (SELECT DATE(DateTime) ts_date, MIN(ETotalToday) AS MinVal FROM DayData GROUP BY ts_date) d1 ON DATE(t1.DateTime) = d1.ts_date WHERE t1.DateTime>=Date_Sub(CURDATE(),INTERVAL 10 DAY) and t1.PVOutput IS NULL and t1.CurrentPower>0 ORDER BY t1.DateTime ASC" ); if (debug == 1) printf("%s\n",SQLQUERY); DoQuery1(SQLQUERY); while ((row1 = mysql_fetch_row(res1))) //Need to update these { sprintf(SQLQUERY,"UPDATE DayData set PVOutput=NOW() WHERE DateTime=\"%s\" ", row1[4] ); if (debug == 1) printf("%s\n",SQLQUERY); DoQuery2(SQLQUERY); } mysql_free_result( res1 ); } } batch_count = 0; } } mysql_free_result( res ); mysql_close(conn); } close(s); if( archdatalen > 0 ) free( archdatalist ); archdatalen=0; free(last_sent); } if ((repost ==1)&&(error==0)){ sma_repost( &conf, debug, verbose ); } return 0; }