ads1115 analog-to-digital 4-channel added
[i2c-telemetry.git] / lcdd.c
1 /* Simple daemon to communicate with Newhaven I2C LCD module.
2  *
3  * This is a trivial interface for Newhaven LCD module connected to a Linux machine
4  * over an I2C interface. Any standard Linux I2C bus should work (including USB I2C
5  * adapters that correctly hook into the i2c kernel driver framework).
6  *
7  * Opens a named pipe at /dev/lcd which can be written to by other applications.
8  * Text written to this file will appear on the LCD.
9  * Send a bell ('\b')to clear the screen.
10  *
11  * Tested with NHD-0420D3Z-FL-GBW (Digikey part number NHD-0420D3Z-FL-GBW-ND).
12  * http://www.newhavendisplay.com/specs/NHD-0420D3Z-FL-GBW.pdf
13  * http://www.newhavendisplay.com/index.php?main_page=product_info&cPath=253&products_id=922
14  *
15  * FIXME how to do other commands etc.
16  * FIXME some rate limiting should be added... the I2C controller on the LCD
17  * module gets confused if you write to it too fast.
18  *
19  * Hardware note:
20  * To enable I2C mode of LCD, Pads of R1 must be jumpered on back of LCD
21  * See the manual for the LCD module.
22  *
23  * ./lcdd -a 0x47 -d /dev/i2c-4 -f /dev/lcd -n
24  *
25  * Hugo Vincent, June 2009 - https://github.com/hugovincent/i2c-lcdd
26  * Daemonization based on code by Peter Lombardo <peter@lombardo.info>, 2006
27  */
28
29
30 #define _GNU_SOURCE // for strsignal()
31
32 #include <stdio.h>
33 #include <string.h>
34 #include <stdlib.h>
35 #include <unistd.h>
36 #include <fcntl.h>
37 #include <sys/types.h>
38 #include <sys/stat.h>
39 #include <sys/ioctl.h>
40 #include <stdint.h>
41 #include <errno.h>
42 #include <syslog.h>
43 #include <assert.h>
44 #include <signal.h>
45 #include <poll.h>
46 #include <setjmp.h>
47 #include <linux/i2c-dev.h>
48
49 #define DAEMON_NAME "lcdd"
50 #define PID_FILE "/var/run/lcdd.pid"
51
52 #define LCD_ADDR 0x27
53
54 //#define DEBUG
55
56 static int daemonize = 1, running = 1, have_lcd = 1;
57 static jmp_buf my_jmp_buf;
58
59 #define LOG_MSG(format, ...) if (daemonize == 1) \
60                                                                                         syslog(LOG_INFO, format, ##__VA_ARGS__); \
61 else \
62 printf("i2c-lcdd: " format "\n", ##__VA_ARGS__);
63
64 void usage(int argc, char **argv)
65 {
66         if (argc >=1)
67         {
68                 printf("Usage: %s -a 0x50 -d /dev/i2c-3 -f /dev/lcd -n -?\n", argv[0]);
69                 printf("  Options:\n");
70                 printf("     -a\tI2C slave address (in hex) for LCD module\n");
71                 printf("     -d\tI2C device\n");
72                 printf("     -f\tnamed pipe for input\n");
73                 printf("     -n\tDon't daemonize.\n");
74                 printf("     -?\tShow this help screen.\n");
75         }
76 }
77
78 void signal_handler(int sig)
79
80         LOG_MSG("received signal %s - exiting", strsignal(sig));
81         running = 0;
82 }
83
84 void i2c_send(int fd, uint8_t *buf, int count)
85 {
86         if (have_lcd)
87         {
88                 if (write(fd, buf, count) != count)
89                 {
90                         LOG_MSG("error writing to I2C: %s", strerror(errno));
91                         longjmp(my_jmp_buf, EXIT_FAILURE);
92                         //exit(EXIT_FAILURE);
93                 }
94         }
95         else
96         {
97 #ifdef DEBUG
98                 static char tmp[1024];
99                 strncpy(tmp, buf, count);
100                 tmp[count] = 0;
101                 printf("I2C send: \"%s\"\n", tmp);
102 #endif
103         }
104 }
105
106 uint8_t linenum_to_cursor(int linenum)
107 {
108         switch (linenum)
109         {
110                 case 1:
111                         return 0x40;
112                         break;
113                 case 2:
114                         return 0x14;
115                         break;
116                 case 3:
117                         return 0x54;
118                         break;
119                 case 0:
120                 default:
121                         return 0x0;
122                         break;
123         }
124 }
125
126 #define min(a, b) ((a <= b) ? a : b)
127
128 void handle_pipe_input(int i2c_fd, char *buffer, int size)
129 {
130         // FIXME this should handle escape sequences (other than \b, \n) too
131
132         static int line_number = 0;
133
134         // LCD clear
135         if (size >= 1 && buffer[0] == '\b')
136         {
137                 uint8_t bytes[] = {0xFE, 0x51};
138                 i2c_send(i2c_fd, bytes, sizeof(bytes));
139                 buffer++; size--;
140                 line_number = 0;
141         }
142
143         // Split input into lines
144         char *line = strtok(buffer, "\n");
145         while (line != NULL)
146         {
147                 // Because of weird cursor positioning on the LCD module, if we write
148                 // more than 20 characters on line 1 it'll flow over onto line 3, (or
149                 // off line 2 on to line 4). Thus, we limit lines to 20 chars.
150
151                 i2c_send(i2c_fd, line, min(strlen(line), 20));
152                 line_number = (line_number < 3) ? (line_number + 1) : 0;
153
154                 // Go to specified line
155                 uint8_t bytes[] = {0xFE, 0x45, 0x0};
156                 bytes[2] = linenum_to_cursor(line_number);
157                 i2c_send(i2c_fd, bytes, sizeof(bytes));
158
159                 // Get next line
160                 line = strtok(NULL, "\n");
161         }
162 }
163
164 int main(int argc, char **argv)
165 {
166         char device[64] = "/dev/i2c-3", fifo_name[64] = "/dev/lcd";
167         int addr = LCD_ADDR, addr_sscanf_ret = 1;
168
169         // Setup signal handling before we start
170         signal(SIGHUP, signal_handler);
171         siginterrupt(SIGHUP, 1);
172         signal(SIGQUIT, signal_handler);
173         siginterrupt(SIGQUIT, 1);
174         signal(SIGTERM, signal_handler);
175         siginterrupt(SIGTERM, 1);
176         signal(SIGINT, signal_handler);
177         siginterrupt(SIGINT, 1);
178
179         int c;
180         while( (c = getopt(argc, argv, "a:d:f:n?")) != -1) {
181                 switch(c){
182                         case 'a':
183                                 addr_sscanf_ret = sscanf(optarg, "0x%x", &addr);
184                                 break;
185                         case 'd':
186                                 strncpy(device, optarg, sizeof(device));
187                                 break;
188                         case 'f':
189                                 strncpy(fifo_name, optarg, sizeof(fifo_name));
190                                 break;
191                         case 'n':
192                                 daemonize = 0;
193                                 break;
194                         case '?':
195                         default:
196                                 usage(argc, argv);
197                                 exit(0);
198                                 break;
199                 }
200         }
201
202         // Setup syslog logging
203         if (daemonize == 1)
204         {
205                 setlogmask(LOG_UPTO(LOG_INFO));
206                 openlog(DAEMON_NAME, LOG_CONS, LOG_USER);
207         }
208         LOG_MSG("daemon starting up");
209
210         // Check sscanf above worked (delayed until after syslog is initialized)
211         if (addr_sscanf_ret != 1)
212                 LOG_MSG("supplied address invalid, using 0x%02x", addr);
213
214         // Our process ID and Session ID
215         pid_t pid, sid;
216
217         if (daemonize)
218         {
219                 // Fork off the parent process
220                 pid = fork();
221                 if (pid < 0)
222                 {
223                         LOG_MSG("failed to fork daemon process");
224                         exit(EXIT_FAILURE);
225                 }
226
227                 // If we got a good PID, then we can exit the parent process
228                 if (pid > 0)
229                         exit(EXIT_SUCCESS);
230
231                 // Change the file mode mask
232                 umask(0);
233
234                 // Create a new SID for the child process
235                 sid = setsid();
236                 if (sid < 0)
237                 {
238                         LOG_MSG("failed to set daemon process SID");
239                         exit(EXIT_FAILURE);
240                 }
241
242                 // Change the current working directory
243                 if ((chdir("/")) < 0)
244                 {
245                         LOG_MSG("failed to change daemon working directory");
246                         exit(EXIT_FAILURE);
247                 }
248
249                 // Close out the standard file descriptors
250                 close(STDIN_FILENO);
251 #ifndef DEBUG
252                 close(STDOUT_FILENO);
253 #endif
254                 close(STDERR_FILENO);
255         }
256
257         //************************************************************************
258
259         addr >>= 1; // to suit LCD addressing sematics of Newhaven LCD modules
260
261         // Initialise FIFO on which to accept input
262         if (mkfifo(fifo_name, S_IRWXU) < 0)
263         {
264                 if (errno != EEXIST)
265                 {
266                         LOG_MSG("error creating named pipe for input: %s", strerror(errno));
267                         exit(EXIT_FAILURE);
268                 }
269                 else
270                 {
271                         LOG_MSG("warning, named pipe already exists: %s", strerror(errno));
272                         // FIXME unlink and reopen
273                 }
274         }
275
276         // Connect to I2C LCD and set slave address
277         int i2c_fd = open(device, O_RDWR);
278         if (i2c_fd < 0)
279         {
280                 LOG_MSG("error opening device: %s", strerror(errno));
281                 have_lcd = 0;
282                 goto nolcd;
283         }
284         if (ioctl(i2c_fd, I2C_SLAVE, addr) < 0) 
285         {
286                 LOG_MSG("error setting I2C slave address: %s", strerror(errno));
287                 close(i2c_fd);
288                 have_lcd = 0;
289         }
290
291 nolcd:
292         if (setjmp(my_jmp_buf) == EXIT_FAILURE)
293                 goto exit1;
294
295         if (!have_lcd)
296         {
297                 LOG_MSG("could not open I2C LCD, printing to stdout for debugging");
298         }
299         else
300         {
301                 LOG_MSG("using Nehaven LCD attached to %s at real address 0x%x", device, addr);
302
303                 // Initialise LCD
304                 uint8_t bytes[] = {0xFE, 0x41};         // LCD on
305                 i2c_send(i2c_fd, bytes, sizeof(bytes));
306                 bytes[1] = 0x51;                                        // LCD clear
307                 i2c_send(i2c_fd, bytes, sizeof(bytes));
308
309                 uint8_t *string = (uint8_t*)"  Initialising...";
310                 i2c_send(i2c_fd, string, strlen((char*)string));
311         }
312
313         char buffer[256];
314         int fifo_fd = -1;
315         struct pollfd fds = { .events = POLLIN };
316
317         // Main Loop: read on named pipe, output on LCD
318         while (running)
319         {
320                 if (fifo_fd == -1)
321                 {
322 #ifdef DEBUG
323                         LOG_MSG("waiting for connection on %s...", fifo_name);
324 #endif
325                         fifo_fd = open(fifo_name, O_RDONLY); // this will block until other end of pipe is opened
326                         if (fifo_fd < 0)
327                         {
328                                 if (errno == EINTR)
329                                         continue;
330                                 LOG_MSG("error opening named pipe for input: %s", strerror(errno));
331                                 goto exit3;
332                         }
333                         fds.fd = fifo_fd;
334 #ifdef DEBUG
335                         LOG_MSG("connected, accepting input on %s", fifo_name);
336 #endif
337                 }
338
339                 // Wait for input on the named pipe
340                 int poll_ret = poll(&fds, 1, 1000);
341                 if (poll_ret == 1 && fds.revents | POLLIN)
342                 {
343                         memset(&buffer, 0, sizeof(buffer));
344                         int bytes_read = read(fifo_fd, &buffer, sizeof(buffer));
345
346                         // Write input to LCD
347                         if (bytes_read > 0)
348                                 handle_pipe_input(i2c_fd, buffer, bytes_read);
349                         else if (bytes_read == 0)
350                         {
351 #ifdef DEBUG
352                                 LOG_MSG("named pipe closed");
353 #endif
354                                 close(fifo_fd);
355                                 fifo_fd = -1;
356                         }
357                         else
358                         {
359                                 LOG_MSG("error reading from named pipe: %s", strerror(errno));
360                                 goto exit1;
361                         }
362                 }
363                 else if (poll_ret < 0 && running)
364                 {
365                         LOG_MSG("error waiting for input on named pipe: %s", strerror(errno));
366                         goto exit1;
367                 }
368                 else
369                 {
370                         // Timeout in poll(), no need to worry (timeout is only enabled as insurance anyway)
371                 }
372         }
373
374         LOG_MSG("daemon exiting cleanly");
375
376         close(fifo_fd);
377         unlink(fifo_name);
378         if (have_lcd)
379                 close(i2c_fd);
380
381         return 0;
382
383         // Cleanup on error
384 exit1:
385         if (have_lcd)
386                 close(i2c_fd);
387 exit2:
388         close(fifo_fd);
389 exit3:
390         unlink(fifo_name);
391         LOG_MSG("daemon exiting with failure");
392         exit(EXIT_FAILURE);
393 }
394