Skip to content

Commit

Permalink
Implement event functions
Browse files Browse the repository at this point in the history
  • Loading branch information
paly2 committed Sep 4, 2016
1 parent e480891 commit d3f9eab
Show file tree
Hide file tree
Showing 8 changed files with 582 additions and 22 deletions.
12 changes: 8 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
Grammar mistake in this file ? Please open an issue !

# GPIOEmu

GPIOEmu is a python module able to emulate some functions of the RPi.GPIO module, for testing purpose (or educational, but remember that students also need reality).
Expand All @@ -9,15 +11,17 @@ GPIOEmu is a python module able to emulate some functions of the RPi.GPIO module
You need a UNIX-like system to run this emulator, such as GNU/Linux, FreeBSD... It *will not* work on Micro$oft Windows, but it is designed to be easy to port (it mostly uses the standard library or cross-plateform libraries).

Dependencies:
* Python 2 or 3 (yes, it runs with both, like the RPi.GPIO module)
* SDL 2 library
* Python3
* SDL2 library

Run, as root:
```
# apt-get install libsdl2-dev # Install the SDL 2 library
# python3 setup.py install # Install the GPIOEmu python extension
```

Note: it should be able to run with Python2, however, there may be bugs.

## How to use it

Documentation: mostly the same as [RPi.GPIO](https://sourceforge.net/p/raspberry-gpio-python/wiki/Home/). See "differences with the RPi.GPIO module" for... differences.
Expand All @@ -40,15 +44,15 @@ The GPIO is for the moment always rev 3, but the code is already designed to be

Functions are the same, warnings are the same...

* The interrupt functions (`add_event_detect`...) are not available yet.
* The interrupt functions (`add_event_detect`, `wait_for_edge`, `remove_event_detect`, `event_detected`, `add_event_callback`) may have different behaviour, because of their different internal implementation.
* The RPi.GPIO module provides a RPI_INFO dictionary containing 6 fields. This dictionary is also providded by GPIOEmu, but it contains only one field (P1_REVISION), also accessible as the RPI_REVISION deprecated variable (provided both by RPi.GPIO and GPIOEmu).
* The RPi.GPIO module is able to tell you many more modes using `gpio_mode()`. This function of the GPIOEmu module returns either INPUT, OUTPUT or PWM (or -1).

## How does it work

Near all the python interface code is from the RPi.GPIO module.

Unlike the RPi.GPIO module, it has two threads:
Unlike the RPi.GPIO module, it has two threads (without counting the optional event thread):
* The main thread (your python program). GPIOEmu API calls in the main thread change some variable values, or read them.
* The GUI thread, which periodically reads variable values to draw the window and handles X events.

Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
url = 'https://github.com/paly2/GPIOEmu',
classifiers = classifiers,
#packages = ['GPIOEmu'],
ext_modules = [Extension(name='GPIOEmu', sources=['src/py_gpio.c', 'src/GUI.c', 'src/common.c', 'src/constants.c', 'src/py_pwm.c'], libraries=["SDL2", "pthread"])])
ext_modules = [Extension(name='GPIOEmu', sources=['src/py_gpio.c', 'src/GUI.c', 'src/common.c', 'src/constants.c', 'src/py_pwm.c', 'src/event_gpio.c'], libraries=["SDL2", "pthread"])])

# Copy images to /usr/share/GPIOEmu
print("copying src/images/ -> /usr/share/GPIOEmu/images/")
Expand Down
20 changes: 19 additions & 1 deletion src/GUI.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include "GUI.h"
#include "common.h"
#include "constants.h"
#include "event_gpio.h"

static SDL_Window* window = NULL;
static SDL_Renderer* renderer = NULL;
Expand All @@ -27,10 +28,16 @@ void close_GUI(void) {
SDL_DestroyTexture(GPIO_image);
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_DestroyCond(event_cond);
SDL_DestroyMutex(event_lock);
SDL_Quit();
}

