/* * The MySensors Arduino library handles the wireless radio link and protocol * between your home built sensors/actuators and HA controller of choice. * The sensors forms a self healing radio network with optional repeaters. Each * repeater and gateway builds a routing tables in EEPROM which keeps track of the * network topology allowing messages to be routed to nodes. * * Created by Henrik Ekblad * Copyright (C) 2013-2018 Sensnology AB * Full contributor list: https://github.com/mysensors/MySensors/graphs/contributors * * Documentation: http://www.mysensors.org * Support Forum: http://forum.mysensors.org * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * version 2 as published by the Free Software Foundation. * * Based on wiringPi Copyright (c) 2012 Gordon Henderson. */ #include "interrupt.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "log.h" struct ThreadArgs { void (*func)(); int gpioPin; }; volatile bool interruptsEnabled = true; static pthread_mutex_t intMutex = PTHREAD_MUTEX_INITIALIZER; static pthread_t *threadIds[64] = {NULL}; // sysFds: // Map a file descriptor from the /sys/class/gpio/gpioX/value static int sysFds[64] = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, }; /* * Part of wiringPi: Simple way to get your program running at high priority * with realtime schedulling. */ int piHiPri(const int pri) { struct sched_param sched ; memset (&sched, 0, sizeof(sched)) ; if (pri > sched_get_priority_max (SCHED_RR)) { sched.sched_priority = sched_get_priority_max (SCHED_RR) ; } else { sched.sched_priority = pri ; } return sched_setscheduler (0, SCHED_RR, &sched) ; } void *interruptHandler(void *args) { int fd; struct pollfd polls; char c; struct ThreadArgs *arguments = (struct ThreadArgs *)args; int gpioPin = arguments->gpioPin; void (*func)() = arguments->func; delete arguments; (void)piHiPri(55); // Only effective if we run as root if ((fd = sysFds[gpioPin]) == -1) { logError("Failed to attach interrupt for pin %d\n", gpioPin); return NULL; } // Setup poll structure polls.fd = fd; polls.events = POLLPRI | POLLERR; while (1) { // Wait for it ... int ret = poll(&polls, 1, -1); if (ret < 0) { logError("Error waiting for interrupt: %s\n", strerror(errno)); break; } // Do a dummy read to clear the interrupt // A one character read appars to be enough. if (lseek (fd, 0, SEEK_SET) < 0) { logError("Interrupt handler error: %s\n", strerror(errno)); break; } if (read (fd, &c, 1) < 0) { logError("Interrupt handler error: %s\n", strerror(errno)); break; } // Call user function. pthread_mutex_lock(&intMutex); if (interruptsEnabled) { pthread_mutex_unlock(&intMutex); func(); } else { pthread_mutex_unlock(&intMutex); } } close(fd); return NULL; } void attachInterrupt(uint8_t gpioPin, void (*func)(), uint8_t mode) { FILE *fd; char fName[40]; char c; int count, i; if (threadIds[gpioPin] == NULL) { threadIds[gpioPin] = new pthread_t; } else { // Cancel the existing thread for that pin pthread_cancel(*threadIds[gpioPin]); // Wait a bit usleep(1000); } // Export pin for interrupt if ((fd = fopen("/sys/class/gpio/export", "w")) == NULL) { logError("attachInterrupt: Unable to export pin %d for interrupt: %s\n", gpioPin, strerror(errno)); exit(1); } fprintf(fd, "%d\n", gpioPin); fclose(fd); // Wait a bit the system to create /sys/class/gpio/gpio usleep(1000); snprintf(fName, sizeof(fName), "/sys/class/gpio/gpio%d/direction", gpioPin) ; if ((fd = fopen (fName, "w")) == NULL) { logError("attachInterrupt: Unable to open GPIO direction interface for pin %d: %s\n", gpioPin, strerror(errno)); exit(1) ; } fprintf(fd, "in\n") ; fclose(fd) ; snprintf(fName, sizeof(fName), "/sys/class/gpio/gpio%d/edge", gpioPin) ; if ((fd = fopen(fName, "w")) == NULL) { logError("attachInterrupt: Unable to open GPIO edge interface for pin %d: %s\n", gpioPin, strerror(errno)); exit(1) ; } switch (mode) { case CHANGE: fprintf(fd, "both\n"); break; case FALLING: fprintf(fd, "falling\n"); break; case RISING: fprintf(fd, "rising\n"); break; case NONE: fprintf(fd, "none\n"); break; default: logError("attachInterrupt: Invalid mode\n"); fclose(fd); return; } fclose(fd); if (sysFds[gpioPin] == -1) { snprintf(fName, sizeof(fName), "/sys/class/gpio/gpio%d/value", gpioPin); if ((sysFds[gpioPin] = open(fName, O_RDWR)) < 0) { logError("Error reading pin %d: %s\n", gpioPin, strerror(errno)); exit(1); } } // Clear any initial pending interrupt ioctl(sysFds[gpioPin], FIONREAD, &count); for (i = 0; i < count; ++i) { if (read(sysFds[gpioPin], &c, 1) == -1) { logError("attachInterrupt: failed to read pin status: %s\n", strerror(errno)); } } struct ThreadArgs *threadArgs = new struct ThreadArgs; threadArgs->func = func; threadArgs->gpioPin = gpioPin; // Create a thread passing the pin and function pthread_create(threadIds[gpioPin], NULL, interruptHandler, (void *)threadArgs); } void detachInterrupt(uint8_t gpioPin) { // Cancel the thread if (threadIds[gpioPin] != NULL) { pthread_cancel(*threadIds[gpioPin]); delete threadIds[gpioPin]; threadIds[gpioPin] = NULL; } // Close filehandle if (sysFds[gpioPin] != -1) { close(sysFds[gpioPin]); sysFds[gpioPin] = -1; } FILE *fp = fopen("/sys/class/gpio/unexport", "w"); if (fp == NULL) { logError("Unable to unexport pin %d for interrupt\n", gpioPin); exit(1); } fprintf(fp, "%d", gpioPin); fclose(fp); } void interrupts() { pthread_mutex_lock(&intMutex); interruptsEnabled = true; pthread_mutex_unlock(&intMutex); } void noInterrupts() { pthread_mutex_lock(&intMutex); interruptsEnabled = false; pthread_mutex_unlock(&intMutex); }