/ src / hal / components / panelui.c
panelui.c
  1  /********************************************************************
  2  * Description:  panelui.c
  3                A User space program to connect to "sampler", a HAL component that
  4  *       can be used to sample data from HAL pins.
  5  *       It decodes keyboard codes then update pins/linuxcnc commands.
  6  *       The key codes are patterned after the MESA 7i73 card output.
  7  *       There is also a realtime program sim_matrix_kb that outputs keycodes.
  8  *       Users can add a handler.py file to include their own python routines.
  9  *
 10  * based on sampler_usr.c written by
 11  * John Kasunich <jmkasunich at sourceforge dot net>
 12  * Author: Chris Morley <chrisinnanaimo at hotmail dot com>
 13  * License: GPL Version 2
 14  *    
 15  * Copyright (c) 2006 All rights reserved.
 16  *
 17  ********************************************************************/
 18  /** This file, 'sampler_usr.c', is the user part of a HAL component
 19      that allows values to be sampled from HAL pins at a uniform 
 20      realtime sample rate.  When the realtime module
 21      is loaded, it creates a fifo in shared memory and begins capturing
 22      samples to the fifo.  Then, the user space program 'sampler_py'
 23      is invoked to read from the fifo and decode the data to keycodes.
 24      It then calls a python program to update pins or send linuxcnc commands.
 25      The python part uses INI style configs to define 'buttons', that either
 26      update hal pins, call linuxcnc commands or optionally call a user function.
 27      adding a handler.py file to a config's folder will be parsed and its functions
 28      added to available commands.
 29  
 30      Invoking:
 31  
 32      panelui [-c chan_num] [-n num_samples] [-t] [-d] [-v]
 33  
 34      'chan_num', if present, specifies the sampler channel to use.
 35      The default is channel zero.
 36  
 37      'num_samples', if present, specifies the number of samples
 38      to be printed, after which the program will exit.  If ommitted
 39      it will print continuously until killed.
 40  
 41      '-t' tells sampler to print the sample number at the start
 42      of each line.
 43  
 44     '-d' tells sampler to print debug info such as button and command text.
 45  
 46     '-v' tells sampler to print lots of debug info .
 47  */
 48  
 49  /** This program is free software; you can redistribute it and/or
 50      modify it under the terms of version 2 of the GNU General
 51      Public License as published by the Free Software Foundation.
 52      This library is distributed in the hope that it will be useful,
 53      but WITHOUT ANY WARRANTY; without even the implied warranty of
 54      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 55      GNU General Public License for more details.
 56  
 57      You should have received a copy of the GNU General Public
 58      License along with this library; if not, write to the Free Software
 59      Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 60  
 61      THE AUTHORS OF THIS LIBRARY ACCEPT ABSOLUTELY NO LIABILITY FOR
 62      ANY HARM OR LOSS RESULTING FROM ITS USE.  IT IS _EXTREMELY_ UNWISE
 63      TO RELY ON SOFTWARE ALONE FOR SAFETY.  Any machinery capable of
 64      harming persons must have provisions for completely removing power
 65      from all motors, etc, before persons enter any danger area.  All
 66      machinery must be designed to comply with local and national safety
 67      codes, and the authors of this software can not, and do not, take
 68      any responsibility for such compliance.
 69  
 70      This code was written as part of the LINUXCNC HAL project.  For more
 71      information, go to www.linuxcnc.org.
 72  */
 73  #include <Python.h>
 74  #include <time.h>
 75  
 76  #include <stdio.h>
 77  #include <stdlib.h>
 78  #include <ctype.h>
 79  #include <string.h>
 80  #include <unistd.h>
 81  #include <signal.h>
 82  #include <time.h>
 83  #include <sys/types.h>
 84  #include <sys/stat.h>
 85  #include <fcntl.h>
 86  
 87  #include "rtapi.h"      /* RTAPI realtime OS API */
 88  #include "hal.h"                /* HAL public API decls */
 89  #include "streamer.h"
 90  
 91  /***********************************************************************
 92  *                  LOCAL FUNCTION DECLARATIONS                         *
 93  ************************************************************************/
 94  
 95  /***********************************************************************
 96  *                         GLOBAL VARIABLES                             *
 97  ************************************************************************/
 98  
 99  int comp_id = -1;   /* -1 means hal_init() not called yet */
