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 }