SolarMax Datenlogger

Allgemein über Datenlogger, Kommunikation, DFÜ

Moderator: Mod-Team

 

3.83 (6 Bewertungen)

SolarMax Datenlogger

Beitragvon zagibu » 27.07.2010, 16:41

Ich habe hier ein extrem simples C-Programm erstellt, welches ich benutze, um die Daten meines SolarMax Wechselrichters zu loggen. Der Mäxe ist per Ethernet an mein LAN angeschlossen, und das Programm läuft auf einer Linux-Maschine. Die Auswertung fehlt leider noch. Ich dachte mir, ich poste das mal, damit vielleicht der eine oder andere, der im Gegensatz zu mir wirklich C programmieren kann, einen Open-Source SolarMax Logger starten kann. Oder halt auch für den Eigengebrauch. Verbesserungsvorschläge sind herzlich willkommen.

Code: Alles auswählen
/*
   Simple solarmax logger c program written by zagibu@gmx.ch in July 2010
   This program is licensed under WTFPL 2 http://sam.zoy.org/wtfpl/

   # ./logger hostname port logfile loginterval

   Sources:
      - http://www.linuxhowtos.org/C_C++/socket.htm
      - http://wwwuser.gwdg.de/~kboehm/ebook/21_kap15_w6.html#49329
      - http://man.cx/setbuf%283%29
      - http://www.hinz.fdns.net/timestamp-in-c-and-python.html
*/

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <time.h>

void error(char *msg)
{
   perror(msg);
   exit(0);
}

int main(int argc, char *argv[])
{
   int sockfd, portno, n, log_interval;
   struct sockaddr_in serv_addr;
   struct hostent *server;
   char *message1 = "{FB;01;3A|64:KDY;KMT;KYR;KT0;TNF;TKK;PAC;PRL;SYS;SAL|0E8E}";
   char *message2 = "{FB;01;3E|64:IL1;IDC;UL1;UDC;PAC;PRL;KDY;KYR;KT0;SYS;SAL|0F53}";
   char buffer[256];
   FILE *file;
   char *file_name;
   char *mode = "a";
   time_t *timestamp;

   // Check commandline arguments
   if (argc < 5)
      error("ERROR program needs hostname, port, logfile and loginterval (in seconds) as parameters");

   // Get log file path from command line argument
   file_name = argv[3];

   // Get log interval from command line argument
   log_interval = atoi(argv[4]);

   // Try to open socket
   portno = atoi(argv[2]);
   sockfd = socket(AF_INET, SOCK_STREAM, 0);
   if (sockfd < 0)
      error("ERROR opening socket");

   // Try to resolve host
   server = gethostbyname(argv[1]);
   if (server == NULL)
      error("ERROR resolving host");

   // Try to establish a connection
   bzero((char *) &serv_addr, sizeof(serv_addr));
   serv_addr.sin_family = AF_INET;
   bcopy((char *)server->h_addr,
   (char *)&serv_addr.sin_addr.s_addr,
   server->h_length);
   serv_addr.sin_port = htons(portno);
   if (connect(sockfd,&serv_addr,sizeof(serv_addr)) < 0)
      error("ERROR connecting");

   // Try to open log file
   if ((file = fopen(file_name, mode)) == NULL)
      error("ERROR opening log file");

   // Make file unbuffered
   setbuf(file, NULL);

   // Start sending the data requests and logging the answers
   while (1) {
      // Send message 1
      n = write(sockfd,message1,strlen(message1));
      if (n < 0)
         error("ERROR writing to socket");

      // Read answer
      bzero(buffer, 256);
      n = read(sockfd, buffer, 255);
      if (n < 0)
         error("ERROR reading from socket");

      // Write the current timestamp to file
      timestamp = time(NULL);
      fprintf(file, "NOW=%d;", (int) timestamp);

      // Write the answer data to file
      fprintf(file, "%s;", buffer + 13);

      // Send message 2
      n = write(sockfd,message2,strlen(message2));
      if (n < 0)
         error("ERROR writing to socket");

      // Read answer
      bzero(buffer, 256);
      n = read(sockfd, buffer, 255);
      if (n < 0)
         error("ERROR reading from socket");

      // Write the answer data to file
      fprintf(file, "%s;\n", buffer + 13);

      // Wait for the specified number of seconds
      sleep(log_interval);
   }

   return 0;
}


Falls es schon ein anderes Open-Source Programm mit dem gleichen Zweck geben sollte, wäre ich für einen Link darauf dankbar.
zagibu
Vielschreiber
Vielschreiber
 
- Threadstarter -
 
Beiträge: 196
Registriert: 27.07.2010, 16:31
PV-Anlage [kWp]: 3
Info: Betreiber

Re: SolarMax Datenlogger

