thermometers/thermometers.c

changeset 51
a03b6dac5398
parent 45
053c4657105f
child 144
3446371e0bdb
equal deleted inserted replaced
50:8b5e8f1e172d 51:a03b6dac5398
1 /*****************************************************************************
2 * Copyright (C) 2014
3 *
4 * Michiel Broek <mbroek at mbse dot eu>
5 *
6 * This file is part of the mbsePi-apps
7 *
8 * This is free software; you can redistribute it and/or modify it
9 * under the terms of the GNU General Public License as published by the
10 * Free Software Foundation; either version 2, or (at your option) any
11 * later version.
12 *
13 * mbsePi-apps is distributed in the hope that it will be useful, but
14 * WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with EC-65K; see the file COPYING. If not, write to the Free
20 * Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
21 *****************************************************************************/
22
23 #include "thermometers.h"
24
25
26 #define STATUS_CONNECTING 0
27 #define STATUS_CONNACK_RECVD 1
28 #define STATUS_WAITING 2
29
30 /* Global variables for use in callbacks. */
31 static int qos = 0;
32 static int status = STATUS_CONNECTING;
33 static int mid_sent = 0;
34 static int last_mid = -1;
35 static int last_mid_sent = -1;
36 static bool connected = true;
37 static bool disconnect_sent = false;
38 static bool connect_lost = false;
39 static bool my_shutdown = false;
40 static pid_t pgrp, mypid;
41
42 extern bool debug;
43 extern sys_config Config;
44 #ifdef HAVE_WIRINGPI_H
45 extern int lcdHandle;
46 #endif
47
48 int server(void);
49 void help(void);
50 void die(int);
51
52
53 void help(void)
54 {
55 fprintf(stdout, "mbsePi-apps thermometers v%s starting\n\n", VERSION);
56 fprintf(stdout, "Usage: thermomeneters [-d] [-h]\n");
57 fprintf(stdout, " -d --debug Debug and run in foreground\n");
58 fprintf(stdout, " -h --help Display this help\n");
59 }
60
61
62
63 void die(int onsig)
64 {
65 switch (onsig) {
66 case SIGHUP: syslog(LOG_NOTICE, "Got SIGHUP, shutting down");
67 break;
68 case SIGINT: syslog(LOG_NOTICE, "Keyboard interrupt, shutting down");
69 break;
70 case SIGTERM: syslog(LOG_NOTICE, "Got SIGTERM, shutting down");
71 break;
72 default: syslog(LOG_NOTICE, "die() on signal %d", onsig);
73 }
74
75 my_shutdown = true;
76 }
77
78
79
80 void my_connect_callback(struct mosquitto *mosq, void *obj, int result)
81 {
82 if (connect_lost) {
83 connect_lost = false;
84 syslog(LOG_NOTICE, "Reconnect: %s", mosquitto_connack_string(result));
85 }
86
87 if (!result) {
88 status = STATUS_CONNACK_RECVD;
89 } else {
90 syslog(LOG_NOTICE, "my_connect_callback: %s\n", mosquitto_connack_string(result));
91 }
92 }
93
94
95
96 void my_disconnect_callback(struct mosquitto *mosq, void *obj, int rc)
97 {
98 if (my_shutdown) {
99 syslog(LOG_NOTICE, "Acknowledged DISCONNECT from %s", Config.mosq_host);
100 connected = false;
101 } else {
102 /*
103 * The remove server was brought down. We must keep running
104 */
105 syslog(LOG_NOTICE, "Received DISCONNECT from %s, connection lost", Config.mosq_host);
106 connect_lost = true;
107 }
108 }
109
110
111
112 void my_publish_callback(struct mosquitto *mosq, void *obj, int mid)
113 {
114 last_mid_sent = mid;
115 }
116
117
118
119 void my_log_callback(struct mosquitto *mosq, void *obj, int level, const char *str)
120 {
121 syslog(LOG_NOTICE, "MQTT: %s", str);
122 printf("MQTT: %s\n", str);
123 }
124
125
126
127 #ifdef HAVE_WIRINGPI_H
128 void stopLCD(void)
129 {
130 lcdClear(lcdHandle);
131 setBacklight(0);
132 }
133 #endif
134
135
136
137 int main(int argc, char *argv[])
138 {
139 int rc, c, i;
140 pid_t frk;
141 #ifdef HAVE_WIRINGPI_H
142 char buf[80];
143 #endif
144
145 while (1) {
146 int option_index = 0;
147 static struct option long_options[] = {
148 {"debug", 0, 0, 'c'},
149 {"help", 0, 0, 'h'},
150 {0, 0, 0, 0}
151 };
152
153 c = getopt_long(argc, argv, "dh", long_options, &option_index);
154 if (c == -1)
155 break;
156
157 switch (c) {
158 case 'd': debug = true;
159 break;
160 case 'h': help();
161 return 1;
162 }
163 }
164
165 openlog("thermometers", LOG_PID|LOG_CONS|LOG_NOWAIT, LOG_USER);
166 syslog(LOG_NOTICE, "mbsePi-apps thermometers v%s starting", VERSION);
167 if (debug)
168 fprintf(stdout, "mbsePi-apps thermometers v%s starting\n", VERSION);
169
170 if (rdconfig((char *)"thermometers.conf")) {
171 fprintf(stderr, "Error reading configuration\n");
172 syslog(LOG_NOTICE, "halted");
173 return 1;
174 }
175
176 /*
177 * Catch all the signals we can, and ignore the rest. Note that SIGKILL can't be ignored
178 * but that's live. This daemon should only be stopped by SIGTERM.
179 * Don't catch SIGCHLD.
180 */
181 for (i = 0; i < NSIG; i++) {
182 if ((i != SIGCHLD) && (i != SIGKILL) && (i != SIGSTOP))
183 signal(i, (void (*))die);
184 }
185
186 #ifdef HAVE_WIRINGPI_H
187
188 if (wiringPiSetup () )
189 return 1;
190
191 if ((rc = initLCD (16, 2))) {
192 fprintf(stderr, "Cannot initialize LCD display, rc=%d\n", rc);
193 return 1;
194 }
195
196 lcdPosition(lcdHandle, 0, 0);
197 lcdPuts(lcdHandle, "Thermometers");
198 lcdPosition(lcdHandle, 0, 1);
199 sprintf(buf, "Version %s", VERSION);
200 lcdPuts(lcdHandle, buf);
201 #endif
202
203 if (debug) {
204 /*
205 * For debugging run in foreground.
206 */
207 rc = server();
208 } else {
209 /*
210 * Server initialization is complete. Now we can fork the
211 * daemon and return to the user. We need to do a setpgrp
212 * so that the daemon will no longer be assosiated with the
213 * users control terminal. This is done before the fork, so
214 * that the child will not be a process group leader. Otherwise,
215 * if the child were to open a terminal, it would become
216 * associated with that terminal as its control terminal.
217 */
218 if ((pgrp = setpgid(0, 0)) == -1) {
219 syslog(LOG_NOTICE, "setpgpid failed");
220 }
221
222 frk = fork();
223 switch (frk) {
224 case -1:
225 syslog(LOG_NOTICE, "Daemon fork failed: %s", strerror(errno));
226 syslog(LOG_NOTICE, "Finished, rc=1");
227 #ifdef HAVE_WIRINGPI_H
228 stopLCD();
229 #endif
230 exit(1);
231 case 0: /*
232 * Run the daemon
233 */
234 fclose(stdin);
235 if (open("/dev/null", O_RDONLY) != 0) {
236 syslog(LOG_NOTICE, "Reopen of stdin to /dev/null failed");
237 _exit(2);
238 }
239 fclose(stdout);
240 if (open("/dev/null", O_WRONLY | O_APPEND | O_CREAT,0600) != 1) {
241 syslog(LOG_NOTICE, "Reopen of stdout to /dev/null failed");
242 _exit(2);
243 }
244 fclose(stderr);
245 if (open("/dev/null", O_WRONLY | O_APPEND | O_CREAT,0600) != 2) {
246 syslog(LOG_NOTICE, "Reopen of stderr to /dev/null failed");
247 _exit(2);
248 }
249 mypid = getpid();
250 rc = server();
251 break;
252 /* Not reached */
253 default:
254 /*
255 * Here we detach this process and let the child
256 * run the deamon process.
257 */
258 syslog(LOG_NOTICE, "Starting daemon with pid %d", frk);
259 exit(0);
260 }
261 }
262
263 syslog(LOG_NOTICE, "Finished, rc=%d", rc);
264 return rc;
265 }
266
267
268
269 int server(void)
270 {
271 char *id = NULL, *state = NULL;
272 struct mosquitto *mosq = NULL;
273 char hostname[256], buf[1024];
274 int temp, rc, deviation, keepalive = 60;
275 #ifdef HAVE_WIRINGPI_H
276 int lcdupdate;
277 #endif
278 unsigned int max_inflight = 20;
279 char err[1024];
280 w1_therm *tmp1, *old1;
281 char *device, *alias, line[60], *p = NULL;
282 FILE *fp;
283
284 /*
285 * Initialize mosquitto communication
286 */
287 mosquitto_lib_init();
288
289 /*
290 * Build MQTT id
291 */
292 hostname[0] = '\0';
293 gethostname(hostname, 256);
294 hostname[255] = '\0';
295
296 id = xstrcpy((char *)"thermometers/");
297 id = xstrcat(id, hostname);
298 if(strlen(id) > MOSQ_MQTT_ID_MAX_LENGTH) {
299 /*
300 * Enforce maximum client id length of 23 characters
301 */
302 id[MOSQ_MQTT_ID_MAX_LENGTH] = '\0';
303 }
304
305 mosq = mosquitto_new(id, true, NULL);
306 if(!mosq) {
307 switch(errno) {
308 case ENOMEM:
309 syslog(LOG_NOTICE, "mosquitto_new: Out of memory");
310 break;
311 case EINVAL:
312 syslog(LOG_NOTICE, "mosquitto_new: Invalid id");
313 break;
314 }
315 mosquitto_lib_cleanup();
316 return 1;
317 }
318
319 if (debug) {
320 mosquitto_log_callback_set(mosq, my_log_callback);
321 }
322
323 /*
324 * Set our will
325 */
326 state = xstrcpy((char *)"clients/");
327 state = xstrcat(state, hostname);
328 state = xstrcat(state, (char *)"/thermometers/state");
329 sprintf(buf, "0");
330 if ((rc = mosquitto_will_set(mosq, state, strlen(buf), buf, qos, true))) {
331 if (rc == MOSQ_ERR_INVAL) {
332 syslog(LOG_NOTICE, "mosquitto_will_set: input parameters invalid");
333 } else if (rc == MOSQ_ERR_NOMEM) {
334 syslog(LOG_NOTICE, "mosquitto_will_set: Out of Memory");
335 } else if (rc == MOSQ_ERR_PAYLOAD_SIZE) {
336 syslog(LOG_NOTICE, "mosquitto_will_set: invalid payload size");
337 }
338 mosquitto_lib_cleanup();
339 return rc;
340 }
341
342 mosquitto_max_inflight_messages_set(mosq, max_inflight);
343 mosquitto_connect_callback_set(mosq, my_connect_callback);
344 mosquitto_disconnect_callback_set(mosq, my_disconnect_callback);
345 mosquitto_publish_callback_set(mosq, my_publish_callback);
346
347 if ((rc = mosquitto_connect(mosq, Config.mosq_host, Config.mosq_port, keepalive))) {
348 if (rc == MOSQ_ERR_ERRNO) {
349 strerror_r(errno, err, 1024);
350 syslog(LOG_NOTICE, "mosquitto_connect: error: %s", err);
351 } else {
352 syslog(LOG_NOTICE, "mosquitto_connect: unable to connect (%d)", rc);
353 }
354 mosquitto_lib_cleanup();
355 return rc;
356 }
357 syslog(LOG_NOTICE, "Connected with %s:%d", Config.mosq_host, Config.mosq_port);
358
359 /*
360 * Initialise is complete, report our presence state
361 */
362 mosquitto_loop_start(mosq);
363 sprintf(buf, "1");
364 rc = mosquitto_publish(mosq, &mid_sent, state, strlen(buf), buf, qos, 1);
365 #ifdef HAVE_WIRINGPI_H
366 // setBacklight(0);
367 #endif
368
369 /*
370 * Report alias names
371 */
372 for (tmp1 = Config.w1therms; tmp1; tmp1 = old1) {
373 old1 = tmp1->next;
374
375 alias = xstrcpy((char *)"/raw/");
376 alias = xstrcat(alias, hostname);
377 alias = xstrcat(alias, (char *)"/thermometers/w1/");
378 alias = xstrcat(alias, tmp1->master);
379 alias = xstrcat(alias, (char *)"/");
380 alias = xstrcat(alias, tmp1->name);
381 alias = xstrcat(alias, (char *)"/alias");
382
383 sprintf(buf, "%s", tmp1->alias);
384 if ((rc = mosquitto_publish(mosq, &mid_sent, alias, strlen(buf), buf, qos, 1))) {
385 if (rc == MOSQ_ERR_NO_CONN)
386 mosquitto_reconnect(mosq);
387 else
388 syslog(LOG_NOTICE, "mainloop: error %d from mosquitto_publish", rc);
389 }
390
391 free(alias);
392 alias = NULL;
393 }
394
395 if (debug)
396 fprintf(stdout, (char *)"Enter loop, connected %d\n", connected);
397
398 do {
399 if (status == STATUS_CONNACK_RECVD) {
400 #ifdef HAVE_WIRINGPI_H
401 lcdupdate = FALSE;
402 #endif
403
404 /*
405 * Here send our 1-wire sensors values
406 */
407 for (tmp1 = Config.w1therms; tmp1; tmp1 = old1) {
408 old1 = tmp1->next;
409
410 /*
411 * Build path and alias topic
412 */
413 device = xstrcpy((char *)"/sys/bus/w1/devices/");
414 device = xstrcat(device, tmp1->master);
415 device = xstrcat(device, (char *)"/");
416 device = xstrcat(device, tmp1->name);
417 device = xstrcat(device, (char *)"/w1_slave");
418 alias = xstrcpy((char *)"/raw/");
419 alias = xstrcat(alias, hostname);
420 alias = xstrcat(alias, (char *)"/thermometers/w1/");
421 alias = xstrcat(alias, tmp1->master);
422 alias = xstrcat(alias, (char *)"/");
423 alias = xstrcat(alias, tmp1->name);
424 alias = xstrcat(alias, (char *)"/temperature");
425
426 /*
427 * Read sensor data
428 */
429 if ((fp = fopen(device, "r"))) {
430 /*
431 * The output looks like:
432 * 72 01 4b 46 7f ff 0e 10 57 : crc=57 YES
433 * 72 01 4b 46 7f ff 0e 10 57 t=23125
434 */
435 fgets(line, 50, fp);
436 line[strlen(line)-1] = '\0';
437 if ((line[36] == 'Y') && (line[37] == 'E')) {
438 /*
439 * CRC is Ok, continue
440 */
441 fgets(line, 50, fp);
442 line[strlen(line)-1] = '\0';
443 strtok(line, (char *)"=");
444 p = strtok(NULL, (char *)"=");
445 rc = sscanf(p, "%d", &temp);
446 if ((rc == 1) && (tmp1->lastval != temp)) {
447 /*
448 * It is possible to have read errors or extreme values.
449 * This can happen with bad connections so we compare the
450 * value with the previous one. If the difference is too
451 * much, we don't send that value. That also means that if
452 * the next value is ok again, it will be marked invalid too.
453 * Maximum error is 20 degrees for now.
454 */
455 deviation = 20000;
456 if ((tmp1->lastval == 0) ||
457 (tmp1->lastval && (temp > (tmp1->lastval - deviation)) && (temp < (tmp1->lastval + deviation)))) {
458 /*
459 * Temperature is changed and valid, update and publish this.
460 */
461 sprintf(buf, "%.1f", temp / 1000.0);
462 if ((rc = mosquitto_publish(mosq, &mid_sent, alias, strlen(buf), buf, qos, 1))) {
463 if (rc == MOSQ_ERR_NO_CONN)
464 mosquitto_reconnect(mosq);
465 else
466 syslog(LOG_NOTICE, "mainloop: error %d from mosquitto_publish", rc);
467 }
468 } else {
469 syslog(LOG_NOTICE, "deviation error deviation=%d, old=%d new=%d", deviation, tmp1->lastval, temp);
470 if (debug) {
471 fprintf(stdout, "deviation error deviation=%d, old=%d new=%d\n", deviation, tmp1->lastval, temp);
472 }
473 }
474 tmp1->lastval = temp;
475 #ifdef HAVE_WIRINGPI_H
476 lcdupdate = TRUE;
477 #endif
478 }
479 } else {
480 syslog(LOG_NOTICE, "sensor %s/%s CRC error", tmp1->master, tmp1->name);
481 }
482 fclose(fp);
483 tmp1->present = 1;
484 } else {
485 tmp1->present = 0;
486 if (debug)
487 printf("sensor %s is missing\n", tmp1->name);
488 }
489
490 free(device);
491 device = NULL;
492 free(alias);
493 alias = NULL;
494 }
495
496 #ifdef HAVE_WIRINGPI_H
497 if (lcdupdate) {
498 lcdPosition(lcdHandle, 0, 0);
499 tmp1 = Config.w1therms;
500 snprintf(buf, 16, "%5.1f %cC %s ", tmp1->lastval / 1000.0, 0xdf, tmp1->alias);
501 lcdPuts(lcdHandle, buf);
502 old1 = tmp1->next;
503 tmp1 = old1;
504 lcdPosition(lcdHandle, 0, 1);
505 snprintf(buf, 16, "%5.1f %cC %s ", tmp1->lastval / 1000.0, 0xdf, tmp1->alias);
506 lcdPuts(lcdHandle, buf);
507 }
508 #endif
509
510 if (my_shutdown) {
511 /*
512 * Final publish 0 to clients/<hostname>/thermometers/state
513 */
514 sprintf(buf, "0");
515 mosquitto_publish(mosq, &mid_sent, state, strlen(buf), buf, qos, true);
516 last_mid = mid_sent;
517 status = STATUS_WAITING;
518 #ifdef HAVE_WIRINGPI_H
519 lcdClear(lcdHandle);
520 lcdPosition(lcdHandle, 0, 0);
521 lcdPuts(lcdHandle, "Shuting down ...");
522 #endif
523 }
524
525 usleep(100000);
526
527 } else if (status == STATUS_WAITING) {
528 if (debug)
529 fprintf(stdout, (char *)"Waiting\n");
530 if (last_mid_sent == last_mid && disconnect_sent == false) {
531 mosquitto_disconnect(mosq);
532 disconnect_sent = true;
533 }
534 usleep(100000);
535 }
536 rc = MOSQ_ERR_SUCCESS;
537
538 } while (rc == MOSQ_ERR_SUCCESS && connected);
539
540 if (debug)
541 fprintf(stdout, (char *)"Out of loop\n");
542
543 mosquitto_loop_stop(mosq, false);
544 mosquitto_destroy(mosq);
545 mosquitto_lib_cleanup();
546
547 #ifdef HAVE_WIRINGPI_H
548 stopLCD();
549 #endif
550
551 return rc;
552 }
553

mercurial