Hello,
Le Protocol OBIS IDS est un standard depuis des décennies pour une communication des compteurs énergétiques dans le monde.
Quelqu’un serait t’il capable de faire l’intégration dans jeedom du code « c » ici bas ? Je suis dispo pour les tests avec un datamanager M ( V2 du protocol ) , un Energy meter (V1 du protocol ) et des onduleurs de la même marque SMA .
D’apres shun84, il faut ajouter les dépendances pour un code C dans Jeedom .
Merci
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h> // for open
#include <unistd.h> // for close
#include <arpa/inet.h>
#include <string.h>
#include <math.h>
#define TRUE 1
#define FALSE 0
/* BEGIN OBIS */
typedef struct
{ unsigned char sma_header[12];
unsigned char datlen[2];
unsigned char skip[4];
unsigned char susy[2];
unsigned char serno[4];
unsigned char ticker[4];
unsigned char channels[1500];
} EMETER_DATA;
typedef struct
{
unsigned char channel;
unsigned char idx;
unsigned char typ;
unsigned char tariff;
unsigned char value[8];
} OBIS_TAG;
#define GETW(_a) ((((unsigned int)(_a)[0]) << 8)+ ((_a)[1]))
#define GETDW(_a) (\
( GETW((unsigned char *)(_a)) << 16)\
+ (GETW((unsigned char *)(_a)+2) ) )
static EMETER_DATA emeter_data;
#define CHNTYP_CNTR 8
#define CHNTYP_MEAS 4
static OBIS_TAG notfound={0xFF, 0xFF, 0xFF, 0xFF, 0,0,0,0,0,0,0,0}; // empty tag for notfound entries
/* END OBIS */
static void usage(char *pname)
{
printf("Usage: %s [-s serialnumber] [-l]\n", pname);
printf("\t-s 123456789 Serial number, 0 if not needed\n");
printf("\t-l Run in loop mode\n");
printf("\t-? This help info\n");
printf("\n");
}
/* BEGIN OBIS */
static unsigned char * find_tag(unsigned char * emdat, int len, unsigned char typ, unsigned char idx)
{
OBIS_TAG *dat;
while(len > 0)
{
dat = (OBIS_TAG*) emdat;
if((dat->typ == typ) && (dat->idx == idx)) // match?
return dat->value;
if(dat->typ == CHNTYP_CNTR) // counters are 8 bytes long
{
emdat += 12;
len -= 12;
}
else // measurements are 4 bytes long
{
emdat += 8;
len -= 8;
}
}
return (unsigned char*)¬found.channel; // no match
}
static int handle_emeter_32(EMETER_DATA *emeter_data, int idx)
{
int rawvalue;
int datlen = GETW(emeter_data->datlen);
rawvalue = GETDW(find_tag( emeter_data->channels, datlen, CHNTYP_MEAS, idx));
return rawvalue;
}
static unsigned long long handle_emeter_64(EMETER_DATA *emeter_data, int idx)
{
unsigned long long rawvalue;
unsigned int high, low;
int datlen = GETW(emeter_data->datlen);
high = GETDW(find_tag(emeter_data->channels, datlen, CHNTYP_CNTR, idx));
low = GETDW(4 + find_tag(emeter_data->channels, datlen, CHNTYP_CNTR, idx));
rawvalue = (unsigned long long) high << 32 | low;
return rawvalue;
}
/* END OBIS */
int main( int argc, char *argv[])
{
int udp_sock;
int datalen;
struct sockaddr_in server_address;
struct ip_mreq group;
int checkCall, recvStringLen;
int serialnumber = 0;
int loop = 0;
/* BEGIN OBIS */
int typ = 4;
int idx = 1;
int cnt = 1;
/* END OBIS */
int p;
/* Start at p = 1 to skip the command name. */
if (argc == 1) {
usage(argv[0]);
return EXIT_SUCCESS;
}
for (p = 1; p < argc; p++) {
/* Check for a switch (leading "-"). */
if (argv[p][0] == '-') {
/* Use the next character to decide what to do. */
switch (argv[p][1]) {
case 's':
serialnumber = atoi(argv[++p]);
break;
case 'l':
loop = 1;
break;
case '?':
usage(argv[0]);
return EXIT_SUCCESS;
}
}
}
/* Create socket */
udp_sock=socket(AF_INET, SOCK_DGRAM, 0);
if(udp_sock == -1)
perror("Error: socket failed");
int reuse = 1;
if(setsockopt(udp_sock, SOL_SOCKET, SO_REUSEADDR, (char *)&reuse, sizeof(reuse)) < 0) {
perror("Error: setting SO_REUSEADDR");
close(udp_sock);
exit(1);
}
bzero((char*) &server_address, sizeof(server_address));
/* server's sockaddr_in*/
server_address.sin_family=AF_INET;
server_address.sin_addr.s_addr=INADDR_ANY;
server_address.sin_port=htons(9522);
/* bind server socket and listen for incoming clients */
checkCall = bind(udp_sock, (struct sockaddr *) &server_address, sizeof(struct sockaddr));
if(checkCall == -1)
perror("Error: bind call failed");
/* joining multicast group */
group.imr_multiaddr.s_addr = inet_addr("239.12.255.254");
group.imr_interface.s_addr = htonl(INADDR_ANY);
if(setsockopt(udp_sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char *)&group, sizeof(group)) < 0) {
perror("Error: adding multicast group");
close(udp_sock);
exit(1);
}
unsigned long em_serial = 0; /* energy meter serial number */
unsigned long em_timestamp = 0; /* energy meter utc timestamp in seconds since start */
int em_real_pow = 0; /* energy meter real power from the grid minus to the grid in W, negative means delivery */
int em_pow_1 = 0; /* energy meter real power L1 in W, negative means delivery */
int em_pow_2 = 0; /* energy meter real power L2 in W, negative means delivery */
int em_pow_3 = 0; /* energy meter real power L3 in W , negative means delivery*/
int em_pf = 0; /* energy meter power factor in 0,1 (cosphi) */
int em_fr = 0; /* energy meter power frequency in 0,001 (Hz) */
int em_u1 = 0; /* energy meter voltage on L1 in V */
int em_u2 = 0; /* energy meter voltage on L2 in V */
int em_u3 = 0; /* energy meter voltage on L3 in V */
int em_i1 = 0; /* energy meter current on L1 in mA, negative means delivery */
int em_i2 = 0; /* energy meter current on L2 in mA, negative means delivery */
int em_i3 = 0; /* energy meter current on L3 in mA, negative means delivery */
uint64_t iConsumptionHours = 0;
uint64_t iSurplusHours = 0;
//clientLength = sizeof(client_address);
datalen = sizeof(emeter_data);
while (TRUE) {
recvStringLen = read(udp_sock, &emeter_data, datalen);
if(recvStringLen == -1)
perror("Error: reading datagram message");
if (recvStringLen >= 600 && recvStringLen <= 1500) {
if (serialnumber == 0 || serialnumber == GETDW(emeter_data.serno)) {
/* 4 byte serial */
em_serial = GETDW(emeter_data.serno);
/* 4 byte sma timestamp */
em_timestamp = GETDW(emeter_data.ticker)/1000U;
/* 4 byte voltage / 1000 */
em_u1 = handle_emeter_32(&emeter_data, 32)/1000;
em_u2 = handle_emeter_32(&emeter_data, 52)/1000;
em_u3 = handle_emeter_32(&emeter_data, 72)/1000;
/* real power sum consumption - real power sum surplus / 10 */
em_real_pow = (handle_emeter_32(&emeter_data, 1) - handle_emeter_32(&emeter_data, 2))/10;
/* 4 byte real real power on L1 */
em_pow_1 = (handle_emeter_32(&emeter_data, 21) - handle_emeter_32(&emeter_data, 22))/10;
/* 4 byte real real power on L2 */
em_pow_2 = (handle_emeter_32(&emeter_data, 41) - handle_emeter_32(&emeter_data, 42))/10;
/* 4 byte real real power on L3 */
em_pow_3 = (handle_emeter_32(&emeter_data, 61) - handle_emeter_32(&emeter_data, 62))/10;
/* 4 byte real current */
em_i1 = handle_emeter_32(&emeter_data, 31);
/* 4 byte real current */
em_i2 = handle_emeter_32(&emeter_data, 51);
/* 4 byte real current */
em_i3 = handle_emeter_32(&emeter_data, 71);
/* 4 byte sum power factor all phases */
em_pf = handle_emeter_32(&emeter_data, 13);
/* 8 byte consumption hours */
iConsumptionHours = handle_emeter_64(&emeter_data, 1);
/* 8 byte surplus hours */
iSurplusHours = handle_emeter_64(&emeter_data, 2);
if (recvStringLen > 600) {
/* 4 byte sum frequency all phases */
em_fr = handle_emeter_32(&emeter_data, 14);
}
fprintf(stdout, "SN:%lu; TS:%lu; U1:%d; U2:%d; U3:%d; I1:%d; I2:%d; I3:%d; P:%d; P1:%d; P2:%d; P3:%d; PF:%.3f; FR:%.1f; CON:%.1f; SUR:%.1f\n", em_serial, em_timestamp, em_u1, em_u2, em_u3, em_i1, em_i2, em_i3, em_real_pow, em_pow_1, em_pow_2, em_pow_3, (float)em_pf/1000.0, (float)em_fr/1000.0, (float)iConsumptionHours/3600.0/1000.0, (float)iSurplusHours/3600.0/1000.0);
fflush(stdout);
if (loop == FALSE)
break;
}
}
}
close(udp_sock);
}
/* OBIS Kennzahlen
Die folgende Tabelle gibt eine Übersicht der Messkanäle und deren Definition:
ID Aktueller Mittelwert Zählerstand Beschreibung
4-byte 8-byte
Summen
1 1:1.4.0 1:1.8.0 Wirkleistung/-arbeit +
2 1:2.4.0 1:2.8.0 Wirkleistung/-arbeit −
3 1:3.4.0 1:3.8.0 Blindleistung/-arbeit +
4 1:4.4.0 1:4.8.0 Blindleistung/-arbeit −
9 1:9.4.0 1:9.8.0 Scheinleistung/-arbeit +
10 1:10.4.0 1:10.8.0 Scheinleistung/-arbeit −
13 1:13.4.0 Leistungsfaktor
14 1:14.4.0 Netzfrequenz (erst ab 608 Byte Datagramm verfügbar, FW HM20: >=2.03.4.R)
Phase 1
21 1:21.4.0 1:21.8.0 Wirkleistung/-arbeit +
22 1:22.4.0 1:22.8.0 Wirkleistung/-arbeit −
23 1:23.4.0 1:23.8.0 Blindleistung/-arbeit +
24 1:24.4.0 1:24.8.0 Blindleistung/-arbeit −
29 1:29.4.0 1:29.8.0 Scheinleistung/-arbeit +
30 1:30.4.0 1:30.8.0 Scheinleistung/-arbeit −
31 1:31.4.0 Strom
32 1:32.4.0 Spannung
Phase 2
41 1:41.4.0 1:41.8.0 Wirkleistung/-arbeit +
42 1:42.4.0 1:42.8.0 Wirkleistung/-arbeit −
43 1:43.4.0 1:43.8.0 Blindleistung/-arbeit +
44 1:44.4.0 1:44.8.0 Blindleistung/-arbeit −
49 1:49.4.0 1:49.8.0 Scheinleistung/-arbeit +
50 1:50.4.0 1:50.8.0 Scheinleistung/-arbeit −
51 1:51.4.0 Strom
52 1:52.4.0 Spannung
Phase 3
61 1:61.4.0 1:61.8.0 Wirkleistung/-arbeit +
62 1:62.4.0 1:62.8.0 Wirkleistung/-arbeit −
63 1:63.4.0 1:63.8.0 Blindleistung/-arbeit +
64 1:64.4.0 1:64.8.0 Blindleistung/-arbeit −
69 1:69.4.0 1:69.8.0 Scheinleistung/-arbeit +
70 1:70.4.0 1:70.8.0 Scheinleistung/-arbeit −
71 1:71.4.0 Strom
72 1:72.4.0 Spannung
Sonstige
127 144:0.0.0 Softwareversion (4 Byte)
*/
https://www.sma.de/fr/produits/sma-developer.html
https://www.sma.de/fileadmin/content/global/Partner/Documents/sma_developer/SpeedwireDD-TI-de-10.pdf
https://www.sma.de/fileadmin/content/global/Partner/Documents/SMA_Labs/EMETER-Protokoll-TI-en-10.pdf
Richard Barkestam=> The communication with the Inverter is via TCP on port 502. Your homey will connect and pull information from the inverter.
The communication from EnergyMeter/HomeManager is based on UDP multicast where the EnergyMeter once per second pushes out the current values. In this case, the only thing the app does is open a local socket on your Homey on port 9522 and multicast address 239.12.255.254. Assuming your Homey is able to receive UDP multicast from your EnergyMeter it should work since it is a quite simple and stable setup.
When you add the inverter to your Homey, is the SMA app able to auto-discover it for you or do you have to add it by entering the IP address of it? If the latter, then it would confirm that UDP multicast is blocked in your local network. The discovery of SMA devices is also based on UDP multicast, they call it Speedwire.
No clue I’m afraid if any of this code would work on Solaredge. I guess the foundation of the app would work, like the modbus communication and structure for supporting different inverters which require the use of different modbus registry addresses. But since SMA isn’t using these modbus registry addresses consistently across their product families I would be surprised if it would work across to a different brand.