Beitragvon zagibu » 28.07.2010, 11:17

Ich hab das Programm noch ein wenig abgeändert, so dass es nur bei kritischen Fehlern beendet, und z.B. bei Verbindungsabbruch weiterläuft und versucht, die Verbindung wieder aufzubauen:

Code: Alles auswählen
/*
   Simple solarmax logger c program written by zagibu@gmx.ch in July 2010
   This program is licensed under WTFPL 2 http://sam.zoy.org/wtfpl/

   # ./logger hostname port logfile loginterval

   Sources:
      - http://www.linuxhowtos.org/C_C++/socket.htm
      - http://wwwuser.gwdg.de/~kboehm/ebook/21_kap15_w6.html#49329
      - http://man.cx/setbuf%283%29
      - http://www.hinz.fdns.net/timestamp-in-c-and-python.html
*/

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <time.h>

void error(char *msg)
{
   perror(msg);
   exit(0);
}

int main(int argc, char *argv[])
{
   int sockfd, portno, n, log_interval;
   struct sockaddr_in serv_addr;
   struct hostent *server;
   char *message1 = "{FB;01;3A|64:KDY;KMT;KYR;KT0;TNF;TKK;PAC;PRL;SYS;SAL|0E8E}";
   char *message2 = "{FB;01;3E|64:IL1;IDC;UL1;UDC;PAC;PRL;KDY;KYR;KT0;SYS;SAL|0F53}";
   char buffer[256];
   FILE *file;
   char *file_name;
   char *mode = "a";
   time_t *timestamp;

   // Check commandline arguments
   if (argc < 5)
      error("ERROR program needs hostname, port, logfile and loginterval (in seconds) as parameters");

   // Get log file path from command line argument
   file_name = argv[3];

   // Get log interval from command line argument
   log_interval = atoi(argv[4]);

   // Try to open log file
   if ((file = fopen(file_name, mode)) == NULL)
      error("ERROR opening log file");

   // Make file unbuffered
   setbuf(file, NULL);

   while (1) {
      // Try to open socket
      portno = atoi(argv[2]);
      sockfd = socket(AF_INET, SOCK_STREAM, 0);
      if (sockfd < 0) {
         sleep(60);
         continue;
      }

      // Try to resolve host
      server = gethostbyname(argv[1]);
      if (server == NULL) {
         sleep(60);
         continue;
      }

      // Try to establish a connection
      bzero((char *) &serv_addr, sizeof(serv_addr));
      serv_addr.sin_family = AF_INET;
      bcopy((char *)server->h_addr,
         (char *)&serv_addr.sin_addr.s_addr,
         server->h_length);
      serv_addr.sin_port = htons(portno);
      if (connect(sockfd,&serv_addr,sizeof(serv_addr)) < 0) {
         sleep(60);
         continue;
      }

      // Start sending the data requests and logging the answers
      while (1) {
         // Send message 1
         n = write(sockfd,message1,strlen(message1));
         if (n < 0)
            break;

         // Read answer
         bzero(buffer, 256);
         n = read(sockfd, buffer, 255);
         if (n < 0)
            break;

         // Write the current timestamp to file
         timestamp = time(NULL);
         fprintf(file, "NOW=%d;", (int) timestamp);

         // Write the answer data to file
         fprintf(file, "%s;", buffer + 13);

         // Send message 2
         n = write(sockfd,message2,strlen(message2));
         if (n < 0)
            break;

         // Read answer
         bzero(buffer, 256);
         n = read(sockfd, buffer, 255);
         if (n < 0)
            break;

         // Write the answer data to file
         fprintf(file, "%s;\n", buffer + 13);

         // Wait for the specified number of seconds
         sleep(log_interval);
      }
   }

   return 0;
}
zagibu
Vielschreiber
Vielschreiber
 
- Threadstarter -
 
Beiträge: 196
Registriert: 27.07.2010, 16:31
PV-Anlage [kWp]: 3
Info: Betreiber

Re: SolarMax Datenlogger

Beitragvon borsti87 » 01.08.2010, 14:20

Hallo,

da ich selber gerade versuche ein Progamm zu schreiben mit dem ich meine Wechselrichter abfrage
und ich bei der suche nach hilfreichen Informationen auf diesen Beitrag gestoßen bin hab ich mal eine kurze frage.
Was wird mit TNF, SYS und SAL abgefragt?
Dazu finde ich nichts in der Protokoldokumentation.
By the way, vieleicht kann mir ja auch wer sagen was folgende Keys abfragen:
KLD, SE1, SE2, SPR, SCD (Gefunden bei SolarView)

Danke und Gruß
borsti87
borsti87
Neu hier
Neu hier
 
Beiträge: 6
Registriert: 09.04.2010, 12:53
Info: Interessent