100  int shmem_id = -1;
101  int exitval = 1;    /* program return code - 1 means error */
102  int ignore_sig = 0; /* used to flag critical regions */
103  char comp_name[HAL_NAME_LEN+1]; /* name for this instance of psnelui */
104  int dbg = 0;
105  int vdbg = 0;
106  int keydown = 0xC0;
107  int keyup = 0x80;
108  int nochange = 0x40;
109  int rowshift;
110  int ncols = 8;
111  int nrows = 8;
112  int rollover = 2; //maximuim number of simultaneous keys presses recongnised
113  int s = 0; //key press state argument to pass to python
114  int r; //row argument passed to python
115  int c; //column argument passed to python
116  int num_keys; //count of the number of keys currently pressed
117  long int raw; //the raw data passed from shared memory realtime component 
118  long int keycode; // calculated from raw by masking keyup and keydown codes
119  long int start_time;
120  long int last_heartbeat;
121  long int heartbeat_difference;
122  struct timespec gettime_now;
123  PyObject *pExit,*pValue;
124  /***********************************************************************
125  *                            MAIN PROGRAM                              *
126  ************************************************************************/
127  
128  /* signal handler */
129  static sig_atomic_t stop;
130  static void quit(int sig)
131  {
132      if ( ignore_sig ) {
133      return;
134      }
135      stop = 1;
136  }
137  
138  #define BUF_SIZE 4000
139  
140  int main(int argc, char **argv)
141  {
142      int n, channel, tag;
143      long int samples;
144      unsigned this_sample, last_sample=0;
145      char *cp, *cp2;
146      hal_stream_t stream;
147  
148      /* set return code to "fail", clear it later if all goes well */
149      exitval = 1;
150      channel = 0;
151      tag = 0;
152      samples = -1;  /* -1 means run forever */
153      /* FIXME - if I wasn't so lazy I'd learn how to use getopt() here */
154      for ( n = 1 ; n < argc ; n++ ) {
155      cp = argv[n];
156      if ( *cp != '-' ) {
157          break;
158      }
159      switch ( *(++cp) ) {
160      case 'c':
161          if (( *(++cp) == '\0' ) && ( ++n < argc )) { 
162          cp = argv[n];
163          }
164          channel = strtol(cp, &cp2, 10);
165          if (( *cp2 ) || ( channel < 0 ) || ( channel >= MAX_SAMPLERS )) {
166          fprintf(stderr,"ERROR: invalid channel number '%s'\n", cp );
167          exit(1);
168          }
169          break;
170      case 'n':
171          if (( *(++cp) == '\0' ) && ( ++n < argc )) { 
172          cp = argv[n];
173          }
174          samples = strtol(cp, &cp2, 10);
175          if (( *cp2 ) || ( samples < 0 )) {
176          fprintf(stderr, "ERROR: invalid sample count '%s'\n", cp );
177          exit(1);
178          }
179          break;
180      case 't':
181          tag = 1;
182          break;
183      case 'd':
184          dbg = 1;
185          break;
186      case 'v':
187          vdbg = 1;
188          break;
189      default:
190          fprintf(stderr,"ERROR: unknown option '%s'\n", cp );
191          exit(1);
192          break;
193      }
194      }
195      if(n < argc) {
196      int fd;
197      if(argc > n+1) {
198          fprintf(stderr, "ERROR: At most one filename may be specified\n");
199          exit(1);
200      }
201      // make stdout be the named file
202      fd = open(argv[n], O_WRONLY | O_CREAT, 0666);
203      close(1);
204      dup2(fd, 1);
205      }
206  
207      /* import the python module and get references for needed function */
208      PyObject *pModule, *pFunc, *pPeriodicFunc, *pClass;
209      Py_SetProgramName("panelui");  /* optional but recommended */
210      Py_Initialize();
211      PyRun_SimpleString("import pyui\n"
212                       "pyui.instance = pyui.master.keyboard()\n"
213                       "pyui.exit_funct = pyui.instance.exit\n"
214                       "pyui.update_funct =  pyui.master.keyboard.update\n"
215                       "pyui.periodic_funct =  pyui.master.keyboard.periodic\n");
216      if (vdbg == 1){
217          PyRun_SimpleString("pyui.intilize = pyui.instance.build(2)\n");
218      }else if (dbg == 1){
219          PyRun_SimpleString("pyui.intilize = pyui.instance.build(1)\n");
220      }else{
221          PyRun_SimpleString("pyui.intilize = pyui.instance.build(0)\n");
222      }
223      pModule = PyImport_ImportModule("pyui");
224  
225      if (pModule != NULL) {
226          pFunc = PyObject_GetAttrString(pModule, "update_funct");
227          pPeriodicFunc = PyObject_GetAttrString(pModule, "periodic_funct");
228          pClass = PyObject_GetAttrString(pModule, "instance");
229          pExit = PyObject_GetAttrString(pModule, "exit_funct");
230          if (pFunc == NULL){
231              fprintf(stderr, "Panelui: Failed to find update function in python section\n");
232              exit(1);
233          }
234          /* pFunc is a new reference */
235          /* preset update function's arguments*/
236          /* class instance, raw read code, row, column, state */
237          if (pFunc && PyCallable_Check(pFunc)) {
238              pValue = PyObject_CallFunction(pFunc, "Olllh", pClass, 0, 0, 0, 0);
239              if (pValue == NULL){
240                  fprintf(stderr, "Panelui: update function failed: returned NULL\n");
241              }
242          }else{
243              if (PyErr_Occurred()){
244                  PyErr_Print();
245              }
246                  fprintf(stderr, "Panelui: Failed python function");
247                  exit(1);
248          }
249      }else {
250          PyErr_Print();
251          fprintf(stderr, "Panelui: Failed to load \"%s\"\n", "pyui");
252          exit(1);
253      }
254  
255      /* rowshoft is calculated from number of columns*/
256      for (rowshift = 1; ncols > (1 << rowshift); rowshift++);
257  
258  
259  
260  
261      /* register signal handlers - if the process is killed
262         we need to call hal_exit() to free the shared memory */
263      signal(SIGINT, quit);
264      signal(SIGTERM, quit);
265      signal(SIGPIPE, quit);
266      /* connect to HAL */
267      /* create a unique module name, to allow for multiple samplers */
268      snprintf(comp_name, sizeof(comp_name), "panelui");
269      /* connect to the HAL */
270      ignore_sig = 1;
271      comp_id = hal_init(comp_name);
272      ignore_sig = 0;
273      /* check result */
274      if (comp_id < 0) {
275          fprintf(stderr, "ERROR: hal_init() failed: %d\n", comp_id );
276          goto out;
277      }
278      hal_ready(comp_id);
279      int res = hal_stream_attach(&stream, comp_id, SAMPLER_SHMEM_KEY+channel, "u");
280      if (res < 0) {
281          errno = -res;
282          perror("hal_stream_attach");
283          goto out;
284      }
285  
286      /***********************************************************************
287      *                            Sample Loop                               *
288      ************************************************************************/
289      clock_gettime(CLOCK_REALTIME, &gettime_now);
290      last_heartbeat = gettime_now.tv_nsec;
291      while ( samples != 0 ) {
292          union hal_stream_data buf[1];
293          hal_stream_wait_readable(&stream, &stop);
294          if(stop) break;
295          int res = hal_stream_read(&stream, buf, &this_sample);
296          if (res < 0) {
297              errno = -res;
298              perror("hal_stream_read");
299              goto out;
300          }
301          ++last_sample;
302          if ( this_sample != last_sample ) {
303              printf ( "overrun\n");
304              last_sample = this_sample;
305          }
306          if ( tag ) {
307              printf ( "%d ", this_sample-1 );
308          }
309          /* get raw value, mask keyup and keydown codes */
310          /* compute row and column */
311          raw = (unsigned long)buf[0].u;
312          keycode = (raw & ~(keydown | keyup));
313          r = keycode >> rowshift;
314          c = keycode & ~(0xFFFFFFFF << rowshift);
315          //printf ( "raw= %lu row= %d col = %d\n", (unsigned long)buf[n].u,r,c);
316          /* error checking of row and columns */
317          if  (r < 0 
318              || c < 0
319              || r >= nrows 
320              || c >= ncols){
321               goto skip;
322          }
323          /* skip 'no change' key code */
324          if (raw == nochange) goto skip;
325          /* KEY_DOWN: skip if too many keys down,
326          add to rollover count and set state: True */
327          if ((raw & keydown) == keydown){
328              if (num_keys >= rollover) goto skip;
329              num_keys++;
330              s = 1;
331          /* KEY_UP: subtract a key from rollover count and set state: False */
332          }else if ((raw & keyup) == keyup){
333              if (num_keys > 0) num_keys--;
334              s = 0;
335          /* zero is the only other valid keycode: it means all keys up*/
336          }else if (raw  != 0) goto skip;
337          //printf ( "output raw:%ld row: %ld col %ld state %d \n", raw, r, c, s );
338          /* call python function with: class raw, row, and column arguments */
339          pValue = PyObject_CallFunction(pFunc, "Olllh",pClass, raw, r, c, s);
340          if (PyErr_Occurred()) PyErr_Print();
341          if (pValue == NULL){
342              fprintf(stderr, "Panelui's python update function failed: returned NULL\n");
343          }
344  skip:
345          if ( samples > 0 ) samples--;
346          //do python periodic function every 100 ms
347          clock_gettime(CLOCK_REALTIME, &gettime_now);
348          heartbeat_difference = gettime_now.tv_nsec - last_heartbeat;//Get nS value
349          if (heartbeat_difference < 0)
350              heartbeat_difference += 1000000000;//(Rolls over every 1 second)
351          if (heartbeat_difference > 100000000){//<<< Heartbeat every 100mS
352              last_heartbeat += 100000000;
353              if (last_heartbeat > 1000000000)//(Rolls over every 1 second)
354                  last_heartbeat -= 1000000000;
355              pValue = PyObject_CallFunction(pPeriodicFunc, "O",pClass);
356              if (PyErr_Occurred()) PyErr_Print();
357          }
358      }
359      /* run was succesfull */
360      exitval = 0;
361  
362  out:
363      ignore_sig = 1;
364      hal_stream_detach(&stream);
365      if ( comp_id >= 0 ) {
366          hal_exit(comp_id);
367      }
368      PyRun_SimpleString("print '''Exiting panelui's python module '''");
369      pValue = PyObject_CallObject(pExit, NULL);
370      if (PyErr_Occurred()) {
371          PyErr_Print();
372      }
373      Py_Finalize();
374      return exitval;
375  }