Capture inverter data yourself? Or want to process generated data? Instead of using the Omnik Portal App?
I found out that it is possible to configure the wifi kit to send TCP/UDP packets to an remote server. After that i started googling around. With some minimal data i started write a small C program.
Some other related project on the internet:
- GitHub – Woutrrr / Omnik-Data-Logger | Language: Python | Found first
- GitHub – micromys / Omnik | Language: PHP
- GitHub – arjenv / omnikstatus | Language: C | Found after completing my own script 🙁
Those scripts are polling the inverter each few minutes. When my research started, i found out, by configuring the web interface, that Omnik also offers the possibility to push TCP or UDP packets to an specified server. With this enabled i started testing en developing.
Reading the message (which byte means what?) was based on the GitHub projects. So someone else did the hard work 😉
Getting started… (read on)
Use a webbrowser to login on the inverter management interface, the navigate to ‘Advanced / Remote server’
You are free to fill in servers B and C. Server A seems to point to the Omnik Portal and cannot be disabled. Some more advanced users can make some firewall tricks to block this behavior. (if wished).
Setting up…
- Have a debian 8 machine
- Download and unzip OmnikListenerTCP to your machine (or compile your own)
- Make it executeable
# chmod +x OmnikListenerTCP
- Execute
# ./OmnikListenerTCP --csv-file=./output.csv
- Ensure your port 8989 is opened, on this port OmnikListerenTCP is listening. (change in source code and recompile)
- Fill in your machine IP/hostname into the Omnik Inverter Management interface (see above) and choose port TCP 8989
- Wait for the sun 🙂
Usage (csv output)
to enable csv output and write to "output.csv" start with # ./OmnikListenerTCP --csv-file=output.csv to enable mysql output
Usage (mysql output)
# ./OmnikListenerTCP \ --mysql-host=127.0.0.1 \ --mysql-user=omnikdata \ --mysql-pass=mysqlpassword \ --mysql-db=omnikdata
-- Required table CREATE TABLE `omnikdata` ( `inverterid` VARCHAR(16) NOT NULL, `moment` DATETIME NOT NULL, `temperature` DECIMAL(5,2) NOT NULL, `p_today` DECIMAL(5,2) UNSIGNED NOT NULL, `p_total` DECIMAL(10,2) UNSIGNED NOT NULL, `h_total` SMALLINT(5) UNSIGNED NOT NULL, `pv1_u` DECIMAL(5,2) NOT NULL, `pv1_i` DECIMAL(5,2) NOT NULL, `pv2_u` DECIMAL(5,2) NOT NULL, `pv2_i` DECIMAL(5,2) NOT NULL, `pv3_u` DECIMAL(5,2) NOT NULL, `pv3_i` DECIMAL(5,2) NOT NULL, `ac1_u` DECIMAL(5,2) NOT NULL, `ac1_i` DECIMAL(5,2) NOT NULL, `ac1_f` DECIMAL(5,2) NOT NULL, `ac1_p` SMALLINT(6) NOT NULL, `ac2_u` DECIMAL(5,2) NOT NULL, `ac2_i` DECIMAL(5,2) NOT NULL, `ac2_f` DECIMAL(5,2) NOT NULL, `ac2_p` SMALLINT(6) NOT NULL, `ac3_u` DECIMAL(5,2) NOT NULL, `ac3_i` DECIMAL(5,2) NOT NULL, `ac3_f` DECIMAL(5,2) NOT NULL, `ac3_p` SMALLINT(6) NOT NULL, INDEX `inverterid` (`inverterid`), INDEX `inverterid_moment` (`inverterid`, `moment`) );
OmnikListenerTCP_c_source
/* * I used Eclipse IDE to help me compile and debug. Eclipse compiles with: * gcc -O0 -g3 -Wall -c -fmessage-length=0 -MMD -MP -MF"main.d" -MT"main.d" -o "main.o" "../main.c" * gcc -L/usr/lib -L/usr/lib/mysql -o "OmnikListenerTCP" ./main.o ./readconfig.o -lmysqlclient */ /* * main.c * * Beej's Guide to Network Programming * http://beej.us/guide/bgnet/output/html/multipage/index.html * http://beej.us/guide/bgnet/output/html/multipage/clientserver.html * * MySQL and C * http://zetcode.com/db/mysqlc/ * http://www.kitebird.com/mysql-book/ch06-3ed.pdf * * Omnik message info found here: * Written in PHP * https://github.com/micromys/Omnik * Written in Python * https://github.com/Woutrrr/Omnik-Data-Logger/blob/master/InverterMsg.py * Written in C * https://github.com/arjenv/omnikstats * */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <sys/socket.h> #include <netdb.h> #include <arpa/inet.h> #include <netinet/in.h> #include <errno.h> #include <unistd.h> #include <sys/wait.h> #include <signal.h> #include <time.h> #include <mysql/mysql.h> #define OMNIKPORTAL_PULL "8899" #define PORT_LISTEN "8989" #define BACKLOG 10 FILE * fp; MYSQL *conn; MYSQL_RES *res; MYSQL_ROW row; unsigned char mysql_enabled = 0; char mysql_host[100] = ""; char mysql_user[32] = "omniklistener"; char mysql_pass[32] = ""; char mysql_db[100] = "energyportal"; unsigned int mysql_port = 3306; unsigned char csv_enabled = 0; char csvfile[250] = ""; unsigned char silent = 0; void doChildProcess(int fd_client); void printMessage(unsigned char *message, int length); void sigchld_handler(int s){ while(waitpid(-1, NULL,WNOHANG) > 0); } // get sockaddr, IPv4 or IPv6: void *get_in_addr(struct sockaddr *sa) { if (sa->sa_family == AF_INET) { return &(((struct sockaddr_in*)sa)->sin_addr); } return &(((struct sockaddr_in6*)sa)->sin6_addr); } int startswith(const char *prefix, const char *string){ size_t lenstring = strlen(string); size_t lenprefix = strlen(prefix); if(lenstring < lenprefix){ return 0; } else{ return (strncmp(string,prefix,lenprefix) == 0); } } int main(int argc, char *argv[]){ int fd_server; //Listening socket int fd_client; //Client connected socket struct addrinfo hints, *res, *p; struct sockaddr_storage their_addr; struct sigaction sa; int status; char ipstr[INET6_ADDRSTRLEN]; socklen_t addr_size; char s[INET6_ADDRSTRLEN]; pid_t pid; //Loop through command line arguments int i = 0; for(i = 0; i < argc; i++){ if(startswith("--mysql-host=",argv[i])){ strcpy(mysql_host,argv[i]+strlen("--mysql-host=")); } if(startswith("--mysql-user=",argv[i])){ strcpy(mysql_user,argv[i]+strlen("--mysql-user=")); } if(startswith("--mysql-pass=",argv[i])){ strcpy(mysql_pass,argv[i]+strlen("--mysql-pass=")); } if(startswith("--mysql-db=",argv[i])){ strcpy(mysql_db,argv[i]+strlen("--mysql-db=")); } if(startswith("--mysql-port=",argv[i])){ sscanf(argv[i]+strlen("--mysql-port="),"%d",&mysql_port); } if(startswith("--csv-file=",argv[i])){ strcpy(csvfile,argv[i]+strlen("--csv-file=")); } if(startswith("--silent",argv[i])){ silent = 1; } } //When --mysql-host is filled, then enable mysql export if(strlen(mysql_host) > 0){ mysql_enabled = 1; } //When --csv-file is filled, then enable csv export if(strlen(csvfile) > 0){ csv_enabled = 1; } //Print out active configuration printf("OmnikListenerTCP\n"); printf(" MySQL enabled : %d\n",mysql_enabled); if(mysql_enabled){ printf(" MySQL host : %s\n",mysql_host); printf(" MySQL port : %d\n",mysql_port); printf(" MySQL user : %s\n",mysql_user); //Maybe hide password? ;) //printf(" MySQL pass : %s\n",mysql_pass); printf(" MySQL db : %s\n",mysql_db); } printf(" CSV enabled : %d\n",csv_enabled); if(csv_enabled){ printf(" CSV file : %s\n",csvfile); } memset(&hints, 0, sizeof hints); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_flags = AI_PASSIVE; if((status = getaddrinfo(NULL, PORT_LISTEN, &hints, &res))!=0){ fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(status)); return 2; } for(p = res; p != NULL; p=p->ai_next){ void *addr; char *ipver; if(p->ai_family == AF_INET){ struct sockaddr_in *ipv4 = (struct sockaddr_in *)p->ai_addr; addr = &(ipv4->sin_addr); ipver = "IPv4"; } else{ struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)p->ai_addr; addr = &(ipv6->sin6_addr); ipver = "IPv6"; } inet_ntop(p->ai_family, addr, ipstr, sizeof ipstr); //printf(" %s: %s\n",ipver,ipstr); } fd_server = socket(res->ai_family, res->ai_socktype, res->ai_protocol); if(fd_server == -1){ perror("socket"); return 3; } else{ printf("Socket opened %d\n",fd_server); } if(bind(fd_server, res->ai_addr, res->ai_addrlen) == -1){ perror("bind"); return 4; } if(listen(fd_server,BACKLOG) == -1){ perror("listen"); return 5; } sa.sa_handler = sigchld_handler; sigemptyset(&sa.sa_mask); sa.sa_flags = SA_RESTART; if(sigaction(SIGCHLD, &sa, NULL) == -1){ perror("sigaction"); exit(1); } printf("server: waiting for connections...\n"); while(1){ addr_size = sizeof their_addr; fd_client = accept(fd_server, (struct sockaddr *)&their_addr, &addr_size); if(fd_client == -1){ perror("accept"); continue; } else{ printf("Socket accepted %d\n",fd_client); } inet_ntop(their_addr.ss_family, get_in_addr((struct sockaddr *)&their_addr), s, sizeof s); printf("server: get connection from %s\n",s); pid = fork(); if(pid == 0){ close(fd_server); //child doesn't need the listener doChildProcess(fd_client); } close(fd_client); } return 0; } void doChildProcess(int fd_client) { int numbytes; unsigned char recvbuf[512]; if((numbytes = recv(fd_client, recvbuf, 511, 0)) == -1){ perror("recv"); exit(1); } printf("client: received bytes %d \n",numbytes); printMessage(recvbuf,numbytes); close(fd_client); exit(0); } void printMessage(unsigned char *message, int length){ time_t timer; char timebuffer[26]; struct tm* tm_info; int i = 0; unsigned char byte; char p_id[17]; short p_temp; long p_etoday; long p_etotal; long p_htotal; short p_vpv1; short p_vpv2; short p_vpv3; short p_ipv1; short p_ipv2; short p_ipv3; short p_vac1; short p_vac2; short p_vac3; short p_iac1; short p_iac2; short p_iac3; short p_fac1; short p_fac2; short p_fac3; short p_pac1; short p_pac2; short p_pac3; time(&timer); tm_info = localtime(&timer); if(length < 143) { printf("Message to short? Do not process.\n"); return; } for(i = 0; i < length; i++){ byte = message[i]; printf("%02X ",byte); if((i + 1) % 8 == 0){ printf("\n"); } } printf("\n"); if(message[0] == 0x68 && message[1] == 0x81 && message[2] == 0x41 && message[3] == 0xB0){ //OK } else{ printf("Header does not match hex: 68 81 41 B0\n"); printf("Skip message"); return; } for(i = 0; i < length; i++){ byte = message[i]; //Byte 15, start of SerialNumber if(i >= 15 && i <= 30){ p_id[i-15]=byte; } //Byte 31-32 contain temperature if(i == 31){ p_temp = 0; p_temp = byte << 8; } if(i == 32){ p_temp = p_temp | byte; } //Byte 33-34 PV 1 input voltage if(i == 33){ p_vpv1 = 0; p_vpv1 = byte << 8; } if(i == 34){ p_vpv1 = p_vpv1 | byte; } //Byte 35-36 PV 2 input voltage if(i == 35){ p_vpv2 = 0; p_vpv2 = byte << 8; } if(i == 36){ p_vpv2 = p_vpv2 | byte; } //Byte 37-38 PV 3 input voltage if(i == 37){ p_vpv3 = 0; p_vpv3 = byte << 8; } if(i == 38){ p_vpv3 = p_vpv3 | byte; } //Byte 39-40 PV 1 input current if(i == 39){ p_ipv1 = 0; p_ipv1 = byte << 8; } if(i == 40){ p_ipv1 = p_ipv1 | byte; } //Byte 41-42 PV 2 input current if(i == 41){ p_ipv2 = 0; p_ipv2 = byte << 8; } if(i == 42){ p_ipv2 = p_ipv2 | byte; } //Byte 43-44 PV 3 input current if(i == 43){ p_ipv3 = 0; p_ipv3 = byte << 8; } if(i == 44){ p_ipv3 = p_ipv3 | byte; } //Byte 45-46 AC output current phase 1 if(i == 45){ p_iac1 = 0; p_iac1 = byte << 8; } if(i == 46){ p_iac1 = p_iac1 | byte; } //Byte 47-48 AC output current phase 2 if(i == 47){ p_iac2 = 0; p_iac2 = byte << 8; } if(i == 48){ p_iac2 = p_iac2 | byte; } //Byte 49-50 AC output current phase 3 if(i ==49){ p_iac3 = 0; p_iac3 = byte << 8; } if(i == 50){ p_iac3 = p_iac3 | byte; } //Byte 51-52 AC output voltage phase 1 if(i == 51){ p_vac1 = 0; p_vac1 = byte << 8; } if(i == 52){ p_vac1 = p_vac1 | byte; } //Byte 53-54 AC output voltage phase 2 if(i == 53){ p_vac2 = 0; p_vac2 = byte << 8; } if(i == 54){ p_vac2 = p_vac2 | byte; } //Byte 55-56 AC output voltage phase 3 if(i == 55){ p_vac3 = 0; p_vac3 = byte << 8; } if(i == 56){ p_vac3 = p_vac3 | byte; } //Byte 57-58 AC output ??what?? phase 1 if(i == 57){ p_fac1 = 0; p_fac1 = byte << 8; } if(i == 58){ p_fac1 = p_fac1 | byte; } //Byte 59-60 AC output power phase 1 if(i == 59){ p_pac1 = 0; p_pac1 = byte << 8; } if(i == 60){ p_pac1 = p_pac1 | byte; } //Byte 61-62 AC output ??what?? phase 2 if(i == 61){ p_fac2 = 0; p_fac2 = byte << 8; } if(i == 62){ p_fac2 = p_fac2 | byte; } //Byte 63-64 AC output power phase 2 if(i == 63){ p_pac2 = 0; p_pac2 = byte << 8; } if(i == 64){ p_pac2 = p_pac2 | byte; } //Byte 63-64 AC output ??what?? phase 3 if(i == 65){ p_fac3 = 0; p_fac3 = byte << 8; } if(i == 66){ p_fac3 = p_fac3 | byte; } //Byte 67-68 AC output power phase 3 if(i == 67){ p_pac3 = 0; p_pac3 = byte << 8; } if(i == 68){ p_pac3 = p_pac3 | byte; } //Byte 69-70 contain total kwh of today if(i == 69){ p_etoday = 0; p_etoday = byte << 8; } if(i == 70){ p_etoday = p_etoday | byte; } //Byte 71-74 contain total kwh if(i == 71){ p_etotal = 0; p_etotal = p_etotal | (byte << 24); } if(i == 72){ p_etotal = p_etotal | (byte << 16); } if(i == 73){ p_etotal = p_etotal | (byte << 8); } if(i == 74){ p_etotal = p_etotal | byte; } //Byte 75-78 contain hours online if(i == 75){ p_htotal = 0; p_htotal = p_htotal | (byte << 24); } if(i == 76){ p_htotal = p_htotal | (byte << 16); } if(i == 77){ p_htotal = p_htotal | (byte << 8); } if(i == 78){ p_htotal = p_htotal | byte; } } p_id[16] = 0; if(!silent){ strftime(timebuffer, sizeof(timebuffer), "%Y-%m-%d %H:%M:%S", tm_info); printf("Time : %s \n", timebuffer); printf("Serial : %s \n", p_id); printf("Temp : %f \n", ((double) p_temp / 10)); printf("E-Today : %f kWh \n", ((double) p_etoday / 100)); printf("E-Total : %f kWh \n", ((double) p_etotal / 10)); printf("H-Total : %ld H \n", p_htotal); printf("PV 1 U : %f V \n", ((double) p_vpv1 / 10)); printf("PV 1 I : %f A \n", ((double) p_ipv1 / 10)); printf("PV 2 U : %f V \n", ((double) p_vpv2 / 10)); printf("PV 2 I : %f A \n", ((double) p_ipv2 / 10)); printf("PV 3 U : %f V \n", ((double) p_vpv3 / 10)); printf("PV 3 I : %f A \n", ((double) p_ipv3 / 10)); printf("AC1 U : %f V \n", ((double) p_vac1 / 10)); printf("AC1 I : %f A \n", ((double) p_iac1 / 10)); printf("AC1 ? : %f ? \n", ((double) p_fac1 / 100)); printf("AC1 P : %d W \n", p_pac1); printf("AC2 U : %f V \n", ((double) p_vac2 / 10)); printf("AC2 I : %f A \n", ((double) p_iac2 / 10)); printf("AC2 ? : %f ? \n", ((double) p_fac2 / 100)); printf("AC2 P : %d W \n", p_pac2); printf("AC3 V : %f V \n", ((double) p_vac3 / 10)); printf("AC3 I : %f A \n", ((double) p_iac3 / 10)); printf("AC3 ? : %f ? \n", ((double) p_fac3 / 100)); printf("AC3 P : %d W \n", p_pac3); strftime(timebuffer, sizeof(timebuffer), "%Y-%m-%d %H:%M:%S", tm_info); printf("%s,%s,%f,%f,%f,%ld,%f,%f,%f,%f,%f,%f,%f,%f,%f,%d,%f,%f,%f,%d,%f,%f,%f,%d\n", timebuffer, p_id, ((double) p_temp / 10), ((double) p_etoday / 100), ((double) p_etotal / 10), p_htotal, ((double) p_vpv1 / 10), ((double) p_ipv1 / 10), ((double) p_vpv2 / 10), ((double) p_ipv2 / 10), ((double) p_vpv3 / 10), ((double) p_ipv3 / 10), ((double) p_vac1 / 10), ((double) p_iac1 / 10), ((double) p_fac1 / 100), p_pac1, ((double) p_vac2 / 10), ((double) p_iac2 / 10), ((double) p_fac2 / 100), p_pac2, ((double) p_vac3 / 10), ((double) p_iac3 / 10), ((double) p_fac3 / 100), p_pac3); } //END !silent if(csv_enabled){ fp = fopen(csvfile,"a"); if(fp == NULL){ perror("fopen"); } else{ printf("Opened file for appending\n"); strftime(timebuffer, sizeof(timebuffer), "%Y-%m-%d %H:%M:%S", tm_info); fprintf(fp, "%s,%s,%f,%f,%f,%ld,%f,%f,%f,%f,%f,%f,%f,%f,%f,%d,%f,%f,%f,%d,%f,%f,%f,%d\n", timebuffer, p_id, ((double) p_temp / 10), ((double) p_etoday / 100), ((double) p_etotal / 10), p_htotal, ((double) p_vpv1 / 10), ((double) p_ipv1 / 10), ((double) p_vpv2 / 10), ((double) p_ipv2 / 10), ((double) p_vpv3 / 10), ((double) p_ipv3 / 10), ((double) p_vac1 / 10), ((double) p_iac1 / 10), ((double) p_fac1 / 100), p_pac1, ((double) p_vac2 / 10), ((double) p_iac2 / 10), ((double) p_fac2 / 100), p_pac2, ((double) p_vac3 / 10), ((double) p_iac3 / 10), ((double) p_fac3 / 100), p_pac3); fflush(fp); fclose(fp); } } if(mysql_enabled){ //Try to write to mysql conn = mysql_init(NULL); if(conn == NULL){ perror("mysql_init"); } else{ if(!mysql_real_connect(conn,mysql_host,mysql_user,mysql_pass,mysql_db,mysql_port,NULL,0)){ fprintf(stderr, "%s\n",mysql_error(conn)); } else{ char stmt_buf[1024]; sprintf(stmt_buf,"" "INSERT INTO `omnikdata` (" "`inverterid`,`moment`,`temperature`,`p_today`,`p_total`,`h_total`," "`pv1_u`,`pv1_i`,`pv2_u`,`pv2_i`,`pv3_u`,`pv3_i`," "`ac1_u`,`ac1_i`,`ac1_f`,`ac1_p`," "`ac2_u`,`ac2_i`,`ac2_f`,`ac2_p`," "`ac3_u`,`ac3_i`,`ac3_f`,`ac3_p`) " " VALUES (" "'%s',NOW(),%f,%f,%f,%ld,%f,%f,%f,%f,%f,%f,%f,%f,%f,%d,%f,%f,%f,%d,%f,%f,%f,%d);", p_id, ((double) p_temp / 10), ((double) p_etoday / 100), ((double) p_etotal / 10), p_htotal, ((double) p_vpv1 / 10), ((double) p_ipv1 / 10), ((double) p_vpv2 / 10), ((double) p_ipv2 / 10), ((double) p_vpv3 / 10), ((double) p_ipv3 / 10), ((double) p_vac1 / 10), ((double) p_iac1 / 10), ((double) p_fac1 / 100), p_pac1, ((double) p_vac2 / 10), ((double) p_iac2 / 10), ((double) p_fac2 / 100), p_pac2, ((double) p_vac3 / 10), ((double) p_iac3 / 10), ((double) p_fac3 / 100), p_pac3); int nQueryResult = mysql_query(conn,stmt_buf); if (nQueryResult > 0){ fprintf(stdout,"%s\n",mysql_error(conn)); } else{ } //res = mysql_use_result(conn); //while((row = mysql_fetch_row(res)) != NULL){ // printf("Database: %s\n",row[0]); // //int dumpresult = system("") //} //mysql_free_result(res); mysql_close(conn); } //END mysql_real_connect } //END mysql_init } //END mysql_enabled }
Super handig. Ik stond op het punt om zelf te beginnen toen ik jouw werk tegenkwam. Binnen 5 minuten lopend.
Ik wil de data integreren met andere verbruiksgegevens in Openhab, dus ik moet de C skills weer eens oppoetsen (of via csv.)
Bedankt
Niels
Goed om te horen dat er binnen 5 minuten resultaten geboekt kunnen worden.
Met openHAB ben ik niet bekend (wel interessant om later eens verder naar te kijken). Ik heb even kort onderzoek gedaan en wellicht kun je een deel code samenvoegen uit het volgende project:
https://github.com/openhab/openhab/wiki/Open-Energy-Monitor-Binding
Ik kan er compleet naast zitten, maar het lijkt er op dat deze Open-Energy-Monitor addon een udp connectie naar localhost (openHab) legt om data aan te bieden.
Dan hoef je in de OmnikListener.c alleen maar een extra udp connectie te openen en een beetje dataformatting te doen. Heel simpel gezegd 🙂
Mvgr,
Robin