Re: SolarMax Datenlogger

Beitragvon zagibu » 01.08.2010, 16:15

TNF ist die Netzfrequenz, SYS ist vermutlich "system state" und ist bei mir entweder 4E24,0 oder 4E28,0, was MaxTalk als 20004,0 (Betrieb auf MPP) und 20008,0 (Netzbetrieb) anzeigt. KLD ist Energie gestern (kWh last day). SAL könnte die "Anlagennummer" des Verbundes sein, also 0 für die erste, 1 für die zweite, etc. Ist bei mir jedenfalls immer 0. Die anderen Werte kenne ich leider auch nicht.

Dein Post hat mich übrigens noch hier rauf geführt: http://blog.dest-unreach.be/2009/04/15/ ... engineered

Ich denke, mit dieser Information kann ich mein Programm noch einiges verbessern. Hätte auch Zeit sparen können, statt das ganze mit Wireshark selbst auszulesen.
zagibu
Vielschreiber
Vielschreiber
 
- Threadstarter -
 
Beiträge: 196
Registriert: 27.07.2010, 16:31
PV-Anlage [kWp]: 3
Info: Betreiber

Re: SolarMax Datenlogger

Beitragvon zagibu » 04.08.2010, 16:42

Ich hab mal den Code noch etwas optimiert, so dass unter anderem nur noch eine Anfrage an den Mäxe mit allen interessanten Werten drin geschickt wird. Als nächsten Schritt werde ich versuchen statt einem Textfile als Log in eine MySQL DB zu schreiben, damit das Zeug dann per PHP + GD oder so ausgewertet werden kann.

Übrigens, die Checksum, wie im Link in obigem Post beschrieben, ist keine übliche CRC-16 oder so was, sondern einfach alle Bytes aufsummiert und modulo 2^16 genommen. Musste ich mühsam aus seinem Perl Code herauslesen :). Beim Erstellen von eigenen Messages auch das Anpassen des Messagelänge-Feldes nicht vergessen, sonst antwortet der Mäxe nicht. Hat mich einige Zeit gekostet...

Code: Alles auswählen
/*
   Simple solarmax logger c program written by zagibu@gmx.ch in August 2010
   This program is licensed under WTFPL 2 http://sam.zoy.org/wtfpl/

   # ./logger hostname port logfile loginterval

   Sources:
      - http://www.linuxhowtos.org/C_C++/socket.htm
      - http://wwwuser.gwdg.de/~kboehm/ebook/21_kap15_w6.html#49329
      - http://man.cx/setbuf%283%29
      - http://www.hinz.fdns.net/timestamp-in-c-and-python.html
*/

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <time.h>

void error(char *msg)
{
   perror(msg);
   exit(0);
}

int main(int argc, char *argv[])
{
   int sockfd, portno, n, log_interval;
   struct sockaddr_in serv_addr;
   struct hostent* server;
   char* message = "{FB;01;46|64:KDY;KMT;KYR;KT0;TNF;TKK;PAC;PRL;IL1;IDC;UL1;UDC;SYS|1199}";
   char buffer[256];
   FILE* file;
   char* file_name;
   char* mode = "a";
   time_t* timestamp;

   // Check commandline arguments
   if (argc < 5)
      error("ERROR program needs hostname, port, logfile and loginterval (in seconds) as parameters");

   // Get log file path from command line argument
   file_name = argv[3];

   // Get log interval from command line argument
   log_interval = atoi(argv[4]);

   // Try to open log file
   if ((file = fopen(file_name, mode)) == NULL)
      error("ERROR opening log file");

   // Make file unbuffered
   setbuf(file, NULL);

   while (1) {
      // Try to open socket
      portno = atoi(argv[2]);
      sockfd = socket(AF_INET, SOCK_STREAM, 0);
      if (sockfd < 0) {
         sleep(60);
         continue;
      }

      // Try to resolve host
      server = gethostbyname(argv[1]);
      if (server == NULL) {
         sleep(60);
         continue;
      }

      // Try to establish a connection
      bzero((char *) &serv_addr, sizeof(serv_addr));
      serv_addr.sin_family = AF_INET;
      bcopy((char *)server->h_addr,
         (char *)&serv_addr.sin_addr.s_addr,
         server->h_length);
      serv_addr.sin_port = htons(portno);
      if (connect(sockfd,&serv_addr,sizeof(serv_addr)) < 0) {
         sleep(60);
         continue;
      }

      // Start sending the data requests and logging the answers
      while (1) {
         printf("sending message: %s\n", message);
         // Send message 1
         n = write(sockfd,message,strlen(message));
         if (n < 0)
            break;

         // Read answer
         bzero(buffer, 256);
         n = read(sockfd, buffer, 255);
         if (n < 0)
            break;
         printf("received answer: %s\n", buffer);

         // Write the current timestamp to file
         timestamp = time(NULL);
         fprintf(file, "NOW=%d;", (int) timestamp);

         // Write the answer data to file
         fprintf(file, "%s;", buffer + 13);

         // Wait for the specified number of seconds
         sleep(log_interval);
      }
   }

   return 0;
}
zagibu
Vielschreiber
Vielschreiber
 
