Sungo SXL MQTT Anbindung
Inhaltsverzeichnis
Allgemeines
Nach einer kleinen Pause (5 Monate) versuche ich die SunGO SXL Anbindung aus dem Versuchstadium zu befreien und mal alles ordentlich zu machen. Die Hardware kommt in eine Gehäuse und die Software soll ihren ersten Stresztest bestehen.
Ausgangssituation
Abgesehen von der SunGo SXL Steuerung sind aktuelle noch ein RasPi 3B, ein CP2102 USBSER und Arduino NANO und 4 DS18B20 im Einsatz. Das Netzteil ist nur eine Übergangslösung. Am liebsten wuerde ich eine MiniUSV mit ins Gehaeuse packen. Mal schauen was es da von der Stange gibt. Auf dem RasPi laeuft derzeit nur eine Software die die Aufgabe hat die Daten des SunGo-Interfaces und die Temperaturen der 4 DS18B20 (Vor-/Ruecklauf-Heizkreis1 und Vor-/Ruecklauf-Fuszbodenheizung) einzulesen und per MQTT an den FHEM-Broker (MQTT-Server) zu liefern.
Software
Sungo_MQTT
1 #include <string.h>
2 #include <stdlib.h>
3 #include <stdio.h>
4 #include <unistd.h>
5 #include <stdbool.h>
6 #include <time.h>
7 #include <sys/types.h>
8 #include <sys/stat.h>
9 #include <fcntl.h>
10 #include <termios.h> // termios is the API that is in general recommended for serial I/O in Unix.
11 #include "MQTTClient.h"
12
13 // MQTT defines
14 #define ADDRESS "tcp://192.168.178.12:1883"
15 //#define ADDRESS "tcp://localhost:1883"
16 #define CLIENTID "SunGo"
17 //#define TOPIC "MQTTExamples"
18 //#define PAYLOAD "Hello World!"
19 #define QOS 0
20 #define TIMEOUT 10000L
21
22 // SERCOM defines
23 #define BAUDRATE B115200
24 #define MODEMDEVICE "/dev/ttyS1"
25 #define _POSIX_SOURCE 1 /* POSIX compliant source */
26 #define FALSE 0
27 #define TRUE 1
28
29
30 // SunGo defines
31 #define DUMMY 0
32 #define TEMPERATUR 1 // °C
33 #define STRAHLUNG 2 // W/m²
34 #define SPEICHER1 7 // kwh
35 #define DATUM 8
36 #define UHRZEIT 9
37 #define PROZENT 10 // %
38 #define FUNKTION1 11
39 #define FUNKTION2 12
40 #define SPEICHER2 15 // h
41 #define VOLUMENSTROM 19 // l/min
42
43
44 volatile int STOP=FALSE; // braucht man eigentlich nicht
45
46
47 // globale Variablen
48 char datum[11];
49 char uhrzeit[7];
50 char temperatur[25];
51 char prozent[7];
52 char strahlung[8];
53 char fehler[17];
54 char stunden[8];
55 char dummy[7];
56
57 // hexadezimale Darstellung in dezimale umstellen
58 unsigned int hextodec(char hex[10]){
59 unsigned int decimal=0;
60 unsigned int length=0;
61 unsigned int base = 1;
62 int i=0;
63
64 while(hex[i]!='\0'){
65 i++;
66 }
67 length=i;
68
69 //printf("hextodec %s\n\r",hex);
70 //printf("length %d\n\r",length);
71
72 for(i = length-1; i >= 0; i--){
73 //printf("i: %d\n\r",i);
74 if (i<0)i=0;
75 if(hex[i] >= '0' && hex[i] <= '9'){
76 decimal += ((unsigned char)hex[i] - 48) * base;
77 base *= 16;
78 }
79 else if(hex[i] >= 'A' && hex[i] <= 'F'){
80 decimal += ((unsigned char)hex[i] - 55) * base;
81 base *= 16;
82 }
83 else if(hex[i] >= 'a' && hex[i] <= 'f'){
84 decimal += ((unsigned char)hex[i] - 87) * base;
85 base *= 16;
86 }
87 }
88 //printf("decimal %d\n\r",decimal);
89 return decimal;
90 }
91
92 void delay(int number_of_seconds) // wird doch nicht gebraucht
93 {
94 // Converting time into milli_seconds
95 int milli_seconds = 1000 * number_of_seconds;
96
97 // Storing start time
98 clock_t start_time = clock();
99
100 // looping till required time is not achieved
101 while (clock() < start_time + milli_seconds)
102 ;
103 }
104
105 // allgemeine Funktion; die verschiedenen Typen werden ueber switch case angesprochen
106 void formatieren(unsigned char typ,unsigned char lowbyte,unsigned char highbyte){
107 char data[10]=""; // dient der Aufnahme des zu wandelnden Wertes gebildet aus highbyte und lowbyte
108 char tmp[3]=""; // hilfvariable
109 char formatted[25]="";
110 char dataStr[5]="";
111 unsigned int dataInteger;
112 float dataFloat;
113 unsigned int dataStunden, dataMinuten;
114
115 //memset(formatted,'\0',sizeof(formatted));
116 //memset(data,'\0',sizeof(data));
117 //memset(tmp,'\0',sizeof(tmp));
118
119 // aus den beiden Einzelwerten wird der "Gesamtwert in der "richtigen" Reihenfolge gebildet
120 sprintf(tmp,"%02X",highbyte);
121 strcpy(data,tmp);
122 sprintf(tmp,"%02X",lowbyte);
123 strcat(data,tmp);
124
125 //printf("Data: %s\n",data);
126
127 dataInteger=hextodec(data);
128
129 switch(typ) {
130 case DUMMY: if(TRUE){
131 strcpy(dummy,data);
132 }
133 else{
134 strcpy(dummy,"Error!");
135 }
136 break;
137 case DATUM: sprintf(dataStr,"%d",dataInteger);
138 strcpy(formatted,"00.00.2021");
139 if(strlen(dataStr)==3){
140 formatted[0]=dataStr[1];
141 formatted[1]=dataStr[2];
142 formatted[4]=dataStr[0];
143 strcpy(datum,formatted);
144 }
145 else if(strlen(dataStr)==4){
146 formatted[0]=dataStr[2];
147 formatted[1]=dataStr[3];
148 formatted[3]=dataStr[0];
149 formatted[4]=dataStr[1];
150 strcpy(datum,formatted);
151 }
152 else{
153 strcpy(datum,"Error!");
154 }
155 break;
156 case UHRZEIT: dataStunden=(unsigned int)dataInteger/60;
157 dataMinuten=dataInteger-(dataStunden*60);
158 if(TRUE){ // hier koennte man die gueltig der stunden und minuten noch testen
159 sprintf(formatted,"%02d:%02d",dataStunden,dataMinuten);
160 strcpy(uhrzeit,formatted);
161 }
162 else{
163 strcpy(uhrzeit,"Error!");
164 }
165 break;
166 case TEMPERATUR: dataFloat=(float)dataInteger/10.0;
167 if(TRUE){
168 //sprintf(formatted,"%5.1f °C (%d) ",dataFloat,dataInteger);
169 sprintf(formatted,"%5.1f °C",dataFloat);
170 strcpy(temperatur,formatted);
171 }
172 else{
173 strcpy(temperatur,"Error!\0");
174 }
175 break;
176 case SPEICHER2: if(TRUE){
177 sprintf(formatted,"%dh",dataInteger);
178 strcpy(stunden,formatted);
179 }
180 else{
181 strcpy(stunden,"Error!");
182 }
183 break;
184 case PROZENT: if(TRUE){
185 sprintf(formatted,"%3d%%",dataInteger);
186 strcpy(prozent,formatted);
187 }
188 else{
189 strcpy(prozent,"Error!");
190 }
191 break;
192 case STRAHLUNG: if(TRUE){
193 sprintf(formatted,"%d W/m² ",dataInteger);
194 strcpy(strahlung,formatted);
195 }
196 else{
197 strcpy(strahlung,"Error!\0");
198 }
199 break;
200 default: printf("Type Error (unknown)\n"); break;
201 }
202
203 return;
204 }
205
206 int main(int argc, char **argv)
207 { int i; // Schleifenzaehler allgemein
208 unsigned int state=0, syncstate=0; // state 10,20,30 oder 31 entsprechend 0100, 0200, 0300, 0301
209 unsigned int dsIndex, dfIndex; // Zeiger auf Datensatz und Datenfeld
210 unsigned long zeitzaehler=0; // Ausgabe verlangsamen 500000 etwa 1 Minute
211
212 unsigned char buf[1];
213 unsigned char merker[6];
214 unsigned char datensatz[100];
215 unsigned char datenfeld[100];
216 unsigned char datenfelder[50][16];
217 bool printflag;
218
219 // MQTT Vorbereitung
220 MQTTClient client;
221 MQTTClient_connectOptions conn_opts = MQTTClient_connectOptions_initializer;
222 MQTTClient_message pubmsg = MQTTClient_message_initializer;
223 MQTTClient_deliveryToken token;
224 int mqttrc;
225 char topic[255]; // zur Aufnahme Werte unterhalb des basetopic
226 char *basetopic = "Haus/Heizung/Solaranlage/SunGo/";
227
228 // SerCOM Vorbereitung
229 struct termios oldtio,newtio;
230 int fd; // FileDescriptor SerCOM
231 int res; // SerCOM Einlesekontrolle
232
233 // MQTT Cleint Struktur anlegen
234 if ((mqttrc = MQTTClient_create(&client, ADDRESS, CLIENTID, MQTTCLIENT_PERSISTENCE_NONE, NULL)) != MQTTCLIENT_SUCCESS){
235 printf("Failed to create mqttclient, return code %d\n", mqttrc);
236 exit(EXIT_FAILURE);
237 }
238
239 // wenn in Kommandozeile die Schnittstelle mitgegeben wird, dann nimm diese sonst versuche default
240 if (argc > 1)
241 fd = open(argv[1], O_RDWR | O_NOCTTY );
242 else
243 fd = open(MODEMDEVICE, O_RDWR | O_NOCTTY );
244 if (fd <0) {
245 perror(MODEMDEVICE);
246 exit(-1);
247 }
248
249 // sichere die aktuellen Einstellungen
250 tcgetattr(fd,&oldtio);
251
252 // baue die neuen Einstellungen zusammen
253 bzero(&newtio, sizeof(newtio));
254 newtio.c_cflag = BAUDRATE | CS8 | CLOCAL | CREAD;
255 newtio.c_iflag = IGNPAR | IXON | IXOFF;
256 newtio.c_oflag = 0;
257 newtio.c_lflag = 0;
258 newtio.c_cc[VTIME] = 0; /* inter-character timer unused */
259 newtio.c_cc[VMIN] = 1; /* blocking read until 1 chars received */
260
261 tcflush(fd, TCIFLUSH);
262 tcsetattr(fd,TCSANOW,&newtio);
263
264 setvbuf(stdout, (char *)NULL, _IONBF, 0);
265
266 while (STOP==FALSE) { // Endlosschleife
267
268 res = read(fd,buf,1);
269
270 //sobald ein AA kommt beginne die Beobachtung
271 if ((buf[0]==170) && (syncstate==5)){
272 syncstate=0;
273 }
274 if ((buf[0]==170) && (syncstate==0)){
275 syncstate=1;
276 merker[0]=buf[0];
277 }
278 if ((buf[0]==85) && (syncstate==1)){
279 syncstate=2;
280 merker[1]=buf[0];
281 }
282 if ((buf[0]==85) && (syncstate==2)){
283 syncstate=3;
284 merker[2]=buf[0];
285 }
286 if ((buf[0]==170) && (syncstate==3)){
287 syncstate=4;
288 merker[3]=buf[0];
289 state=0;
290 }
291 if (((buf[0]==1)||(buf[0]==2)||(buf[0]==3)) && (merker[0]==170) && (merker[1]==85) && (merker[2]==85) && (merker[3]==170) && (syncstate==4)){
292 merker[4]=buf[0];
293 syncstate=5;
294 }
295
296
297 if ((buf[0]==0) && (merker[4]==1) && (merker[0]==170) && (merker[1]==85) && (merker[2]==85) && (merker[3]==170) && (syncstate==5)){
298 state=10; // state 10 bereit fuer 0100
299 printflag=FALSE;
300 for(i=0;i<5;i++)merker[i]=0;
301
302 }
303 if ((buf[0]==0) && (merker[4]==2) && (merker[0]==170) && (merker[1]==85) && (merker[2]==85) && (merker[3]==170) && (syncstate==5)){
304 state=20; // state 10 bereit fuer 0100
305 printflag=FALSE;
306 for(i=0;i<5;i++)merker[i]=0;
307
308 }
309 if ((buf[0]==0) && (merker[4]==3) && (merker[0]==170) && (merker[1]==85) && (merker[2]==85) && (merker[3]==170) && (syncstate==5)){
310 state=30; // state 10 bereit fuer 0100
311 printflag=FALSE;
312 for(i=0;i<5;i++)merker[i]=0;
313 dsIndex=0;
314
315 }
316 if ((buf[0]==1) && (merker[4]==3) && (merker[0]==170) && (merker[1]==85) && (merker[2]==85) && (merker[3]==170) && (syncstate==5)){
317 state=31; // state 10 bereit fuer 0100
318 printflag=FALSE;
319 for(i=0;i<5;i++)merker[i]=0;
320 dfIndex=0;
321
322 }
323
324
325
326 if ((state==10) && printflag) {
327
328 }
329 else if ((state==20) && printflag) {
330
331 }
332 else if ((state==30) && printflag){
333
334 if(dsIndex<100)datensatz[dsIndex++]=buf[0];
335 }
336 else if ((state==31) && printflag) {
337
338 if(dfIndex<100)datenfeld[dfIndex++]=buf[0];
339 }
340 else{
341 //printf("syncstate: %d, state: %d, %X\n\r",syncstate,state,buf[0]);
342 }
343
344 printflag=TRUE;
345
346 if (zeitzaehler==10000){
347 conn_opts.keepAliveInterval = 20;
348 conn_opts.cleansession = 1;
349 // Baue MQTT Verbindung auf
350 if ((mqttrc = MQTTClient_connect(client, &conn_opts)) != MQTTCLIENT_SUCCESS){
351 printf("Failed to connect, return code %d\n", mqttrc);
352 exit(EXIT_FAILURE);
353 }
354 //Datum
355 formatieren(DATUM,datensatz[2],datensatz[3]);
356 printf("Datum: %s\n",datum);
357 strcpy(topic,basetopic);
358 strcat(topic,"Datum");
359 pubmsg.payload = datum;
360 pubmsg.payloadlen = (int)strlen(datum);
361 pubmsg.qos = QOS;
362 pubmsg.retained = 0;
363 if ((mqttrc = MQTTClient_publishMessage(client, topic, &pubmsg, &token)) != MQTTCLIENT_SUCCESS){
364 printf("Failed to publish message, return code %d\n", mqttrc);
365 exit(EXIT_FAILURE);
366 }else{
367 printf("Message published, return code %d\n", mqttrc);
368 }
369 //Uhrzeit
370 formatieren(UHRZEIT,datensatz[4],datensatz[5]);
371 printf("Uhrzeit: %s\n",uhrzeit);
372 strcpy(topic,basetopic);
373 strcat(topic,"Uhrzeit");
374 pubmsg.payload = uhrzeit;
375 pubmsg.payloadlen = (int)strlen(uhrzeit);
376 pubmsg.qos = QOS;
377 pubmsg.retained = 0;
378 if ((mqttrc = MQTTClient_publishMessage(client, topic, &pubmsg, &token)) != MQTTCLIENT_SUCCESS){
379 printf("Failed to publish message, return code %d\n", mqttrc);
380 exit(EXIT_FAILURE);
381 }else{
382 printf("Message published, return code %d\n", mqttrc);
383 }
384 //Kollektor
385 formatieren(TEMPERATUR,datensatz[6],datensatz[7]);
386 printf("Kollektor: %s\n",temperatur);
387 strcpy(topic,basetopic);
388 strcat(topic,"Kollektor/Temperatur");
389 pubmsg.payload = temperatur;
390 pubmsg.payloadlen = (int)strlen(temperatur);
391 pubmsg.qos = QOS;
392 pubmsg.retained = 0;
393 if ((mqttrc = MQTTClient_publishMessage(client, topic, &pubmsg, &token)) != MQTTCLIENT_SUCCESS){
394 printf("Failed to publish message, return code %d\n", mqttrc);
395 exit(EXIT_FAILURE);
396 }else{
397 printf("Message published, return code %d\n", mqttrc);
398 }
399 //Speicher unten
400 formatieren(TEMPERATUR,datensatz[8],datensatz[9]);
401 printf("Speicher unten: %s\n",temperatur);
402 strcpy(topic,basetopic);
403 strcat(topic,"Speicher/unten/Temperatur");
404 pubmsg.payload = temperatur;
405 pubmsg.payloadlen = (int)strlen(temperatur);
406 pubmsg.qos = QOS;
407 pubmsg.retained = 0;
408 if ((mqttrc = MQTTClient_publishMessage(client, topic, &pubmsg, &token)) != MQTTCLIENT_SUCCESS){
409 printf("Failed to publish message, return code %d\n", mqttrc);
410 exit(EXIT_FAILURE);
411 }else{
412 printf("Message published, return code %d\n", mqttrc);
413 }
414 //Speicher oben
415 formatieren(TEMPERATUR,datensatz[10],datensatz[11]);
416 printf("Speicher oben: %s\n",temperatur);
417 strcpy(topic,basetopic);
418 strcat(topic,"Speicher/oben/Temperatur");
419 pubmsg.payload = temperatur;
420 pubmsg.payloadlen = (int)strlen(temperatur);
421 pubmsg.qos = QOS;
422 pubmsg.retained = 0;
423 if ((mqttrc = MQTTClient_publishMessage(client, topic, &pubmsg, &token)) != MQTTCLIENT_SUCCESS){
424 printf("Failed to publish message, return code %d\n", mqttrc);
425 exit(EXIT_FAILURE);
426 }else{
427 printf("Message published, return code %d\n", mqttrc);
428 }
429 //Rücklaufanhebung T7
430 formatieren(TEMPERATUR,datensatz[18],datensatz[19]);
431 printf("Rücklaufanhebung T7: %s\n",temperatur);
432 strcpy(topic,basetopic);
433 strcat(topic,"Rücklaufanhebung/T7/Temperatur");
434 pubmsg.payload = temperatur;
435 pubmsg.payloadlen = (int)strlen(temperatur);
436 pubmsg.qos = QOS;
437 pubmsg.retained = 0;
438 if ((mqttrc = MQTTClient_publishMessage(client, topic, &pubmsg, &token)) != MQTTCLIENT_SUCCESS){
439 printf("Failed to publish message, return code %d\n", mqttrc);
440 exit(EXIT_FAILURE);
441 }else{
442 printf("Message published, return code %d\n", mqttrc);
443 }
444 //Rücklaufanhebung T8
445 formatieren(TEMPERATUR,datensatz[20],datensatz[21]);
446 printf("Rücklaufanhebung T8: %s\n",temperatur);
447 strcpy(topic,basetopic);
448 strcat(topic,"Rücklaufanhebung/T8/Temperatur");
449 pubmsg.payload = temperatur;
450 pubmsg.payloadlen = (int)strlen(temperatur);
451 pubmsg.qos = QOS;
452 pubmsg.retained = 0;
453 if ((mqttrc = MQTTClient_publishMessage(client, topic, &pubmsg, &token)) != MQTTCLIENT_SUCCESS){
454 printf("Failed to publish message, return code %d\n", mqttrc);
455 exit(EXIT_FAILURE);
456 }else{
457 printf("Message published, return code %d\n", mqttrc);
458 }
459 //Ausgang 1
460 formatieren(PROZENT,datensatz[32],0);
461 printf("Ausgang 1: %s\n",prozent);
462 strcpy(topic,basetopic);
463 strcat(topic,"Ausgang/1/Prozent");
464 pubmsg.payload = prozent;
465 pubmsg.payloadlen = (int)strlen(prozent);
466 pubmsg.qos = QOS;
467 pubmsg.retained = 0;
468 if ((mqttrc = MQTTClient_publishMessage(client, topic, &pubmsg, &token)) != MQTTCLIENT_SUCCESS){
469 printf("Failed to publish message, return code %d\n", mqttrc);
470 exit(EXIT_FAILURE);
471 }else{
472 printf("Message published, return code %d\n", mqttrc);
473 }
474 //Ausgang 6
475 formatieren(PROZENT,datensatz[37],0);
476 printf("Ausgang 6: %s\n",prozent);
477 strcpy(topic,basetopic);
478 strcat(topic,"Ausgang/6/Prozent");
479 pubmsg.payload = prozent;
480 pubmsg.payloadlen = (int)strlen(prozent);
481 pubmsg.qos = QOS;
482 pubmsg.retained = 0;
483 if ((mqttrc = MQTTClient_publishMessage(client, topic, &pubmsg, &token)) != MQTTCLIENT_SUCCESS){
484 printf("Failed to publish message, return code %d\n", mqttrc);
485 exit(EXIT_FAILURE);
486 }else{
487 printf("Message published, return code %d\n", mqttrc);
488 }
489 // Speicherstunden
490 formatieren(SPEICHER2,datensatz[50],datensatz[51]);
491 printf("Speicher 2: %s\n",stunden);
492 strcpy(topic,basetopic);
493 strcat(topic,"Speicher/Stunden");
494 pubmsg.payload = stunden;
495 pubmsg.payloadlen = (int)strlen(stunden);
496 pubmsg.qos = QOS;
497 pubmsg.retained = 0;
498 if ((mqttrc = MQTTClient_publishMessage(client, topic, &pubmsg, &token)) != MQTTCLIENT_SUCCESS){
499 printf("Failed to publish message, return code %d\n", mqttrc);
500 exit(EXIT_FAILURE);
501 }else{
502 printf("Message published, return code %d\n", mqttrc);
503 }
504 /* printf("Waiting for up to %d seconds for publication of %s\n"
505 "on topic %s for client with ClientID: %s\n",
506 (int)(TIMEOUT/1000), PAYLOAD, TOPIC, CLIENTID);
507 rc = MQTTClient_waitForCompletion(client, token, TIMEOUT);
508 printf("Message with delivery token %d delivered\n", token);
509 */
510 if ((mqttrc = MQTTClient_disconnect(client, 10000)) != MQTTCLIENT_SUCCESS){
511 printf("Failed to disconnect, return code %d\n", mqttrc);
512 }
513 zeitzaehler=0;
514 }
515 zeitzaehler++;
516 if (zeitzaehler%100000==0)printf("%d\n",zeitzaehler); // Lebenszeichen
517
518 }
519
520 close(fd);
521
522 tcsetattr(fd,TCSANOW,&oldtio);
523
524 MQTTClient_destroy(&client);
525
526 return EXIT_SUCCESS;
527 }