Introduction
One of the core elements of the Meditech software is the controller, managing the user interaction for the entire system. We can consider the controller module as an application - running as a process that never stops - as a simple task-manager and task launcher. The rest of the job is done by the automation components of the Meditech device.
The only one part that is directly managed by the controller software is the control panel communication. The reason is that in the system architecture one of the roles of the control panel is just to be part of the user interaction mechanism.
The updated software sources are available on GitHub at the following address: https://github.com/alicemirror/chipkit_serial_pi/tree/master/Meditech_RaspianControlPanel
The last compiled documentation is available at: Meditech control panel software: Main Page while the pdf version can be found in attach to this post.
(note: some class methods and documentation are derived from the chipkit control panel documentation so please ignore the references to the ChipKit board. It's a beta version! )
How it works
The controller application is launched on startup of the RPI main device (Raspberry PI 2) and runs indefinitely in a main loop. This part consumes very few resources and only when a button on the IR controller is pressed the process activates the parsing of the button for the request recognition.
In the main.cpp there are tre important global objects:
//! UART file stream to manage the serial connection int uart0_filestream = -1; //! Status flags structure states controllerStatus; //! The command string to be sent to the control panel char* cmdString = '\0';
The start sequence of the module is almost simple:
- Activate the IR lirc process and open a thread
- If there are no problems on activating the infrared controller, open the serial connection with the control panel board (ChipKit)
- Setup the serial speed then start the infinite loop.
Until the RPI master is powered on the serial interface is locked and the lirc communication protocol is active with the IR controller.
IR commands via lirc library
if(lirc_init((char *)LIRC_CLIENT, 1) == -1) exit(EXIT_FAILURE); //Read the default LIRC config at /etc/lirc/lircd.conf if(lirc_readconfig(NULL, &config, NULL) == 0) { // Set the lirc status flag controllerStatus.isLircRunning = true; // As lirc is working intialise the serial connection uart0_filestream = open(UART_DEVICE, O_RDWR | O_NOCTTY | O_NDELAY); // Check the UART opening status. If a problem occur, the application exits. if(uart0_filestream == -1) { //Frees the data structures associated with config. lirc_freeconfig(config); // Closes the connection to lircd and does some internal clean-up stuff. lirc_deinit(); // Set the lirc status flag controllerStatus.isLircRunning = false; exit(EXIT_FAILURE); // The /etc/lirc/lircd,conf file does not exist. } // Problem opening the UART. Exit with error
The code above shows how the IR and serial (via direct UART connection) are launched. After the lirc library has been initalized it is read the configuration This means that the known IR keycodes previously set when configuring the Infrared in the raspian Linux are loaded for parsing. At this level when a command is recognised it is simply parsed (see the code below) and depending on the kind of command the corresponding task is launched. In this way it is granted a good reactivity of the controller, despite the actual running task in the system.
switch(infraredID) { case CMD_MENU: if(infraredID != controllerStatus.lastKey) { cmdString = cProc.buildCommandDisplayTemplate(TID_DEFAULT); setPowerOffStatus(POWEROFF_NONE); } break; case CMD_POWER: if(infraredID != controllerStatus.lastKey) { setPowerOffStatus(POWEROFF_REQUEST); } break; case CMD_NUMERIC_0: setPowerOffStatus(POWEROFF_NONE); break; case CMD_NUMERIC_1: if(infraredID != controllerStatus.lastKey) { cmdString = cProc.buildCommandDisplayTemplate(TID_STETHOSCOPE); controllerStatus.serialState = SERIAL_READY_TO_SEND; setPowerOffStatus(POWEROFF_NONE); // Check the serial status manageSerial(); } break; case CMD_NUMERIC_2: if(infraredID != controllerStatus.lastKey) { cmdString = cProc.buildCommandDisplayTemplate(TID_BLOODPRESS); controllerStatus.serialState = SERIAL_READY_TO_SEND; setPowerOffStatus(POWEROFF_NONE); // Check the serial status manageSerial(); } break; case CMD_NUMERIC_3: if(infraredID != controllerStatus.lastKey) { cmdString = cProc.buildCommandDisplayTemplate(TID_HEARTBEAT); controllerStatus.serialState = SERIAL_READY_TO_SEND; setPowerOffStatus(POWEROFF_NONE); // Check the serial status manageSerial(); } break; case CMD_NUMERIC_4: if(infraredID != controllerStatus.lastKey) { cmdString = cProc.buildCommandDisplayTemplate(TID_TEMPERATURE); controllerStatus.serialState = SERIAL_READY_TO_SEND; setPowerOffStatus(POWEROFF_NONE); // Check the serial status manageSerial(); } break; case CMD_NUMERIC_5: if(infraredID != controllerStatus.lastKey) { cmdString = cProc.buildCommandDisplayTemplate(TID_ECG); controllerStatus.serialState = SERIAL_READY_TO_SEND; setPowerOffStatus(POWEROFF_NONE); // Check the serial status manageSerial(); } break; case CMD_NUMERIC_6: setPowerOffStatus(POWEROFF_NONE); break; case CMD_NUMERIC_7: setPowerOffStatus(POWEROFF_NONE); break; case CMD_NUMERIC_8: setPowerOffStatus(POWEROFF_NONE); break; case CMD_NUMERIC_9: setPowerOffStatus(POWEROFF_NONE); break; case CMD_UP: setPowerOffStatus(POWEROFF_NONE); break; case CMD_DOWN: setPowerOffStatus(POWEROFF_NONE); break; case CMD_LEFT: setPowerOffStatus(POWEROFF_NONE); break; case CMD_RIGHT: setPowerOffStatus(POWEROFF_NONE); break; case CMD_RED: if(infraredID != controllerStatus.lastKey) { cmdString = cProc.buildCommandDisplayTemplate(TID_TEST); controllerStatus.serialState = SERIAL_READY_TO_SEND; setPowerOffStatus(POWEROFF_NONE); // Check the serial status manageSerial(); } break; case CMD_GREEN: if(infraredID != controllerStatus.lastKey) { cmdString = cProc.buildCommandDisplayTemplate(TID_INFO); controllerStatus.serialState = SERIAL_READY_TO_SEND; setPowerOffStatus(POWEROFF_NONE); // Check the serial status manageSerial(); } break; case CMD_YELLOW: setPowerOffStatus(POWEROFF_NONE); break; case CMD_BLUE: setPowerOffStatus(POWEROFF_NONE); break; case CMD_OK: if(infraredID != controllerStatus.lastKey) { if(controllerStatus.powerOff == POWEROFF_REQUEST) setPowerOffStatus(POWEROFF_CONFIRMED); else setPowerOffStatus(POWEROFF_NONE); } break; case CMD_MUTE: setPowerOffStatus(POWEROFF_NONE); break; case CMD_VOLUMEUP: setPowerOffStatus(POWEROFF_NONE); break; case CMD_VOLUMEDOWN: setPowerOffStatus(POWEROFF_NONE); break; case CMD_CHANNELUP: setPowerOffStatus(POWEROFF_NONE); break; case CMD_CHANNELDOWN: setPowerOffStatus(POWEROFF_NONE); break; default: break; } // Button ID case // Update the last key ID controllerStatus.lastKey = infraredID; }
Note that some commands should work in continuous while the user keep them pressed (e.g. the +/- buttons) In the cases instead where the command should be pressed once, the control on the lastKey (the previous key pressed) act as a debouncing method avoiding that pressing continuously a button (e.g. the Power button) generates problems.
For more details after this short introduction I suggest to see in details the sources documentation for further information.