- Threadstarter -
 
Beiträge: 196
Registriert: 27.07.2010, 16:31
PV-Anlage [kWp]: 3
Info: Betreiber

Re: SolarMax Datenlogger

Beitragvon lasser » 05.08.2010, 19:30

Moin!

zagibu hat geschrieben:Ich hab mal den Code noch etwas optimiert, so dass unter anderem nur noch eine Anfrage an den Mäxe mit allen interessanten Werten drin geschickt wird. Als nächsten Schritt werde ich versuchen statt einem Textfile als Log in eine MySQL DB zu schreiben, damit das Zeug dann per PHP + GD oder so ausgewertet werden kann.


Hm, funktioniert hier auch gut, unsere Anlage ist seit heut in Betrieb.

Bei den evtl. anfallenden Datenmengen ist es sicher sinnvoll eine Datenbank zu verwenden. Muss ich mich dann wohl auch mal mit auseinandersetzen :-(

Eines ist mir an der Antwort des WR aufgefallen: Der HEX-Wert von P ac ist viel zu hoch, ca. das doppelte des auf dem Display angezeigten Wertes. Stellst Du das bei Dir auch fest? Muss da vllt. eine Konstante abgezogen werden?
lasser
Stammmitglied
Stammmitglied
 
Beiträge: 112
Registriert: 16.04.2010, 10:42
PV-Anlage [kWp]: 5,5
Info: Betreiber

Re: SolarMax Datenlogger

Beitragvon zagibu » 05.08.2010, 21:20

Bei mir ist es genau doppelt so hoch wie es im MaxTalk angezeigt wird. Warum weiss ich nicht. Ich hab den Logger mit DB-Anbindung fertig, aber leider konnte ich ihn noch nicht vollständig testen, weil mittlerweile mein Max ausgeschaltet hat :). Hier ist er trotzdem mal:

Code: Alles auswählen
/*
   Simple solarmax logger c program written by zagibu@gmx.ch in July 2010
   This program is licensed under WTFPL 2 http://sam.zoy.org/wtfpl/

   # ./logger hostname port loginterval

   Sources:
      - http://www.linuxhowtos.org/C_C++/socket.htm
      - http://wwwuser.gwdg.de/~kboehm/ebook/21_kap15_w6.html#49329
      - http://man.cx/setbuf%283%29
      - http://allfaq.org/forums/t/169895.aspx
      - http://dev.mysql.com/tech-resources/articles/mysql-capi-tutorial.html
*/

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <mysql/mysql.h>
#include <regex.h>

void error(char *msg)
{
   perror(msg);
   exit(0);
}

