Skip to content

Commit

Permalink
Merge pull request #2 from jbalonso/use-gpiod
Browse files Browse the repository at this point in the history
Rewrite gpio.cpp routines to use gpiod for OSPI/OSBO
  • Loading branch information
jbalonso committed Sep 4, 2023
2 parents 31a2a8c + 25e01fd commit 8ad2dac
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 239 deletions.
19 changes: 5 additions & 14 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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/" ]
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
301 changes: 77 additions & 224 deletions gpio.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -186,262 +186,115 @@ byte digitalReadExt(byte pin) {
#include <string.h>
#include <poll.h>
#include <pthread.h>
#include <gpiod.h>

#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<count; i++)
read (sysFds[pin], &c, 1) ;

// record isr function
isrFunctions[pin] = isr;

pthread_t threadId ;
pthread_mutex_lock (&pinMutex) ;
pinPass = pin ;
pthread_create (&threadId, NULL, interruptHandler, NULL) ;
while (pinPass != -1)
delay(1) ;
pthread_mutex_unlock (&pinMutex) ;
}

void attachInterrupt(int pin, const char* mode, void (*isr)(void)) {}
void gpio_write(int fd, byte value) {}
int gpio_fd_open(int pin, int mode) {return 0;}
void gpio_fd_close(int fd) {}
#else

void pinMode(int pin, byte mode) {}
Expand Down

0 comments on commit 8ad2dac

Please sign in to comment.