diff --git a/Dockerfile b/Dockerfile index bf211f22..deaa5c3a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,34 +1,25 @@ FROM debian:bookworm-slim as base ######################################## -## 1st stage compiles OpenSprinkler runtime dependency raspi-gpio -FROM base as raspi-gpio-build - -ENV DEBIAN_FRONTEND=noninteractive -RUN apt-get update && apt-get install -y git gcc make automake && rm -rf /var/lib/apt/lists/* -RUN mkdir /raspi-gpio && cd /raspi-gpio && git clone --depth 1 https://github.com/RPi-Distro/raspi-gpio.git . && autoreconf -f -i && (./configure || cat config.log) && make - -######################################## -## 2nd stage compiles OpenSprinkler code +## 1st stage compiles OpenSprinkler code FROM base as os-build ENV DEBIAN_FRONTEND=noninteractive -RUN apt-get update && apt-get install -y bash g++ make libmosquittopp-dev && rm -rf /var/lib/apt/lists/* +RUN apt-get update && apt-get install -y bash g++ make libgpiod-dev libmosquittopp-dev && rm -rf /var/lib/apt/lists/* COPY . /OpenSprinkler RUN cd /OpenSprinkler && make ######################################## -## 3rd stage is minimal runtime + executable +## 2nd stage is minimal runtime + executable FROM base ENV DEBIAN_FRONTEND=noninteractive -RUN apt-get update && apt-get install -y libstdc++6 libmosquittopp1 && rm -rf /var/lib/apt/lists/* \ +RUN apt-get update && apt-get install -y libstdc++6 libgpiod2 libmosquittopp1 && rm -rf /var/lib/apt/lists/* \ && \ mkdir /OpenSprinkler && \ mkdir -p /data/logs COPY --from=os-build /OpenSprinkler/OpenSprinkler /OpenSprinkler/OpenSprinkler -COPY --from=raspi-gpio-build /raspi-gpio/raspi-gpio /usr/bin/raspi-gpio WORKDIR /OpenSprinkler #-- Logs and config information go into the volume on /data @@ -38,4 +29,4 @@ VOLUME /data EXPOSE 8080 #-- By default, start OS using /data for saving data/NVM/log files -CMD [ "/OpenSprinkler/OpenSprinkler", "-d", "/data" ] +CMD [ "/OpenSprinkler/OpenSprinkler", "-d", "/data/" ] diff --git a/Makefile b/Makefile index 40934d05..dc43a010 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ CXX=g++ # -std=gnu++17 CXXFLAGS=-std=gnu++14 -DOSPI -Wall LD=$(CXX) -LIBS=pthread mosquitto +LIBS=gpiod pthread mosquitto LDFLAGS=$(addprefix -l,$(LIBS)) BINARY=OpenSprinkler SOURCES=main.cpp OpenSprinkler.cpp program.cpp opensprinkler_server.cpp utils.cpp weather.cpp gpio.cpp etherport.cpp mqtt.cpp diff --git a/gpio.cpp b/gpio.cpp index 5b7378e2..7e0b5b17 100644 --- a/gpio.cpp +++ b/gpio.cpp @@ -186,262 +186,115 @@ byte digitalReadExt(byte pin) { #include #include #include +#include + +#include "utils.h" #define BUFFER_MAX 64 #define GPIO_MAX 64 -// GPIO file descriptors -static int sysFds[GPIO_MAX] = { - -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, -} ; - -// Interrupt service routine functions -static void (*isrFunctions [GPIO_MAX])(void); - -static volatile int pinPass = -1 ; -static pthread_mutex_t pinMutex ; - -/** Export gpio pin */ -static byte GPIOExport(int pin) { - char buffer[BUFFER_MAX]; - int fd, len; - - fd = open("/sys/class/gpio/export", O_WRONLY); - if (fd < 0) { - DEBUG_PRINTLN("failed to open export for writing"); - return 0; - } - - len = snprintf(buffer, sizeof(buffer), "%d", pin); - write(fd, buffer, len); - close(fd); - return 1; -} - -#if 0 -/** Unexport gpio pin */ -static byte GPIOUnexport(int pin) { - char buffer[BUFFER_MAX]; - int fd, len; - - fd = open("/sys/class/gpio/unexport", O_WRONLY); - if (fd < 0) { - DEBUG_PRINTLN("failed to open unexport for writing"); - return 0; +// GPIO interfaces +const char *gpio_chipname = "gpiochip0"; +const char *gpio_consumer = "opensprinkler"; + +struct gpiod_chip *chip = NULL; +struct gpiod_line* gpio_lines[] = { + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, +}; + +int assert_gpiod_chip() { + if( !chip ) { + chip = gpiod_chip_open_by_name(gpio_chipname); + if( !chip ) { + DEBUG_PRINTLN("failed to open gpio chip"); + return -1; + } else { + DEBUG_PRINTLN("gpio chip opened"); + return 0; + } } - - len = snprintf(buffer, sizeof(buffer), "%d", pin); - write(fd, buffer, len); - close(fd); - return 1; + return 0; } -#endif - -/** Set interrupt edge mode */ -static byte GPIOSetEdge(int pin, const char *edge) { - char path[BUFFER_MAX]; - int fd; - snprintf(path, BUFFER_MAX, "/sys/class/gpio/gpio%d/edge", pin); - - fd = open(path, O_WRONLY); - if (fd < 0) { - DEBUG_PRINTLN("failed to open gpio edge for writing"); - return 0; +int assert_gpiod_line(int pin) { + if( !gpio_lines[pin] ) { + if( assert_gpiod_chip() ) { return -1; } + gpio_lines[pin] = gpiod_chip_get_line(chip, pin); + if( !gpio_lines[pin] ) { + DEBUG_PRINT("failed to open gpio line "); + DEBUG_PRINTLN(pin); + return -1; + } else { + DEBUG_PRINT("opened gpio line "); + DEBUG_PRINT(pin); + return 0; + } } - write(fd, edge, strlen(edge)+1); - close(fd); - return 1; + return 0; } /** Set pin mode, in or out */ void pinMode(int pin, byte mode) { - static const char dir_str[] = "in\0out"; - - char path[BUFFER_MAX]; - int fd; - - snprintf(path, BUFFER_MAX, "/sys/class/gpio/gpio%d/direction", pin); - - struct stat st; - if(stat(path, &st)) { - if (!GPIOExport(pin)) return; - } - - fd = open(path, O_WRONLY); - if (fd < 0) { - DEBUG_PRINTLN("failed to open gpio direction for writing"); - return; - } - - if (-1 == write(fd, &dir_str[(INPUT==mode)||(INPUT_PULLUP==mode)?0:3], (INPUT==mode)||(INPUT_PULLUP==mode)?2:3)) { - DEBUG_PRINTLN("failed to set direction"); - return; + if( assert_gpiod_line(pin) ) { return; } + switch(mode) { + case INPUT: + gpiod_line_request_input(gpio_lines[pin], gpio_consumer); + break; + case INPUT_PULLUP: + gpiod_line_request_input_flags(gpio_lines[pin], gpio_consumer, GPIOD_LINE_REQUEST_FLAG_BIAS_PULL_UP); + break; + case OUTPUT: + gpiod_line_request_output(gpio_lines[pin], gpio_consumer, LOW); + break; + default: + DEBUG_PRINTLN("invalid pin direction"); + break; } - close(fd); -#if defined(OSPI) - if(mode==INPUT_PULLUP) { - char cmd[BUFFER_MAX]; - //snprintf(cmd, BUFFER_MAX, "gpio -g mode %d up", pin); - snprintf(cmd, BUFFER_MAX, "raspi-gpio set %d pu", pin); - system(cmd); - } -#endif return; } -/** Open file for digital pin */ -int gpio_fd_open(int pin, int mode) { - char path[BUFFER_MAX]; - int fd; - - snprintf(path, BUFFER_MAX, "/sys/class/gpio/gpio%d/value", pin); - fd = open(path, mode); - if (fd < 0) { - DEBUG_PRINTLN("failed to open gpio"); - return -1; - } - return fd; -} - -/** Close file */ -void gpio_fd_close(int fd) { - close(fd); -} - /** Read digital value */ byte digitalRead(int pin) { - char value_str[3]; - - int fd = gpio_fd_open(pin, O_RDONLY); - if (fd < 0) { + if( !gpio_lines[pin] ) { + DEBUG_PRINT("tried to read uninitialized pin "); + DEBUG_PRINTLN(pin); return 0; } - - if (read(fd, value_str, 3) < 0) { - DEBUG_PRINTLN("failed to read value"); + int val = gpiod_line_get_value(gpio_lines[pin]); + if( val < 0 ) { + DEBUG_PRINT("failed to read value on pin "); + DEBUG_PRINTLN(pin); return 0; } - - close(fd); - return atoi(value_str); -} - -/** Write digital value given file descriptor */ -void gpio_write(int fd, byte value) { - static const char value_str[] = "01"; - - if (1 != write(fd, &value_str[LOW==value?0:1], 1)) { - DEBUG_PRINT("failed to write value on pin "); - } + return val; } /** Write digital value */ void digitalWrite(int pin, byte value) { - int fd = gpio_fd_open(pin); - if (fd < 0) { - return; - } - gpio_write(fd, value); - close(fd); -} - -static int HiPri (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) ; -} - -static int waitForInterrupt (int pin, int mS) -{ - int fd, x ; - uint8_t c ; - struct pollfd polls ; - - if((fd=sysFds[pin]) < 0) - return -2; - - polls.fd = fd ; - polls.events = POLLPRI ; // Urgent data! - - x = poll (&polls, 1, mS) ; -// Do a dummy read to clear the interrupt -// A one character read appars to be enough. -// Followed by a seek to reset it. - - (void)read (fd, &c, 1); - lseek (fd, 0, SEEK_SET); - - return x ; -} - -static void *interruptHandler (void *arg) { - int myPin ; - - (void) HiPri (55) ; // Only effective if we run as root - - myPin = pinPass ; - pinPass = -1 ; - - for (;;) - if (waitForInterrupt (myPin, -1) > 0) - isrFunctions[myPin]() ; - - return NULL ; -} - -#include "utils.h" -/** Attach an interrupt function to pin */ -void attachInterrupt(int pin, const char* mode, void (*isr)(void)) { - if((pin<0)||(pin>GPIO_MAX)) { - DEBUG_PRINTLN("pin out of range"); + if( !gpio_lines[pin] ) { + DEBUG_PRINT("tried to write uninitialized pin "); + DEBUG_PRINTLN(pin); return; } - // set pin to INPUT mode and set interrupt edge mode - pinMode(pin, INPUT); - GPIOSetEdge(pin, mode); - - char path[BUFFER_MAX]; - snprintf(path, BUFFER_MAX, "/sys/class/gpio/gpio%d/value", pin); - - // open gpio file - if(sysFds[pin]==-1) { - if((sysFds[pin]=open(path, O_RDWR))<0) { - DEBUG_PRINTLN("failed to open gpio value for reading"); - return; - } + int res; + res = gpiod_line_set_value(gpio_lines[pin], value); + if( res ) { + DEBUG_PRINT("failed to write value on pin "); + DEBUG_PRINTLN(pin); } - - int count, i; - char c; - // clear any pending interrupts - ioctl (sysFds[pin], FIONREAD, &count) ; - for (i=0; i