int main(int argc, char *argv[])
{
   int i = 0;
   int sockfd, portno, n, log_interval, result;
   struct sockaddr_in serv_addr;
   struct hostent* server;
   char* message = "{FB;01;46|64:KDY;KMT;KYR;KT0;TNF;TKK;PAC;PRL;IL1;IDC;UL1;UDC;SYS|1199}";
   char* expression = "...=([0-9A-F]*);...=([0-9A-F]*);...=([0-9A-F]*);...=([0-9A-F]*);...=([0-9A-F]*);...=([0-9A-F]*);...=([0-9A-F]*);...=([0-9A-F]*);...=([0-9A-F]*);...=([0-9A-F]*);...=([0-9A-F]*);...=([0-9A-F]*)";
   int kdy, kmt, kyr, kt0, tnf, tkk, pac, prl, il1, idc, ul1, udc, sys;
   char buffer[512];
   char query[512];
   char value[32];
   regex_t rx;
   regmatch_t* matches;
   MYSQL* connection = NULL;

   // Check commandline arguments
   if (argc < 4)
      error("ERROR program needs hostname, port and loginterval (in seconds) as parameters");

   // Get log interval from command line argument
   log_interval = strtol(argv[3]);

   // Try to compile regular expression
   result = regcomp(&rx, expression, REG_EXTENDED);
   if (result != 0) {
      regerror(result, &rx, expression, sizeof(expression));
      //fprintf(stderr, "Invalid regular expression: %s\n", expression);
      regfree(&rx);
   }

   // Try to reserve memory for matches
   matches = (regmatch_t *) malloc((rx.re_nsub + 1) * sizeof(regmatch_t));
   if (!matches)
      error("Out of memory.");

   while (1) {
      // Try to establish connection to db server
      if (!connection)
         connection = mysql_init(NULL);

      // Connect to database
      if (!mysql_real_connect(connection, "localhost", "root",
         "password", "solarmax", 0, NULL, 0))
         error(mysql_error(connection));

      // Try to open socket for communication with solarmax
      portno = strtol(argv[2]);
      sockfd = socket(AF_INET, SOCK_STREAM, 0);
      if (sockfd < 0) {
         sleep(60);
         continue;
      }

      // Try to resolve solarmax address/hostname
      server = gethostbyname(argv[1]);
      if (server == NULL) {
         sleep(60);
         continue;
      }

      // Try to establish a connection with solarmax
      bzero((char *) &serv_addr, sizeof(serv_addr));
      serv_addr.sin_family = AF_INET;
      bcopy((char *)server->h_addr,
         (char *)&serv_addr.sin_addr.s_addr,
         server->h_length);
      serv_addr.sin_port = htons(portno);
      if (connect(sockfd,&serv_addr,sizeof(serv_addr)) < 0) {
         sleep(60);
         continue;
      }

      // Start sending the data requests and logging the answers
      while (1) {
         printf("sending message: %s\n", message);
         // Send message
         n = write(sockfd,message,strlen(message));
         if (n < 0)
            break;

         // Read answer
         bzero(buffer, 256);
         n = read(sockfd, buffer, 255);
         if (n < 0)
            break;
         printf("received answer: %s\n", buffer);

         // Extract the data fields from answer
         result = regexec(&rx, buffer, rx.re_nsub + 1, matches, 0);
         if (result) {
            regerror(result, &rx, buffer, sizeof(buffer));
            error("ERROR no regexp match");
         }

         // Convert the extracted data fields to integer values
         kdy = strtol(strndup(buffer + matches[1].rm_so, matches[1].rm_eo - matches[1].rm_so), NULL, 16);
         kmt = strtol(strndup(buffer + matches[2].rm_so, matches[2].rm_eo - matches[2].rm_so), NULL, 16);
         kyr = strtol(strndup(buffer + matches[3].rm_so, matches[3].rm_eo - matches[3].rm_so), NULL, 16);
         kt0 = strtol(strndup(buffer + matches[4].rm_so, matches[4].rm_eo - matches[4].rm_so), NULL, 16);
         tnf = strtol(strndup(buffer + matches[5].rm_so, matches[5].rm_eo - matches[5].rm_so), NULL, 16);
         tkk = strtol(strndup(buffer + matches[6].rm_so, matches[6].rm_eo - matches[6].rm_so), NULL, 16);
         pac = strtol(strndup(buffer + matches[7].rm_so, matches[7].rm_eo - matches[7].rm_so), NULL, 16);
         prl = strtol(strndup(buffer + matches[8].rm_so, matches[8].rm_eo - matches[8].rm_so), NULL, 16);
         il1 = strtol(strndup(buffer + matches[9].rm_so, matches[9].rm_eo - matches[9].rm_so), NULL, 16);
         idc = strtol(strndup(buffer + matches[10].rm_so, matches[10].rm_eo - matches[10].rm_so), NULL, 16);
         ul1 = strtol(strndup(buffer + matches[11].rm_so, matches[11].rm_eo - matches[11].rm_so), NULL, 16);
         udc = strtol(strndup(buffer + matches[12].rm_so, matches[12].rm_eo - matches[12].rm_so), NULL, 16);
         sys = strtol(strndup(buffer + matches[13].rm_so, matches[13].rm_eo - matches[13].rm_so), NULL, 16);

         // Construct the query
         sprintf(query, "INSERT INTO log\
            (kdy, kmt, kyr, kt0, tnf, tkk, pac, prl, il1, idc, ul1, udc, sys)\
            VALUES (%d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d);",
            kdy, kmt, kyr, kt0, tnf, tkk, pac, prl, il1, idc, ul1, udc, sys);

         // Execute the query to write the data into db
         mysql_query(connection, query);
         if (mysql_errno(connection))
            error(mysql_error(connection));

         // Wait for the specified number of seconds
         sleep(log_interval);
      }
   }

   return 0;
}


Zum Kompilieren braucht man die libmysqlclient und libmysqlclient-dev (heisst evtl. leicht anders je nach Distro) und muss den Schalter -lmysqlclient an den gcc Aufruf anhängen. Die DB-Verbindung ist halt noch hardcodiert. Bei mir sieht die DB so aus:

Code: Alles auswählen
mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| solarmax           |
+--------------------+
3 rows in set (0.00 sec)

mysql> use solarmax;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
mysql> show tables;
+--------------------+
| Tables_in_solarmax |
+--------------------+
| log                |
+--------------------+
1 row in set (0.00 sec)

