/* Copyright (c) 2012-2016 Ben Croston Copyright (c) 2018 PHYTEC America, LLC. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "py_gpio.h" #include "common.h" #include "c_gpio.h" /* TODO: Event support struct py_callback { uint32_t gpio; PyObject *py_cb; struct py_callback *next; }; static struct py_callback *py_callbacks = NULL; */ static int check_mode_set(void) { if (gpio_mode != BOARD && gpio_mode != BCM) { PyErr_SetString(PyExc_RuntimeError, "Please set pin numbering mode using " "GPIO.setmode(GPIO.BOARD) or " "GPIO.setmode(GPIO.BCM)"); return -1; } return 0; } static int get_bcm_pin(int channel) { int pin; pin = channel_to_bcm_pin(channel); if (pin < 0) PyErr_SetString(PyExc_ValueError, "Channel is invalid for the Raspberry " "Pi!"); return pin; } static void define_constants(PyObject *module) { high = Py_BuildValue("i", HIGH); PyModule_AddObject(module, "HIGH", high); low = Py_BuildValue("i", LOW); PyModule_AddObject(module, "LOW", low); output = Py_BuildValue("i", OUTPUT); PyModule_AddObject(module, "OUT", output); input = Py_BuildValue("i", INPUT); PyModule_AddObject(module, "IN", input); board = Py_BuildValue("i", BOARD); PyModule_AddObject(module, "BOARD", board); bcm = Py_BuildValue("i", BCM); PyModule_AddObject(module, "BCM", bcm); pud_off = Py_BuildValue("i", PUD_OFF); PyModule_AddObject(module, "PUD_OFF", pud_off); pud_up = Py_BuildValue("i", PUD_UP); PyModule_AddObject(module, "PUD_UP", pud_up); pud_down = Py_BuildValue("i", PUD_DOWN); PyModule_AddObject(module, "PUD_DOWN", pud_down); } /* * python function cleanup(channel=None) */ static PyObject *py_cleanup(PyObject *self, PyObject *args, PyObject *kwargs) { static char *kwlist[] = {"channel", NULL}; int chancount; PyObject *chanlist; int channel; PyObject *chantuple; int i; int pin; PyObject *tempobj; chancount = -1; chanlist = NULL; channel = -1; chantuple = NULL; int cleanup_one(void) { pin = get_bcm_pin(channel); if (pin < 0) return -1; /* TODO: Event support event_cleanup(board_pin); */ return cleanup_gpio(pin); } if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|O", kwlist, &chanlist)) return NULL; if (chanlist == NULL) { /* do nothing */ #if PY_MAJOR_VERSION >= 3 } else if (PyLong_Check(chanlist)) { channel = (int)PyLong_AsLong(chanlist); #else } else if (PyInt_Check(chanlist)) { channel = (int)PyInt_AsLong(chanlist); #endif if (PyErr_Occurred()) return NULL; chanlist = NULL; } else if (PyList_Check(chanlist)) { chancount = PyList_Size(chanlist); } else if (PyTuple_Check(chanlist)) { chantuple = chanlist; chanlist = NULL; chancount = PyTuple_Size(chantuple); } else { PyErr_SetString(PyExc_ValueError, "Channel must be an integer or list/tuple of " "integers!"); return NULL; } if (module_setup && !setup_error) { if (channel == -1 && chancount == -1) { /* channel not set: cleanup everything */ /* TODO: Event support event_cleanup_all(); */ /* release all held pins */ cleanup(); gpio_mode = MODE_UNKNOWN; } else if (channel != -1) { if (cleanup_one()) return NULL; } else { /* channel was a list/tuple */ for (i = 0; i < chancount; i++) { if (chanlist) { tempobj = PyList_GetItem(chanlist, i); if (!tempobj) return NULL; } else { tempobj = PyTuple_GetItem(chantuple, i); if (!tempobj) return NULL; } #if PY_MAJOR_VERSION >= 3 if (PyLong_Check(tempobj)) { channel = (int)PyLong_AsLong(tempobj); #else if (PyInt_Check(tempobj)) { channel = (int)PyInt_AsLong(tempobj); #endif if (PyErr_Occurred()) return NULL; } else { PyErr_SetString(PyExc_ValueError, "Channel must be an integer!"); return NULL; } if (cleanup_one()) return NULL; } } } Py_RETURN_NONE; } /* * python function setmode(mode) */ static PyObject *py_setmode(PyObject *self, PyObject *args) { int new_mode; if (!PyArg_ParseTuple(args, "i", &new_mode)) return NULL; if (setup_error) { PyErr_SetString(PyExc_RuntimeError, "Module not imported correctly!"); return NULL; } if (gpio_mode != MODE_UNKNOWN && new_mode != gpio_mode) { PyErr_SetString(PyExc_ValueError, "A different mode has already been set!"); return NULL; } if (new_mode != BOARD && new_mode != BCM) { PyErr_SetString(PyExc_ValueError, "Mode must be BOARD or BCM!"); return NULL; } gpio_mode = new_mode; Py_RETURN_NONE; } static PyObject *py_setup_channel(PyObject *self, PyObject *args, PyObject *kwargs) { static char *kwlist[] = {"channel", "direction", "pull_up_down", "initial", NULL}; int chancount; PyObject *chanlist; int channel; PyObject *chantuple; int dir; int i; int initial; int pin; int pud; PyObject *tempobj; chanlist = NULL; channel = -1; chantuple = NULL; initial = LOW; pud = 0; int setup_one(void) { if (check_mode_set()) return -1; pin = get_bcm_pin(channel); if (pin < 0) return -1; if (setup_gpio(pin, dir, initial)) { PyErr_SetString(PyExc_RuntimeError, "Unable to configure GPIO!"); return -1; } return 0; } if (!PyArg_ParseTupleAndKeywords(args, kwargs, "Oi|ii", kwlist, &chanlist, &dir, &pud, &initial)) return NULL; #if PY_MAJOR_VERSION >= 3 if (PyLong_Check(chanlist)) { channel = (int)PyLong_AsLong(chanlist); #else if (PyInt_Check(chanlist)) { channel = (int)PyInt_AsLong(chanlist); #endif if (PyErr_Occurred()) return NULL; chanlist = NULL; } else if (PyList_Check(chanlist)) { /* do nothing */ } else if (PyTuple_Check(chanlist)) { chantuple = chanlist; chanlist = NULL; } else { PyErr_SetString(PyExc_ValueError, "Channel must be an integer or list/tuple of " "integers!"); return NULL; } if (setup_error) { PyErr_SetString(PyExc_RuntimeError, "Module not imported correctly!"); return NULL; } if (dir != INPUT && dir != OUTPUT) { PyErr_SetString(PyExc_ValueError, "Direction must be INPUT or OUTPUT!"); return NULL; } if (dir == INPUT && initial != LOW) { PyErr_SetString(PyExc_ValueError, "Initial value is not valid for inputs!"); return NULL; } if (pud != PUD_OFF && pud != PUD_DOWN && pud != PUD_UP) { PyErr_SetString(PyExc_ValueError, "Pull setting must be PUD_OFF, PUD_UP or " "PUD_DOWN!"); return NULL; } if (pud == PUD_UP || pud == PUD_DOWN) if (gpio_warnings) PyErr_WarnEx(NULL, "Setting pulls is not supported by this " "module. If you need a pull on a pin, " "please configure it through your " "board's pinmuxing.", 1); if (chanlist) { chancount = PyList_Size(chanlist); } else if (chantuple) { chancount = PyTuple_Size(chantuple); } else { if (setup_one()) return NULL; Py_RETURN_NONE; } for (i = 0; i < chancount; i++) { if (chanlist) { tempobj = PyList_GetItem(chanlist, i); if (!tempobj) return NULL; } else { /* chantuple */ tempobj = PyTuple_GetItem(chantuple, i); if (!tempobj) return NULL; } #if PY_MAJOR_VERSION >= 3 if (PyLong_Check(tempobj)) { channel = (int)PyLong_AsLong(tempobj); #else if (PyInt_Check(tempobj)) { channel = (int)PyInt_AsLong(tempobj); #endif if (PyErr_Occurred()) return NULL; } else { PyErr_SetString(PyExc_ValueError, "Channel must be an integer!"); return NULL; } if (setup_one()) return NULL; } Py_RETURN_NONE; } /* * python function input(channel) */ static PyObject *py_input_gpio(PyObject *self, PyObject *args) { int channel; int pin; int ret; PyObject *value; if (!PyArg_ParseTuple(args, "i", &channel)) return NULL; pin = get_bcm_pin(channel); if (pin < 0) return NULL; if (gpio_direction(pin) != INPUT) { PyErr_SetString(PyExc_RuntimeError, "GPIO channel has not been configured " "as INPUT!"); return NULL; } ret = input_gpio(pin); if (ret < 0) return NULL; if (ret) { value = Py_BuildValue("i", HIGH); } else { value = Py_BuildValue("i", LOW); } return value; } /* * python function output(channel(s), value(s)) */ static PyObject *py_output_gpio(PyObject *self, PyObject *args) { int chancount; PyObject *chanlist; int channel; PyObject *chantuple; int i; int pin; PyObject *tempobj; int value; int valuecount; PyObject *valuelist; PyObject *valuetuple; chancount = -1; chanlist = NULL; channel = -1; chantuple = NULL; value = -1; valuecount = -1; valuelist = NULL; valuetuple = NULL; int output(void) { if (check_mode_set()) return -1; pin = get_bcm_pin(channel); if (pin < 0) return -1; if (gpio_direction(pin) != OUTPUT) { PyErr_SetString(PyExc_RuntimeError, "GPIO channel has not been configured " "as OUTPUT!"); return -1; } output_gpio(pin, value); return 0; } if (!PyArg_ParseTuple(args, "OO", &chanlist, &valuelist)) return NULL; #if PY_MAJOR_VERSION >= 3 if (PyLong_Check(chanlist)) { channel = (int)PyLong_AsLong(chanlist); #else if (PyInt_Check(chanlist)) { channel = (int)PyInt_AsLong(chanlist); #endif if (PyErr_Occurred()) return NULL; chanlist = NULL; } else if (PyList_Check(chanlist)) { /* do nothing */ } else if (PyTuple_Check(chanlist)) { chantuple = chanlist; chanlist = NULL; } else { PyErr_SetString(PyExc_ValueError, "Channel must be an integer " "or list/tuple of integers!"); return NULL; } #if PY_MAJOR_VERSION >= 3 if (PyLong_Check(valuelist)) { value = (int)PyLong_AsLong(valuelist); #else if (PyInt_Check(valuelist)) { value = (int)PyInt_AsLong(valuelist); #endif if (PyErr_Occurred()) return NULL; valuelist = NULL; } else if (PyList_Check(valuelist)) { /* do nothing */ } else if (PyTuple_Check(valuelist)) { valuetuple = valuelist; valuelist = NULL; } else { PyErr_SetString(PyExc_ValueError, "Value must be an integer/" "boolean or a list/tuple of " "integers/booleans"); return NULL; } if (chanlist) chancount = PyList_Size(chanlist); if (chantuple) chancount = PyTuple_Size(chantuple); if (valuelist) valuecount = PyList_Size(valuelist); if (valuetuple) valuecount = PyTuple_Size(valuetuple); if ((chancount != -1 && chancount != valuecount && valuecount != -1) || (chancount == -1 && valuecount != -1)) { PyErr_SetString(PyExc_RuntimeError, "Number of channels must match number of " "values!"); return NULL; } if (chancount == -1) { if (output()) return NULL; Py_RETURN_NONE; } for (i = 0; i < chancount; i++) { if (chanlist) { tempobj = PyList_GetItem(chanlist, i); if (!tempobj) return NULL; } else { tempobj = PyList_GetItem(chantuple, i); if (!tempobj) return NULL; } #if PY_MAJOR_VERSION >= 3 if (PyLong_Check(tempobj)) { channel = (int)PyLong_AsLong(tempobj); #else if (PyInt_Check(tempobj)) { channel = (int)PyInt_AsLong(tempobj); #endif if (PyErr_Occurred()) return NULL; } else { PyErr_SetString(PyExc_ValueError, "Channel must be an integer!"); return NULL; } if (valuecount > 0) { if (valuelist) { tempobj = PyList_GetItem(valuelist, i); if (!tempobj) return NULL; } else { tempobj = PyTuple_GetItem(valuetuple, i); if (!tempobj) return NULL; } #if PY_MAJOR_VERSION >= 3 if (PyLong_Check(tempobj)) { value = (int)PyLong_AsLong(tempobj); #else if (PyInt_Check(tempobj)) { value = (int)PyInt_AsLong(tempobj); #endif if (PyErr_Occurred()) return NULL; } else { PyErr_SetString(PyExc_ValueError, "Value must be an integer or " "boolean!"); return NULL; } } if (output()) return NULL; } Py_RETURN_NONE; } /* * python function gpio_function(channel) */ static PyObject *py_gpio_function(PyObject *self, PyObject *args) { int dir; int pin; int channel; PyObject *func; if (!PyArg_ParseTuple(args, "i", &channel)) return NULL; pin = get_bcm_pin(channel); if (pin < 0) return NULL; dir = gpio_direction(pin); switch (dir) { case 1 : dir = INPUT; break; case 2 : dir = OUTPUT; break; default : dir = MODE_UNKNOWN; break; } func = Py_BuildValue("i", dir); return func; } /* * python function setwarnings(state) */ static PyObject *py_setwarnings(PyObject *self, PyObject *args) { if (!PyArg_ParseTuple(args, "i", &gpio_warnings)) return NULL; if (setup_error) { PyErr_SetString(PyExc_RuntimeError, "Module not imported correctly!"); return NULL; } Py_RETURN_NONE; } /* * python function getmode() */ static PyObject *py_getmode(PyObject *self, PyObject *args) { PyObject *value; if (setup_error) { PyErr_SetString(PyExc_RuntimeError, "Module not imported correctly!"); return NULL; } if (gpio_mode == MODE_UNKNOWN) Py_RETURN_NONE; value = Py_BuildValue("i", gpio_mode); return value; } PyMethodDef rpi_gpio_methods[] = { {"setup", (PyCFunction)py_setup_channel, METH_VARARGS | METH_KEYWORDS, "Configure a GPIO channel or list of channels with a direction\n" \ "channel\t\t- BCM or RPi pin number depending on which mode is set" \ "\ndirection\t- IN or OUT\n[pull_up_down]\t- not supported\n" \ "[initial]\t- Initial value for an output channel" }, {"cleanup", (PyCFunction)py_cleanup, METH_VARARGS | METH_KEYWORDS, "Clean up GPIO channel configuration by releasing all requested " \ "GPIOs\n[channel]\t- individual channel or list of channels to " \ "clean up" }, {"output", py_output_gpio, METH_VARARGS, "Output to a GPIO channel or list of channels\nchannel\t- BCM or " \ "RPi pin number depending on which mode is set\nvalue\t- 0, False " \ "or LOW for logical low or 1, True, or HIGH for logical high" }, {"input", py_input_gpio, METH_VARARGS, "Read input from a GPIO channel\nchannel\t- BCM or RPi pin number " \ "depending on which mode is set\nReturns LOW=0=False or HIGH=1=True" }, {"setmode", py_setmode, METH_VARARGS, "Configure the numbering scheme used for channels\nmode\t- " \ "numbering scheme to use: BOARD for RPi numbering or BCM for " \ "Broadcom GPIO numbering" }, {"getmode", py_getmode, METH_VARARGS, "Get numbering scheme used for channels\nReturns BOARD, BCM, or None" }, {"gpio_function", py_gpio_function, METH_VARARGS, "Get the current GPIO function\nchannel\t- BCM or RPi pin number " \ "depending on which mode is set\nReturns IN or OUT" }, {"setwarnings", py_setwarnings, METH_VARARGS, "Enable or disable warning messages\nstate\t- 0 or False for " \ "disabled, 1 or True for enabled" }, {NULL, NULL, 0, NULL} }; static const char module_doc_string[] = "Drop-in replacement for RPi.GPIO on " "PHYTEC hardware using Python and " "libgpiod"; #if PY_MAJOR_VERSION >= 3 static strict PyModuleDef rpi_gpio_module = { PyModuleDef_HEAD_INIT, "RPi._GPIO", module_doc_string, -1, rpi_gpio_methods }; #endif #if PY_MAJOR_VERSION >= 3 PyMODINIT_FUNC PyInit__GPIO(void) #else PyMODINIT_FUNC init_GPIO(void) #endif { PyObject *module = NULL; #if PY_MAJOR_VERSION >= 3 module = PyModule_Create(&rpi_gpio_module); if (!module) return NULL; #else module = Py_InitModule3("RPi._GPIO", rpi_gpio_methods, module_doc_string); if (!module) return; #endif define_constants(module); if (!PyEval_ThreadsInitialized()) PyEval_InitThreads(); if (Py_AtExit(cleanup)) { setup_error = 1; cleanup(); #if PY_MAJOR_VERSION >= 3 return NULL; #else return; #endif } /* TODO: Event support if (Py_AtExit(event_cleanup_all)) { setup_error = 1; cleanup() #if PY_MAJOR_VERSION >= 3 return NULL; #else return; #endif } */ #if PY_MAJOR_VERSION >= 3 return module; #else return; #endif }