int load_GUI(void) {
// Initialize the event_cond condition and the event_lock mutex
event_cond = SDL_CreateCond();
event_lock = SDL_CreateMutex();

// SDL_Init / SDL_Quit
Py_AtExit(close_GUI);
if (SDL_Init(SDL_INIT_VIDEO) == -1) {
Expand Down Expand Up @@ -88,6 +95,17 @@ int load_GUI(void) {
return 0;
}

static void change_state(int gpio) {
gpio_state[gpio] = (gpio_state[gpio] == STATE_LOW) ? STATE_HIGH : STATE_LOW;

// Signal event_cond
SDL_LockMutex(event_lock);
event_channel = gpio;
event_edge = (gpio_state[gpio] == STATE_HIGH) ? RISING_EDGE : FALLING_EDGE;
SDL_CondBroadcast(event_cond);
SDL_UnlockMutex(event_lock);
}

static void on_click(Sint32 x, Sint32 y) {
int row = y/32, column;
unsigned int pin;
Expand All @@ -107,7 +125,7 @@ static void on_click(Sint32 x, Sint32 y) {
if (gpio_direction[gpio] != INPUT)
return;

gpio_state[gpio] = (gpio_state[gpio] == STATE_LOW) ? STATE_HIGH : STATE_LOW;
change_state(gpio);
}

static void draw_static(void) {
Expand Down
2 changes: 0 additions & 2 deletions src/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,4 @@ int gpio_state[28];
int pull_up_down[28];
int rpi_p1_revision;
int setup_error;
int check_gpio_priv(void);
int get_gpio_number(int channel, unsigned int *gpio);
unsigned int bcm_from_board(unsigned int gpio);
5 changes: 2 additions & 3 deletions src/constants.c
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#include "Python.h"
#include "constants.h"
#include "common.h"
//#include "event_gpio.h"
#include "event_gpio.h"

void define_constants(PyObject *module)
{
Expand Down Expand Up @@ -46,7 +46,7 @@ void define_constants(PyObject *module)

pud_down = Py_BuildValue("i", PUD_DOWN + PY_PUD_CONST_OFFSET);
PyModule_AddObject(module, "PUD_DOWN", pud_down);
/*

rising_edge = Py_BuildValue("i", RISING_EDGE + PY_EVENT_CONST_OFFSET);
PyModule_AddObject(module, "RISING", rising_edge);

Expand All @@ -55,5 +55,4 @@ void define_constants(PyObject *module)

both_edge = Py_BuildValue("i", BOTH_EDGE + PY_EVENT_CONST_OFFSET);
PyModule_AddObject(module, "BOTH", both_edge);
*/
}
262 changes: 262 additions & 0 deletions src/event_gpio.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,262 @@
#include <SDL2/SDL_thread.h>
#include <SDL2/SDL_timer.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "event_gpio.h"

/* Note about this file:
* I try to respect the functions of the RPi.GPIO event_gpio.c file. However,
* the implementation differs a lot, and their behaviour also differ. We use
* SDL_Thread instead of pthread.h. The callbacks are in the gpios structure.
* The wait_for_edge function does not use an event. Etc.
* Finally the code is much simpler, but I repeat: it may have a different
* behaviour.
*/
const char *stredge[4] = {"none", "rising", "falling", "both"};

SDL_mutex* event_lock;
SDL_cond* event_cond;

struct gpios {
unsigned int gpio;
int edge;
void (*callback)(unsigned int gpio);
int bouncetime;
struct gpios *next;
};
struct gpios *gpio_list = NULL;

SDL_Thread* threads;

int event_occurred[28] = {0};
int thread_running = 0;

/********* gpio list functions **********/
struct gpios *get_gpio(unsigned int gpio) {
struct gpios *g = gpio_list;
while (g != NULL) {
if (g->gpio == gpio)
return g;
g = g->next;
}
return NULL;
}

struct gpios *new_gpio(unsigned int gpio) {
struct gpios *new_gpio;

new_gpio = malloc(sizeof(struct gpios));
if (new_gpio == 0)
return NULL; // out of memory

new_gpio->gpio = gpio;
new_gpio->callback = NULL;

if (gpio_list == NULL)
new_gpio->next = NULL;
else
new_gpio->next = gpio_list;
gpio_list = new_gpio;
return new_gpio;
}

void delete_gpio(unsigned int gpio) {
struct gpios *g = gpio_list;
struct gpios *temp;
struct gpios *prev = NULL;

while (g != NULL) {
if (g->gpio == gpio) {
if (prev == NULL)
gpio_list = g->next;
else
prev->next = g->next;
temp = g;
g = g->next;
free(temp);
return;
} else {
prev = g;
g = g->next;
}
}
}

int gpio_event_added(unsigned int gpio) {
struct gpios *g = gpio_list;
while (g != NULL) {
if (g->gpio == gpio)
return g->edge;
g = g->next;
}
return 0;
}

/******* callback list functions ********/
int add_edge_callback(unsigned int gpio, void (*func)(unsigned int gpio)) {
struct gpios *g;

if ((g = get_gpio(gpio)) == NULL)
return -1;

g->callback = func;

return 0;
}


int poll_thread(void *threadarg) {
struct gpios *g;

SDL_LockMutex(event_lock);
thread_running = 1;
while (1) {
SDL_CondWait(event_cond, event_lock);
if (event_channel == -1) { // Stop
thread_running = 0;
break;
}

if ((g = get_gpio(event_channel)) == NULL)
continue;
if (g->edge == event_edge || g->edge == BOTH_EDGE) {
event_occurred[g->gpio] = 1;
if (g->callback != NULL)
g->callback(event_channel);
}
}
SDL_UnlockMutex(event_lock);

return 0;
}

void remove_edge_detect(unsigned int gpio) {
struct gpios *g = get_gpio(gpio);

if (g == NULL)
return;

delete_gpio(gpio);

event_occurred[gpio] = 0;
}

int event_detected(unsigned int gpio) {
if (event_occurred[gpio]) {
event_occurred[gpio] = 0;
return 1;
} else {
return 0;
}
}

void event_cleanup(unsigned int gpio)
// gpio of -666 means clean every channel used
{
struct gpios *g = gpio_list;
struct gpios *temp = NULL;

while (g != NULL) {
if ((gpio == -666) || (g->gpio == gpio)) {
temp = g->next;
remove_edge_detect(g->gpio);
g = temp;
}
}

// Stop poll thread and pending "wait_for_edge"
SDL_LockMutex(event_lock);
event_channel = -1;
SDL_CondBroadcast(event_cond);
SDL_UnlockMutex(event_lock);
}

void event_cleanup_all(void) {
event_cleanup(-666);
}

int add_edge_detect(unsigned int gpio, unsigned int edge, int bouncetime)
// return values:
// 0 - Success
// 1 - Edge detection already added
// 2 - Other error
{
int i = -1;
struct gpios* g;

i = gpio_event_added(gpio);
if (i == 0) { // event not already added
if ((g = new_gpio(gpio)) == NULL)
return 2;

g->edge = edge;
g->bouncetime = bouncetime;
} else if (i == edge) { // get existing event
g = get_gpio(gpio);
if ((bouncetime != -666 && g->bouncetime != bouncetime)) // different event bouncetime used
return 1;
} else {
return 1;
}

// start poll thread if it is not already running
if (!thread_running) {
if ((threads = SDL_CreateThread(poll_thread, "PollThread", (void *)NULL)) == NULL) {
remove_edge_detect(gpio);
return 2;
}
}
return 0;
}

int blocking_wait_for_edge(unsigned int gpio, unsigned int edge, int bouncetime, int timeout)
// return values:
// 1 - Success (edge detected)
// 0 - Timeout
// -1 - Edge detection already added
// -2 - Other error
{
int ed;
struct gpios *g = NULL;
int ret;

if (timeout == -1) // No timeout
timeout = SDL_MUTEX_MAXWAIT;

/* We add a GPIO event to get a behaviour similar the RPi.GPIO module
* one, but we don't use it.
*/
// add gpio if it has not been added already
ed = gpio_event_added(gpio);
if (ed == edge) { // get existing record
g = get_gpio(gpio);
if (g->bouncetime != -666 && g->bouncetime != bouncetime)
return -1;
} else if (ed == NO_EDGE) { // not found so add event
if ((g = new_gpio(gpio)) == NULL)
return -2;
g->edge = edge;
g->bouncetime = bouncetime;
} else { // ed != edge - event for a different edge
g = get_gpio(gpio);
g->edge = edge;
g->bouncetime = bouncetime;
}

// wait for edge
SDL_LockMutex(event_lock);
while (1) {
ret = SDL_CondWaitTimeout(event_cond, event_lock, timeout);
if (ret == SDL_MUTEX_TIMEDOUT) { // timed out
SDL_UnlockMutex(event_lock);
return 0;
}
if (event_channel == -1) // Stop
return -2;
if (event_channel == gpio && (event_edge == edge || edge == BOTH_EDGE)) // event detected
break;
}
SDL_UnlockMutex(event_lock);
return 1;
}
Loading

0 comments on commit d3f9eab

Please sign in to comment.