mysql> show columns from log;
+---------+-----------+------+-----+-------------------+-------+
| Field   | Type      | Null | Key | Default           | Extra |
+---------+-----------+------+-----+-------------------+-------+
| created | timestamp | NO   | PRI | CURRENT_TIMESTAMP |       |
| kdy     | int(11)   | NO   |     | NULL              |       |
| kmt     | int(11)   | NO   |     | NULL              |       |
| kyr     | int(11)   | NO   |     | NULL              |       |
| kt0     | int(11)   | NO   |     | NULL              |       |
| tnf     | int(11)   | NO   |     | NULL              |       |
| tkk     | int(11)   | NO   |     | NULL              |       |
| pac     | int(11)   | NO   |     | NULL              |       |
| prl     | int(11)   | NO   |     | NULL              |       |
| il1     | int(11)   | NO   |     | NULL              |       |
| idc     | int(11)   | NO   |     | NULL              |       |
| ul1     | int(11)   | NO   |     | NULL              |       |
| udc     | int(11)   | NO   |     | NULL              |       |
| sys     | int(11)   | NO   |     | NULL              |       |
+---------+-----------+------+-----+-------------------+-------+
14 rows in set (0.00 sec)


Die Werte werden bereits im Logger zu Dezimalzahlen konvertiert. Evtl. muss man das noch anpassen, wenn man den Logger auf nem Mikrocontroller oder so laufen lassen will, bin nämlich nicht sicher ob das Regexp-Zeug dort auch so problemlos funktioniert. Vermutlich werde ich den Logger in einen Client und einen Server aufteilen, so dass der Client dann auf nem Mikro im LAN laufen kann, und nur die Daten vom Solarmax holt und an den Serverteil weiterschickt, welcher dann die Werte extrahiert, umwandelt und in der DB speichert. Aber das kommt wohl noch später als der Visualisierer, wenn überhaupt.
zagibu
Vielschreiber
Vielschreiber
 
- Threadstarter -
 
Beiträge: 196
Registriert: 27.07.2010, 16:31
PV-Anlage [kWp]: 3
Info: Betreiber

Re: SolarMax Datenlogger

Beitragvon zagibu » 06.08.2010, 11:54

Hier ist eine getestete Version, die zusammen mit der oben geposteten DB funktioniert (braucht natürlich immer noch libmysqlclient, libmysqlclient-dev und den gcc-Schalter -lmysqlclient am Ende):

Code: Alles auswählen
/*
   Simple solarmax logger c program written by zagibu@gmx.ch in July 2010
   This program is licensed under WTFPL 2 http://sam.zoy.org/wtfpl/

   # ./logger hostname port loginterval

   Sources:
      - http://www.linuxhowtos.org/C_C++/socket.htm
      - http://wwwuser.gwdg.de/~kboehm/ebook/21_kap15_w6.html#49329
      - http://man.cx/setbuf%283%29
      - http://allfaq.org/forums/t/169895.aspx
      - http://dev.mysql.com/tech-resources/articles/mysql-capi-tutorial.html
*/

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <mysql/mysql.h>
#include <regex.h>

#define DEBUG 0

void error(char *msg)
{
   perror(msg);
   exit(0);
}

