diff --git a/config/buttons.json b/config/buttons.json index c7b9e38..6f1a4f1 100644 --- a/config/buttons.json +++ b/config/buttons.json @@ -1,10 +1,10 @@ { - "pins": { - "buttonUp": 11, - "buttonDown": 13 + "gpios": { + "buttonUp": 17, + "buttonDown": 27 }, "options": { - "pressed": 200, - "clicked": 400 + "socketPath": "/var/run/pi-buttons.sock", + "reconnectTimeout": 3000 } } diff --git a/lib/hid-menu/hid-menu.js b/lib/hid-menu/hid-menu.js index cfb427a..fec9b85 100644 --- a/lib/hid-menu/hid-menu.js +++ b/lib/hid-menu/hid-menu.js @@ -8,38 +8,36 @@ const Menube = require('menube'); function createHIDMenu(configButtons, configMenus) { - if (!configButtons.pins || !configButtons.pins.buttonUp || !configButtons.pins.buttonDown) { + if (!configButtons.gpios || !configButtons.gpios.buttonUp || !configButtons.gpios.buttonDown) { throw new Error('Incomplete pins definition in configuration.'); } - var pins = configButtons.pins; + var gpios = configButtons.gpios; var buttonOptions = configButtons.options || {}; var onChange = configMenus.onChange; var menu = Menube(configMenus.menuFile, configMenus.menuSettings); var displayDirty = false; - // var buttons = require('rpi-gpio-buttons')([pins.buttonUp, pins.buttonDown], buttonOptions); - var piButtons = require('../pi-buttons'); + var piButtons = require('node-pi-buttons')(configButtons.options); menu.on('menu_changed', function () { displayDirty = false; // the parent will redraw the display }); -// buttons piButtons - .on('clicked', function (pin) { + .on('clicked', function (gpio, data) { if (displayDirty) { // fake menu changed to force redraw menu.emit('menu_changed'); displayDirty = false; } else { - switch(pin) { - case pins.buttonUp: + switch(parseInt(gpio, 10)) { + case gpios.buttonUp: if (!displayDirty) { menu.menuUp(); } break; - case pins.buttonDown: + case gpios.buttonDown: if (!displayDirty) { menu.menuDown(); } @@ -47,31 +45,34 @@ function createHIDMenu(configButtons, configMenus) { } } }) - .on('double_clicked', function (pin) { + .on('double_clicked', function (gpio, data) { if (displayDirty) { // fake menu changed to force redraw menu.emit('menu_changed'); displayDirty = false; } else { - switch (pin) { - case pins.buttonUp: + switch (parseInt(gpio, 10)) { + case gpios.buttonUp: menu.menuBack(); break; - case pins.buttonDown: + case gpios.buttonDown: displayDirty = true; // activate may write something to the display menu.activateSelect(); break; } } }) - .on('released', function (pin) { + .on('released', function (gpio, data) { if (displayDirty) { // fake menu changed to force redraw menu.emit('menu_changed'); displayDirty = false; } + }) + .on('error', function (data) { + console.log('ERROR: ', data.error); }); return menu; diff --git a/lib/pi-buttons/a.out b/lib/pi-buttons/a.out deleted file mode 100755 index b8e6cf4..0000000 Binary files a/lib/pi-buttons/a.out and /dev/null differ diff --git a/lib/pi-buttons/build.sh b/lib/pi-buttons/build.sh deleted file mode 100755 index cbe167a..0000000 --- a/lib/pi-buttons/build.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash - -# TODO convert to Makefile - -gcc buttons.c -lpthread -lrt diff --git a/lib/pi-buttons/buttons.c b/lib/pi-buttons/buttons.c deleted file mode 100644 index 701d365..0000000 --- a/lib/pi-buttons/buttons.c +++ /dev/null @@ -1,393 +0,0 @@ -// configure INPUT - -// poll INPUT - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include "buttons.h" - - -int main(int argc, char *argv[]) { - int l, gpioCount; - buttonDefinition * buttons[MAX_BUTTONS]; - pthread_t buttonThread; - pthread_t socketThread; - int clients[MAX_CLIENTS]; - - - // TODO these need to come from a config file or command line args - const char * gpios[] = { - "17", - "27" - }; - gpioCount = 2; - - // init client file descriptors - for(l = 0; l < MAX_CLIENTS; l++) { - clients[l] = -1; - } - pthread_create(&socketThread, NULL, &socketServer, &clients); - - // init buttons - for(l = 0; l < gpioCount; l++) { - buttons[l] = (buttonDefinition *)malloc(sizeof(buttonDefinition)); - buttons[l]->gpio = gpios[l]; - pthread_mutex_init(&buttons[l]->lockControl, NULL); - pthread_barrier_init(&buttons[l]->barrierControl, NULL, 2); - buttons[l]->clients = clients; - pthread_create(&buttons[l]->parent, NULL, &buttonParent, buttons[l]); - } - - // TODO can all threads be monitored simultaneously and restart if one fails? - for(l = 0; l < gpioCount; l++) { - pthread_join(buttons[l]->parent, NULL); - } - -} - - -void * buttonParent(void * args) { - buttonDefinition * button; - button = (buttonDefinition *)args; - char buff[30]; - struct pollfd pollfdStruct; - int pollStatus; - uint8_t c; - - sprintf(buff, "/sys/class/gpio/gpio%s/value", button->gpio); - if ((button->fd = open(buff, O_RDWR)) < 0) { - printf("Failed to open gpio%d.\n", button->gpio); - exit(1); - } - - // configure polling structure - pollfdStruct.fd = button->fd; - pollfdStruct.events = POLLPRI | POLLERR; - - // configure button structure - button->state = STATE_INIT; - button->debounceState = INACTIVE; - button->value = RELEASED; - - // clear out any waiting gpio values - pollStatus = poll(&pollfdStruct, 1, 10); // 10 millisecond wait for input - if (pollStatus > 0) { - if (pollfdStruct.revents & POLLPRI) { - lseek (pollfdStruct.fd, 0, SEEK_SET) ; // Rewind - (void)read (pollfdStruct.fd, &c, 1) ; // Read & clear - } - } - - // reset button state - button->state = STATE_IDLE; - pthread_mutex_lock(&button->lockControl); - pthread_create(&button->child, NULL, &buttonChild, button); - - for(;;) { - pollStatus = poll(&pollfdStruct, 1, -1) ; - if (pollStatus > 0) { - if (pollfdStruct.revents & POLLPRI) { - lseek (pollfdStruct.fd, 0, SEEK_SET) ; // Rewind - (void)read (pollfdStruct.fd, &c, 1) ; // Read & clear - - button->lastValue = c; - - if (button->debounceState == INACTIVE) { - // when not in a debounce state then signal child about button change - pthread_mutex_unlock(&button->lockControl); // signal child button event has started - pthread_barrier_wait(&button->barrierControl); // wait on begin sychronization - pthread_mutex_lock(&button->lockControl); // regain lock for next event - pthread_barrier_wait(&button->barrierControl); // signal child synchronized - } - else { - // TODO do we need to do anything if in debounce? probably not. - } - } - - } - else { - // something wrong with poll status - - } - } -} - - -void * buttonChild(void * args) { - buttonDefinition * button; - button = (buttonDefinition *)args; - int lockStatus, startDebounce = 0; - long lockTimeout = TIMEOUT_FALSE; - char eventMsg[EVENT_MSG_MAX_LENGTH]; - - // main loop - for(;;) { - // wait for next event, unlock or timeout - if (lockTimeout) { - lockStatus = pthread_mutex_timedlock(&button->lockControl, &button->conditionTime); - } - else { - lockStatus = pthread_mutex_lock(&button->lockControl); // wait for parent to signal button event started - } - - lockTimeout = TIMEOUT_FALSE; - if (lockStatus != ETIMEDOUT && button->debounceState == INACTIVE) { - // not a timeout and not in debounce state, start debounce of button value - button->debounceState = ACTIVE; - button->value = button->lastValue; - clock_gettime(CLOCK_REALTIME, &button->lastTime); - setConditionNS(&button->lastTime, &button->conditionTime, DEBOUNCE_NS); - lockTimeout = TIMEOUT_TRUE; - emitFormattedMessage(eventMsg, EVENT_STRING[button_changed], button); - } - else if (lockStatus == ETIMEDOUT && button->debounceState == ACTIVE) { - button->debounceState = INACTIVE; - // timed out while in the debounce state, perform state transition - switch(button->state) { - case STATE_IDLE: - if (button->value == PRESSED && button->lastValue == PRESSED) { - // button pressed and held - button->state = STATE_PRESSED; - emitFormattedMessage(eventMsg, EVENT_STRING[button_press], button); - setConditionNS(&button->lastTime, &button->conditionTime, PRESSED_NS); - lockTimeout = TIMEOUT_TRUE; - } - else if (button->value == PRESSED) { - // button pressed and released within debounce - emitFormattedMessage(eventMsg, EVENT_STRING[button_press], button); - emitFormattedMessage(eventMsg, EVENT_STRING[button_release], button); - button->state = STATE_CLICKED; - setConditionNS(&button->lastTime, &button->conditionTime, CLICKED_NS); - lockTimeout = TIMEOUT_TRUE; - } - break; - - case STATE_PRESSED: - if (button->lastValue == RELEASED) { - // button released - emitFormattedMessage(eventMsg, EVENT_STRING[button_release], button); - button->state = STATE_CLICKED; - setConditionNS(&button->lastTime, &button->conditionTime, CLICKED_NS); - lockTimeout = TIMEOUT_TRUE; - } - else if (button->value == RELEASED && button->lastValue == PRESSED) { - // button released and pressed within debounce - emitFormattedMessage(eventMsg, EVENT_STRING[button_release], button); - emitFormattedMessage(eventMsg, EVENT_STRING[button_press], button); - button->state = STATE_CLICKED_PRESSED; - setConditionNS(&button->lastTime, &button->conditionTime, PRESSED_NS); - lockTimeout = TIMEOUT_TRUE; - } - else { - // unknown, reset state - button->state = STATE_IDLE; - } - break; - - case STATE_CLICKED: - if (button->lastValue == PRESSED) { - // after clicked button pressed and held - button->state = STATE_CLICKED_PRESSED; - emitFormattedMessage(eventMsg, EVENT_STRING[button_press], button); - setConditionNS(&button->lastTime, &button->conditionTime, PRESSED_NS); - lockTimeout = TIMEOUT_TRUE; - } - else if (button->value == PRESSED && button->lastValue == RELEASED) { - // after clicked button pressed and released within debounce - emitFormattedMessage(eventMsg, EVENT_STRING[button_press], button); - emitFormattedMessage(eventMsg, EVENT_STRING[button_release], button); - button->state = STATE_DOUBLE_CLICKED; - setConditionNS(&button->lastTime, &button->conditionTime, CLICKED_NS); - lockTimeout = TIMEOUT_TRUE; - } - else { - // unknown, reset state - button->state = STATE_IDLE; - } - break; - - case STATE_CLICKED_PRESSED: - if (button->lastValue == RELEASED) { - emitFormattedMessage(eventMsg, EVENT_STRING[button_release], button); - button->state = STATE_DOUBLE_CLICKED; - emitState(eventMsg, button); - button->state = STATE_IDLE; - } - else { - emitState(eventMsg, button); - button->state = STATE_RELEASE_WAIT; - } - break; - - case STATE_DOUBLE_CLICKED: - case STATE_RELEASE_WAIT: - emitState(eventMsg, button); - button->state = STATE_IDLE; - break; - } - } - else { - // emit state and transition state - emitState(eventMsg, button); - switch(button->state) { - case STATE_PRESSED: - case STATE_CLICKED_PRESSED: - button->state = STATE_RELEASE_WAIT; - break; - - default: - button->state = STATE_IDLE; - break; - } - } - - // if child has lock then perform handshake with parent - if (!lockStatus) { - pthread_mutex_unlock(&button->lockControl); // release for synchronization - pthread_barrier_wait(&button->barrierControl); // begin synchronization - pthread_barrier_wait(&button->barrierControl); // wait for synchronized - } - } -} - - - -void * socketServer(void * args) { - int * clients; - clients = (int *)args; - int l, fd, socket = openSocket(); - - while (1) { - // wait for connection - if ( (fd = accept(socket, NULL, NULL)) == -1) { - fprintf(stderr, "Error accepting incoming connection.\n"); - continue; - } - - for(l = 0; l < MAX_CLIENTS; l++) { - if (clients[l] == -1) { - clients[l] = fd; - break; - } - } - - if (l == MAX_CLIENTS) { - send(fd, ERROR_MAX_CLIENTS, strlen(ERROR_MAX_CLIENTS), MSG_NOSIGNAL); - } - else { - // add connection to empty slot -printf("Connect %d\n", fd); - } - } -} - - -// emit event for the current button state -void emitState(char * buffer, buttonDefinition * button) { - switch (button->state) { - case STATE_PRESSED: - // emit event and transition to release wait - emitFormattedMessage(buffer, EVENT_STRING[pressed], button); - break; - - case STATE_CLICKED: - // emit event and transition to idle - emitFormattedMessage(buffer, EVENT_STRING[clicked], button); - break; - - case STATE_CLICKED_PRESSED: - // emit event and transition to release wait - emitFormattedMessage(buffer, EVENT_STRING[clicked_pressed], button); - break; - - case STATE_DOUBLE_CLICKED: - // emit event and transition to idle - emitFormattedMessage(buffer, EVENT_STRING[double_clicked], button); - break; - - case STATE_RELEASE_WAIT: - // emit event and transition to idle - emitFormattedMessage(buffer, EVENT_STRING[released], button); - break; - } -} - - -void emitFormattedMessage(char * buffer, const char * eventString, buttonDefinition * button) { - if (strlen(EVENT_MSG_FORMAT) + strlen(eventString) + strlen(button->gpio) >= EVENT_MSG_MAX_LENGTH) { - fprintf(stderr, "Emit message too large for buffer."); - } - else { - sprintf(buffer, EVENT_MSG_FORMAT, eventString, button->gpio, button->lastTime.tv_sec, button->lastTime.tv_nsec); - emitMessage(buffer, button->clients); - } -} - - -void emitMessage(const char * msg, int * clients) { - int l, wl; - for(l = 0; l < MAX_CLIENTS; l++) { - if (clients[l] != -1) { - wl = send(clients[l], msg, strlen(msg), MSG_NOSIGNAL); - if (wl == -1) { - // failure, remove client - close(clients[l]); - clients[l] = -1; - } - } - } -} - - -int openSocket() { - struct sockaddr_un addr; - int fd; - - if ( (fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { - fprintf(stderr, "Error opening event socket."); - exit(-1); - } - - memset(&addr, 0, sizeof(addr)); - addr.sun_family = AF_UNIX; - strncpy(addr.sun_path, EVENT_SOCKET_PATH, sizeof(addr.sun_path)-1); - unlink(EVENT_SOCKET_PATH); - - if (bind(fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) { - fprintf(stderr, "Event socket bind error."); - exit(-1); - } - - if (listen(fd, 5) == -1) { - fprintf(stderr, "Event socket listen error."); - exit(-1); - } - - return fd; -} - - -void setConditionNS(struct timespec * currentTime, struct timespec * targetTime, uint32_t ns) { - targetTime->tv_sec = currentTime->tv_sec; - targetTime->tv_nsec = currentTime->tv_nsec; - if (1000000000 - targetTime->tv_nsec < ns) { - targetTime->tv_sec += 1; - targetTime->tv_nsec = ns - (targetTime->tv_nsec - 1000000000); - } - else { - targetTime->tv_nsec = targetTime->tv_nsec + ns; - } -} diff --git a/lib/pi-buttons/buttons.h b/lib/pi-buttons/buttons.h deleted file mode 100644 index 4949758..0000000 --- a/lib/pi-buttons/buttons.h +++ /dev/null @@ -1,108 +0,0 @@ -#define EVENT_SOCKET_PATH "./buttonevents" -#define MAX_BUTTONS 10 -#define MAX_CLIENTS 2 -#define ERROR_MAX_CLIENTS "error {\"error\": \"Maximum client connections exceeded.\"}" - -enum ButtonState { - STATE_INIT, - STATE_IDLE, - STATE_PRESSED, - STATE_CLICKED, - STATE_CLICKED_PRESSED, - STATE_DOUBLE_CLICKED, - STATE_RELEASE_WAIT -}; - -enum DebounceState { - INACTIVE, - ACTIVE -}; - -enum LockTimeoutState { - TIMEOUT_FALSE, - TIMEOUT_TRUE -}; - -enum ButtonValue { - PRESSED = 48, - RELEASED -}; - -// I.E. 'button_changed {"gpio": "17", "time": 012345678900123456789}' -#define EVENT_MSG_MAX_LENGTH 128 -static const char * EVENT_MSG_FORMAT = "%s {\"gpio\": \"%s\", \"time\": {\"tv_sec\": %ld, \"tv_nsec\": %ld}}\n"; - -// define events -#define FOREACH_EVENT(EVENT) \ - EVENT(button_changed) \ - EVENT(button_press) \ - EVENT(button_release) \ - EVENT(pressed) \ - EVENT(clicked) \ - EVENT(clicked_pressed) \ - EVENT(double_clicked) \ - EVENT(released) \ - -#define GENERATE_ENUM(ENUM) ENUM, -#define GENERATE_STRING(STRING) #STRING, - -enum EVENT_ENUM { - FOREACH_EVENT(GENERATE_ENUM) -}; - -static const char *EVENT_STRING[] = { - FOREACH_EVENT(GENERATE_STRING) -}; - - -#define SEC_NSEC 1000000000 -#define DEBOUNCE_MS 30 -#define DEBOUNCE_NS 20000000 -#define PRESSED_MS 200 -#define PRESSED_NS 200000000 -#define CLICKED_MS 200 -#define CLICKED_NS 200000000 - -typedef struct { - pthread_mutex_t lockControl; - pthread_barrier_t barrierControl; - struct timespec lastTime; - struct timespec conditionTime; - const char * gpio; - int fd; // file descriptor for button input - enum ButtonState state; - int debounceState; - uint8_t value; - uint8_t lastValue; - int * clients; - pthread_t parent; - pthread_t child; - long debounce_ns; -} buttonDefinition; - -typedef struct { - char ** gpios; - int gpioCount; - int * clients; -} pollerThreadArgs; - -typedef struct { - int index; - int fd; // file descriptor for button input - enum ButtonState state; - int debouncing; - uint8_t value; - uint8_t lastValue; - int * clients; -} gpioButton; - -void * buttonPoller(void * args); -void * buttonDebounce(void * args); -void * socketServer(void * args); -int openSocket(); -void emitMessage(const char * msg, int * clients); -void * buttonParent(void * args); -void * buttonChild(void * args); -void emitState(char * buffer, buttonDefinition * button); -void emitFormattedMessage(char * buffer, const char * eventString, buttonDefinition * button); -void setConditionNS(struct timespec * currentTime, struct timespec * targetTime, uint32_t ns); diff --git a/lib/pi-buttons/index.js b/lib/pi-buttons/index.js deleted file mode 100644 index 7f92464..0000000 --- a/lib/pi-buttons/index.js +++ /dev/null @@ -1,28 +0,0 @@ -'use strict'; - -const net = require('net'); -const events = require('events'); -const emitter = new events.EventEmitter(); -const pins = { [17]: 11, [27]: 13 }; - -var client = net.createConnection(__dirname + "/buttonevents"); - -client.on("connect", function() { - -}); - -client.on("data", function(data) { - let packets = data.toString().split(/\r?\n/); - packets.forEach(packet => { - var parts = /^([^{]+)\s({.*})/.exec(packet); - if (parts) { - try { - var d = JSON.parse(parts[2]); - emitter.emit(parts[1], pins[d.gpio], d); - } - catch (e) {} - } - }); -}); - -module.exports = emitter; diff --git a/lib/pi-buttons/setup.sh b/lib/pi-buttons/setup.sh deleted file mode 100755 index 14cf0a6..0000000 --- a/lib/pi-buttons/setup.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/sh - -echo 17 > /sys/class/gpio/export -echo 22 > /sys/class/gpio/export -echo 27 > /sys/class/gpio/export - -sleep 3 - -echo "in" > /sys/class/gpio/gpio17/direction -echo "both" > /sys/class/gpio/gpio17/edge - -echo "in" > /sys/class/gpio/gpio27/direction -echo "both" > /sys/class/gpio/gpio27/edge - -echo "out" > /sys/class/gpio/gpio22/direction diff --git a/openaps-menu.sh b/openaps-menu.sh index ea3f3f0..3cfeb21 100755 --- a/openaps-menu.sh +++ b/openaps-menu.sh @@ -1,3 +1,2 @@ #!/bin/bash - -(cd lib/pi-buttons/ && ./setup.sh && ./a.out) & node index.js +node index.js diff --git a/package.json b/package.json index 8994aa2..3fb697e 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "oled-font-5x7": "^1.0.0", "oled-i2c-bus": "git+https://github.com/bnielsen1965/oled-i2c-bus.git", "pngparse": "^2.0.1", - "rpi-gpio": "^0.9.1", - "rpi-gpio-buttons": "^1.0.2" + "node-pi-buttons": "git+https://github.com/bnielsen1965/node-pi-buttons.git", + "rpi-gpio": "^0.9.1" } }