1 /*
2 * Monitor function.
3 *
4 * Greg Lehey, 18 May 2004
5 *
6 * Copyright (c) 2004 by Greg Lehey
7 *
8 * This software is distributed under the so-called ``Berkeley
9 * License'':
10 *
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
13 * are met:
14 *
15 * 1. Redistributions of source code must retain the above copyright
16 * notice, this list of conditions and the following disclaimer.
17 * 2. Redistributions in binary form must reproduce the above copyright
18 * notice, this list of conditions and the following disclaimer in the
19 * documentation and/or other materials provided with the distribution.
20 *
21 * This software is provided ``as is'', and any express or implied
22 * warranties, including, but not limited to, the implied warranties
23 * of merchantability and fitness for a particular purpose are
24 * disclaimed. In no event shall Greg Lehey be liable for any direct,
25 * indirect, incidental, special, exemplary, or consequential damages
26 * (including, but not limited to, procurement of substitute goods or
27 * services; loss of use, data, or profits; or business interruption)
28 * however caused and on any theory of liability, whether in contract,
29 * strict liability, or tort (including negligence or otherwise)
30 * arising in any way out of the use of this software, even if advised
31 * of the possibility of such damage.
32 *
33 * $Id: monitor.c,v 1.24 2005/08/17 03:48:32 grog Exp grog $
34 */
35
36 #include <syslog.h>
37 #include <stdarg.h>
38 #include <sys/types.h>
39 #include <sys/socket.h>
40 #include <netinet/in.h>
41 #ifdef __FreeBSD__
42 #include <libutil.h> /* for realhostname */
43 #include <dev/ppbus/ppi.h>
44 #include <dev/ppbus/ppbconf.h>
45 #endif
46 #ifdef __DragonFly__
47 #include <libutil.h> /* for realhostname */
48 #include <dev/misc/ppi/ppi.h>
49 #include <bus/ppbus/ppbconf.h>
50 #endif
51 #include "main.h"
52 #include <termios.h>
53 #include "statemachine.h"
54
55 /*
56 * For tradition's sake, we make odd buffers the length of a punched
57 * card.
58 */
59 #define CARDSIZE 80
60
61 /*
62 * xterm command sequences to clear the screen (termcap cl capability)
63 * and home the cursor (termcap ho capability). We don't use termcap
64 * here because that requires some assumption about the kind of
65 * terminal, and there's no particular reason to assume that it
66 * relates to the TERM environment variable, but mainly because we're
67 * too lazy. FIXME. Anyway, this is pretty ubiquitous nowadays.
68 */
69 char *xterm_cls = "[H[2J"; /* clear screen sequence for xterm (cl) */
70 char *xterm_home = "[H"; /* home cursor sequence (ho) */
71 char *xterm_cleol = "[K"; /* clear to EOL sequence (ce) */
72
73 int linefd; /* file descriptor of input serial line */
74 int relayfd; /* file descriptor of relay output */
75 FILE *logfd; /* for logging */
76 FILE *graphlogfd; /* for writing graph info */
77 FILE *displayfd; /* for writing direct status info */
78 FILE *debugfd; /* for writing debug information */
79
80 char firmwarerev [CARDSIZE]; /* save temperature probe startup info here */
81 #define NOTEMP -500
82
83 /*
84 * Temperatures read from serial line. The sensors are numbered 1 to
85 * 4, so we use 0 for a null value.
86 */
87 float temps [5] = {0, NOTEMP, NOTEMP, NOTEMP, NOTEMP}; /* one temperature per probe */
88 time_t lastreading [5]; /* and last reading time, to detect problems */
89
90 /*
91 * start temperature at last configuration read in time, used for
92 * ramping calculations Normally this is the start temperature in the
93 * configuration file, but if we change the end temperature without
94 * changing the start temperature, we'll set it to the current
95 * temperature.
96 */
97 float realstarttemp;
98
99 /*
100 * temperature to base our calculations on. Somewhere between the two
101 * fermenter temperatures. See comments in probe2factor in commands.c
102 * for more details.
103 */
104 float basetemp;
105
106 /* The earliest time we can do certain things. */
107 time_t nextheateron; /* next time we can turn the heater on */
108 time_t nextcooleron; /* next time we can turn the cooler on */
109
110 time_t nextheateroff; /* next time we can turn the heater off */
111 time_t nextcooleroff; /* next time we can turn the cooler off */
112
113 time_t probetimeout = 60; /* probe timeout in seconds */
114
115 char idletext [80]; /* a text to print when we go idle */
116 time_t idletexttime; /* and when we did it */
117
118 /*
119 * And, for completely unrelated purposes, the last time we did the
120 * same certain things. We use these values to calculate when to turn
121 * heater and cooler off without overshooting our envelope.
122 *
123 * The algorithm looks like this: when we heat or cool, note the
124 * temperatures when we turn the heat on and off. Then note the
125 * maximum temperature overshoot before the temperature turns around.
126 * Use these three values next time to guess where to stop heating or
127 * cooling to just stay within the envelope.
128 *
129 * There's a margin for error here, of course, particularly when
130 * starting from way outside the envelope. This will tend towards
131 * underestimating the overshoot (since it's relatively small compared
132 * to the start and end temperature difference). That's OK: it's
133 * still better than no estimate.
134 */
135 time_t lastheateron; /* last time we turned the heater on */
136 time_t lastcooleron; /* last time we turned the cooler on */
137
138 time_t lastheateroff; /* and times we turned them off */
139 time_t lastcooleroff;
140
141 /*
142 * The fermenter temperatures corresponding to the last times above.
143 */
144
145 float lastheaterontemp = NOTEMP; /* temp last time we turned the heater on */
146 float lastcoolerontemp = NOTEMP; /* temp last time we turned the cooler on */
147
148 float lastheaterofftemp = NOTEMP; /* temp last time we turned the heater off */
149 float lastcoolerofftemp = NOTEMP; /* temp last time we turned the cooler off */
150
151 /*
152 * The highest and lowest the temperature ascended or descended after
153 * turning off. These values are initialized to make sense the first
154 * time through the loop.
155 */
156 float lastheaterhightemp = NOTEMP; /* highest temp after turning the heater off */
157 float lastcoolerlowtemp = -NOTEMP; /* lowest temp after turning the cooler off */
158
159 float thiscoolerofftemp = NOTEMP; /* temperature to choose this time around */
160 float thisheaterofftemp = NOTEMP; /* temperature to choose this time around */
161
162 float overshootratio; /* ratio of temperature overshoot */
163 float thisgoal; /* temperature we want to get to this time */
164
165 /* What are we doing at the moment? */
166 enum coolstatus coolstatus;
167 enum machinestate state;
168 enum machinestate lastcoolstate = M_IDLE; /* last kind of temperature manipulation */
169
170 time_t now; /* current time */
171 time_t nextlogtime; /* and time next log entry is due */
172 time_t nextgraphlogtime; /* and time next graph log entry is due */
173 time_t lastdisplay; /* time of last display output */
174
175 int monitoring; /* set when we're in monitor () */
176 int doinfodump; /* set to dump info if we're debugging */
177
178 struct termios seriallineparms; /* parameters for the input line */
179
180 /* Stuff for network reporting of temperatures. */
181
182 int listenfd;
183 int connfd;
184 pid_t childpid;
185 socklen_t clilen;
186 struct sockaddr_in cliaddr;
187 struct sockaddr_in servaddr;
188
189 /*
190 * Open and set parameters for serial line.
191 * Return 1 on failure.
192 */
openserialline()193 int openserialline ()
194 {
195 if (linefd) /* stuff left behind? */
196 close (linefd);
197 linefd = open (serialline, O_RDONLY);
198 if (linefd < 0)
199 {
200 fprintf (stderr,
201 "Can't open serial line %s: %s (%d)\n",
202 serialline,
203 strerror (errno),
204 errno );
205 return 1;
206 }
207
208 /* Set up the line the way we want it. */
209 if (tcgetattr (linefd, &seriallineparms) < 0) /* can't get line info */
210 {
211 fprintf (stderr,
212 "Can't get parameters for serial line %s: %s (%d)\n",
213 serialline,
214 strerror (errno),
215 errno );
216 return 1;
217 }
218
219 seriallineparms.c_iflag = 0; /* input flags: none */
220 seriallineparms.c_oflag = 0; /* no output flags */
221 seriallineparms.c_cflag = CS8 | CREAD; /* control flags: 8 bits, read enable */
222 seriallineparms.c_lflag = ICANON; /* local flags: canonical input */
223 seriallineparms.c_ispeed = linespeed; /* set line speed */
224 /*
225 * We don't need to output, but if the speeds aren't the same, the
226 * tcsetattr fails with EINVAL.
227 */
228 seriallineparms.c_ospeed = linespeed; /* set line speed */
229
230 if (tcsetattr (linefd, TCSANOW, &seriallineparms) < 0) /* can't get line info */
231 {
232 fprintf (stderr,
233 "Can't get parameters for serial line %s: %s (%d)\n",
234 serialline,
235 strerror (errno),
236 errno );
237 return 1;
238 }
239 return 0;
240 }
241
242 /*
243 * Open relay line.
244 * Return 1 on failure.
245 */
openrelayline()246 int openrelayline ()
247 {
248 if (relayfd)
249 close (relayfd);
250
251 /* Now open the files. */
252 relayfd = open (relayline, O_WRONLY);
253 if (relayfd < 0)
254 {
255 fprintf (stderr,
256 "Can't open relay controller %s: %s (%d)\n",
257 relayline,
258 strerror (errno),
259 errno );
260 return 1;
261 }
262 return 0;
263 }
264
265 /* Open graph log file. Fail silently. */
opengraphlogfile()266 void opengraphlogfile ()
267 {
268 if (graphlogfd)
269 fclose (graphlogfd);
270 if (graphlogfile [0])
271 {
272 graphlogfd = fopen (graphlogfile, "a");
273 if (graphlogfd == NULL) /* error, don't let this stop us */
274 fprintf (stderr,
275 "Can't open graph log file %s: %s (%d)\n",
276 graphlogfile,
277 strerror (errno),
278 errno );
279 }
280 }
281
282 /* Open log file. Fail silently. */
openlogfile()283 void openlogfile ()
284 {
285 if (logfd)
286 fclose (logfd);
287 if (logfile [0])
288 {
289 logfd = fopen (logfile, "a");
290 if (logfd == NULL) /* error, don't let this stop us */
291 fprintf (stderr,
292 "Can't open log file %s: %s (%d)\n",
293 logfile,
294 strerror (errno),
295 errno );
296 }
297 }
298
299 /* Open display log file. Fail silently. */
opendisplayfile()300 void opendisplayfile ()
301 {
302 if (displayfd)
303 fclose (displayfd);
304 if (displayfile [0])
305 {
306 displayfd = fopen (displayfile, "a");
307 if (displayfd == NULL) /* error, don't let this stop us */
308 fprintf (stderr,
309 "Can't open display log file %s: %s (%d)\n",
310 displayfile,
311 strerror (errno),
312 errno );
313 /*
314 * Clear the screen. We don't use termcap here because that
315 * requires some assumption about the kind of terminal, and
316 * there's no particular reason to assume that it relates to the
317 * TERM environment variable. Anyway, this is pretty ubiquitous
318 * nowadays.
319 */
320 else if (isatty (fileno (displayfd)))
321 fputs (xterm_cls, displayfd); /* clear screen */
322 }
323 }
324
325 /*
326 * This is a wrapper around checkrcfile which checks for file name
327 * changes.
328 */
checkourrcfile()329 void checkourrcfile () /* see if we need to update things */
330 {
331 char oldserialline [MAXPATHLEN];
332 char oldlogfile [MAXPATHLEN];
333 char oldgraphlogfile [MAXPATHLEN];
334 char oldrelayline [MAXPATHLEN];
335 char olddisplayfile [MAXPATHLEN];
336 char olddebugfile [MAXPATHLEN];
337 float oldstarttemp;
338 float oldendtemp;
339 time_t oldtempchangeend;
340
341 /* Keep track of what we had before */
342 strcpy (oldserialline, serialline);
343 strcpy (oldlogfile, logfile);
344 strcpy (oldgraphlogfile, graphlogfile);
345 strcpy (oldrelayline, relayline);
346 strcpy (olddisplayfile, displayfile);
347 oldstarttemp = starttemp;
348 oldendtemp = endtemp;
349 oldtempchangeend = tempchangeend;
350
351 if (checkrcfile ()) /* see if anything has changed */
352 {
353 if (debugfd)
354 dump_state ("rcfile changed");
355 if (strcmp (oldserialline, serialline))
356 openserialline ();
357 if (strcmp (oldlogfile, logfile))
358 openlogfile ();
359 if (strcmp (oldgraphlogfile, graphlogfile))
360 opengraphlogfile ();
361 if (strcmp (oldrelayline, relayline))
362 openrelayline ();
363 if (strcmp (olddisplayfile, displayfile))
364 {
365 if (displayfd) /* currently displaying, */
366 {
367 if (strlen (displayfile) == 0) /* no display file any more */
368 fprintf (displayfd, "\n*** Terminating status display ***\n");
369 else
370 fprintf (displayfd, "\n*** Moving status display to %s ***\n", displayfile);
371 }
372 opendisplayfile ();
373 }
374 if (strcmp (olddebugfile, debugfile))
375 opendebugfile ();
376
377 /*
378 * Yet another attempt at the start/end temperature stuff.
379 *
380 * 1: If we change the start temperature, it should take effect
381 * immediately. If the end temperature hasn't changed, we
382 * approach it at the specified end time. It's not clear how much
383 * use this is.
384 *
385 * 2: If we change the end temperature but not the start
386 * temperature, it should start from the current temperature and
387 * approach this temperature at the specified time. If the time
388 * is in the past, it should take place immediately (yes, this is
389 * just another way to shoot yourself in the foot).
390 *
391 * 3: If both start and end temperature change, we just set them
392 * normally.
393 */
394 if (starttemp != oldstarttemp) /* start temp changed, */
395 {
396 setrelay (0); /* turn things off for the time being */
397 state = M_IDLE; /* and start again */
398 strcpy (idletext, "Due to setting new parameters");
399 idletexttime = now;
400 /* Forget our timeouts */
401 nextheateron = now;
402 nextcooleron = now;
403 nextheateroff = now;
404 nextcooleroff = now;
405 if (starttemp != oldstarttemp) /* start temp has changed, */
406 {
407 realstarttemp = starttemp; /* start at this temperature */
408 tempchangestart = time (NULL); /* now */
409 }
410 }
411 else if ((endtemp != oldendtemp) /* or end temp changed */
412 || (tempchangeend != oldtempchangeend) )
413 {
414 realstarttemp = goaltemp; /* current goal temperature, */
415 tempchangestart = time (NULL); /* start measuring from now */
416 }
417 }
418 }
419
420 /* Format and print a time */
timestring(time_t time,int local)421 char *timestring (time_t time, int local)
422 {
423 struct tm *bursttime;
424 static char string [20];
425
426 /*
427 * You'd think we'd always want local time, but we use this function
428 * to represent differences between two times, and localtime adds
429 * the time zone offset :-(
430 */
431 if (local)
432 bursttime = localtime (&time);
433 else
434 bursttime = gmtime (&time);
435 sprintf (string,
436 "%02d:%02d:%02d ",
437 bursttime->tm_hour,
438 bursttime->tm_min,
439 bursttime->tm_sec );
440 return string;
441 }
442
443 /* Format and print a time */
printtime(time_t time,FILE * fd)444 void printtime (time_t time, FILE *fd)
445 {
446 fputs (timestring (time, 1), fd);
447 }
448
449 /*
450 * Create a current status log message and return to 'text'. Include
451 * a header if 'header' is set, and assume it's an xterm if 'screen'
452 * is set.
453 */
statustext(int header,int screen,char * text,int textlen)454 void statustext (int header, int screen, char *text, int textlen)
455 {
456 char *et = text; /* point to where we put next text */
457 char *cleol = ""; /* clear to EOL when required */
458
459 if (header)
460 {
461 if (screen) /* displaying to screen, */
462 {
463 cleol = xterm_cleol; /* set clear to EOL string */
464 strcpy (et, xterm_cls);
465 et = &et [strlen (et)];
466 }
467 sprintf (et,
468 "Time %6s %7s Base Ambient Goal Offset Room\n"
469 " %6s %7s%s\n",
470 fermenter1label1,
471 fermenter2label1,
472 fermenter1label2,
473 fermenter2label2,
474 cleol );
475 }
476 strlcat (et, timestring (now, 1), textlen);
477 et = &et [strlen (et)];
478 sprintf (et,
479 "%6.2f %6.2f %6.2f %6.2f %6.2f %6.2f %6.2f%s\n",
480 temps [fermenterprobe],
481 temps [fermenter2probe],
482 basetemp,
483 temps [ambientprobe],
484 goaltemp,
485 basetemp - goaltemp,
486 temps [roomtempprobe],
487 cleol );
488 et = &et [strlen (et)];
489 sprintf (et, "Status: %s ", machinestatetext [state]); /* display state */
490
491 /* For the time wait states, show what we're waiting for */
492 switch (state)
493 {
494 case M_IDLE:
495 if (idletext [0]) /* we have a comment to make */
496 {
497 strlcat (et, idletext, textlen);
498 et = &et [strlen (et)];
499 if (now > (idletexttime + idledisplaytime)) /* outlived its usefulness, */
500 idletext [0] = '\0'; /* and never more */
501 }
502 break;
503
504 case M_COOL_ON_WAIT: /* waiting for cooler on time */
505 strlcat (et, timestring (nextcooleron, 1), textlen);
506 et = &et [strlen (et)];
507 break;
508
509 case M_COOL_OFF_WAIT: /* waiting for cooler off time */
510 strlcat (et, timestring (nextcooleroff, 1), textlen);
511 et = &et [strlen (et)];
512 break;
513
514 case M_HEAT_ON_WAIT: /* waiting for heater on time */
515 strlcat (et, timestring (nextheateron, 1), textlen);
516 et = &et [strlen (et)];
517 break;
518
519 case M_HEAT_OFF_WAIT: /* waiting for heater off time */
520 strlcat (et, timestring (nextheateroff, 1), textlen);
521 et = &et [strlen (et)];
522 break;
523
524 default:
525 break;
526 }
527
528 strlcat (et, xterm_cleol, textlen); /* clear to end of line */
529 et = &et [strlen (et)];
530 strlcat (et, "\n", textlen); /* only \n if not on screen */
531 }
532
533 /*
534 * Log current status. Print a header if 'header' is set,
535 * and assume it's an xterm if 'screen' is set.
536 */
logstatus(FILE * fd,int header,int screen)537 void logstatus (FILE *fd, int header, int screen)
538 {
539 char mystatus [1024];
540
541 statustext (header, screen, mystatus, 1024);
542 fputs (mystatus, fd);
543 }
544
545 /*
546 * Write current fermenter status for plotting purposes. If force
547 * !=0, print even if our time is not yet up. This is for events like
548 * relays on and off.
549 */
printstatus(int force)550 void printstatus (int force)
551 {
552 static int outlines = 0; /* output lines on log file */
553
554 if (logfd /* we're logging */
555 && ((now >= nextlogtime) || force ) )
556 {
557 logstatus (logfd, outlines == 0, 0);
558 if (++outlines > logpagesize)
559 outlines = 0; /* force a new page every 50 lines */
560 if (! force)
561 nextlogtime = now + loginterval; /* next time for normal log */
562 }
563 if (displayfd && (now > lastdisplay)) /* not more than once a second */
564 {
565 fseek (displayfd, 0, SEEK_SET);
566 logstatus (displayfd, 1, isatty (fileno (displayfd)));
567 lastdisplay = now;
568 }
569
570 if (graphlogfd /* got a graph log */
571 && (state != M_INIT) /* and we're not initializing */
572 && ((now >= nextgraphlogtime) /* and we're ready to write */
573 || force ) ) /* or we have no choice */
574 {
575 fprintf (graphlogfd,
576 "%d %6.2f %6.2f %6.2f %6.2f %6.2f %6.2f %6.2f %d\n",
577 (int) now,
578 temps [fermenterprobe],
579 temps [fermenter2probe],
580 basetemp,
581 temps [ambientprobe],
582 goaltemp,
583 temps [fermenterprobe] - goaltemp,
584 temps [roomtempprobe],
585 coolstatus );
586 fflush (graphlogfd);
587 if (! force)
588 nextgraphlogtime = now + graphloginterval; /* next time for normal log */
589 }
590 }
591
592 /*
593 * Read temperature information from the serial line and save it
594 * somewhere safe.
595 */
gettemp()596 void gettemp ()
597 {
598 int probe;
599 float temp;
600 char linein [CARDSIZE]; /* read from serial line */
601 int fields;
602 int inlen;
603 time_t now; /* current time */
604
605 inlen = read (linefd, linein, CARDSIZE); /* get a temperature */
606
607 if (inlen != PROBERECORDLEN) /* invalid probe data record */
608 {
609 if (inlen < 0)
610 fprintf (stderr,
611 "Can't read serial line: %s (%d)\n",
612 strerror (errno),
613 errno );
614 else if (state != M_INIT) /* this can happen during initialization */
615 fprintf (stderr,
616 "Invalid temperature sensor input: \"%s\", length %d\n",
617 linein,
618 inlen );
619 return;
620 }
621
622 time (&now);
623 fields = sscanf (linein, "%d %g\n", &probe, &temp); /* get a temperature */
624 if (fields != 2)
625 /*
626 * We don't bother mentioning errors. They can happen when we
627 * start the program and read a partial record.
628 */
629 return;
630
631 if ((probe < 1) || (probe > 4)) /* invalid probe number */
632 return;
633 temps [probe] = temp;
634 lastreading [probe] = now;
635
636 if (state == M_INIT) /* no calculations yet */
637 return;
638
639 if (lastreading [fermenterprobe] < now - probetimeout) /* probe 1 has timed out */
640 {
641 if (lastreading [fermenter2probe] < now - probetimeout) /* and so has probe 2 */
642 {
643 if (dosyslog)
644 syslog (LOG_USER | LOG_EMERG,
645 "Both temperature sensors timed out\n");
646 fprintf (stderr,"Both temperature sensors timed out\n");
647 sleep (1);
648 }
649 else if (probe2factor == 0) /* probe 2 up, but we're ignoring it */
650 {
651 if (dosyslog)
652 syslog (LOG_USER | LOG_EMERG,
653 "Temperature sensor timed out\n");
654 fprintf (stderr,"Temperature sensor timed out\n");
655 sleep (1);
656 }
657 else /* only sensor 1 down */
658 {
659 if (dosyslog)
660 syslog (LOG_USER | LOG_CRIT,
661 "Temperature sensor 1 timed out\n");
662 fprintf (stderr,"Temperature sensor 1 timed out\n");
663 basetemp = temps [fermenter2probe];
664 }
665 }
666 else if (lastreading [fermenter2probe] < now - probetimeout) /* probe 2 timed out */
667 {
668 if (dosyslog)
669 syslog (LOG_USER | LOG_CRIT,
670 "Temperature sensor 2 timed out\n");
671 fprintf (stderr,"Temperature sensor 2 timed out\n");
672 basetemp = temps [fermenterprobe];
673 }
674 else /* all as it should be */
675 basetemp = temps [fermenterprobe] * (1 - probe2factor) /* proportion of 1st fermenter */
676 + temps [fermenter2probe] * probe2factor; /* proportion of 2nd fermenter */
677 }
678
679 /* Talk to the relay board */
setrelay(int bits)680 void setrelay (int bits)
681 {
682 #if defined(__FreeBSD__) || defined(__DragonFly__)
683 ioctl (relayfd, PPISDATA, &bits); /* just output the bits */
684 #else
685 /* This kludge to work around potential endianness problems */
686 char cbits [8];
687
688 cbits [0] = bits; /* only last byte */
689
690 if (write (relayfd, cbits, 1) < 1)
691 fprintf (stderr,
692 "Can't write to relay board: %s (%d)\n",
693 strerror (errno),
694 errno );
695 #endif
696 printstatus (1); /* force status output */
697 }
698
turn_cooler_on()699 void turn_cooler_on ()
700 {
701 /* First update some times */
702 state = M_COOLING;
703 coolstatus = cooling; /* we're cooling now */
704 nextheateroff = now; /* in case we change the defaults */
705 nextcooleroff = now + cooleronmin; /* earliest time to turn the cooler off */
706 nextcooleron = now + cooleroffmin; /* don't turn cooler on until this long */
707
708 /* The temperature we want to drop to */
709 thisgoal = goaltemp - coolerovershoot * coolerholdoff;
710
711 /*
712 * Calculate when to turn off the cooler. This can't be outside the
713 * envelope, but it could be before we hit the edge to compensate
714 * for overshoot.
715 *
716 * The rationale here is that when we turn off the cooler, the
717 * ambient temperature will be lower than the wort temperature, so
718 * the wort will continue to cool for some time. There are a number
719 * of models we can apply:
720 *
721 * 1. Assume that the wort temperature drops by a fixed value. In
722 * this case, we can use linear interpolation.
723 *
724 * 2. Assume that the wort temperature drops by a fixed factor in
725 * relationship to the on and off temperatures. In this case,
726 * we can also use linear interpolation.
727 *
728 * 3. Accept the fact that the truth is more complex and attempt to
729 * describe it as a polynomial. In this case, we apply the
730 * polynomial.
731 *
732 * Model 3 clearly is the most accurate, but it's almost impossible
733 * to derive a polynomial with so inaccurate data (temperature
734 * resolution of only 0.06�C), and where the normal circumstances
735 * are that the wort temperature cycles quickly from the upper limit
736 * to the lower limit, and then more slowly from the lower limit to
737 * the upper limit. Since the limits are (almost) the same every
738 * time, it's difficult to find a way to get enough information.
739 *
740 * Under these circumstances, model 1 looks more than adequate. It
741 * falls down, though, if we change the goal temperature during
742 * operation. In this case, a combination of model 1 and model 2
743 * might be best, but we can't work out what it should be.
744 *
745 * The current compromise is: use model 1. Based on prior
746 * experience, turn the cooler off at a point which should ensure
747 * that the cooler low temperature is at that point between the goal
748 * temperature and the lower bound that has been defined by the
749 * variable 'coolerovershoot' (which defaults to 1, meaning that we
750 * aim for a cooler low temperature equal to the lower bound).
751 *
752 * If the cooler off temperature or the cooler low temperature are
753 * obviously wrong, presumably due to a goal temperature change,
754 * drop the adjustment and assme that there's no overshoot
755 * (i.e. assume that cooler off and cooler low temperatures are the
756 * same).
757 */
758
759 if ((lastcoolerontemp > lastcoolerofftemp)
760 && (lastcoolerofftemp > lastcoolerlowtemp) )
761 /* This is guaranteed to be between 0 and 1 */
762 overshootratio = (lastcoolerofftemp - lastcoolerlowtemp) / (lastcoolerontemp - lastcoolerlowtemp);
763 else
764 overshootratio = 0;
765
766 /*
767 * Temperature at which to turn off the cooler. This can't be lower
768 * than the lower bound, but it can be higher than the upper bound.
769 * That's possible, but unlikely. The most likely cause for this
770 * would be a change of the goal temperature, so for the moment
771 * we'll assume that we want to cool at least to the upper bound.
772 */
773
774 thiscoolerofftemp = thisgoal + (basetemp - goaltemp) * overshootratio;
775 if (thiscoolerofftemp > (goaltemp + heaterholdoff)) /* too warm, */
776 thiscoolerofftemp = goaltemp + heaterholdoff; /* correct */
777
778 lastcoolerontemp = basetemp; /* note current temperature */
779 lastcooleron = now; /* and time */
780
781 /*
782 * Then set relays. This prints status info, so we need to do it
783 * after the updates above.
784 */
785 setrelay (1 << coolrelay); /* turn the cooler on */
786 if (dosyslog)
787 syslog (syslog_prio,
788 "Cooler on, fermenter %2.2f, goal %2.2f\n",
789 basetemp,
790 goaltemp );
791 if (debugfd)
792 dumptempcalcs ("Cooler on");
793 }
794
turn_heater_on()795 void turn_heater_on ()
796 {
797 /* First update some times */
798 state = M_HEATING;
799 coolstatus = heating; /* we're heating now */
800 nextcooleroff = now; /* in case we change the defaults */
801 nextheateroff = now + heateronmin; /* earliest time to turn the heater off */
802 nextheateron = now + heateroffmin; /* don't turn heater on until this long */
803
804 /* The temperature we want to heat to */
805 thisgoal = goaltemp + heaterovershoot * heaterholdoff;
806
807 /*
808 * Calculate the temperature at which to turn off the heater. See
809 * the comments at turn_cooler_on () above for the details.
810 */
811 if ((lastheaterofftemp > lastheaterontemp)
812 && (lastheaterhightemp > lastheaterofftemp) )
813 /* This is guaranteed to be between 0 and 1 */
814 overshootratio = (lastheaterofftemp - lastheaterhightemp) / (lastheaterontemp - lastheaterhightemp);
815 else
816 overshootratio = 0;
817
818 thisheaterofftemp = thisgoal - (goaltemp - basetemp) * overshootratio;
819 if (thisheaterofftemp < (goaltemp - coolerholdoff)) /* too cool, */
820 thisheaterofftemp = goaltemp - coolerholdoff; /* correct */
821
822 lastheaterontemp = basetemp; /* note temperature */
823 lastheateron = now; /* and time */
824
825 /*
826 * Then set relays. This prints status info, so we need to do it
827 * after the updates above.
828 */
829 setrelay (1 << heatrelay); /* turn the heater on */
830 if (dosyslog)
831 syslog (syslog_prio,
832 "Heater on, fermenter %2.2f, goal %2.2f\n",
833 basetemp,
834 goaltemp );
835 if (debugfd)
836 dumptempcalcs ("Heater on");
837 }
838
839 /*
840 * Stop cooling. Set a machine state from what's passed to us.
841 */
turn_cooler_off(int newstate)842 void turn_cooler_off (int newstate)
843 {
844 coolstatus = idle;
845 state = newstate; /* set the sta */
846 lastcoolstate = M_COOLING; /* last thing we did was to cool */
847 lastcoolerofftemp = thisgoal; /* note temperature we were aiming for */
848 lastcoolerlowtemp = basetemp; /* and the lowest we've been this cycle */
849 lastcooleroff = now;
850 setrelay (0); /* turn both things off on */
851
852 /*
853 * XXX do we want a separate minimum time between cooler off and
854 * heater on? It seems reasonable; for now, use heateroffmin.
855 * Interestingly, this also means that we can use the same code
856 * for turning off both relays.
857 */
858 nextcooleron = now + cooleroffmin; /* don't turn cooler on until this long */
859 nextheateron = now + coolertoheaterdelay;
860 if (dosyslog)
861 syslog (syslog_prio,
862 "Cooler off, fermenter %2.2f, goal %2.2f\n",
863 basetemp,
864 goaltemp );
865 if (debugfd)
866 dumptempcalcs ("Cooler off ");
867 if (newstate == M_IDLE) /* which I think it must be */
868 {
869 sprintf (idletext, "Cooled for %s", timestring (lastcooleroff - lastcooleron, 0));
870 idletexttime = now;
871 }
872 }
873
874 /*
875 * Stop heating. Set a machine state from what's passed to us.
876 */
turn_heater_off(int newstate)877 void turn_heater_off (int newstate)
878 {
879 /* Changed state: stop heating */
880 coolstatus = idle;
881 state = newstate; /* set the sta */
882 lastcoolstate = M_HEATING; /* last thing we did was to heat */
883 lastcoolerofftemp = thisgoal; /* note temperature we were aiming for */
884 lastheaterhightemp = basetemp; /* and the highest we've been this cycle */
885 lastheateroff = now;
886 setrelay (0); /* turn both things off on */
887
888 nextcooleron = now + heatertocoolerdelay; /* don't turn cooler on until this long */
889 nextheateron = now + heateroffmin;
890 if (dosyslog)
891 syslog (syslog_prio,
892 "Heater off, fermenter %2.2f, goal %2.2f\n",
893 basetemp,
894 goaltemp );
895 if (debugfd)
896 dumptempcalcs ("Heater off ");
897 if (newstate == M_IDLE) /* which I think it must be */
898 {
899 sprintf (idletext, "Heated for %s", timestring (lastheateroff - lastheateron, 0));
900 idletexttime = now;
901 }
902 }
903
904 /* This is the main monitor function. */
monitor_command(int argc,char * argv[],char * arg0[])905 void monitor_command (int argc, char *argv [], char *arg0 [])
906 {
907 int i;
908
909 /*
910 * Decide whether we want to be here. Do this before anything else.
911 *
912 * We re-read the .rc file when it changes. It probably contains a
913 * 'monitor' command, which will cause us to reenter. Catch that
914 * situation with the following:
915 */
916 if (monitoring)
917 return;
918 monitoring = 1;
919
920 /*
921 * XXX We should take into account having been stopped in mid-ramp.
922 */
923 realstarttemp = starttemp; /* start at speficied temperature */
924 tempchangestart = time (NULL); /* in case we're ramping */
925
926 /*
927 * Open our files. We absolutely need the serial line and the relay
928 * line, so we fail if we can't open them. If the others fail, we
929 * can limp on regardless.
930 */
931 if (openserialline ())
932 return;
933 if (openrelayline ())
934 return;
935 opengraphlogfile ();
936 openlogfile ();
937 opendisplayfile ();
938 opendebugfile ();
939
940 /*
941 * Now we need to get at least one temperature for each sensor, so
942 * that things can work at all.
943 *
944 * It's possible that our first read will get an incomplete first
945 * line, so take a couple more tries than theoretically necessary.
946 *
947 * We do this in state M_INIT, which tells gettemp () that we don't
948 * yet have enough information to calculate temperature parameters.
949 * After we move on, in the main loop, we need to call gettemp ()
950 * again to calculate them.
951 */
952 for (i = 0; i < 6; i++)
953 gettemp ();
954
955 if ((basetemp == NOTEMP)
956 || (temps [ambientprobe] == NOTEMP) )
957 {
958 fprintf (stderr,
959 "Couldn't get temperatures: Ambient %g, %s %g, %s %g\n",
960 temps [ambientprobe],
961 fermenter1label,
962 temps [fermenterprobe],
963 fermenter2label,
964 temps [fermenter2probe] );
965 return;
966 }
967
968 /*
969 * Point of no return. If we get this far, we stay here and loop.
970 */
971
972 /*
973 * Set up a listen socket for information feedback. We don't really
974 * need TCP, but this way anybody can get the information with
975 * telnet.
976 *
977 * This code grew from something in Stevens' UNIX Network
978 * Programming. I don't think much is left except possibly some
979 * names.
980 */
981
982 listenfd = socket (AF_INET, SOCK_STREAM, 0);
983 if (listenfd < 0)
984 perror ("Can't open TCP listen socket");
985
986 if ((i = fcntl (listenfd, F_GETFL, 0)) < 0)
987 perror ("Can't get TCP listen socket flags");
988
989 if ((i = fcntl (listenfd, F_SETFL, i | O_NONBLOCK)) < 0)
990 perror ("Can't set TCP listen socket flags");
991
992 bzero (&servaddr, sizeof (servaddr));
993 servaddr.sin_family = AF_INET;
994 servaddr.sin_addr.s_addr = htonl (INADDR_ANY);
995 servaddr.sin_port = htons (tcpport);
996
997 if (bind (listenfd, (struct sockaddr *) & servaddr, sizeof (servaddr)) < 0)
998 perror ("Can't bind to TCP listen socket");
999
1000 if (listen (listenfd, SOMAXCONN) < 0)
1001 perror ("Can't listen on TCP listen socket");
1002
1003 state = M_INIT; /* initialize */
1004 coolstatus = idle;
1005 time (&now); /* get the time */
1006 setrelay (0); /* paranoia */
1007 dump_state ("start monitor"); /* log to debug if wanted */
1008
1009 /* Decide how we're going to change our temperatures. */
1010 goaltemp = realstarttemp; /* start with the start temperature */
1011
1012 /*
1013 * Now set plausible defaults for the last event temperatures, so
1014 * that the algorithm will work correctly the first time (it will
1015 * not apply any correction to the temperatures).
1016 */
1017 lastcoolerontemp = goaltemp + coolerholdoff; /* temp last time we turned the cooler on */
1018 lastcoolerofftemp = goaltemp - heaterholdoff; /* temp last time we turned the cooler off */
1019 lastcoolerlowtemp = goaltemp - heaterholdoff; /* lowest temp after turning the cooler off */
1020 lastheaterontemp = goaltemp - heaterholdoff; /* temp last time we turned the heater on */
1021 lastheaterofftemp = goaltemp + coolerholdoff; /* temp last time we turned the heater off */
1022 lastheaterhightemp = goaltemp + coolerholdoff; /* highest temp after turning the heater off */
1023
1024 /* OK, ready to go. Run the state machine. */
1025 while (1)
1026 {
1027 int connfd;
1028 FILE *fpout;
1029
1030 if (tempchangeend != 0) /* we're changing temperature over time */
1031 {
1032 if (now < tempchangeend) /* still changing */
1033 goaltemp = realstarttemp + (endtemp - realstarttemp)
1034 * (now - tempchangestart)
1035 / (tempchangeend - tempchangestart); /* interpolate */
1036 else /* got to the end */
1037 {
1038 tempchangeend = 0; /* done its dash */
1039 realstarttemp = endtemp; /* we're staying here now */
1040 goaltemp = endtemp;
1041 }
1042 }
1043 else
1044 goaltemp = realstarttemp;
1045 gettemp ();
1046 if (doinfodump) /* we've had a signal telling us to dump state */
1047 {
1048 doinfodump = 0;
1049 dump_state ("User request");
1050 }
1051 checkourrcfile (); /* see if we need to update things */
1052 time (&now); /* get the time */
1053
1054 /* See if any TCP connections are waiting for us */
1055 clilen = sizeof (cliaddr);
1056 if ((connfd = accept (listenfd,
1057 (struct sockaddr *) &cliaddr,
1058 &clilen ))
1059 >= 0 ) /* got a request */
1060 {
1061 /* XXX This is tacky. Tidy up. */
1062 char mystatus [1024];
1063 unsigned char *addrbyte = (unsigned char *) &(cliaddr.sin_addr.s_addr);
1064
1065 if (realhostname (mystatus, 1024, &cliaddr.sin_addr) == HOSTNAME_FOUND)
1066 syslog (LOG_INFO | LOG_SECURITY,
1067 "Query from %s (%u.%u.%u.%u)\n",
1068 mystatus,
1069 addrbyte [0],
1070 addrbyte [1],
1071 addrbyte [2],
1072 addrbyte [3] );
1073 else
1074 syslog (LOG_INFO | LOG_SECURITY,
1075 "Query from unknown (%u.%u.%u.%u)\n",
1076 addrbyte [0],
1077 addrbyte [1],
1078 addrbyte [2],
1079 addrbyte [3] );
1080 fpout = fdopen (connfd, "w");
1081 if (fpout == NULL)
1082 perror ("Can't reopen connection fd");
1083
1084 statustext (1, 0, mystatus, 1024);
1085 fputs (mystatus, fpout);
1086 fclose (fpout);
1087 }
1088 else if ((errno != EWOULDBLOCK) /* real error, */
1089 && (errno != EINTR) )
1090 perror ("Can't listen to TCP listen socket");
1091
1092 /* Update our extremes. */
1093 if (lastcoolstate == M_COOLING) /* we were cooling */
1094 {
1095 if (basetemp < lastcoolerlowtemp)
1096 lastcoolerlowtemp = basetemp; /* this is the lowest we've been since */
1097 }
1098 else if (lastcoolstate == M_HEATING) /* we were heating */
1099 {
1100 if (basetemp > lastheaterhightemp)
1101 lastheaterhightemp = basetemp; /* this is the highest we've been since */
1102 }
1103
1104 switch (state)
1105 {
1106 case M_INIT: /* initializing */
1107 state = M_IDLE; /* don't think we need to do anything */
1108 gettemp (); /* now do our first calculation */
1109 /* Fall through */
1110
1111 case M_IDLE: /* not doing anything */
1112 /*
1113 * We originally stopped heating or cooling when we come within
1114 * heaterholdoff or coolerholdoff degrees of the temperature.
1115 * It now seems better to stop at exactly the temperature,
1116 * otherwise we have to rely on overrun to get the goal
1117 * temperature. There will be some overrun, though, so this is
1118 * another parameter to watch.
1119 */
1120
1121 if (basetemp > (goaltemp + coolerholdoff)) /* too warm */
1122 {
1123 /* Want to cool. Can we? */
1124 if (nextcooleron > now) /* can't start yet */
1125 {
1126 dump_state ("set cooler on wait");
1127 state = M_COOL_ON_WAIT;
1128 }
1129 else if ((temps [ambientprobe] < (goaltemp - maxcooltempdiff))
1130 || (temps [ambientprobe] <= minambienttemp) )
1131 state = M_COOL_AMBIENT_ABS; /* absolute difference too high */
1132 /* Passed all hurdles. Turn the cooler on. */
1133 else
1134 turn_cooler_on ();
1135 }
1136 else if (basetemp < (goaltemp - heaterholdoff)) /* too cool */
1137 {
1138 /* Want to heat. Can we? */
1139 if (nextheateron > now) /* can't start yet */
1140 {
1141 dump_state ("set heater on wait");
1142 state = M_HEAT_ON_WAIT;
1143 }
1144 else if ((temps [ambientprobe] > (goaltemp + maxheattempdiff))
1145 || (temps [ambientprobe] >= maxambienttemp) )
1146 state = M_HEAT_AMBIENT_ABS; /* absolute difference too high */
1147 /* Passed all hurdles. Turn the heater on. */
1148 else
1149 turn_heater_on ();
1150 }
1151 break;
1152
1153 /* Cooling states */
1154
1155 case M_COOLING: /* actively cooling */
1156 if (now >= nextcooleroff) /* we could turn it off if we wanted to */
1157 {
1158 if (basetemp < thiscoolerofftemp) /* no longer too warm */
1159 turn_cooler_off (M_IDLE);
1160 else if ((temps [ambientprobe] < (goaltemp - maxcooltempdiff))
1161 || (temps [ambientprobe] <= minambienttemp))
1162 turn_cooler_off (M_COOL_AMBIENT_ABS);
1163 }
1164 /* We have a condition to turn off the heater, but it's too early yet. */
1165 else if ((basetemp < (goaltemp - coolerovershoot * coolerholdoff)) /* no longer too warm */
1166 || (temps [ambientprobe] < (goaltemp - maxcooltempdiff)) )
1167 state = M_COOL_OFF_WAIT;
1168 break;
1169
1170 break;
1171
1172 case M_COOL_ON_WAIT: /* waiting for cooler on time */
1173 /*
1174 * Do we still want to cool? First check the ambient
1175 * temperature.
1176 */
1177 if ((temps [ambientprobe] < (goaltemp - maxcooltempdiff))
1178 || (temps [ambientprobe] <= minambienttemp))
1179 state = M_COOL_AMBIENT_ABS;
1180 /*
1181 * If we've just been heating, this may be due to an overshoot,
1182 * so we only cool if we're outside our range. Otherwise we
1183 * should be at least as low as our goal temperature.
1184 */
1185 else if (((lastcoolstate != M_HEATING) /* temperature still low enough? */
1186 && (basetemp <= goaltemp) )
1187 || ((lastcoolstate == M_HEATING)
1188 && (basetemp <= (goaltemp + coolerholdoff)) ) )
1189 /*
1190 * We don't consider the case here that the temperature has
1191 * risen high enough to require cooling. That's very
1192 * unlikely, and in any case it'll be caught on the next
1193 * iteration.
1194 */
1195 {
1196 /*
1197 * We don't consider the case here that the temperature has
1198 * dropped low enough to require heating. That's very
1199 * unlikely, and in any case it'll be caught on the next
1200 * iteration.
1201 */
1202 state = M_IDLE; /* yes, just go idle */
1203 strcpy (idletext, "cancelled cooler on wait");
1204 idletexttime = now;
1205 dump_state (idletext);
1206 }
1207 else if (now >= nextcooleron) /* we can turn it on now */
1208 turn_cooler_on ();
1209 break;
1210
1211 case M_COOL_OFF_WAIT: /* waiting for cooler off time */
1212 /* Do we still want to stop cooling? */
1213 if (now >= nextcooleroff) /* we can turn it off now */
1214 {
1215 if (basetemp > (goaltemp + coolerholdoff)) /* is the temperature still high enough? */
1216 state = M_COOLING; /* no, carry on cooling */
1217 else
1218 turn_cooler_off (M_IDLE); /* yes, do it */
1219 }
1220 break;
1221
1222 case M_COOL_AMBIENT_ABS: /* waiting for absolute ambient temperature */
1223 /* Do we still want to cool? */
1224 if (basetemp < goaltemp) /* temperature low enough? */
1225 {
1226 /*
1227 * We don't consider the case here that the temperature has
1228 * dropped low enough to require heating. That's very
1229 * unlikely, and in any case it'll be caught on the next
1230 * iteration.
1231 */
1232 state = M_IDLE; /* yes, just go idle */
1233 strcpy (idletext, "Temperature in range");
1234 idletexttime = now;
1235 }
1236 else if ((temps [ambientprobe] > (goaltemp - maxcooltempdiff)) /* ambient difference OK now? */
1237 && (temps [ambientprobe] > minambienttemp)
1238 && (now >= nextcooleron) ) /* and we can turn it on */
1239 turn_cooler_on (); /* yes, turn cooler on */
1240 break;
1241
1242 /* Heating states */
1243
1244 case M_HEATING: /* actively heating */
1245 if (now >= nextheateroff) /* we could turn it off if we wanted to */
1246 {
1247 if (basetemp > thisheaterofftemp) /* no longer too cool */
1248 turn_heater_off (M_IDLE);
1249 else if ((temps [ambientprobe] > (goaltemp + maxheattempdiff))
1250 || (temps [ambientprobe] >= maxambienttemp) )
1251 turn_heater_off (M_HEAT_AMBIENT_ABS);
1252 }
1253 /* We have a condition to turn off the heater, but it's too early yet. */
1254 else if ((basetemp > (goaltemp + heaterovershoot * heaterholdoff)) /* no longer too cool */
1255 || (temps [ambientprobe] > (goaltemp + maxheattempdiff)) )
1256 state = M_HEAT_OFF_WAIT;
1257 break;
1258
1259 case M_HEAT_ON_WAIT: /* waiting for heater on time */
1260
1261 /*
1262 * Do we still want to heat? First check the ambient
1263 * temperature.
1264 */
1265 if ((temps [ambientprobe] > (goaltemp + maxheattempdiff))
1266 || (temps [ambientprobe] >= maxambienttemp) )
1267 state = M_HEAT_AMBIENT_ABS;
1268 /*
1269 * If we've just been cooling, this may be due to an overshoot,
1270 * so we only heat if we're below our range. Otherwise we
1271 * should be at least as high as our goal temperature.
1272 */
1273 else if (((lastcoolstate != M_COOLING)
1274 && (basetemp >= goaltemp) )
1275 || ((lastcoolstate == M_COOLING)
1276 && (basetemp >= (goaltemp - heaterholdoff)) ) )
1277 /*
1278 * We don't consider the case here that the temperature has
1279 * risen high enough to require cooling. That's very
1280 * unlikely, and in any case it'll be caught on the next
1281 * iteration.
1282 */
1283 {
1284 strcpy (idletext, "cancelled heater on wait");
1285 idletexttime = now;
1286 dump_state (idletext);
1287 state = M_IDLE; /* yes, just go idle */
1288 }
1289 else if (now >= nextheateron) /* we can turn it on now */
1290 turn_heater_on ();
1291 break;
1292
1293 case M_HEAT_OFF_WAIT: /* waiting for heater off time */
1294 /* Do we still want to stop heating? */
1295 if (now >= nextheateroff) /* we can turn it off now */
1296 {
1297 if (basetemp < (goaltemp - heaterholdoff)) /* is the temperature still high enough? */
1298 state = M_HEATING; /* no, carry on heating */
1299 else
1300 turn_heater_off (M_IDLE);
1301 }
1302 break;
1303
1304 case M_HEAT_AMBIENT_ABS: /* waiting for absolute ambient temperature */
1305 /* Do we still want to heat? */
1306 if (basetemp > goaltemp) /* temperature high enough? */
1307 {
1308 /*
1309 * We don't consider the case here that the temperature has
1310 * risen high enough to require cooling. That's very
1311 * unlikely, and in any case it'll be caught on the next
1312 * iteration.
1313 */
1314 strcpy (idletext, "Temperature in range");
1315 idletexttime = now;
1316 state = M_IDLE; /* yes, just go idle */
1317 }
1318 else if ((temps [ambientprobe] < (goaltemp + maxheattempdiff)) /* ambient difference OK now? */
1319 || (temps [ambientprobe] < maxambienttemp)
1320 && (now >= nextheateron) ) /* and we can turn it on */
1321 turn_heater_on (); /* yes, turn heater on */
1322
1323 break;
1324 }
1325 printstatus (0);
1326 }
1327 }
1328
1329 /*
1330 * Debug stuff. This is here because it references static local
1331 * variables, and I can't be bothered to rearrange.
1332 */
1333
1334 /* Open debug log file. Fail silently. */
opendebugfile()1335 void opendebugfile ()
1336 {
1337 if (debugfd)
1338 fclose (debugfd);
1339 if (debugfile [0]) /* we have a name */
1340 {
1341 debugfd = fopen (debugfile, "a");
1342 if (debugfd == NULL) /* error, don't let this stop us */
1343 fprintf (stderr,
1344 "Can't open debug log file %s: %s (%d)\n",
1345 debugfile,
1346 strerror (errno),
1347 errno );
1348 }
1349 }
1350
1351
1352 /* Called only from other debug routines */
dumpcycleinfo()1353 void dumpcycleinfo ()
1354 {
1355 fprintf (debugfd,
1356 "Temperatures:\n"
1357 "Goal temperature:\t%6.2f\n"
1358 "Probe 2 factor:\t\t%6.2f\n"
1359 "Cooler holdoff:\t\t%6.2f\n"
1360 "Heater holdoff:\t\t%6.2f\n"
1361 "Cooler overshoot:\t%6.2f\n"
1362 "Heater overshoot:\t%6.2f\n"
1363 "Max ambient temp:\t%6.2f\n"
1364 "Min ambient temp:\t%6.2f\n"
1365 "Max ambient temp diff:\t%6.2f\n"
1366 "Min ambient temp diff:\t%6.2f\n\n",
1367 goaltemp,
1368 probe2factor,
1369 coolerholdoff,
1370 heaterholdoff,
1371 coolerovershoot,
1372 heaterovershoot,
1373 maxambienttemp,
1374 minambienttemp,
1375 maxcooltempdiff,
1376 maxheattempdiff );
1377
1378 fprintf (debugfd,
1379 "Current temperatures:\n"
1380 "Fermenter 1 (%10s):\t%6.2f\n"
1381 "Fermenter 2 (%10s):\t%6.2f\n"
1382 "Base temp:\t\t%6.2f (could be out of date)\n"
1383 "Ambient:\t\t%6.2f\n"
1384 "Room:\t\t\t%6.2f\n\n",
1385 fermenter1label,
1386 temps [fermenterprobe],
1387 fermenter2label,
1388 temps [fermenter2probe],
1389 basetemp,
1390 temps [ambientprobe],
1391 temps [roomtempprobe] );
1392
1393 fprintf (debugfd,
1394 "Last cycle temperatures\n"
1395 "lastheaterontemp:\t%6.2f\n"
1396 "lastheaterofftemp:\t%6.2f\n"
1397 "lastheaterhightemp:\t%6.2f\n"
1398 "thisheaterofftemp:\t%6.2f\n"
1399 "lastcoolerontemp:\t%6.2f\n"
1400 "lastcoolerofftemp:\t%6.2f\n"
1401 "lastcoolerlowtemp:\t%6.2f\n"
1402 "thiscoolerofftemp:\t%6.2f\n"
1403 "overshootratio:\t\t%6.2f\n"
1404 "thisgoal:\t\t%6.2f\n\n",
1405 lastheaterontemp,
1406 lastheaterofftemp,
1407 lastheaterhightemp,
1408 thisheaterofftemp,
1409 lastcoolerontemp,
1410 lastcoolerofftemp,
1411 lastcoolerlowtemp,
1412 thiscoolerofftemp,
1413 overshootratio,
1414 thisgoal );
1415 }
1416
1417 /* Main dump routine */
dump_state(char * reason)1418 void dump_state (char *reason)
1419 {
1420 time_t now;
1421
1422 if (debugfd)
1423 {
1424 fprintf (debugfd,
1425 "================================================================================\n"
1426 "Dumping state at ");
1427 time (&now);
1428 printtime (now, debugfd);
1429 fprintf (debugfd, " due to %s\n", reason);
1430
1431 fprintf (debugfd,
1432 "Files:\n"
1433 "Log file:\t\t%s\n"
1434 "Graph log file:\t\t%s\n"
1435 "Display file:\t\t%s\n"
1436 "Debug file:\t\t%s\n\n",
1437 logfile,
1438 graphlogfile,
1439 displayfile,
1440 debugfile );
1441
1442 fprintf (debugfd,
1443 "Probes and relays:\n"
1444 "Ambient:\t\t%d\n"
1445 "Room:\t\t\t%d\n"
1446 "Fermenter 1 (%10s):\t%d\n"
1447 "Fermenter 2 (%10s):\t%d\n"
1448 "Cooler relay:\t\t%d\n"
1449 "Heater relay:\t\t%d\n\n",
1450 roomtempprobe,
1451 ambientprobe,
1452 fermenter1label,
1453 fermenterprobe,
1454 fermenter2label,
1455 fermenter2probe,
1456 coolrelay,
1457 heatrelay );
1458
1459 fprintf (debugfd,
1460 "Log info:\n"
1461 "Log interval:\t\t%d\n"
1462 "Log page size:\t\t%d\n"
1463 "Graph log interval:\t%d\n"
1464 "Do syslog:\t\t%d\n"
1465 "Syslog priority:\t%d\n\n",
1466 loginterval,
1467 logpagesize,
1468 graphloginterval,
1469 dosyslog,
1470 syslog_prio );
1471
1472 fprintf (debugfd,
1473 "Timeouts:\n"
1474 "Heater on min:\t\t%d\n"
1475 "Heater off min:\t\t%d\n"
1476 "Cooler on min:\t\t%d\n"
1477 "Cooler off min:\t\t%d\n"
1478 "Cooler to heater delay:\t%d\n"
1479 "Heater to cooler delay:\t%d\n\n",
1480 heateronmin,
1481 heateroffmin,
1482 cooleronmin,
1483 cooleroffmin,
1484 coolertoheaterdelay,
1485 heatertocoolerdelay );
1486
1487 fprintf (debugfd,
1488 "Seconds to timeouts:\n"
1489 "nextheateron:\t\t%d\n"
1490 "nextheateroff:\t\t%d\n"
1491 "lastheateron:\t\t%d\n"
1492 "lastheateroff:\t\t%d\n"
1493 "nextcooleron:\t\t%d\n"
1494 "nextcooleroff:\t\t%d\n"
1495 "lastcooleron:\t\t%d\n"
1496 "lastcooleroff:\t\t%d\n\n",
1497 (int) (nextheateron - now),
1498 (int) (nextheateroff - now),
1499 (int) (lastheateron - now),
1500 (int) (lastheateroff - now),
1501 (int) (nextcooleron - now),
1502 (int) (nextcooleroff - now),
1503 (int) (lastcooleron - now),
1504 (int) (lastcooleroff - now) );
1505
1506 dumpcycleinfo (); /* information about on/off calcs */
1507 fprintf (debugfd,
1508 "Currrent status\n"
1509 "coolstatus:\t\t%s\n"
1510 "state:\t\t\t%d\n"
1511 "lastcoolstate:\t\t%s\n\n",
1512 machinestatetext [coolstatus],
1513 state,
1514 machinestatetext [lastcoolstate] );
1515
1516 fprintf (debugfd,
1517 "Relative times:\n"
1518 "Now:\t\t\t%d\n"
1519 "Next logtime:\t\t%d\n"
1520 "Next graphlogtime:\t%d\n"
1521 "Last display:\t\t%d\n",
1522 (int) (now),
1523 (int) (nextlogtime - now),
1524 (int) (nextgraphlogtime - now),
1525 (int) (lastdisplay - now) );
1526 fprintf (debugfd,
1527 "================================================================================\n");
1528 fflush (debugfd);
1529 }
1530 }
1531
1532 /* Print info about state changes */
dumpstatechange(char * reason)1533 void dumpstatechange (char *reason)
1534 {
1535 if (debugfd)
1536 {
1537 fprintf (debugfd,
1538 "====================\n%s ",
1539 reason);
1540 printtime (now, debugfd);
1541 fprintf (debugfd,
1542 "\nTemperatures:\n"
1543 "Goal temperature:\t%6.2f\n"
1544 "Cooler holdoff:\t\t%6.2f\n"
1545 "Heater holdoff:\t\t%6.2f\n"
1546 "Cooler overshoot:\t%6.2f of holdoff\n"
1547 "Heater overshoot:\t%6.2f of holdoff\n"
1548 "Max ambient temp:\t%6.2f\n"
1549 "Min ambient temp:\t%6.2f\n"
1550 "Max ambient temp diff:\t%6.2f\n"
1551 "Min ambient temp diff:\t%6.2f\n\n",
1552 goaltemp,
1553 coolerholdoff,
1554 heaterholdoff,
1555 coolerovershoot,
1556 heaterovershoot,
1557 maxambienttemp,
1558 minambienttemp,
1559 maxcooltempdiff,
1560 maxheattempdiff );
1561 fflush (debugfd);
1562 }
1563 }
1564
dumptempcalcs(char * reason)1565 void dumptempcalcs (char *reason)
1566 {
1567 if (debugfd)
1568 {
1569 dumpstatechange (reason);
1570 dumpcycleinfo ();
1571 fflush (debugfd);
1572 }
1573 }
1574