int main(int argc, char *argv[])
{
   int i = 0;
   int sockfd, portno, n, log_interval, result;
   struct sockaddr_in serv_addr;
   struct hostent* server;
   char* message = "{FB;01;46|64:KDY;KMT;KYR;KT0;TNF;TKK;PAC;PRL;IL1;IDC;UL1;UDC;SYS|1199}";
   char* expression = "...=([0-9A-F]*);...=([0-9A-F]*);...=([0-9A-F]*);...=([0-9A-F]*);...=([0-9A-F]*);...=([0-9A-F]*);...=([0-9A-F]*);...=([0-9A-F]*);...=([0-9A-F]*);...=([0-9A-F]*);...=([0-9A-F]*);...=([0-9A-F]*);...=([0-9A-F]*)";
   int kdy, kmt, kyr, kt0, tnf, tkk, pac, prl, il1, idc, ul1, udc, sys;
   char buffer[512];
   char query[512];
   char value[32];
   regex_t rx;
   regmatch_t* matches;
   MYSQL* connection = NULL;

   // Check commandline arguments
   if (argc < 4)
      error("ERROR program needs hostname, port and loginterval (in seconds) as parameters");

   // Get log interval from command line argument
   log_interval = atoi(argv[3]);

   // Try to compile regular expression
   result = regcomp(&rx, expression, REG_EXTENDED);
   if (result != 0) {
      regerror(result, &rx, expression, sizeof(expression));
      //fprintf(stderr, "Invalid regular expression: %s\n", expression);
      regfree(&rx);
   }

   // Try to reserve memory for matches
   matches = (regmatch_t *) malloc((rx.re_nsub + 1) * sizeof(regmatch_t));
   if (!matches)
      error("Out of memory.");

   while (1) {
      // Try to establish connection to db server
      if (!connection)
         connection = mysql_init(NULL);

      // Connect to database
      if (!mysql_real_connect(connection, "localhost", "root",
         "password", "solarmax", 0, NULL, 0))
         error(mysql_error(connection));

      // Try to open socket for communication with solarmax
      portno = atoi(argv[2]);
      sockfd = socket(AF_INET, SOCK_STREAM, 0);
      if (sockfd < 0) {
         sleep(60);
         continue;
      }

      // Try to resolve solarmax address/hostname
      server = gethostbyname(argv[1]);
      if (server == NULL) {
         sleep(60);
         continue;
      }

      // Try to establish a connection with solarmax
      bzero((char *) &serv_addr, sizeof(serv_addr));
      serv_addr.sin_family = AF_INET;
      bcopy((char *)server->h_addr,
         (char *)&serv_addr.sin_addr.s_addr,
         server->h_length);
      serv_addr.sin_port = htons(portno);
      if (connect(sockfd,&serv_addr,sizeof(serv_addr)) < 0) {
         sleep(60);
         continue;
      }

      // Start sending the data requests and logging the answers
      while (1) {
         if (DEBUG)
            printf("sending message: %s\n", message);

         // Send message
         n = write(sockfd,message,strlen(message));
         if (n < 0)
            break;

         // Read answer
         bzero(buffer, 256);
         n = read(sockfd, buffer, 255);
         if (n < 0)
            break;
         
         if (DEBUG)
            printf("received answer: %s\n", buffer);

         // Extract the data fields from answer
         result = regexec(&rx, buffer, rx.re_nsub + 1, matches, 0);
         if (result) {
            regerror(result, &rx, buffer, sizeof(buffer));
            error("ERROR no regexp match");
         }

         // Convert the extracted data fields to integer values
         kdy = strtol(strndup(buffer + matches[1].rm_so, matches[1].rm_eo - matches[1].rm_so), NULL, 16);
         kmt = strtol(strndup(buffer + matches[2].rm_so, matches[2].rm_eo - matches[2].rm_so), NULL, 16);
         kyr = strtol(strndup(buffer + matches[3].rm_so, matches[3].rm_eo - matches[3].rm_so), NULL, 16);
         kt0 = strtol(strndup(buffer + matches[4].rm_so, matches[4].rm_eo - matches[4].rm_so), NULL, 16);
         tnf = strtol(strndup(buffer + matches[5].rm_so, matches[5].rm_eo - matches[5].rm_so), NULL, 16);
         tkk = strtol(strndup(buffer + matches[6].rm_so, matches[6].rm_eo - matches[6].rm_so), NULL, 16);
         pac = strtol(strndup(buffer + matches[7].rm_so, matches[7].rm_eo - matches[7].rm_so), NULL, 16);
         prl = strtol(strndup(buffer + matches[8].rm_so, matches[8].rm_eo - matches[8].rm_so), NULL, 16);
         il1 = strtol(strndup(buffer + matches[9].rm_so, matches[9].rm_eo - matches[9].rm_so), NULL, 16);
         idc = strtol(strndup(buffer + matches[10].rm_so, matches[10].rm_eo - matches[10].rm_so), NULL, 16);
         ul1 = strtol(strndup(buffer + matches[11].rm_so, matches[11].rm_eo - matches[11].rm_so), NULL, 16);
         udc = strtol(strndup(buffer + matches[12].rm_so, matches[12].rm_eo - matches[12].rm_so), NULL, 16);
         sys = strtol(strndup(buffer + matches[13].rm_so, matches[13].rm_eo - matches[13].rm_so), NULL, 16);

         // Construct the query
         sprintf(query, "INSERT INTO log\
            (kdy, kmt, kyr, kt0, tnf, tkk, pac, prl, il1, idc, ul1, udc, sys)\
            VALUES (%d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d);",
            kdy, kmt, kyr, kt0, tnf, tkk, pac, prl, il1, idc, ul1, udc, sys);

         // Execute the query to write the data into db
         mysql_query(connection, query);
         if (mysql_errno(connection))
            error(mysql_error(connection));

         // Wait for the specified number of seconds
         sleep(log_interval);
      }
   }

   return 0;
}
zagibu
Vielschreiber
Vielschreiber
 
- Threadstarter -
 
Beiträge: 196
Registriert: 27.07.2010, 16:31
PV-Anlage [kWp]: 3
Info: Betreiber

Re: SolarMax Datenlogger

Beitragvon lasser » 06.08.2010, 12:30

Moin!
zagibu hat geschrieben:Hier ist eine getestete Version, die zusammen mit der oben geposteten DB funktioniert (braucht natürlich immer noch libmysqlclient, libmysqlclient-dev und den gcc-Schalter -lmysqlclient am Ende)...

So, ich denke ich bin raus. Bei mir wird der logger zwar kompiliert - mit warnings:
Code: Alles auswählen
user@rechner:~/test$ gcc -o logger logger.c -lmysqlclient
logger.c: In function ‘error’:
logger.c:28: warning: incompatible implicit declaration of built-in function ‘exit’
logger.c: In function ‘main’:
logger.c:63: warning: incompatible implicit declaration of built-in function ‘malloc’
logger.c:75: warning: passing argument 1 of ‘error’ discards qualifiers from pointer target type
logger.c:93: warning: incompatible implicit declaration of built-in function ‘bzero’
logger.c:95: warning: incompatible implicit declaration of built-in function ‘bcopy’
logger.c:99: warning: passing argument 2 of ‘connect’ from incompatible pointer type
logger.c:110: warning: incompatible implicit declaration of built-in function ‘strlen’
logger.c:131: warning: incompatible implicit declaration of built-in function ‘strndup’
logger.c:154: warning: passing argument 1 of ‘error’ discards qualifiers from pointer target type

- aber es scheint mir nicht so, dass in die Datenbank, die bei mir so aussieht:
Code: Alles auswählen
mysql> show columns from log;
+---------+-----------+------+-----+-------------------+-------+
| Field   | Type      | Null | Key | Default           | Extra |
+---------+-----------+------+-----+-------------------+-------+
| created | timestamp | NO   | PRI | CURRENT_TIMESTAMP |       |
| kdy     | int(11)   | NO   |     | 0                 |       |
| kmt     | int(11)   | NO   |     | 0                 |       |
| kyr     | int(11)   | NO   |     | 0                 |       |
| kt0     | int(11)   | NO   |     | 0                 |       |
| tnf     | int(11)   | NO   |     | 0                 |       |
| tkk     | int(11)   | NO   |     | 0                 |       |
| pac     | int(11)   | NO   |     | 0                 |       |
| prl     | int(11)   | NO   |     | 0                 |       |
| il1     | int(11)   | NO   |     | 0                 |       |
| idc     | int(11)   | NO   |     | 0                 |       |
| ul1     | int(11)   | NO   |     | 0                 |       |
| udc     | int(11)   | NO   |     | 0                 |       |
| sys     | int(11)   | NO   |     | 0                 |       |
+---------+-----------+------+-----+-------------------+-------+
14 rows in set (0.08 sec)

irgendetwas hineingeschrieben wird. Ich weiß nicht, wo ich nachschauen soll, logs gibt's auch keine. Im Feld "Default" steht bei Dir allerdings "Null", bei mir die Ziffer 0. Macht das einen Unterschied? Wenn ja, wie erstelle ich die Tabelle denn? Wenn ich beim erstellen der Tabelle "NULL" angebe, erhalte ich die Meldung, dass das ein falscher Default-Wert wäre, bei der Ziffer 0 läuft alles durch.
Wenn der logger läuft gibt es allerdings auch keine Fehlermeldungen...

Ich wäre um Hilfestellung dankbar, auch wenn es dann OT wird und Du mich vielleicht zu irgendeiner manpage oder zu einem Forum jagst ;-)
lasser
Stammmitglied
Stammmitglied
 
Beiträge: 112
Registriert: 16.04.2010, 10:42
PV-Anlage [kWp]: 5,5
Info: Betreiber

Re: SolarMax Datenlogger

Beitragvon RasGer » 06.08.2010, 14:33

zagibu hat geschrieben:Die Auswertung fehlt leider noch.


Hi @all,

falls ihr Euch bald auf ein Datenformat einigt und es mir bekannt gebt, könntet ihr (solange ihr noch nichts eigenes gebastelt habt :wink: ) mein Tool für die Visualisierung der Daten benutzen. Ich würde dann einen Importfilter für Eure Daten schreiben. Details gerne per PN.

Viele Grüße,
Stephan
SolarAnalyzer - Die Apps für Ihre Photovoltaik-Anlage
(für Windows, Linux, Mac OS und Android)
RasGer
Forumsinventar
Forumsinventar
 
Beiträge: 2278
Registriert: 21.11.2007, 16:29
PV-Anlage [kWp]: 5,2
Info: Betreiber

Nächste

Zurück zu Datenlogger



Ähnliche Beiträge


Wer ist online?

Mitglieder in diesem Forum: 0 Mitglieder und 1 Gast