diff options
Diffstat (limited to 'python')
56 files changed, 6126 insertions, 0 deletions
diff --git a/python/config b/python/config new file mode 100644 index 0000000..77b99ec --- /dev/null +++ b/python/config | |||
| @@ -0,0 +1,136 @@ | |||
| 1 | [GPIO] | ||
| 2 | # Initialize following GPIOs with given function and optional value | ||
| 3 | # This is used during WebIOPi start process | ||
| 4 | #21 = IN | ||
| 5 | #23 = OUT 0 | ||
| 6 | #24 = OUT 0 | ||
| 7 | #25 = OUT 1 | ||
| 8 | |||
| 9 | #------------------------------------------------------------------------# | ||
| 10 | |||
| 11 | [~GPIO] | ||
| 12 | # Reset following GPIOs with given function and optional value | ||
| 13 | # This is used at the end of WebIOPi stop process | ||
| 14 | #21 = IN | ||
| 15 | #23 = IN | ||
| 16 | #24 = IN | ||
| 17 | #25 = OUT 0 | ||
| 18 | |||
| 19 | #------------------------------------------------------------------------# | ||
| 20 | |||
| 21 | [SCRIPTS] | ||
| 22 | # Load custom scripts syntax : | ||
| 23 | # name = sourcefile | ||
| 24 | # each sourcefile may have setup, loop and destroy functions and macros | ||
| 25 | #myscript = /home/pi/webiopi/examples/scripts/macros/script.py | ||
| 26 | |||
| 27 | #------------------------------------------------------------------------# | ||
| 28 | |||
| 29 | [HTTP] | ||
| 30 | # HTTP Server configuration | ||
| 31 | enabled = true | ||
| 32 | port = 8000 | ||
| 33 | |||
| 34 | # File containing sha256(base64("user:password")) | ||
| 35 | # Use webiopi-passwd command to generate it | ||
| 36 | passwd-file = /etc/webiopi/passwd | ||
| 37 | |||
| 38 | # Use doc-root to change default HTML and resource files location | ||
| 39 | #doc-root = /home/pi/webiopi/examples/scripts/macros | ||
| 40 | |||
| 41 | # Use welcome-file to change the default "Welcome" file | ||
| 42 | #welcome-file = index.html | ||
| 43 | |||
| 44 | #------------------------------------------------------------------------# | ||
| 45 | |||
| 46 | [COAP] | ||
| 47 | # CoAP Server configuration | ||
| 48 | enabled = true | ||
| 49 | port = 5683 | ||
| 50 | # Enable CoAP multicast | ||
| 51 | multicast = true | ||
| 52 | |||
| 53 | #------------------------------------------------------------------------# | ||
| 54 | |||
| 55 | [DEVICES] | ||
| 56 | # Device configuration syntax: | ||
| 57 | # name = device [args...] | ||
| 58 | # name : used in the URL mapping | ||
| 59 | # device : device name | ||
| 60 | # args : (optional) see device driver doc | ||
| 61 | # If enabled, devices configured here are mapped on REST API /device/name | ||
| 62 | # Devices are also accessible in custom scripts using deviceInstance(name) | ||
| 63 | # See device driver doc for methods and URI scheme available | ||
| 64 | |||
| 65 | # Raspberry native UART on GPIO, uncomment to enable | ||
| 66 | # Don't forget to remove console on ttyAMA0 in /boot/cmdline.txt | ||
| 67 | # And also disable getty on ttyAMA0 in /etc/inittab | ||
| 68 | #serial0 = Serial device:ttyAMA0 baudrate:9600 | ||
| 69 | |||
| 70 | # USB serial adapters | ||
| 71 | #usb0 = Serial device:ttyUSB0 baudrate:9600 | ||
| 72 | #usb1 = Serial device:ttyACM0 baudrate:9600 | ||
| 73 | |||
| 74 | #temp0 = TMP102 | ||
| 75 | #temp1 = TMP102 slave:0x49 | ||
| 76 | #temp2 = DS18B20 | ||
| 77 | #temp3 = DS18B20 slave:28-0000049bc218 | ||
| 78 | |||
| 79 | #bmp = BMP085 | ||
| 80 | |||
| 81 | #gpio0 = PCF8574 | ||
| 82 | #gpio1 = PCF8574 slave:0x21 | ||
| 83 | |||
| 84 | #light0 = TSL2561T | ||
| 85 | #light1 = TSL2561T slave:0b0101001 | ||
| 86 | |||
| 87 | #gpio0 = MCP23017 | ||
| 88 | #gpio1 = MCP23017 slave:0x21 | ||
| 89 | #gpio2 = MCP23017 slave:0x22 | ||
| 90 | |||
| 91 | #pwm0 = PCA9685 | ||
| 92 | #pwm1 = PCA9685 slave:0x41 | ||
| 93 | |||
| 94 | #adc = MCP3008 | ||
| 95 | #dac = MCP4922 chip:1 | ||
| 96 | |||
| 97 | #------------------------------------------------------------------------# | ||
| 98 | |||
| 99 | [REST] | ||
| 100 | # By default, REST API allows to GET/POST on all GPIOs | ||
| 101 | # Use gpio-export to limit GPIO available through REST API | ||
| 102 | #gpio-export = 21, 23, 24, 25 | ||
| 103 | |||
| 104 | # Uncomment to forbid changing GPIO values | ||
| 105 | #gpio-post-value = false | ||
| 106 | |||
| 107 | # Uncomment to forbid changing GPIO functions | ||
| 108 | #gpio-post-function = false | ||
| 109 | |||
| 110 | # Uncomment to disable automatic device mapping | ||
| 111 | #device-mapping = false | ||
| 112 | |||
| 113 | #------------------------------------------------------------------------# | ||
| 114 | |||
| 115 | [ROUTES] | ||
| 116 | # Custom REST API route syntax : | ||
| 117 | # source = destination | ||
| 118 | # source : URL to route | ||
| 119 | # destination : Resulting URL | ||
| 120 | # Adding routes allows to simplify access with Human comprehensive URLs | ||
| 121 | |||
| 122 | # In the next example with have the bedroom light connected to GPIO 25 | ||
| 123 | # and a temperature sensor named temp2, defined in [DEVICES] section | ||
| 124 | # - GET /bedroom/light => GET /GPIO/25/value, returns the light state | ||
| 125 | # - POST /bedroom/light/0 => POST /GPIO/25/value/0, turn off the light | ||
| 126 | # - POST /bedroom/light/1 => POST /GPIO/25/value/1, turn on the light | ||
| 127 | # - GET /bedroom/temperature => GET /devices/temp2/temperature/c, returns the temperature in celsius | ||
| 128 | #/bedroom/light = /GPIO/25/value | ||
| 129 | #/bedroom/temperature = /devices/temp2/temperature/c | ||
| 130 | |||
| 131 | #/livingroom/light = /devices/expander0/0 | ||
| 132 | #/livingroom/brightness = /devices/adc/0/float | ||
| 133 | #/livingroom/temperature = /devices/temp0/temperature/c | ||
| 134 | |||
| 135 | #/weather/temperature = /devices/bmp/temperature/c | ||
| 136 | #/weather/pressure = /devices/bmp/pressure/hpa | ||
diff --git a/python/native/bridge.c b/python/native/bridge.c new file mode 100644 index 0000000..8b2b8da --- /dev/null +++ b/python/native/bridge.c | |||
| @@ -0,0 +1,720 @@ | |||
| 1 | /* | ||
| 2 | Copyright (c) 2012 Ben Croston / 2012-2013 Eric PTAK | ||
| 3 | |||
| 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of | ||
| 5 | this software and associated documentation files (the "Software"), to deal in | ||
| 6 | the Software without restriction, including without limitation the rights to | ||
| 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies | ||
| 8 | of the Software, and to permit persons to whom the Software is furnished to do | ||
| 9 | so, subject to the following conditions: | ||
| 10 | |||
| 11 | The above copyright notice and this permission notice shall be included in all | ||
| 12 | copies or substantial portions of the Software. | ||
| 13 | |||
| 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
| 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
| 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
| 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
| 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
| 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
| 20 | SOFTWARE. | ||
| 21 | */ | ||
| 22 | |||
| 23 | #include "Python.h" | ||
| 24 | #include "gpio.h" | ||
| 25 | #include "cpuinfo.h" | ||
| 26 | |||
| 27 | static PyObject *_SetupException; | ||
| 28 | static PyObject *_InvalidDirectionException; | ||
| 29 | static PyObject *_InvalidChannelException; | ||
| 30 | static PyObject *_InvalidPullException; | ||
| 31 | |||
| 32 | static PyObject *_gpioCount; | ||
| 33 | |||
| 34 | |||
| 35 | static PyObject *_low; | ||
| 36 | static PyObject *_high; | ||
| 37 | |||
| 38 | static PyObject *_in; | ||
| 39 | static PyObject *_out; | ||
| 40 | static PyObject *_alt0; | ||
| 41 | static PyObject *_alt1; | ||
| 42 | static PyObject *_alt2; | ||
| 43 | static PyObject *_alt3; | ||
| 44 | static PyObject *_alt4; | ||
| 45 | static PyObject *_alt5; | ||
| 46 | static PyObject *_pwm; | ||
| 47 | |||
| 48 | static PyObject *_pud_off; | ||
| 49 | static PyObject *_pud_up; | ||
| 50 | static PyObject *_pud_down; | ||
| 51 | |||
| 52 | static PyObject *_board_revision; | ||
| 53 | |||
| 54 | static char* FUNCTIONS[] = {"IN", "OUT", "ALT5", "ALT4", "ALT0", "ALT1", "ALT2", "ALT3", "PWM"}; | ||
| 55 | static char* PWM_MODES[] = {"none", "ratio", "angle"}; | ||
| 56 | |||
| 57 | static int module_state = -1; | ||
| 58 | |||
| 59 | // setup function run on import of the RPi.GPIO module | ||
| 60 | static int module_setup(void) | ||
| 61 | { | ||
| 62 | if (module_state == SETUP_OK) { | ||
| 63 | return SETUP_OK; | ||
| 64 | } | ||
| 65 | |||
| 66 | module_state = setup(); | ||
| 67 | if (module_state == SETUP_DEVMEM_FAIL) | ||
| 68 | { | ||
| 69 | PyErr_SetString(_SetupException, "No access to /dev/mem. Try running as root!"); | ||
| 70 | } else if (module_state == SETUP_MALLOC_FAIL) { | ||
| 71 | PyErr_NoMemory(); | ||
| 72 | } else if (module_state == SETUP_MMAP_FAIL) { | ||
| 73 | PyErr_SetString(_SetupException, "Mmap failed on module import"); | ||
| 74 | } | ||
| 75 | |||
| 76 | return module_state; | ||
| 77 | } | ||
| 78 | |||
| 79 | // python function getFunction(channel) | ||
| 80 | static PyObject *py_get_function(PyObject *self, PyObject *args) | ||
| 81 | { | ||
| 82 | if (module_setup() != SETUP_OK) { | ||
| 83 | return NULL; | ||
| 84 | } | ||
| 85 | |||
| 86 | int channel, f; | ||
| 87 | |||
| 88 | if (!PyArg_ParseTuple(args, "i", &channel)) | ||
| 89 | return NULL; | ||
| 90 | |||
| 91 | if (channel < 0 || channel >= GPIO_COUNT) | ||
| 92 | { | ||
| 93 | PyErr_SetString(_InvalidChannelException, "The GPIO channel is invalid"); | ||
| 94 | return NULL; | ||
| 95 | } | ||
| 96 | |||
| 97 | f = get_function(channel); | ||
| 98 | return Py_BuildValue("i", f); | ||
| 99 | } | ||
| 100 | |||
| 101 | // python function getFunctionString(channel) | ||
| 102 | static PyObject *py_get_function_string(PyObject *self, PyObject *args) | ||
| 103 | { | ||
| 104 | if (module_setup() != SETUP_OK) { | ||
| 105 | return NULL; | ||
| 106 | } | ||
| 107 | |||
| 108 | int channel, f; | ||
| 109 | char *str; | ||
| 110 | |||
| 111 | if (!PyArg_ParseTuple(args, "i", &channel)) | ||
| 112 | return NULL; | ||
| 113 | |||
| 114 | if (channel < 0 || channel >= GPIO_COUNT) | ||
| 115 | { | ||
| 116 | PyErr_SetString(_InvalidChannelException, "The GPIO channel is invalid"); | ||
| 117 | return NULL; | ||
| 118 | } | ||
| 119 | |||
| 120 | f = get_function(channel); | ||
| 121 | str = FUNCTIONS[f]; | ||
| 122 | return Py_BuildValue("s", str); | ||
| 123 | } | ||
| 124 | |||
| 125 | // python function setFunction(channel, direction, pull_up_down=PUD_OFF) | ||
| 126 | static PyObject *py_set_function(PyObject *self, PyObject *args, PyObject *kwargs) | ||
| 127 | { | ||
| 128 | if (module_setup() != SETUP_OK) { | ||
| 129 | return NULL; | ||
| 130 | } | ||
| 131 | |||
| 132 | int channel, function; | ||
| 133 | int pud = PUD_OFF; | ||
| 134 | static char *kwlist[] = {"channel", "function", "pull_up_down", NULL}; | ||
| 135 | |||
| 136 | if (!PyArg_ParseTupleAndKeywords(args, kwargs, "ii|i", kwlist, &channel, &function, &pud)) | ||
| 137 | return NULL; | ||
| 138 | |||
| 139 | if (function != IN && function != OUT && function != PWM) | ||
| 140 | { | ||
| 141 | PyErr_SetString(_InvalidDirectionException, "Invalid function"); | ||
| 142 | return NULL; | ||
| 143 | } | ||
| 144 | |||
| 145 | if (function == OUT || function == PWM) | ||
| 146 | pud = PUD_OFF; | ||
| 147 | |||
| 148 | if (pud != PUD_OFF && pud != PUD_DOWN && pud != PUD_UP) | ||
| 149 | { | ||
| 150 | PyErr_SetString(_InvalidPullException, "Invalid value for pull_up_down - should be either PUD_OFF, PUD_UP or PUD_DOWN"); | ||
| 151 | return NULL; | ||
| 152 | } | ||
| 153 | |||
| 154 | if (channel < 0 || channel >= GPIO_COUNT) | ||
| 155 | { | ||
| 156 | PyErr_SetString(_InvalidChannelException, "The GPIO channel is invalid"); | ||
| 157 | return NULL; | ||
| 158 | } | ||
| 159 | |||
| 160 | set_function(channel, function, pud); | ||
| 161 | |||
| 162 | Py_INCREF(Py_None); | ||
| 163 | return Py_None; | ||
| 164 | } | ||
| 165 | |||
| 166 | // python function value = input(channel) | ||
| 167 | static PyObject *py_input(PyObject *self, PyObject *args) | ||
| 168 | { | ||
| 169 | if (module_setup() != SETUP_OK) { | ||
| 170 | return NULL; | ||
| 171 | } | ||
| 172 | |||
| 173 | int channel; | ||
| 174 | |||
| 175 | if (!PyArg_ParseTuple(args, "i", &channel)) | ||
| 176 | return NULL; | ||
| 177 | |||
| 178 | if (channel < 0 || channel >= GPIO_COUNT) | ||
| 179 | { | ||
| 180 | PyErr_SetString(_InvalidChannelException, "The GPIO channel is invalid"); | ||
| 181 | return NULL; | ||
| 182 | } | ||
| 183 | |||
| 184 | if (input(channel)) | ||
| 185 | Py_RETURN_TRUE; | ||
| 186 | else | ||
| 187 | Py_RETURN_FALSE; | ||
| 188 | } | ||
| 189 | |||
| 190 | // python function output(channel, value) | ||
| 191 | static PyObject *py_output(PyObject *self, PyObject *args, PyObject *kwargs) | ||
| 192 | { | ||
| 193 | if (module_setup() != SETUP_OK) { | ||
| 194 | return NULL; | ||
| 195 | } | ||
| 196 | |||
| 197 | int channel, value; | ||
| 198 | static char *kwlist[] = {"channel", "value", NULL}; | ||
| 199 | |||
| 200 | if (!PyArg_ParseTupleAndKeywords(args, kwargs, "ii", kwlist, &channel, &value)) | ||
| 201 | return NULL; | ||
| 202 | |||
| 203 | if (channel < 0 || channel >= GPIO_COUNT) | ||
| 204 | { | ||
| 205 | PyErr_SetString(_InvalidChannelException, "The GPIO channel is invalid"); | ||
| 206 | return NULL; | ||
| 207 | } | ||
| 208 | |||
| 209 | if (get_function(channel) != OUT) | ||
| 210 | { | ||
| 211 | PyErr_SetString(_InvalidDirectionException, "The GPIO channel is not an OUTPUT"); | ||
| 212 | return NULL; | ||
| 213 | } | ||
| 214 | |||
| 215 | output(channel, value); | ||
| 216 | |||
| 217 | Py_INCREF(Py_None); | ||
| 218 | return Py_None; | ||
| 219 | } | ||
| 220 | |||
| 221 | // python function outputSequence(channel, period, sequence) | ||
| 222 | static PyObject *py_output_sequence(PyObject *self, PyObject *args, PyObject *kwargs) | ||
| 223 | { | ||
| 224 | if (module_setup() != SETUP_OK) { | ||
| 225 | return NULL; | ||
| 226 | } | ||
| 227 | |||
| 228 | int channel, period; | ||
| 229 | char* sequence; | ||
| 230 | static char *kwlist[] = {"channel", "period", "sequence", NULL}; | ||
| 231 | |||
| 232 | if (!PyArg_ParseTupleAndKeywords(args, kwargs, "iis", kwlist, &channel, &period, &sequence)) | ||
| 233 | return NULL; | ||
| 234 | |||
| 235 | if (channel < 0 || channel >= GPIO_COUNT) | ||
| 236 | { | ||
| 237 | PyErr_SetString(_InvalidChannelException, "The GPIO channel is invalid"); | ||
| 238 | return NULL; | ||
| 239 | } | ||
| 240 | |||
| 241 | if (get_function(channel) != OUT) | ||
| 242 | { | ||
| 243 | PyErr_SetString(_InvalidDirectionException, "The GPIO channel is not an OUTPUT"); | ||
| 244 | return NULL; | ||
| 245 | } | ||
| 246 | |||
| 247 | outputSequence(channel, period, sequence); | ||
| 248 | |||
| 249 | Py_INCREF(Py_None); | ||
| 250 | return Py_None; | ||
| 251 | } | ||
| 252 | |||
| 253 | |||
| 254 | static PyObject *py_pulseMilli(PyObject *self, PyObject *args, PyObject *kwargs) | ||
| 255 | { | ||
| 256 | if (module_setup() != SETUP_OK) { | ||
| 257 | return NULL; | ||
| 258 | } | ||
| 259 | |||
| 260 | int channel, function, up, down; | ||
| 261 | static char *kwlist[] = {"channel", "up", "down", NULL}; | ||
| 262 | |||
| 263 | if (!PyArg_ParseTupleAndKeywords(args, kwargs, "iii", kwlist, &channel, &up, &down)) | ||
| 264 | return NULL; | ||
| 265 | |||
| 266 | if (channel < 0 || channel >= GPIO_COUNT) | ||
| 267 | { | ||
| 268 | PyErr_SetString(_InvalidChannelException, "The GPIO channel is invalid"); | ||
| 269 | return NULL; | ||
| 270 | } | ||
| 271 | |||
| 272 | function = get_function(channel); | ||
| 273 | if ((function != OUT) && (function != PWM)) | ||
| 274 | { | ||
| 275 | PyErr_SetString(_InvalidDirectionException, "The GPIO channel is not an OUTPUT or PWM"); | ||
| 276 | return NULL; | ||
| 277 | } | ||
| 278 | |||
| 279 | pulseMilli(channel, up, down); | ||
| 280 | |||
| 281 | Py_INCREF(Py_None); | ||
| 282 | return Py_None; | ||
| 283 | } | ||
| 284 | |||
| 285 | |||
| 286 | static PyObject *py_pulseMilliRatio(PyObject *self, PyObject *args, PyObject *kwargs) | ||
| 287 | { | ||
| 288 | if (module_setup() != SETUP_OK) { | ||
| 289 | return NULL; | ||
| 290 | } | ||
| 291 | |||
| 292 | int channel, function, width; | ||
| 293 | float ratio; | ||
| 294 | static char *kwlist[] = {"channel", "width", "ratio", NULL}; | ||
| 295 | |||
| 296 | if (!PyArg_ParseTupleAndKeywords(args, kwargs, "iif", kwlist, &channel, &width, &ratio)) | ||
| 297 | return NULL; | ||
| 298 | |||
| 299 | if (channel < 0 || channel >= GPIO_COUNT) | ||
| 300 | { | ||
| 301 | PyErr_SetString(_InvalidChannelException, "The GPIO channel is invalid"); | ||
| 302 | return NULL; | ||
| 303 | } | ||
| 304 | |||
| 305 | function = get_function(channel); | ||
| 306 | if ((function != OUT) && (function != PWM)) | ||
| 307 | { | ||
| 308 | PyErr_SetString(_InvalidDirectionException, "The GPIO channel is not an OUTPUT or PWM"); | ||
| 309 | return NULL; | ||
| 310 | } | ||
| 311 | |||
| 312 | pulseMilliRatio(channel, width, ratio); | ||
| 313 | |||
| 314 | Py_INCREF(Py_None); | ||
| 315 | return Py_None; | ||
| 316 | } | ||
| 317 | |||
| 318 | |||
| 319 | static PyObject *py_pulseMicro(PyObject *self, PyObject *args, PyObject *kwargs) | ||
| 320 | { | ||
| 321 | if (module_setup() != SETUP_OK) { | ||
| 322 | return NULL; | ||
| 323 | } | ||
| 324 | |||
| 325 | int channel, function, up, down; | ||
| 326 | static char *kwlist[] = {"channel", "up", "down", NULL}; | ||
| 327 | |||
| 328 | if (!PyArg_ParseTupleAndKeywords(args, kwargs, "iii", kwlist, &channel, &up, &down)) | ||
| 329 | return NULL; | ||
| 330 | |||
| 331 | if (channel < 0 || channel >= GPIO_COUNT) | ||
| 332 | { | ||
| 333 | PyErr_SetString(_InvalidChannelException, "The GPIO channel is invalid"); | ||
| 334 | return NULL; | ||
| 335 | } | ||
| 336 | |||
| 337 | function = get_function(channel); | ||
| 338 | if ((function != OUT) && (function != PWM)) | ||
| 339 | { | ||
| 340 | PyErr_SetString(_InvalidDirectionException, "The GPIO channel is not an OUTPUT or PWM"); | ||
| 341 | return NULL; | ||
| 342 | } | ||
| 343 | |||
| 344 | pulseMicro(channel, up, down); | ||
| 345 | |||
| 346 | Py_INCREF(Py_None); | ||
| 347 | return Py_None; | ||
| 348 | } | ||
| 349 | |||
| 350 | static PyObject *py_pulseMicroRatio(PyObject *self, PyObject *args, PyObject *kwargs) | ||
| 351 | { | ||
| 352 | if (module_setup() != SETUP_OK) { | ||
| 353 | return NULL; | ||
| 354 | } | ||
| 355 | |||
| 356 | int channel, function, width; | ||
| 357 | float ratio; | ||
| 358 | static char *kwlist[] = {"channel", "width", "ratio", NULL}; | ||
| 359 | |||
| 360 | if (!PyArg_ParseTupleAndKeywords(args, kwargs, "iif", kwlist, &channel, &width, &ratio)) | ||
| 361 | return NULL; | ||
| 362 | |||
| 363 | if (channel < 0 || channel >= GPIO_COUNT) | ||
| 364 | { | ||
| 365 | PyErr_SetString(_InvalidChannelException, "The GPIO channel is invalid"); | ||
| 366 | return NULL; | ||
| 367 | } | ||
| 368 | |||
| 369 | function = get_function(channel); | ||
| 370 | if ((function != OUT) && (function != PWM)) | ||
| 371 | { | ||
| 372 | PyErr_SetString(_InvalidDirectionException, "The GPIO channel is not an OUTPUT or PWM"); | ||
| 373 | return NULL; | ||
| 374 | } | ||
| 375 | |||
| 376 | pulseMicroRatio(channel, width, ratio); | ||
| 377 | |||
| 378 | Py_INCREF(Py_None); | ||
| 379 | return Py_None; | ||
| 380 | } | ||
| 381 | |||
| 382 | static PyObject *py_pulseAngle(PyObject *self, PyObject *args, PyObject *kwargs) | ||
| 383 | { | ||
| 384 | if (module_setup() != SETUP_OK) { | ||
| 385 | return NULL; | ||
| 386 | } | ||
| 387 | |||
| 388 | int channel, function; | ||
| 389 | float angle; | ||
| 390 | static char *kwlist[] = {"channel", "angle", NULL}; | ||
| 391 | |||
| 392 | if (!PyArg_ParseTupleAndKeywords(args, kwargs, "if", kwlist, &channel, &angle)) | ||
| 393 | return NULL; | ||
| 394 | |||
| 395 | if (channel < 0 || channel >= GPIO_COUNT) | ||
| 396 | { | ||
| 397 | PyErr_SetString(_InvalidChannelException, "The GPIO channel is invalid"); | ||
| 398 | return NULL; | ||
| 399 | } | ||
| 400 | |||
| 401 | function = get_function(channel); | ||
| 402 | if ((function != OUT) && (function != PWM)) | ||
| 403 | { | ||
| 404 | PyErr_SetString(_InvalidDirectionException, "The GPIO channel is not an OUTPUT or PWM"); | ||
| 405 | return NULL; | ||
| 406 | } | ||
| 407 | |||
| 408 | pulseAngle(channel, angle); | ||
| 409 | |||
| 410 | Py_INCREF(Py_None); | ||
| 411 | return Py_None; | ||
| 412 | } | ||
| 413 | |||
| 414 | static PyObject *py_pulseRatio(PyObject *self, PyObject *args, PyObject *kwargs) | ||
| 415 | { | ||
| 416 | if (module_setup() != SETUP_OK) { | ||
| 417 | return NULL; | ||
| 418 | } | ||
| 419 | |||
| 420 | int channel, function; | ||
| 421 | float ratio; | ||
| 422 | static char *kwlist[] = {"channel", "ratio", NULL}; | ||
| 423 | |||
| 424 | if (!PyArg_ParseTupleAndKeywords(args, kwargs, "if", kwlist, &channel, &ratio)) | ||
| 425 | return NULL; | ||
| 426 | |||
| 427 | if (channel < 0 || channel >= GPIO_COUNT) | ||
| 428 | { | ||
| 429 | PyErr_SetString(_InvalidChannelException, "The GPIO channel is invalid"); | ||
| 430 | return NULL; | ||
| 431 | } | ||
| 432 | |||
| 433 | function = get_function(channel); | ||
| 434 | if ((function != OUT) && (function != PWM)) | ||
| 435 | { | ||
| 436 | PyErr_SetString(_InvalidDirectionException, "The GPIO channel is not an OUTPUT or PWM"); | ||
| 437 | return NULL; | ||
| 438 | } | ||
| 439 | |||
| 440 | pulseRatio(channel, ratio); | ||
| 441 | |||
| 442 | Py_INCREF(Py_None); | ||
| 443 | return Py_None; | ||
| 444 | } | ||
| 445 | |||
| 446 | static PyObject *py_pulse(PyObject *self, PyObject *args) | ||
| 447 | { | ||
| 448 | if (module_setup() != SETUP_OK) { | ||
| 449 | return NULL; | ||
| 450 | } | ||
| 451 | |||
| 452 | int channel; | ||
| 453 | |||
| 454 | if (!PyArg_ParseTuple(args, "i", &channel)) | ||
| 455 | return NULL; | ||
| 456 | |||
| 457 | if (channel < 0 || channel >= GPIO_COUNT) | ||
| 458 | { | ||
| 459 | PyErr_SetString(_InvalidChannelException, "The GPIO channel is invalid"); | ||
| 460 | return NULL; | ||
| 461 | } | ||
| 462 | |||
| 463 | pulseRatio(channel, 0.5); | ||
| 464 | return Py_None; | ||
| 465 | } | ||
| 466 | |||
| 467 | static PyObject *py_getPulse(PyObject *self, PyObject *args) | ||
| 468 | { | ||
| 469 | if (module_setup() != SETUP_OK) { | ||
| 470 | return NULL; | ||
| 471 | } | ||
| 472 | |||
| 473 | int channel; | ||
| 474 | char str[256]; | ||
| 475 | struct pulse *p; | ||
| 476 | |||
| 477 | if (!PyArg_ParseTuple(args, "i", &channel)) | ||
| 478 | return NULL; | ||
| 479 | |||
| 480 | if (channel < 0 || channel >= GPIO_COUNT) | ||
| 481 | { | ||
| 482 | PyErr_SetString(_InvalidChannelException, "The GPIO channel is invalid"); | ||
| 483 | return NULL; | ||
| 484 | } | ||
| 485 | |||
| 486 | p = getPulse(channel); | ||
| 487 | |||
| 488 | sprintf(str, "%s:%.2f", PWM_MODES[p->type], p->value); | ||
| 489 | #if PY_MAJOR_VERSION > 2 | ||
| 490 | return PyUnicode_FromString(str); | ||
| 491 | #else | ||
| 492 | return PyString_FromString(str); | ||
| 493 | #endif | ||
| 494 | } | ||
| 495 | |||
| 496 | static PyObject *py_enablePWM(PyObject *self, PyObject *args) | ||
| 497 | { | ||
| 498 | if (module_setup() != SETUP_OK) { | ||
| 499 | return NULL; | ||
| 500 | } | ||
| 501 | |||
| 502 | int channel; | ||
| 503 | |||
| 504 | if (!PyArg_ParseTuple(args, "i", &channel)) | ||
| 505 | return NULL; | ||
| 506 | |||
| 507 | if (channel < 0 || channel >= GPIO_COUNT) | ||
| 508 | { | ||
| 509 | PyErr_SetString(_InvalidChannelException, "The GPIO channel is invalid"); | ||
| 510 | return NULL; | ||
| 511 | } | ||
| 512 | |||
| 513 | enablePWM(channel); | ||
| 514 | return Py_None; | ||
| 515 | } | ||
| 516 | |||
| 517 | static PyObject *py_disablePWM(PyObject *self, PyObject *args) | ||
| 518 | { | ||
| 519 | if (module_setup() != SETUP_OK) { | ||
| 520 | return NULL; | ||
| 521 | } | ||
| 522 | |||
| 523 | int channel; | ||
| 524 | |||
| 525 | if (!PyArg_ParseTuple(args, "i", &channel)) | ||
| 526 | return NULL; | ||
| 527 | |||
| 528 | if (channel < 0 || channel >= GPIO_COUNT) | ||
| 529 | { | ||
| 530 | PyErr_SetString(_InvalidChannelException, "The GPIO channel is invalid"); | ||
| 531 | return NULL; | ||
| 532 | } | ||
| 533 | |||
| 534 | disablePWM(channel); | ||
| 535 | return Py_None; | ||
| 536 | } | ||
| 537 | |||
| 538 | |||
| 539 | |||
| 540 | static PyObject *py_isPWMEnabled(PyObject *self, PyObject *args) | ||
| 541 | { | ||
| 542 | if (module_setup() != SETUP_OK) { | ||
| 543 | return NULL; | ||
| 544 | } | ||
| 545 | |||
| 546 | int channel; | ||
| 547 | |||
| 548 | if (!PyArg_ParseTuple(args, "i", &channel)) | ||
| 549 | return NULL; | ||
| 550 | |||
| 551 | if (channel < 0 || channel >= GPIO_COUNT) | ||
| 552 | { | ||
| 553 | PyErr_SetString(_InvalidChannelException, "The GPIO channel is invalid"); | ||
| 554 | return NULL; | ||
| 555 | } | ||
| 556 | |||
| 557 | if (isPWMEnabled(channel)) | ||
| 558 | Py_RETURN_TRUE; | ||
| 559 | else | ||
| 560 | Py_RETURN_FALSE; | ||
| 561 | } | ||
| 562 | |||
| 563 | PyMethodDef python_methods[] = { | ||
| 564 | {"getFunction", py_get_function, METH_VARARGS, "Return the current GPIO setup (IN, OUT, ALT0)"}, | ||
| 565 | {"getSetup", py_get_function, METH_VARARGS, "Return the current GPIO setup (IN, OUT, ALT0)"}, | ||
| 566 | |||
| 567 | {"getFunctionString", py_get_function_string, METH_VARARGS, "Return the current GPIO setup (IN, OUT, ALT0) as string"}, | ||
| 568 | {"getSetupString", py_get_function_string, METH_VARARGS, "Return the current GPIO setup (IN, OUT, ALT0) as string"}, | ||
| 569 | |||
| 570 | {"setFunction", (PyCFunction)py_set_function, METH_VARARGS | METH_KEYWORDS, "Setup the GPIO channel, direction and (optional) pull/up down control\nchannel - BCM GPIO number\ndirection - IN or OUT\n[pull_up_down] - PUD_OFF (default), PUD_UP or PUD_DOWN"}, | ||
| 571 | {"setup", (PyCFunction)py_set_function, METH_VARARGS | METH_KEYWORDS, "Setup the GPIO channel, direction and (optional) pull/up down control\nchannel - BCM GPIO number\ndirection - IN or OUT\n[pull_up_down] - PUD_OFF (default), PUD_UP or PUD_DOWN"}, | ||
| 572 | |||
| 573 | {"input", py_input, METH_VARARGS, "Input from a GPIO channel - Deprecated, use digitalRead instead"}, | ||
| 574 | {"digitalRead", py_input, METH_VARARGS, "Read a GPIO channel"}, | ||
| 575 | |||
| 576 | {"output", (PyCFunction)py_output, METH_VARARGS | METH_KEYWORDS, "Output to a GPIO channel - Deprecated, use digitalWrite instead"}, | ||
| 577 | {"digitalWrite", (PyCFunction)py_output, METH_VARARGS | METH_KEYWORDS, "Write to a GPIO channel"}, | ||
| 578 | |||
| 579 | {"outputSequence", (PyCFunction)py_output_sequence, METH_VARARGS | METH_KEYWORDS, "Output a sequence to a GPIO channel"}, | ||
| 580 | |||
| 581 | {"getPulse", py_getPulse, METH_VARARGS, "Read current PWM output"}, | ||
| 582 | {"pwmRead", py_getPulse, METH_VARARGS, "Read current PWM output"}, | ||
| 583 | |||
| 584 | {"pulseMilli", (PyCFunction)py_pulseMilli, METH_VARARGS | METH_KEYWORDS, "Output a PWM to a GPIO channel using milliseconds for both HIGH and LOW state widths"}, | ||
| 585 | {"pulseMilliRatio", (PyCFunction)py_pulseMilliRatio, METH_VARARGS | METH_KEYWORDS, "Output a PWM to a GPIO channel using millisecond for the total width and a ratio (duty cycle) for the HIGH state width"}, | ||
| 586 | {"pulseMicro", (PyCFunction)py_pulseMicro, METH_VARARGS | METH_KEYWORDS, "Output a PWM pulse to a GPIO channel using microseconds for both HIGH and LOW state widths"}, | ||
| 587 | {"pulseMicroRatio", (PyCFunction)py_pulseMicroRatio, METH_VARARGS | METH_KEYWORDS, "Output a PWM to a GPIO channel using microseconds for the total width and a ratio (duty cycle) for the HIGH state width"}, | ||
| 588 | |||
| 589 | {"pulseAngle", (PyCFunction)py_pulseAngle, METH_VARARGS | METH_KEYWORDS, "Output a PWM to a GPIO channel using an angle - Deprecated, use pwmWriteAngle instead"}, | ||
| 590 | {"pwmWriteAngle", (PyCFunction)py_pulseAngle, METH_VARARGS | METH_KEYWORDS, "Output a PWM to a GPIO channel using an angle"}, | ||
| 591 | |||
| 592 | {"pulseRatio", (PyCFunction)py_pulseRatio, METH_VARARGS | METH_KEYWORDS, "Output a PWM to a GPIO channel using a ratio (duty cycle) with the default 50Hz signal - Deprecated, use pwmWrite instead"}, | ||
| 593 | {"pwmWrite", (PyCFunction)py_pulseRatio, METH_VARARGS | METH_KEYWORDS, "Output a PWM to a GPIO channel using a ratio (duty cycle) with the default 50Hz signal"}, | ||
| 594 | |||
| 595 | {"pulse", py_pulse, METH_VARARGS, "Output a PWM to a GPIO channel using a 50% ratio (duty cycle) with the default 50Hz signal"}, | ||
| 596 | |||
| 597 | {"enablePWM", py_enablePWM, METH_VARARGS, "Enable software PWM loop for a GPIO channel"}, | ||
| 598 | {"disablePWM", py_disablePWM, METH_VARARGS, "Disable software PWM loop of a GPIO channel"}, | ||
| 599 | {"isPWMEnabled", py_isPWMEnabled, METH_VARARGS, "Returns software PWM state"}, | ||
| 600 | |||
| 601 | {NULL, NULL, 0, NULL} | ||
| 602 | }; | ||
| 603 | |||
| 604 | #if PY_MAJOR_VERSION > 2 | ||
| 605 | static struct PyModuleDef python_module = { | ||
| 606 | PyModuleDef_HEAD_INIT, | ||
| 607 | "_webiopi.GPIO", /* name of module */ | ||
| 608 | NULL, /* module documentation, may be NULL */ | ||
| 609 | -1, /* size of per-interpreter state of the module, | ||
| 610 | or -1 if the module keeps state in global variables. */ | ||
| 611 | python_methods | ||
| 612 | }; | ||
| 613 | #endif | ||
| 614 | |||
| 615 | #if PY_MAJOR_VERSION > 2 | ||
| 616 | PyMODINIT_FUNC PyInit_GPIO(void) | ||
| 617 | #else | ||
| 618 | PyMODINIT_FUNC initGPIO(void) | ||
| 619 | #endif | ||
| 620 | { | ||
| 621 | PyObject *module = NULL; | ||
| 622 | int revision = -1; | ||
| 623 | |||
| 624 | #if PY_MAJOR_VERSION > 2 | ||
| 625 | if ((module = PyModule_Create(&python_module)) == NULL) | ||
| 626 | goto exit; | ||
| 627 | #else | ||
| 628 | if ((module = Py_InitModule("_webiopi.GPIO", python_methods)) == NULL) | ||
| 629 | goto exit; | ||
| 630 | #endif | ||
| 631 | |||
| 632 | _SetupException = PyErr_NewException("_webiopi.GPIO.SetupException", NULL, NULL); | ||
| 633 | PyModule_AddObject(module, "SetupException", _SetupException); | ||
| 634 | |||
| 635 | _InvalidDirectionException = PyErr_NewException("_webiopi.GPIO.InvalidDirectionException", NULL, NULL); | ||
| 636 | PyModule_AddObject(module, "InvalidDirectionException", _InvalidDirectionException); | ||
| 637 | |||
| 638 | _InvalidChannelException = PyErr_NewException("_webiopi.GPIO.InvalidChannelException", NULL, NULL); | ||
| 639 | PyModule_AddObject(module, "InvalidChannelException", _InvalidChannelException); | ||
| 640 | |||
| 641 | _InvalidPullException = PyErr_NewException("_webiopi.GPIO.InvalidPullException", NULL, NULL); | ||
| 642 | PyModule_AddObject(module, "InvalidPullException", _InvalidPullException); | ||
| 643 | |||
| 644 | _gpioCount = Py_BuildValue("i", GPIO_COUNT); | ||
| 645 | PyModule_AddObject(module, "GPIO_COUNT", _gpioCount); | ||
| 646 | |||
| 647 | _low = Py_BuildValue("i", LOW); | ||
| 648 | PyModule_AddObject(module, "LOW", _low); | ||
| 649 | |||
| 650 | _high = Py_BuildValue("i", HIGH); | ||
| 651 | PyModule_AddObject(module, "HIGH", _high); | ||
| 652 | |||
| 653 | _in = Py_BuildValue("i", IN); | ||
| 654 | PyModule_AddObject(module, "IN", _in); | ||
| 655 | |||
| 656 | _out = Py_BuildValue("i", OUT); | ||
| 657 | PyModule_AddObject(module, "OUT", _out); | ||
| 658 | |||
| 659 | _alt0 = Py_BuildValue("i", ALT0); | ||
| 660 | PyModule_AddObject(module, "ALT0", _alt0); | ||
| 661 | |||
| 662 | _alt1 = Py_BuildValue("i", ALT1); | ||
| 663 | PyModule_AddObject(module, "ALT1", _alt1); | ||
| 664 | |||
| 665 | _alt2 = Py_BuildValue("i", ALT2); | ||
| 666 | PyModule_AddObject(module, "ALT2", _alt2); | ||
| 667 | |||
| 668 | _alt3 = Py_BuildValue("i", ALT3); | ||
| 669 | PyModule_AddObject(module, "ALT3", _alt3); | ||
| 670 | |||
| 671 | _alt4 = Py_BuildValue("i", ALT4); | ||
| 672 | PyModule_AddObject(module, "ALT4", _alt4); | ||
| 673 | |||
| 674 | _alt5 = Py_BuildValue("i", ALT5); | ||
| 675 | PyModule_AddObject(module, "ALT5", _alt5); | ||
| 676 | |||
| 677 | _pwm = Py_BuildValue("i", PWM); | ||
| 678 | PyModule_AddObject(module, "PWM", _pwm); | ||
| 679 | |||
| 680 | _pud_off = Py_BuildValue("i", PUD_OFF); | ||
| 681 | PyModule_AddObject(module, "PUD_OFF", _pud_off); | ||
| 682 | |||
| 683 | _pud_up = Py_BuildValue("i", PUD_UP); | ||
| 684 | PyModule_AddObject(module, "PUD_UP", _pud_up); | ||
| 685 | |||
| 686 | _pud_down = Py_BuildValue("i", PUD_DOWN); | ||
| 687 | PyModule_AddObject(module, "PUD_DOWN", _pud_down); | ||
| 688 | |||
| 689 | // detect board revision and set up accordingly | ||
| 690 | revision = get_rpi_revision(); | ||
| 691 | if (revision == -1) | ||
| 692 | { | ||
| 693 | PyErr_SetString(_SetupException, "This module can only be run on a Raspberry Pi!"); | ||
| 694 | #if PY_MAJOR_VERSION > 2 | ||
| 695 | return NULL; | ||
| 696 | #else | ||
| 697 | return; | ||
| 698 | #endif | ||
| 699 | } | ||
| 700 | |||
| 701 | _board_revision = Py_BuildValue("i", revision); | ||
| 702 | PyModule_AddObject(module, "BOARD_REVISION", _board_revision); | ||
| 703 | |||
| 704 | if (Py_AtExit(cleanup) != 0) | ||
| 705 | { | ||
| 706 | cleanup(); | ||
| 707 | #if PY_MAJOR_VERSION > 2 | ||
| 708 | return NULL; | ||
| 709 | #else | ||
| 710 | return; | ||
| 711 | #endif | ||
| 712 | } | ||
| 713 | |||
| 714 | exit: | ||
| 715 | #if PY_MAJOR_VERSION > 2 | ||
| 716 | return module; | ||
| 717 | #else | ||
| 718 | return; | ||
| 719 | #endif | ||
| 720 | } | ||
diff --git a/python/native/cpuinfo.c b/python/native/cpuinfo.c new file mode 100644 index 0000000..a69d97e --- /dev/null +++ b/python/native/cpuinfo.c | |||
| @@ -0,0 +1,65 @@ | |||
| 1 | /* | ||
| 2 | Copyright (c) 2012 Ben Croston | ||
| 3 | |||
| 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of | ||
| 5 | this software and associated documentation files (the "Software"), to deal in | ||
| 6 | the Software without restriction, including without limitation the rights to | ||
| 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies | ||
| 8 | of the Software, and to permit persons to whom the Software is furnished to do | ||
| 9 | so, subject to the following conditions: | ||
| 10 | |||
| 11 | The above copyright notice and this permission notice shall be included in all | ||
| 12 | copies or substantial portions of the Software. | ||
| 13 | |||
| 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
| 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
| 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
| 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
| 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
| 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
| 20 | SOFTWARE. | ||
| 21 | */ | ||
| 22 | |||
| 23 | #include <stdio.h> | ||
| 24 | #include <string.h> | ||
| 25 | #include "cpuinfo.h" | ||
| 26 | |||
| 27 | char *get_cpuinfo_revision(char *revision) | ||
| 28 | { | ||
| 29 | FILE *fp; | ||
| 30 | char buffer[1024]; | ||
| 31 | char hardware[1024]; | ||
| 32 | int rpi_found = 0; | ||
| 33 | |||
| 34 | if ((fp = fopen("/proc/cpuinfo", "r")) == NULL) | ||
| 35 | return 0; | ||
| 36 | |||
| 37 | while(!feof(fp)) { | ||
| 38 | fgets(buffer, sizeof(buffer) , fp); | ||
| 39 | sscanf(buffer, "Hardware : %s", hardware); | ||
| 40 | if (strcmp(hardware, "BCM2708") == 0) | ||
| 41 | rpi_found = 1; | ||
| 42 | sscanf(buffer, "Revision : %s", revision); | ||
| 43 | } | ||
| 44 | fclose(fp); | ||
| 45 | |||
| 46 | if (!rpi_found) | ||
| 47 | revision = NULL; | ||
| 48 | return revision; | ||
| 49 | } | ||
| 50 | |||
| 51 | int get_rpi_revision(void) | ||
| 52 | { | ||
| 53 | char revision[1024] = {'\0'}; | ||
| 54 | |||
| 55 | if (get_cpuinfo_revision(revision) == NULL) | ||
| 56 | return -1; | ||
| 57 | |||
| 58 | if ((strcmp(revision, "0002") == 0) || | ||
| 59 | (strcmp(revision, "1000002") == 0 ) || | ||
| 60 | (strcmp(revision, "0003") == 0) || | ||
| 61 | (strcmp(revision, "1000003") == 0 )) | ||
| 62 | return 1; | ||
| 63 | else // assume rev 2 (0004 0005 0006 1000004 1000005 1000006) | ||
| 64 | return 2; | ||
| 65 | } | ||
diff --git a/python/native/cpuinfo.h b/python/native/cpuinfo.h new file mode 100644 index 0000000..e84ea7d --- /dev/null +++ b/python/native/cpuinfo.h | |||
| @@ -0,0 +1,23 @@ | |||
| 1 | /* | ||
| 2 | Copyright (c) 2012 Ben Croston | ||
| 3 | |||
| 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of | ||
| 5 | this software and associated documentation files (the "Software"), to deal in | ||
| 6 | the Software without restriction, including without limitation the rights to | ||
| 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies | ||
| 8 | of the Software, and to permit persons to whom the Software is furnished to do | ||
| 9 | so, subject to the following conditions: | ||
| 10 | |||
| 11 | The above copyright notice and this permission notice shall be included in all | ||
| 12 | copies or substantial portions of the Software. | ||
| 13 | |||
| 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
| 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
| 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
| 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
| 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
| 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
| 20 | SOFTWARE. | ||
| 21 | */ | ||
| 22 | |||
| 23 | int get_rpi_revision(void); | ||
diff --git a/python/native/gpio.c b/python/native/gpio.c new file mode 100644 index 0000000..3950b16 --- /dev/null +++ b/python/native/gpio.c | |||
| @@ -0,0 +1,329 @@ | |||
| 1 | /* | ||
| 2 | Copyright (c) 2012 Ben Croston / 2012-2013 Eric PTAK | ||
| 3 | |||
| 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of | ||
| 5 | this software and associated documentation files (the "Software"), to deal in | ||
| 6 | the Software without restriction, including without limitation the rights to | ||
| 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies | ||
| 8 | of the Software, and to permit persons to whom the Software is furnished to do | ||
| 9 | so, subject to the following conditions: | ||
| 10 | |||
| 11 | The above copyright notice and this permission notice shall be included in all | ||
| 12 | copies or substantial portions of the Software. | ||
| 13 | |||
| 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
| 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
| 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
| 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
| 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
| 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
| 20 | SOFTWARE. | ||
| 21 | */ | ||
| 22 | |||
| 23 | #include <stdint.h> | ||
| 24 | #include <stdlib.h> | ||
| 25 | #include <string.h> | ||
| 26 | #include <fcntl.h> | ||
| 27 | #include <sys/mman.h> | ||
| 28 | #include <time.h> | ||
| 29 | #include <pthread.h> | ||
| 30 | #include "gpio.h" | ||
| 31 | |||
| 32 | #define BCM2708_PERI_BASE 0x20000000 | ||
| 33 | #define GPIO_BASE (BCM2708_PERI_BASE + 0x200000) | ||
| 34 | #define FSEL_OFFSET 0 // 0x0000 | ||
| 35 | #define SET_OFFSET 7 // 0x001c / 4 | ||
| 36 | #define CLR_OFFSET 10 // 0x0028 / 4 | ||
| 37 | #define PINLEVEL_OFFSET 13 // 0x0034 / 4 | ||
| 38 | #define EVENT_DETECT_OFFSET 16 // 0x0040 / 4 | ||
| 39 | #define RISING_ED_OFFSET 19 // 0x004c / 4 | ||
| 40 | #define FALLING_ED_OFFSET 22 // 0x0058 / 4 | ||
| 41 | #define HIGH_DETECT_OFFSET 25 // 0x0064 / 4 | ||
| 42 | #define LOW_DETECT_OFFSET 28 // 0x0070 / 4 | ||
| 43 | #define PULLUPDN_OFFSET 37 // 0x0094 / 4 | ||
| 44 | #define PULLUPDNCLK_OFFSET 38 // 0x0098 / 4 | ||
| 45 | |||
| 46 | #define PAGE_SIZE (4*1024) | ||
| 47 | #define BLOCK_SIZE (4*1024) | ||
| 48 | |||
| 49 | static volatile uint32_t *gpio_map; | ||
| 50 | |||
| 51 | struct tspair { | ||
| 52 | struct timespec up; | ||
| 53 | struct timespec down; | ||
| 54 | }; | ||
| 55 | |||
| 56 | static struct pulse gpio_pulses[GPIO_COUNT]; | ||
| 57 | static struct tspair gpio_tspairs[GPIO_COUNT]; | ||
| 58 | static pthread_t *gpio_threads[GPIO_COUNT]; | ||
| 59 | |||
| 60 | void short_wait(void) | ||
| 61 | { | ||
| 62 | int i; | ||
| 63 | |||
| 64 | for (i=0; i<150; i++) // wait 150 cycles | ||
| 65 | { | ||
| 66 | asm volatile("nop"); | ||
| 67 | } | ||
| 68 | } | ||
| 69 | |||
| 70 | int setup(void) | ||
| 71 | { | ||
| 72 | int mem_fd; | ||
| 73 | uint8_t *gpio_mem; | ||
| 74 | |||
| 75 | if ((mem_fd = open("/dev/mem", O_RDWR|O_SYNC) ) < 0) | ||
| 76 | { | ||
| 77 | return SETUP_DEVMEM_FAIL; | ||
| 78 | } | ||
| 79 | |||
| 80 | if ((gpio_mem = malloc(BLOCK_SIZE + (PAGE_SIZE-1))) == NULL) | ||
| 81 | return SETUP_MALLOC_FAIL; | ||
| 82 | |||
| 83 | if ((uint32_t)gpio_mem % PAGE_SIZE) | ||
| 84 | gpio_mem += PAGE_SIZE - ((uint32_t)gpio_mem % PAGE_SIZE); | ||
| 85 | |||
| 86 | gpio_map = (uint32_t *)mmap( (caddr_t)gpio_mem, BLOCK_SIZE, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_FIXED, mem_fd, GPIO_BASE); | ||
| 87 | |||
| 88 | if ((uint32_t)gpio_map < 0) | ||
| 89 | return SETUP_MMAP_FAIL; | ||
| 90 | |||
| 91 | return SETUP_OK; | ||
| 92 | } | ||
| 93 | |||
| 94 | void set_pullupdn(int gpio, int pud) | ||
| 95 | { | ||
| 96 | int clk_offset = PULLUPDNCLK_OFFSET + (gpio/32); | ||
| 97 | int shift = (gpio%32); | ||
| 98 | |||
| 99 | if (pud == PUD_DOWN) | ||
| 100 | *(gpio_map+PULLUPDN_OFFSET) = (*(gpio_map+PULLUPDN_OFFSET) & ~3) | PUD_DOWN; | ||
| 101 | else if (pud == PUD_UP) | ||
| 102 | *(gpio_map+PULLUPDN_OFFSET) = (*(gpio_map+PULLUPDN_OFFSET) & ~3) | PUD_UP; | ||
| 103 | else // pud == PUD_OFF | ||
| 104 | *(gpio_map+PULLUPDN_OFFSET) &= ~3; | ||
| 105 | |||
| 106 | short_wait(); | ||
| 107 | *(gpio_map+clk_offset) = 1 << shift; | ||
| 108 | short_wait(); | ||
| 109 | *(gpio_map+PULLUPDN_OFFSET) &= ~3; | ||
| 110 | *(gpio_map+clk_offset) = 0; | ||
| 111 | } | ||
| 112 | |||
| 113 | //updated Eric PTAK - trouch.com | ||
| 114 | void set_function(int gpio, int function, int pud) | ||
| 115 | { | ||
| 116 | if (function == PWM) { | ||
| 117 | function = OUT; | ||
| 118 | enablePWM(gpio); | ||
| 119 | } | ||
| 120 | else { | ||
| 121 | disablePWM(gpio); | ||
| 122 | } | ||
| 123 | |||
| 124 | int offset = FSEL_OFFSET + (gpio/10); | ||
| 125 | int shift = (gpio%10)*3; | ||
| 126 | |||
| 127 | set_pullupdn(gpio, pud); | ||
| 128 | *(gpio_map+offset) = (*(gpio_map+offset) & ~(7<<shift)) | (function<<shift); | ||
| 129 | } | ||
| 130 | |||
| 131 | //added Eric PTAK - trouch.com | ||
| 132 | int get_function(int gpio) | ||
| 133 | { | ||
| 134 | int offset = FSEL_OFFSET + (gpio/10); | ||
| 135 | int shift = (gpio%10)*3; | ||
| 136 | int value = *(gpio_map+offset); | ||
| 137 | value >>= shift; | ||
| 138 | value &= 7; | ||
| 139 | if ((value == OUT) && isPWMEnabled(gpio)) { | ||
| 140 | value = PWM; | ||
| 141 | } | ||
| 142 | return value; // 0=input, 1=output, 4=alt0 | ||
| 143 | } | ||
| 144 | |||
| 145 | //updated Eric PTAK - trouch.com | ||
| 146 | int input(int gpio) | ||
| 147 | { | ||
| 148 | int offset, value, mask; | ||
| 149 | |||
| 150 | offset = PINLEVEL_OFFSET + (gpio/32); | ||
| 151 | mask = (1 << gpio%32); | ||
| 152 | value = *(gpio_map+offset) & mask; | ||
| 153 | return value; | ||
| 154 | } | ||
| 155 | |||
| 156 | void output(int gpio, int value) | ||
| 157 | { | ||
| 158 | int offset, shift; | ||
| 159 | |||
| 160 | if (value) // value == HIGH | ||
| 161 | offset = SET_OFFSET + (gpio/32); | ||
| 162 | else // value == LOW | ||
| 163 | offset = CLR_OFFSET + (gpio/32); | ||
| 164 | |||
| 165 | shift = (gpio%32); | ||
| 166 | |||
| 167 | *(gpio_map+offset) = 1 << shift; | ||
| 168 | } | ||
| 169 | |||
| 170 | //added Eric PTAK - trouch.com | ||
| 171 | void outputSequence(int gpio, int period, char* sequence) { | ||
| 172 | int i, value; | ||
| 173 | struct timespec ts; | ||
| 174 | ts.tv_sec = period/1000; | ||
| 175 | ts.tv_nsec = (period%1000) * 1000000; | ||
| 176 | |||
| 177 | for (i=0; sequence[i] != '\0'; i++) { | ||
| 178 | if (sequence[i] == '1') { | ||
| 179 | value = 1; | ||
| 180 | } | ||
| 181 | else { | ||
| 182 | value = 0; | ||
| 183 | } | ||
| 184 | output(gpio, value); | ||
| 185 | nanosleep(&ts, NULL); | ||
| 186 | } | ||
| 187 | } | ||
| 188 | |||
| 189 | void resetPWM(int gpio) { | ||
| 190 | gpio_pulses[gpio].type = 0; | ||
| 191 | gpio_pulses[gpio].value = 0; | ||
| 192 | |||
| 193 | gpio_tspairs[gpio].up.tv_sec = 0; | ||
| 194 | gpio_tspairs[gpio].up.tv_nsec = 0; | ||
| 195 | gpio_tspairs[gpio].down.tv_sec = 0; | ||
| 196 | gpio_tspairs[gpio].down.tv_nsec = 0; | ||
| 197 | } | ||
| 198 | |||
| 199 | //added Eric PTAK - trouch.com | ||
| 200 | void pulseTS(int gpio, struct timespec *up, struct timespec *down) { | ||
| 201 | if ((up->tv_sec > 0) || (up->tv_nsec > 0)) { | ||
| 202 | output(gpio, 1); | ||
| 203 | nanosleep(up, NULL); | ||
| 204 | } | ||
| 205 | |||
| 206 | if ((down->tv_sec > 0) || (down->tv_nsec > 0)) { | ||
| 207 | output(gpio, 0); | ||
| 208 | nanosleep(down, NULL); | ||
| 209 | } | ||
| 210 | } | ||
| 211 | |||
| 212 | //added Eric PTAK - trouch.com | ||
| 213 | void pulseOrSaveTS(int gpio, struct timespec *up, struct timespec *down) { | ||
| 214 | if (gpio_threads[gpio] != NULL) { | ||
| 215 | memcpy(&gpio_tspairs[gpio].up, up, sizeof(struct timespec)); | ||
| 216 | memcpy(&gpio_tspairs[gpio].down, down, sizeof(struct timespec)); | ||
| 217 | } | ||
| 218 | else { | ||
| 219 | pulseTS(gpio, up, down); | ||
| 220 | } | ||
| 221 | } | ||
| 222 | |||
| 223 | //added Eric PTAK - trouch.com | ||
| 224 | void pulseMilli(int gpio, int up, int down) { | ||
| 225 | struct timespec tsUP, tsDOWN; | ||
| 226 | |||
| 227 | tsUP.tv_sec = up/1000; | ||
| 228 | tsUP.tv_nsec = (up%1000) * 1000000; | ||
| 229 | |||
| 230 | tsDOWN.tv_sec = down/1000; | ||
| 231 | tsDOWN.tv_nsec = (down%1000) * 1000000; | ||
| 232 | pulseOrSaveTS(gpio, &tsUP, &tsDOWN); | ||
| 233 | } | ||
| 234 | |||
| 235 | //added Eric PTAK - trouch.com | ||
| 236 | void pulseMilliRatio(int gpio, int width, float ratio) { | ||
| 237 | int up = ratio*width; | ||
| 238 | int down = width - up; | ||
| 239 | pulseMilli(gpio, up, down); | ||
| 240 | } | ||
| 241 | |||
| 242 | //added Eric PTAK - trouch.com | ||
| 243 | void pulseMicro(int gpio, int up, int down) { | ||
| 244 | struct timespec tsUP, tsDOWN; | ||
| 245 | |||
| 246 | tsUP.tv_sec = 0; | ||
| 247 | tsUP.tv_nsec = up * 1000; | ||
| 248 | |||
| 249 | tsDOWN.tv_sec = 0; | ||
| 250 | tsDOWN.tv_nsec = down * 1000; | ||
| 251 | pulseOrSaveTS(gpio, &tsUP, &tsDOWN); | ||
| 252 | } | ||
| 253 | |||
| 254 | //added Eric PTAK - trouch.com | ||
| 255 | void pulseMicroRatio(int gpio, int width, float ratio) { | ||
| 256 | int up = ratio*width; | ||
| 257 | int down = width - up; | ||
| 258 | pulseMicro(gpio, up, down); | ||
| 259 | } | ||
| 260 | |||
| 261 | //added Eric PTAK - trouch.com | ||
| 262 | void pulseAngle(int gpio, float angle) { | ||
| 263 | gpio_pulses[gpio].type = ANGLE; | ||
| 264 | gpio_pulses[gpio].value = angle; | ||
| 265 | int up = 1520 + (angle*400)/45; | ||
| 266 | int down = 20000-up; | ||
| 267 | pulseMicro(gpio, up, down); | ||
| 268 | } | ||
| 269 | |||
| 270 | //added Eric PTAK - trouch.com | ||
| 271 | void pulseRatio(int gpio, float ratio) { | ||
| 272 | gpio_pulses[gpio].type = RATIO; | ||
| 273 | gpio_pulses[gpio].value = ratio; | ||
| 274 | int up = ratio * 20000; | ||
| 275 | int down = 20000 - up; | ||
| 276 | pulseMicro(gpio, up, down); | ||
| 277 | } | ||
| 278 | |||
| 279 | struct pulse* getPulse(int gpio) { | ||
| 280 | return &gpio_pulses[gpio]; | ||
| 281 | } | ||
| 282 | |||
| 283 | //added Eric PTAK - trouch.com | ||
| 284 | void* pwmLoop(void* data) { | ||
| 285 | int gpio = (int)data; | ||
| 286 | |||
| 287 | while (1) { | ||
| 288 | pulseTS(gpio, &gpio_tspairs[gpio].up, &gpio_tspairs[gpio].down); | ||
| 289 | } | ||
| 290 | } | ||
| 291 | |||
| 292 | //added Eric PTAK - trouch.com | ||
| 293 | void enablePWM(int gpio) { | ||
| 294 | pthread_t *thread = gpio_threads[gpio]; | ||
| 295 | if (thread != NULL) { | ||
| 296 | return; | ||
| 297 | } | ||
| 298 | |||
| 299 | resetPWM(gpio); | ||
| 300 | |||
| 301 | thread = (pthread_t*) malloc(sizeof(pthread_t)); | ||
| 302 | pthread_create(thread, NULL, pwmLoop, (void*)gpio); | ||
| 303 | gpio_threads[gpio] = thread; | ||
| 304 | } | ||
| 305 | |||
| 306 | //added Eric PTAK - trouch.com | ||
| 307 | void disablePWM(int gpio) { | ||
| 308 | pthread_t *thread = gpio_threads[gpio]; | ||
| 309 | if (thread == NULL) { | ||
| 310 | return; | ||
| 311 | } | ||
| 312 | |||
| 313 | pthread_cancel(*thread); | ||
| 314 | gpio_threads[gpio] = NULL; | ||
| 315 | output(gpio, 0); | ||
| 316 | resetPWM(gpio); | ||
| 317 | } | ||
| 318 | |||
| 319 | //added Eric PTAK - trouch.com | ||
| 320 | int isPWMEnabled(int gpio) { | ||
| 321 | return gpio_threads[gpio] != NULL; | ||
| 322 | } | ||
| 323 | |||
| 324 | |||
| 325 | void cleanup(void) | ||
| 326 | { | ||
| 327 | // fixme - set all gpios back to input | ||
| 328 | munmap((caddr_t)gpio_map, BLOCK_SIZE); | ||
| 329 | } | ||
diff --git a/python/native/gpio.h b/python/native/gpio.h new file mode 100644 index 0000000..cb0147c --- /dev/null +++ b/python/native/gpio.h | |||
| @@ -0,0 +1,73 @@ | |||
| 1 | /* | ||
| 2 | Copyright (c) 2012 Ben Croston / 2012-2013 Eric PTAK | ||
| 3 | |||
| 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of | ||
| 5 | this software and associated documentation files (the "Software"), to deal in | ||
| 6 | the Software without restriction, including without limitation the rights to | ||
| 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies | ||
| 8 | of the Software, and to permit persons to whom the Software is furnished to do | ||
| 9 | so, subject to the following conditions: | ||
| 10 | |||
| 11 | The above copyright notice and this permission notice shall be included in all | ||
| 12 | copies or substantial portions of the Software. | ||
| 13 | |||
| 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
| 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
| 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
| 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
| 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
| 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
| 20 | SOFTWARE. | ||
| 21 | */ | ||
| 22 | |||
| 23 | #define SETUP_OK 0 | ||
| 24 | #define SETUP_DEVMEM_FAIL 1 | ||
| 25 | #define SETUP_MALLOC_FAIL 2 | ||
| 26 | #define SETUP_MMAP_FAIL 3 | ||
| 27 | |||
| 28 | #define GPIO_COUNT 54 | ||
| 29 | |||
| 30 | #define IN 0 | ||
| 31 | #define OUT 1 | ||
| 32 | #define ALT5 2 | ||
| 33 | #define ALT4 3 | ||
| 34 | #define ALT0 4 | ||
| 35 | #define ALT1 5 | ||
| 36 | #define ALT2 6 | ||
| 37 | #define ALT3 7 | ||
| 38 | #define PWM 8 | ||
| 39 | |||
| 40 | #define LOW 0 | ||
| 41 | #define HIGH 1 | ||
| 42 | |||
| 43 | #define PUD_OFF 0 | ||
| 44 | #define PUD_DOWN 1 | ||
| 45 | #define PUD_UP 2 | ||
| 46 | |||
| 47 | #define RATIO 1 | ||
| 48 | #define ANGLE 2 | ||
| 49 | |||
| 50 | struct pulse { | ||
| 51 | int type; | ||
| 52 | float value; | ||
| 53 | }; | ||
| 54 | |||
| 55 | int setup(void); | ||
| 56 | int get_function(int gpio); | ||
| 57 | void set_function(int gpio, int function, int pud); | ||
| 58 | int input(int gpio); | ||
| 59 | void output(int gpio, int value); | ||
| 60 | void outputSequence(int gpio, int period, char* sequence); | ||
| 61 | struct pulse* getPulse(int gpio); | ||
| 62 | void pulseMilli(int gpio, int up, int down); | ||
| 63 | void pulseMilliRatio(int gpio, int width, float ratio); | ||
| 64 | void pulseMicro(int gpio, int up, int down); | ||
| 65 | void pulseMicroRatio(int gpio, int width, float ratio); | ||
| 66 | void pulseAngle(int gpio, float angle); | ||
| 67 | void pulseRatio(int gpio, float ratio); | ||
| 68 | void enablePWM(int gpio); | ||
| 69 | void disablePWM(int gpio); | ||
| 70 | int isPWMEnabled(int gpio); | ||
| 71 | |||
| 72 | void cleanup(void); | ||
| 73 | |||
diff --git a/python/passwd b/python/passwd new file mode 100644 index 0000000..4639dc4 --- /dev/null +++ b/python/passwd | |||
| @@ -0,0 +1 @@ | |||
| a4f849b74f8d12e35fad61c06489b70676affd6ddc599fa3de47210e351b7875 \ No newline at end of file | |||
diff --git a/python/setup.py b/python/setup.py new file mode 100644 index 0000000..671cb27 --- /dev/null +++ b/python/setup.py | |||
| @@ -0,0 +1,37 @@ | |||
| 1 | from setuptools import setup, Extension | ||
| 2 | |||
| 3 | classifiers = ['Development Status :: 3 - Alpha', | ||
| 4 | 'Operating System :: POSIX :: Linux', | ||
| 5 | 'License :: OSI Approved :: MIT License', | ||
| 6 | 'Intended Audience :: Developers', | ||
| 7 | 'Programming Language :: Python :: 2.6', | ||
| 8 | 'Programming Language :: Python :: 2.7', | ||
| 9 | 'Programming Language :: Python :: 3', | ||
| 10 | 'Topic :: Software Development', | ||
| 11 | 'Topic :: Home Automation', | ||
| 12 | 'Topic :: System :: Hardware'] | ||
| 13 | |||
| 14 | setup(name = 'WebIOPi', | ||
| 15 | version = '0.6.2', | ||
| 16 | author = 'Eric PTAK', | ||
| 17 | author_email = 'trouch@trouch.com', | ||
| 18 | description = 'A package to control Raspberry Pi GPIO from the web', | ||
| 19 | long_description = open('../doc/README').read(), | ||
| 20 | license = 'MIT', | ||
| 21 | keywords = 'RaspberryPi GPIO Python REST', | ||
| 22 | url = 'http://code.google.com/p/webiopi/', | ||
| 23 | classifiers = classifiers, | ||
| 24 | packages = ["webiopi", | ||
| 25 | "webiopi.utils", | ||
| 26 | "webiopi.clients", | ||
| 27 | "webiopi.protocols", | ||
| 28 | "webiopi.server", | ||
| 29 | "webiopi.decorators", | ||
| 30 | "webiopi.devices", | ||
| 31 | "webiopi.devices.digital", | ||
| 32 | "webiopi.devices.analog", | ||
| 33 | "webiopi.devices.sensor", | ||
| 34 | "webiopi.devices.shield" | ||
| 35 | ], | ||
| 36 | ext_modules = [Extension('_webiopi.GPIO', ['native/bridge.c', 'native/gpio.c', 'native/cpuinfo.c'])], | ||
| 37 | ) | ||
diff --git a/python/webiopi-passwd.py b/python/webiopi-passwd.py new file mode 100755 index 0000000..7c0fbff --- /dev/null +++ b/python/webiopi-passwd.py | |||
| @@ -0,0 +1,57 @@ | |||
| 1 | #!/usr/bin/python | ||
| 2 | # Copyright 2012-2013 Eric Ptak - trouch.com | ||
| 3 | # | ||
| 4 | # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 5 | # you may not use this file except in compliance with the License. | ||
| 6 | # You may obtain a copy of the License at | ||
| 7 | # | ||
| 8 | # http://www.apache.org/licenses/LICENSE-2.0 | ||
| 9 | # | ||
| 10 | # Unless required by applicable law or agreed to in writing, software | ||
| 11 | # distributed under the License is distributed on an "AS IS" BASIS, | ||
| 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 13 | # See the License for the specific language governing permissions and | ||
| 14 | # limitations under the License. | ||
| 15 | |||
| 16 | import sys | ||
| 17 | file = None | ||
| 18 | |||
| 19 | print("WebIOPi passwd file generator") | ||
| 20 | if len(sys.argv) == 2: | ||
| 21 | file = sys.argv[1] | ||
| 22 | if file == "--help" or file == "-h": | ||
| 23 | print("Usage: webiopi-passwd [--help|file]") | ||
| 24 | print("Compute and display hash used by WebIOPi for Authentication") | ||
| 25 | print("Login and Password are prompted") | ||
| 26 | print("\t--help\tDisplay this help") | ||
| 27 | print("\t-h") | ||
| 28 | print("\tfile\tSave hash to file") | ||
| 29 | sys.exit() | ||
| 30 | else: | ||
| 31 | file = "/etc/webiopi/passwd" | ||
| 32 | |||
| 33 | f = open(file, "w") | ||
| 34 | _LOGIN = "Enter Login: " | ||
| 35 | _PASSWORD = "Enter Password: " | ||
| 36 | _CONFIRM = "Confirm password: " | ||
| 37 | _DONTMATCH = "Passwords don't match !" | ||
| 38 | |||
| 39 | import getpass | ||
| 40 | try: | ||
| 41 | login = raw_input(_LOGIN) | ||
| 42 | except NameError: | ||
| 43 | login = input(_LOGIN) | ||
| 44 | password = getpass.getpass(_PASSWORD) | ||
| 45 | password2 = getpass.getpass(_CONFIRM) | ||
| 46 | while password != password2: | ||
| 47 | print(_DONTMATCH) | ||
| 48 | password = getpass.getpass(_PASSWORD) | ||
| 49 | password2 = getpass.getpass(_CONFIRM) | ||
| 50 | |||
| 51 | from webiopi.utils.crypto import encryptCredentials | ||
| 52 | auth = encryptCredentials(login, password) | ||
| 53 | print("\nHash: %s" % auth) | ||
| 54 | if file: | ||
| 55 | f.write(auth) | ||
| 56 | f.close() | ||
| 57 | print("Saved to %s" % file) | ||
diff --git a/python/webiopi.init.sh b/python/webiopi.init.sh new file mode 100755 index 0000000..29457f5 --- /dev/null +++ b/python/webiopi.init.sh | |||
| @@ -0,0 +1,155 @@ | |||
| 1 | #! /bin/sh | ||
| 2 | ### BEGIN INIT INFO | ||
| 3 | # Provides: webiopi | ||
| 4 | # Required-Start: $remote_fs $syslog $network | ||
| 5 | # Required-Stop: $remote_fs $syslog $network | ||
| 6 | # Default-Start: 2 3 4 5 | ||
| 7 | # Default-Stop: 0 1 6 | ||
| 8 | # Short-Description: WebIOPi initscript | ||
| 9 | # Description: WebIOPi initscript | ||
| 10 | ### END INIT INFO | ||
| 11 | |||
| 12 | # Author: trouch <trouch@trouch.com> | ||
| 13 | LOG_FILE=/var/log/webiopi | ||
| 14 | CONFIG_FILE=/etc/webiopi/config | ||
| 15 | |||
| 16 | PATH=/sbin:/usr/sbin:/bin:/usr/bin | ||
| 17 | DESC="WebIOPi" | ||
| 18 | NAME=webiopi | ||
| 19 | HOME=/usr/share/webiopi/htdocs | ||
| 20 | DAEMON=/usr/bin/python | ||
| 21 | DAEMON_ARGS="-m webiopi -l $LOG_FILE -c $CONFIG_FILE" | ||
| 22 | PIDFILE=/var/run/$NAME.pid | ||
| 23 | SCRIPTNAME=/etc/init.d/$NAME | ||
| 24 | |||
| 25 | # Exit if the package is not installed | ||
| 26 | [ -x "$DAEMON" ] || exit 0 | ||
| 27 | |||
| 28 | # Read configuration variable file if it is present | ||
| 29 | [ -r /etc/default/$NAME ] && . /etc/default/$NAME | ||
| 30 | |||
| 31 | # Load the VERBOSE setting and other rcS variables | ||
| 32 | . /lib/init/vars.sh | ||
| 33 | |||
| 34 | # Define LSB log_* functions. | ||
| 35 | # Depend on lsb-base (>= 3.2-14) to ensure that this file is present | ||
| 36 | # and status_of_proc is working. | ||
| 37 | . /lib/lsb/init-functions | ||
| 38 | |||
| 39 | # | ||
| 40 | # Function that starts the daemon/service | ||
| 41 | # | ||
| 42 | do_start() | ||
| 43 | { | ||
| 44 | # Return | ||
| 45 | # 0 if daemon has been started | ||
| 46 | # 1 if daemon was already running | ||
| 47 | # 2 if daemon could not be started | ||
| 48 | start-stop-daemon --start --quiet --chdir $HOME --pidfile $PIDFILE --exec $DAEMON --test > /dev/null \ | ||
| 49 | || return 1 | ||
| 50 | start-stop-daemon --start --quiet --chdir $HOME --pidfile $PIDFILE --exec $DAEMON --background --make-pidfile -- \ | ||
| 51 | $DAEMON_ARGS \ | ||
| 52 | || return 2 | ||
| 53 | # Add code here, if necessary, that waits for the process to be ready | ||
| 54 | # to handle requests from services started subsequently which depend | ||
| 55 | # on this one. As a last resort, sleep for some time. | ||
| 56 | } | ||
| 57 | |||
| 58 | # | ||
| 59 | # Function that stops the daemon/service | ||
| 60 | # | ||
| 61 | do_stop() | ||
| 62 | { | ||
| 63 | # Return | ||
| 64 | # 0 if daemon has been stopped | ||
| 65 | # 1 if daemon was already stopped | ||
| 66 | # 2 if daemon could not be stopped | ||
| 67 | # other if a failure occurred | ||
| 68 | start-stop-daemon --stop --quiet --pidfile $PIDFILE --name $NAME | ||
| 69 | RETVAL="$?" | ||
| 70 | [ "$RETVAL" = 2 ] && return 2 | ||
| 71 | # Wait for children to finish too if this is a daemon that forks | ||
| 72 | # and if the daemon is only ever run from this initscript. | ||
| 73 | # If the above conditions are not satisfied then add some other code | ||
| 74 | # that waits for the process to drop all resources that could be | ||
| 75 | # needed by services started subsequently. A last resort is to | ||
| 76 | # sleep for some time. | ||
| 77 | start-stop-daemon --stop --quiet --exec $DAEMON | ||
| 78 | [ "$?" = 2 ] && return 2 | ||
| 79 | # Many daemons don't delete their pidfiles when they exit. | ||
| 80 | rm -f $PIDFILE | ||
| 81 | return "$RETVAL" | ||
| 82 | } | ||
| 83 | |||
| 84 | # | ||
| 85 | # Function that sends a SIGHUP to the daemon/service | ||
| 86 | # | ||
| 87 | do_reload() { | ||
| 88 | # | ||
| 89 | # If the daemon can reload its configuration without | ||
| 90 | # restarting (for example, when it is sent a SIGHUP), | ||
| 91 | # then implement that here. | ||
| 92 | # | ||
| 93 | start-stop-daemon --stop --signal 1 --quiet --pidfile $PIDFILE --name $NAME | ||
| 94 | return 0 | ||
| 95 | } | ||
| 96 | |||
| 97 | case "$1" in | ||
| 98 | start) | ||
| 99 | [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME" | ||
| 100 | do_start | ||
| 101 | case "$?" in | ||
| 102 | 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; | ||
| 103 | 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; | ||
| 104 | esac | ||
| 105 | ;; | ||
| 106 | stop) | ||
| 107 | [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME" | ||
| 108 | do_stop | ||
| 109 | case "$?" in | ||
| 110 | 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; | ||
| 111 | 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; | ||
| 112 | esac | ||
| 113 | ;; | ||
| 114 | status) | ||
| 115 | status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $? | ||
| 116 | ;; | ||
| 117 | #reload|force-reload) | ||
| 118 | # | ||
| 119 | # If do_reload() is not implemented then leave this commented out | ||
| 120 | # and leave 'force-reload' as an alias for 'restart'. | ||
| 121 | # | ||
| 122 | #log_daemon_msg "Reloading $DESC" "$NAME" | ||
| 123 | #do_reload | ||
| 124 | #log_end_msg $? | ||
| 125 | #;; | ||
| 126 | restart|force-reload) | ||
| 127 | # | ||
| 128 | # If the "reload" option is implemented then remove the | ||
| 129 | # 'force-reload' alias | ||
| 130 | # | ||
| 131 | log_daemon_msg "Restarting $DESC" "$NAME" | ||
| 132 | do_stop | ||
| 133 | case "$?" in | ||
| 134 | 0|1) | ||
| 135 | do_start | ||
| 136 | case "$?" in | ||
| 137 | 0) log_end_msg 0 ;; | ||
| 138 | 1) log_end_msg 1 ;; # Old process is still running | ||
| 139 | *) log_end_msg 1 ;; # Failed to start | ||
| 140 | esac | ||
| 141 | ;; | ||
| 142 | *) | ||
| 143 | # Failed to stop | ||
| 144 | log_end_msg 1 | ||
| 145 | ;; | ||
| 146 | esac | ||
| 147 | ;; | ||
| 148 | *) | ||
| 149 | #echo "Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload}" >&2 | ||
| 150 | echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2 | ||
| 151 | exit 3 | ||
| 152 | ;; | ||
| 153 | esac | ||
| 154 | |||
| 155 | : | ||
diff --git a/python/webiopi.sh b/python/webiopi.sh new file mode 100755 index 0000000..0cf7bb5 --- /dev/null +++ b/python/webiopi.sh | |||
| @@ -0,0 +1,2 @@ | |||
| 1 | #!/bin/sh | ||
| 2 | python -m webiopi $* | ||
diff --git a/python/webiopi/__init__.py b/python/webiopi/__init__.py new file mode 100644 index 0000000..c0f811e --- /dev/null +++ b/python/webiopi/__init__.py | |||
| @@ -0,0 +1,34 @@ | |||
| 1 | # Copyright 2012-2013 Eric Ptak - trouch.com | ||
| 2 | # | ||
| 3 | # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 4 | # you may not use this file except in compliance with the License. | ||
| 5 | # You may obtain a copy of the License at | ||
| 6 | # | ||
| 7 | # http://www.apache.org/licenses/LICENSE-2.0 | ||
| 8 | # | ||
| 9 | # Unless required by applicable law or agreed to in writing, software | ||
| 10 | # distributed under the License is distributed on an "AS IS" BASIS, | ||
| 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 12 | # See the License for the specific language governing permissions and | ||
| 13 | # limitations under the License. | ||
| 14 | |||
| 15 | |||
| 16 | from time import sleep | ||
| 17 | |||
| 18 | from webiopi.utils.version import BOARD_REVISION, VERSION | ||
| 19 | from webiopi.utils.logger import setInfo, setDebug, info, debug, warn, error, exception | ||
| 20 | from webiopi.utils.thread import runLoop | ||
| 21 | from webiopi.server import Server | ||
| 22 | from webiopi.devices.instance import deviceInstance | ||
| 23 | from webiopi.decorators.rest import macro | ||
| 24 | |||
| 25 | from webiopi.devices import bus as _bus | ||
| 26 | |||
| 27 | try: | ||
| 28 | import _webiopi.GPIO as GPIO | ||
| 29 | except: | ||
| 30 | pass | ||
| 31 | |||
| 32 | |||
| 33 | setInfo() | ||
| 34 | _bus.checkAllBus() | ||
diff --git a/python/webiopi/__main__.py b/python/webiopi/__main__.py new file mode 100644 index 0000000..dc57bc2 --- /dev/null +++ b/python/webiopi/__main__.py | |||
| @@ -0,0 +1,79 @@ | |||
| 1 | # Copyright 2012-2013 Eric Ptak - trouch.com | ||
| 2 | # | ||
| 3 | # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 4 | # you may not use this file except in compliance with the License. | ||
| 5 | # You may obtain a copy of the License at | ||
| 6 | # | ||
| 7 | # http://www.apache.org/licenses/LICENSE-2.0 | ||
| 8 | # | ||
| 9 | # Unless required by applicable law or agreed to in writing, software | ||
| 10 | # distributed under the License is distributed on an "AS IS" BASIS, | ||
| 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 12 | # See the License for the specific language governing permissions and | ||
| 13 | # limitations under the License. | ||
| 14 | |||
| 15 | import sys | ||
| 16 | from webiopi.server import Server | ||
| 17 | from webiopi.utils.loader import loadScript | ||
| 18 | from webiopi.utils.logger import exception, setDebug, info, logToFile | ||
| 19 | from webiopi.utils.version import VERSION_STRING | ||
| 20 | from webiopi.utils.thread import runLoop, stop | ||
| 21 | |||
| 22 | def displayHelp(): | ||
| 23 | print("WebIOPi command-line usage") | ||
| 24 | print("webiopi [-h] [-c config] [-l log] [-s script] [-d] [port]") | ||
| 25 | print("") | ||
| 26 | print("Options:") | ||
| 27 | print(" -h, --help Display this help") | ||
| 28 | print(" -c, --config file Load config from file") | ||
| 29 | print(" -l, --log file Log to file") | ||
| 30 | print(" -s, --script file Load script from file") | ||
| 31 | print(" -d, --debug Enable DEBUG") | ||
| 32 | print("") | ||
| 33 | print("Arguments:") | ||
| 34 | print(" port Port to bind the HTTP Server") | ||
| 35 | exit() | ||
| 36 | |||
| 37 | def main(argv): | ||
| 38 | port = 8000 | ||
| 39 | configfile = None | ||
| 40 | logfile = None | ||
| 41 | |||
| 42 | i = 1 | ||
| 43 | while i < len(argv): | ||
| 44 | if argv[i] in ["-c", "-C", "--config-file"]: | ||
| 45 | configfile = argv[i+1] | ||
| 46 | i+=1 | ||
| 47 | elif argv[i] in ["-l", "-L", "--log-file"]: | ||
| 48 | logfile = argv[i+1] | ||
| 49 | i+=1 | ||
| 50 | elif argv[i] in ["-s", "-S", "--script-file"]: | ||
| 51 | scriptfile = argv[i+1] | ||
| 52 | scriptname = scriptfile.split("/")[-1].split(".")[0] | ||
| 53 | loadScript(scriptname, scriptfile) | ||
| 54 | i+=1 | ||
| 55 | elif argv[i] in ["-h", "-H", "--help"]: | ||
| 56 | displayHelp() | ||
| 57 | elif argv[i] in ["-d", "--debug"]: | ||
| 58 | setDebug() | ||
| 59 | else: | ||
| 60 | try: | ||
| 61 | port = int(argv[i]) | ||
| 62 | except ValueError: | ||
| 63 | displayHelp() | ||
| 64 | i+=1 | ||
| 65 | |||
| 66 | if logfile: | ||
| 67 | logToFile(logfile) | ||
| 68 | |||
| 69 | info("Starting %s" % VERSION_STRING) | ||
| 70 | server = Server(port=port, configfile=configfile) | ||
| 71 | runLoop() | ||
| 72 | server.stop() | ||
| 73 | |||
| 74 | if __name__ == "__main__": | ||
| 75 | try: | ||
| 76 | main(sys.argv) | ||
| 77 | except Exception as e: | ||
| 78 | exception(e) | ||
| 79 | stop() | ||
diff --git a/python/webiopi/clients/__init__.py b/python/webiopi/clients/__init__.py new file mode 100644 index 0000000..d8527af --- /dev/null +++ b/python/webiopi/clients/__init__.py | |||
| @@ -0,0 +1,209 @@ | |||
| 1 | # Copyright 2012-2013 Eric Ptak - trouch.com | ||
| 2 | # | ||
| 3 | # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 4 | # you may not use this file except in compliance with the License. | ||
| 5 | # You may obtain a copy of the License at | ||
| 6 | # | ||
| 7 | # http://www.apache.org/licenses/LICENSE-2.0 | ||
| 8 | # | ||
| 9 | # Unless required by applicable law or agreed to in writing, software | ||
| 10 | # distributed under the License is distributed on an "AS IS" BASIS, | ||
| 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 12 | # See the License for the specific language governing permissions and | ||
| 13 | # limitations under the License. | ||
| 14 | |||
| 15 | from webiopi.utils.logger import LOGGER | ||
| 16 | from webiopi.utils.version import PYTHON_MAJOR | ||
| 17 | from webiopi.utils.crypto import encodeCredentials | ||
| 18 | from webiopi.protocols.coap import COAPClient, COAPGet, COAPPost, COAPPut, COAPDelete | ||
| 19 | |||
| 20 | if PYTHON_MAJOR >= 3: | ||
| 21 | import http.client as httplib | ||
| 22 | else: | ||
| 23 | import httplib | ||
| 24 | |||
| 25 | class PiMixedClient(): | ||
| 26 | def __init__(self, host, port=8000, coap=5683): | ||
| 27 | self.host = host | ||
| 28 | if coap > 0: | ||
| 29 | self.coapport = coap | ||
| 30 | self.coapclient = COAPClient() | ||
| 31 | else: | ||
| 32 | self.coapclient = None | ||
| 33 | if port > 0: | ||
| 34 | self.httpclient = httplib.HTTPConnection(host, port) | ||
| 35 | else: | ||
| 36 | self.httpclient = None | ||
| 37 | self.forceHttp = False | ||
| 38 | self.coapfailure = 0 | ||
| 39 | self.maxfailure = 2 | ||
| 40 | self.auth= None; | ||
| 41 | |||
| 42 | def setCredentials(self, login, password): | ||
| 43 | self.auth = "Basic " + encodeCredentials(login, password) | ||
| 44 | |||
| 45 | def sendRequest(self, method, uri): | ||
| 46 | if self.coapclient != None and not self.forceHttp: | ||
| 47 | if method == "GET": | ||
| 48 | response = self.coapclient.sendRequest(COAPGet("coap://%s:%d%s" % (self.host, self.coapport, uri))) | ||
| 49 | elif method == "POST": | ||
| 50 | response = self.coapclient.sendRequest(COAPPost("coap://%s:%d%s" % (self.host, self.coapport, uri))) | ||
| 51 | |||
| 52 | if response: | ||
| 53 | return str(response.payload) | ||
| 54 | elif self.httpclient != None: | ||
| 55 | self.coapfailure += 1 | ||
| 56 | print("No CoAP response, fall-back to HTTP") | ||
| 57 | if (self.coapfailure > self.maxfailure): | ||
| 58 | self.forceHttp = True | ||
| 59 | self.coapfailure = 0 | ||
| 60 | print("Too many CoAP failure forcing HTTP") | ||
| 61 | |||
| 62 | if self.httpclient != None: | ||
| 63 | headers = {} | ||
| 64 | if self.auth != None: | ||
| 65 | headers["Authorization"] = self.auth | ||
| 66 | |||
| 67 | self.httpclient.request(method, uri, None, headers) | ||
| 68 | response = self.httpclient.getresponse() | ||
| 69 | if response.status == 200: | ||
| 70 | data = response.read() | ||
| 71 | return data | ||
| 72 | elif response.status == 401: | ||
| 73 | raise Exception("Missing credentials") | ||
| 74 | else: | ||
| 75 | raise Exception("Unhandled HTTP Response %d %s" % (response.status, response.reason)) | ||
| 76 | |||
| 77 | raise Exception("No data received") | ||
| 78 | |||
| 79 | class PiHttpClient(PiMixedClient): | ||
| 80 | def __init__(self, host, port=8000): | ||
| 81 | PiMixedClient.__init__(self, host, port, -1) | ||
| 82 | |||
| 83 | class PiCoapClient(PiMixedClient): | ||
| 84 | def __init__(self, host, port=5683): | ||
| 85 | PiMixedClient.__init__(self, host, -1, port) | ||
| 86 | |||
| 87 | class PiMulticastClient(PiMixedClient): | ||
| 88 | def __init__(self, port=5683): | ||
| 89 | PiMixedClient.__init__(self, "224.0.1.123", -1, port) | ||
| 90 | |||
| 91 | class RESTAPI(): | ||
| 92 | def __init__(self, client, path): | ||
| 93 | self.client = client | ||
| 94 | self.path = path | ||
| 95 | |||
| 96 | def sendRequest(self, method, path): | ||
| 97 | return self.client.sendRequest(method, self.path + path) | ||
| 98 | |||
| 99 | class Macro(RESTAPI): | ||
| 100 | def __init__(self, client, name): | ||
| 101 | RESTAPI.__init__(self, client, "/macros/" + name + "/") | ||
| 102 | |||
| 103 | def call(self, *args): | ||
| 104 | values = ",".join(["%s" % i for i in args]) | ||
| 105 | if values == None: | ||
| 106 | values = "" | ||
| 107 | return self.sendRequest("POST", values) | ||
| 108 | |||
| 109 | class Device(RESTAPI): | ||
| 110 | def __init__(self, client, name, category): | ||
| 111 | RESTAPI.__init__(self, client, "/devices/" + name + "/" + category) | ||
| 112 | |||
| 113 | class GPIO(Device): | ||
| 114 | def __init__(self, client, name): | ||
| 115 | Device.__init__(self, client, name, "digital") | ||
| 116 | |||
| 117 | def getFunction(self, channel): | ||
| 118 | return self.sendRequest("GET", "/%d/function" % channel) | ||
| 119 | |||
| 120 | def setFunction(self, channel, func): | ||
| 121 | return self.sendRequest("POST", "/%d/function/%s" % (channel, func)) | ||
| 122 | |||
| 123 | def digitalRead(self, channel): | ||
| 124 | return int(self.sendRequest("GET", "/%d/value" % channel)) | ||
| 125 | |||
| 126 | def digitalWrite(self, channel, value): | ||
| 127 | return int(self.sendRequest("POST", "/%d/value/%d" % (channel, value))) | ||
| 128 | |||
| 129 | def portRead(self): | ||
| 130 | return int(self.sendRequest("GET", "/integer")) | ||
| 131 | |||
| 132 | def portWrite(self, value): | ||
| 133 | return int(self.sendRequest("POST", "/integer/%d" % value)) | ||
| 134 | |||
| 135 | class NativeGPIO(GPIO): | ||
| 136 | def __init__(self, client): | ||
| 137 | RESTAPI.__init__(self, client, "/GPIO") | ||
| 138 | |||
| 139 | class ADC(Device): | ||
| 140 | def __init__(self, client, name): | ||
| 141 | Device.__init__(self, client, name, "analog") | ||
| 142 | |||
| 143 | def read(self, channel): | ||
| 144 | return float(self.sendRequest("GET", "/%d/integer" % channel)) | ||
| 145 | |||
| 146 | def readFloat(self, channel): | ||
| 147 | return float(self.sendRequest("GET", "/%d/float" % channel)) | ||
| 148 | |||
| 149 | def readVolt(self, channel): | ||
| 150 | return float(self.sendRequest("GET", "/%d/volt" % channel)) | ||
| 151 | |||
| 152 | class DAC(ADC): | ||
| 153 | def __init__(self, client, name): | ||
| 154 | Device.__init__(self, client, name, "analog") | ||
| 155 | |||
| 156 | def write(self, channel, value): | ||
| 157 | return float(self.sendRequest("POST", "/%d/integer/%d" % (channel, value))) | ||
| 158 | |||
| 159 | def writeFloat(self, channel, value): | ||
| 160 | return float(self.sendRequest("POST", "/%d/float/%f" % (channel, value))) | ||
| 161 | |||
| 162 | def writeVolt(self, channel, value): | ||
| 163 | return float(self.sendRequest("POST", "/%d/volt/%f" % (channel, value))) | ||
| 164 | |||
| 165 | class PWM(DAC): | ||
| 166 | def __init__(self, client, name): | ||
| 167 | Device.__init__(self, client, name, "pwm") | ||
| 168 | |||
| 169 | def readAngle(self, channel, value): | ||
| 170 | return float(self.sendRequest("GET", "/%d/angle" % (channel))) | ||
| 171 | |||
| 172 | def writeAngle(self, channel, value): | ||
| 173 | return float(self.sendRequest("POST", "/%d/angle/%f" % (channel, value))) | ||
| 174 | |||
| 175 | class Sensor(Device): | ||
| 176 | def __init__(self, client, name): | ||
| 177 | Device.__init__(self, client, name, "sensor") | ||
| 178 | |||
| 179 | class Temperature(Sensor): | ||
| 180 | def getKelvin(self): | ||
| 181 | return float(self.sendRequest("GET", "/temperature/k")) | ||
| 182 | |||
| 183 | def getCelsius(self): | ||
| 184 | return float(self.sendRequest("GET", "/temperature/c")) | ||
| 185 | |||
| 186 | def getFahrenheit(self): | ||
| 187 | return float(self.sendRequest("GET", "/temperature/f")) | ||
| 188 | |||
| 189 | class Pressure(Sensor): | ||
| 190 | def getPascal(self): | ||
| 191 | return float(self.sendRequest("GET", "/pressure/pa")) | ||
| 192 | |||
| 193 | def getHectoPascal(self): | ||
| 194 | return float(self.sendRequest("GET", "/pressure/hpa")) | ||
| 195 | |||
| 196 | class Luminosity(Sensor): | ||
| 197 | def getLux(self): | ||
| 198 | return float(self.sendRequest("GET", "/luminosity/lux")) | ||
| 199 | |||
| 200 | class Distance(Sensor): | ||
| 201 | def getMillimeter(self): | ||
| 202 | return float(self.sendRequest("GET", "/distance/mm")) | ||
| 203 | |||
| 204 | def getCentimeter(self): | ||
| 205 | return float(self.sendRequest("GET", "/distance/cm")) | ||
| 206 | |||
| 207 | def getInch(self): | ||
| 208 | return float(self.sendRequest("GET", "/distance/in")) | ||
| 209 | |||
diff --git a/python/webiopi/decorators/__init__.py b/python/webiopi/decorators/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/python/webiopi/decorators/__init__.py | |||
diff --git a/python/webiopi/decorators/rest.py b/python/webiopi/decorators/rest.py new file mode 100644 index 0000000..7fffacc --- /dev/null +++ b/python/webiopi/decorators/rest.py | |||
| @@ -0,0 +1,19 @@ | |||
| 1 | def request(method="GET", path="", data=None): | ||
| 2 | def wrapper(func): | ||
| 3 | func.routed = True | ||
| 4 | func.method = method | ||
| 5 | func.path = path | ||
| 6 | func.data = data | ||
| 7 | return func | ||
| 8 | return wrapper | ||
| 9 | |||
| 10 | def response(fmt="%s", contentType="text/plain"): | ||
| 11 | def wrapper(func): | ||
| 12 | func.format = fmt | ||
| 13 | func.contentType = contentType | ||
| 14 | return func | ||
| 15 | return wrapper | ||
| 16 | |||
| 17 | def macro(func): | ||
| 18 | func.macro = True | ||
| 19 | return func | ||
diff --git a/python/webiopi/devices/__init__.py b/python/webiopi/devices/__init__.py new file mode 100644 index 0000000..ecd45ec --- /dev/null +++ b/python/webiopi/devices/__init__.py | |||
| @@ -0,0 +1,13 @@ | |||
| 1 | # Copyright 2012-2013 Eric Ptak - trouch.com | ||
| 2 | # | ||
| 3 | # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 4 | # you may not use this file except in compliance with the License. | ||
| 5 | # You may obtain a copy of the License at | ||
| 6 | # | ||
| 7 | # http://www.apache.org/licenses/LICENSE-2.0 | ||
| 8 | # | ||
| 9 | # Unless required by applicable law or agreed to in writing, software | ||
| 10 | # distributed under the License is distributed on an "AS IS" BASIS, | ||
| 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 12 | # See the License for the specific language governing permissions and | ||
| 13 | # limitations under the License. | ||
diff --git a/python/webiopi/devices/analog/__init__.py b/python/webiopi/devices/analog/__init__.py new file mode 100644 index 0000000..4c94b9c --- /dev/null +++ b/python/webiopi/devices/analog/__init__.py | |||
| @@ -0,0 +1,267 @@ | |||
| 1 | # Copyright 2012-2013 Eric Ptak - trouch.com | ||
| 2 | # | ||
| 3 | # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 4 | # you may not use this file except in compliance with the License. | ||
| 5 | # You may obtain a copy of the License at | ||
| 6 | # | ||
| 7 | # http://www.apache.org/licenses/LICENSE-2.0 | ||
| 8 | # | ||
| 9 | # Unless required by applicable law or agreed to in writing, software | ||
| 10 | # distributed under the License is distributed on an "AS IS" BASIS, | ||
| 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 12 | # See the License for the specific language governing permissions and | ||
| 13 | # limitations under the License. | ||
| 14 | |||
| 15 | from webiopi.decorators.rest import request, response | ||
| 16 | from webiopi.utils.types import M_JSON | ||
| 17 | |||
| 18 | class ADC(): | ||
| 19 | def __init__(self, channelCount, resolution, vref): | ||
| 20 | self._analogCount = channelCount | ||
| 21 | self._analogResolution = resolution | ||
| 22 | self._analogMax = 2**resolution - 1 | ||
| 23 | self._analogRef = vref | ||
| 24 | |||
| 25 | def __family__(self): | ||
| 26 | return "ADC" | ||
| 27 | |||
| 28 | def checkAnalogChannel(self, channel): | ||
| 29 | if not 0 <= channel < self._analogCount: | ||
| 30 | raise ValueError("Channel %d out of range [%d..%d]" % (channel, 0, self._analogCount-1)) | ||
| 31 | |||
| 32 | def checkAnalogValue(self, value): | ||
| 33 | if not 0 <= value <= self._analogMax: | ||
| 34 | raise ValueError("Value %d out of range [%d..%d]" % (value, 0, self._analogMax)) | ||
| 35 | |||
| 36 | @request("GET", "analog/count") | ||
| 37 | @response("%d") | ||
| 38 | def analogCount(self): | ||
| 39 | return self._analogCount | ||
| 40 | |||
| 41 | @request("GET", "analog/resolution") | ||
| 42 | @response("%d") | ||
| 43 | def analogResolution(self): | ||
| 44 | return self._analogResolution | ||
| 45 | |||
| 46 | @request("GET", "analog/max") | ||
| 47 | @response("%d") | ||
| 48 | def analogMaximum(self): | ||
| 49 | return int(self._analogMax) | ||
| 50 | |||
| 51 | @request("GET", "analog/vref") | ||
| 52 | @response("%.2f") | ||
| 53 | def analogReference(self): | ||
| 54 | return self._analogRef | ||
| 55 | |||
| 56 | def __analogRead__(self, channel, diff): | ||
| 57 | raise NotImplementedError | ||
| 58 | |||
| 59 | @request("GET", "analog/%(channel)d/integer") | ||
| 60 | @response("%d") | ||
| 61 | def analogRead(self, channel, diff=False): | ||
| 62 | self.checkAnalogChannel(channel) | ||
| 63 | return self.__analogRead__(channel, diff) | ||
| 64 | |||
| 65 | @request("GET", "analog/%(channel)d/float") | ||
| 66 | @response("%.2f") | ||
| 67 | def analogReadFloat(self, channel, diff=False): | ||
| 68 | return self.analogRead(channel, diff) / float(self._analogMax) | ||
| 69 | |||
| 70 | @request("GET", "analog/%(channel)d/volt") | ||
| 71 | @response("%.2f") | ||
| 72 | def analogReadVolt(self, channel, diff=False): | ||
| 73 | if self._analogRef == 0: | ||
| 74 | raise NotImplementedError | ||
| 75 | return self.analogReadFloat(channel, diff) * self._analogRef | ||
| 76 | |||
| 77 | @request("GET", "analog/*/integer") | ||
| 78 | @response(contentType=M_JSON) | ||
| 79 | def analogReadAll(self): | ||
| 80 | values = {} | ||
| 81 | for i in range(self._analogCount): | ||
| 82 | values[i] = self.analogRead(i) | ||
| 83 | return values | ||
| 84 | |||
| 85 | @request("GET", "analog/*/float") | ||
| 86 | @response(contentType=M_JSON) | ||
| 87 | def analogReadAllFloat(self): | ||
| 88 | values = {} | ||
| 89 | for i in range(self._analogCount): | ||
| 90 | values[i] = float("%.2f" % self.analogReadFloat(i)) | ||
| 91 | return values | ||
| 92 | |||
| 93 | @request("GET", "analog/*/volt") | ||
| 94 | @response(contentType=M_JSON) | ||
| 95 | def analogReadAllVolt(self): | ||
| 96 | values = {} | ||
| 97 | for i in range(self._analogCount): | ||
| 98 | values[i] = float("%.2f" % self.analogReadVolt(i)) | ||
| 99 | return values | ||
| 100 | |||
| 101 | class DAC(ADC): | ||
| 102 | def __init__(self, channelCount, resolution, vref): | ||
| 103 | ADC.__init__(self, channelCount, resolution, vref) | ||
| 104 | |||
| 105 | def __family__(self): | ||
| 106 | return "DAC" | ||
| 107 | |||
| 108 | def __analogWrite__(self, channel, value): | ||
| 109 | raise NotImplementedError | ||
| 110 | |||
| 111 | @request("POST", "analog/%(channel)d/integer/%(value)d") | ||
| 112 | @response("%d") | ||
| 113 | def analogWrite(self, channel, value): | ||
| 114 | self.checkAnalogChannel(channel) | ||
| 115 | self.checkAnalogValue(value) | ||
| 116 | self.__analogWrite__(channel, value) | ||
| 117 | return self.analogRead(channel) | ||
| 118 | |||
| 119 | @request("POST", "analog/%(channel)d/float/%(value)f") | ||
| 120 | @response("%.2f") | ||
| 121 | def analogWriteFloat(self, channel, value): | ||
| 122 | self.analogWrite(channel, int(value * self._analogMax)) | ||
| 123 | return self.analogReadFloat(channel) | ||
| 124 | |||
| 125 | @request("POST", "analog/%(channel)d/volt/%(value)f") | ||
| 126 | @response("%.2f") | ||
| 127 | def analogWriteVolt(self, channel, value): | ||
| 128 | self.analogWriteFloat(channel, value /self._analogRef) | ||
| 129 | return self.analogReadVolt(channel) | ||
| 130 | |||
| 131 | |||
| 132 | class PWM(): | ||
| 133 | def __init__(self, channelCount, resolution, frequency): | ||
| 134 | self._pwmCount = channelCount | ||
| 135 | self._pwmResolution = resolution | ||
| 136 | self._pwmMax = 2**resolution - 1 | ||
| 137 | self.frequency = frequency | ||
| 138 | self.period = 1.0/frequency | ||
| 139 | |||
| 140 | # Futaba servos standard | ||
| 141 | self.servo_neutral = 0.00152 | ||
| 142 | self.servo_travel_time = 0.0004 | ||
| 143 | self.servo_travel_angle = 45.0 | ||
| 144 | |||
| 145 | self.reverse = [False for i in range(channelCount)] | ||
| 146 | |||
| 147 | def __family__(self): | ||
| 148 | return "PWM" | ||
| 149 | |||
| 150 | def checkPWMChannel(self, channel): | ||
| 151 | if not 0 <= channel < self._pwmCount: | ||
| 152 | raise ValueError("Channel %d out of range [%d..%d]" % (channel, 0, self._pwmCount-1)) | ||
| 153 | |||
| 154 | def checkPWMValue(self, value): | ||
| 155 | if not 0 <= value <= self._pwmMax: | ||
| 156 | raise ValueError("Value %d out of range [%d..%d]" % (value, 0, self._pwmMax)) | ||
| 157 | |||
| 158 | def __pwmRead__(self, channel): | ||
| 159 | raise NotImplementedError | ||
| 160 | |||
| 161 | def __pwmWrite__(self, channel, value): | ||
| 162 | raise NotImplementedError | ||
| 163 | |||
| 164 | @request("GET", "pwm/count") | ||
| 165 | @response("%d") | ||
| 166 | def pwmCount(self): | ||
| 167 | return self._pwmCount | ||
| 168 | |||
| 169 | @request("GET", "pwm/resolution") | ||
| 170 | @response("%d") | ||
| 171 | def pwmResolution(self): | ||
| 172 | return self._pwmResolution | ||
| 173 | |||
| 174 | @request("GET", "pwm/max") | ||
| 175 | @response("%d") | ||
| 176 | def pwmMaximum(self): | ||
| 177 | return int(self._pwmMax) | ||
| 178 | |||
| 179 | @request("GET", "pwm/%(channel)d/integer") | ||
| 180 | @response("%d") | ||
| 181 | def pwmRead(self, channel): | ||
| 182 | self.checkPWMChannel(channel) | ||
| 183 | return self.__pwmRead__(channel) | ||
| 184 | |||
| 185 | @request("GET", "pwm/%(channel)d/float") | ||
| 186 | @response("%.2f") | ||
| 187 | def pwmReadFloat(self, channel): | ||
| 188 | return self.pwmRead(channel) / float(self._pwmMax) | ||
| 189 | |||
| 190 | @request("POST", "pwm/%(channel)d/integer/%(value)d") | ||
| 191 | @response("%d") | ||
| 192 | def pwmWrite(self, channel, value): | ||
| 193 | self.checkPWMChannel(channel) | ||
| 194 | self.checkPWMValue(value) | ||
| 195 | self.__pwmWrite__(channel, value) | ||
| 196 | return self.pwmRead(channel) | ||
| 197 | |||
| 198 | @request("POST", "pwm/%(channel)d/float/%(value)f") | ||
| 199 | @response("%.2f") | ||
| 200 | def pwmWriteFloat(self, channel, value): | ||
| 201 | self.pwmWrite(channel, int(value * self._pwmMax)) | ||
| 202 | return self.pwmReadFloat(channel) | ||
| 203 | |||
| 204 | def getReverse(self, channel): | ||
| 205 | self.checkChannel(channel) | ||
| 206 | return self.reverse[channel] | ||
| 207 | |||
| 208 | def setReverse(self, channel, value): | ||
| 209 | self.checkChannel(channel) | ||
| 210 | self.reverse[channel] = value | ||
| 211 | return value | ||
| 212 | |||
| 213 | def RatioToAngle(self, value): | ||
| 214 | f = value | ||
| 215 | f *= self.period | ||
| 216 | f -= self.servo_neutral | ||
| 217 | f *= self.servo_travel_angle | ||
| 218 | f /= self.servo_travel_time | ||
| 219 | return f | ||
| 220 | |||
| 221 | def AngleToRatio(self, value): | ||
| 222 | f = value | ||
| 223 | f *= self.servo_travel_time | ||
| 224 | f /= self.servo_travel_angle | ||
| 225 | f += self.servo_neutral | ||
| 226 | f /= self.period | ||
| 227 | return f | ||
| 228 | |||
| 229 | @request("GET", "pwm/%(channel)d/angle") | ||
| 230 | @response("%.2f") | ||
| 231 | def pwmReadAngle(self, channel): | ||
| 232 | f = self.pwmReadFloat(channel) | ||
| 233 | f = self.RatioToAngle(f) | ||
| 234 | if self.reverse[channel]: | ||
| 235 | f = -f | ||
| 236 | else: | ||
| 237 | f = f | ||
| 238 | return f | ||
| 239 | |||
| 240 | @request("POST", "pwm/%(channel)d/angle/%(value)f") | ||
| 241 | @response("%.2f") | ||
| 242 | def pwmWriteAngle(self, channel, value): | ||
| 243 | if self.reverse[channel]: | ||
| 244 | f = -value | ||
| 245 | else: | ||
| 246 | f = value | ||
| 247 | f = self.AngleToRatio(f) | ||
| 248 | self.pwmWriteFloat(channel, f) | ||
| 249 | return self.pwmReadAngle(channel) | ||
| 250 | |||
| 251 | @request("GET", "pwm/*") | ||
| 252 | @response(contentType=M_JSON) | ||
| 253 | def pwmWildcard(self): | ||
| 254 | values = {} | ||
| 255 | for i in range(self._pwmCount): | ||
| 256 | val = self.pwmReadFloat(i) | ||
| 257 | values[i] = {} | ||
| 258 | values[i]["float"] = float("%.2f" % val) | ||
| 259 | values[i]["angle"] = float("%.2f" % self.RatioToAngle(val)) | ||
| 260 | return values | ||
| 261 | |||
| 262 | DRIVERS = {} | ||
| 263 | DRIVERS["ads1x1x"] = ["ADS1014", "ADS1015", "ADS1114", "ADS1115"] | ||
| 264 | DRIVERS["mcp3x0x"] = ["MCP3004", "MCP3008", "MCP3204", "MCP3208"] | ||
| 265 | DRIVERS["mcp4725"] = ["MCP4725"] | ||
| 266 | DRIVERS["mcp492X"] = ["MCP4921", "MCP4922"] | ||
| 267 | DRIVERS["pca9685"] = ["PCA9685"] | ||
diff --git a/python/webiopi/devices/analog/ads1x1x.py b/python/webiopi/devices/analog/ads1x1x.py new file mode 100644 index 0000000..969fd8f --- /dev/null +++ b/python/webiopi/devices/analog/ads1x1x.py | |||
| @@ -0,0 +1,82 @@ | |||
| 1 | # Copyright 2012-2013 Eric Ptak - trouch.com | ||
| 2 | # | ||
| 3 | # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 4 | # you may not use this file except in compliance with the License. | ||
| 5 | # You may obtain a copy of the License at | ||
| 6 | # | ||
| 7 | # http://www.apache.org/licenses/LICENSE-2.0 | ||
| 8 | # | ||
| 9 | # Unless required by applicable law or agreed to in writing, software | ||
| 10 | # distributed under the License is distributed on an "AS IS" BASIS, | ||
| 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 12 | # See the License for the specific language governing permissions and | ||
| 13 | # limitations under the License. | ||
| 14 | |||
| 15 | from time import sleep | ||
| 16 | from webiopi.utils.types import toint, signInteger | ||
| 17 | from webiopi.devices.i2c import I2C | ||
| 18 | from webiopi.devices.analog import ADC | ||
| 19 | |||
| 20 | |||
| 21 | class ADS1X1X(ADC, I2C): | ||
| 22 | VALUE = 0x00 | ||
| 23 | CONFIG = 0x01 | ||
| 24 | LO_THRESH = 0x02 | ||
| 25 | HI_THRESH = 0x03 | ||
| 26 | |||
| 27 | CONFIG_STATUS_MASK = 0x80 | ||
| 28 | CONFIG_CHANNEL_MASK = 0x70 | ||
| 29 | CONFIG_GAIN_MASK = 0x0E | ||
| 30 | CONFIG_MODE_MASK = 0x01 | ||
| 31 | |||
| 32 | def __init__(self, slave, channelCount, resolution, name): | ||
| 33 | I2C.__init__(self, toint(slave)) | ||
| 34 | ADC.__init__(self, channelCount, resolution, 4.096) | ||
| 35 | self._analogMax = 2**(resolution-1) | ||
| 36 | self.name = name | ||
| 37 | |||
| 38 | config = self.readRegisters(self.CONFIG, 2) | ||
| 39 | |||
| 40 | mode = 0 # continuous | ||
| 41 | config[0] &= ~self.CONFIG_MODE_MASK | ||
| 42 | config[0] |= mode | ||
| 43 | |||
| 44 | gain = 0x1 # FS = +/- 4.096V | ||
| 45 | config[0] &= ~self.CONFIG_GAIN_MASK | ||
| 46 | config[0] |= gain << 1 | ||
| 47 | |||
| 48 | self.writeRegisters(self.CONFIG, config) | ||
| 49 | |||
| 50 | def __str__(self): | ||
| 51 | return "%s(slave=0x%02X)" % (self.name, self.slave) | ||
| 52 | |||
| 53 | def __analogRead__(self, channel, diff=False): | ||
| 54 | config = self.readRegisters(self.CONFIG, 2) | ||
| 55 | config[0] &= ~self.CONFIG_CHANNEL_MASK | ||
| 56 | if diff: | ||
| 57 | config[0] |= channel << 4 | ||
| 58 | else: | ||
| 59 | config[0] |= (channel + 4) << 4 | ||
| 60 | self.writeRegisters(self.CONFIG, config) | ||
| 61 | sleep(0.001) | ||
| 62 | d = self.readRegisters(self.VALUE, 2) | ||
| 63 | value = (d[0] << 8 | d[1]) >> (16-self._analogResolution) | ||
| 64 | return signInteger(value, self._analogResolution) | ||
| 65 | |||
| 66 | |||
| 67 | class ADS1014(ADS1X1X): | ||
| 68 | def __init__(self, slave=0x48): | ||
| 69 | ADS1X1X.__init__(self, slave, 1, 12, "ADS1014") | ||
| 70 | |||
| 71 | class ADS1015(ADS1X1X): | ||
| 72 | def __init__(self, slave=0x48): | ||
| 73 | ADS1X1X.__init__(self, slave, 4, 12, "ADS1015") | ||
| 74 | |||
| 75 | class ADS1114(ADS1X1X): | ||
| 76 | def __init__(self, slave=0x48): | ||
| 77 | ADS1X1X.__init__(self, slave, 1, 16, "ADS1114") | ||
| 78 | |||
| 79 | class ADS1115(ADS1X1X): | ||
| 80 | def __init__(self, slave=0x48): | ||
| 81 | ADS1X1X.__init__(self, slave, 4, 16, "ADS1115") | ||
| 82 | |||
diff --git a/python/webiopi/devices/analog/mcp3x0x.py b/python/webiopi/devices/analog/mcp3x0x.py new file mode 100644 index 0000000..6a23bf0 --- /dev/null +++ b/python/webiopi/devices/analog/mcp3x0x.py | |||
| @@ -0,0 +1,75 @@ | |||
| 1 | # Copyright 2012-2013 Eric Ptak - trouch.com | ||
| 2 | # | ||
| 3 | # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 4 | # you may not use this file except in compliance with the License. | ||
| 5 | # You may obtain a copy of the License at | ||
| 6 | # | ||
| 7 | # http://www.apache.org/licenses/LICENSE-2.0 | ||
| 8 | # | ||
| 9 | # Unless required by applicable law or agreed to in writing, software | ||
| 10 | # distributed under the License is distributed on an "AS IS" BASIS, | ||
| 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 12 | # See the License for the specific language governing permissions and | ||
| 13 | # limitations under the License. | ||
| 14 | |||
| 15 | from webiopi.utils.types import toint | ||
| 16 | from webiopi.devices.spi import SPI | ||
| 17 | from webiopi.devices.analog import ADC | ||
| 18 | |||
| 19 | class MCP3X0X(SPI, ADC): | ||
| 20 | def __init__(self, chip, channelCount, resolution, name): | ||
| 21 | SPI.__init__(self, toint(chip), 0, 8, 10000) | ||
| 22 | ADC.__init__(self, channelCount, resolution, 3.3) | ||
| 23 | self.name = name | ||
| 24 | self.MSB_MASK = 2**(resolution-8) - 1 | ||
| 25 | |||
| 26 | def __str__(self): | ||
| 27 | return "%s(chip=%d)" % (self.name, self.chip) | ||
| 28 | |||
| 29 | def __analogRead__(self, channel, diff): | ||
| 30 | data = self.__command__(channel, diff) | ||
| 31 | r = self.xfer(data) | ||
| 32 | return ((r[1] & self.MSB_MASK) << 8) | r[2] | ||
| 33 | |||
| 34 | class MCP300X(MCP3X0X): | ||
| 35 | def __init__(self, chip, channelCount, name): | ||
| 36 | MCP3X0X.__init__(self, chip, channelCount, 10, name) | ||
| 37 | |||
| 38 | def __command__(self, channel, diff): | ||
| 39 | d = [0x00, 0x00, 0x00] | ||
| 40 | d[0] |= 1 | ||
| 41 | d[1] |= (not diff) << 7 | ||
| 42 | d[1] |= ((channel >> 2) & 0x01) << 6 | ||
| 43 | d[1] |= ((channel >> 1) & 0x01) << 5 | ||
| 44 | d[1] |= ((channel >> 0) & 0x01) << 4 | ||
| 45 | return d | ||
| 46 | |||
| 47 | class MCP3004(MCP300X): | ||
| 48 | def __init__(self, chip=0): | ||
| 49 | MCP300X.__init__(self, chip, 4, "MCP3004") | ||
| 50 | |||
| 51 | class MCP3008(MCP300X): | ||
| 52 | def __init__(self, chip=0): | ||
| 53 | MCP300X.__init__(self, chip, 8, "MCP3008") | ||
| 54 | |||
| 55 | class MCP320X(MCP3X0X): | ||
| 56 | def __init__(self, chip, channelCount, name): | ||
| 57 | MCP3X0X.__init__(self, chip, channelCount, 12, name) | ||
| 58 | |||
| 59 | def __command__(self, channel, diff): | ||
| 60 | d = [0x00, 0x00, 0x00] | ||
| 61 | d[0] |= 1 << 2 | ||
| 62 | d[0] |= (not diff) << 1 | ||
| 63 | d[0] |= (channel >> 2) & 0x01 | ||
| 64 | d[1] |= ((channel >> 1) & 0x01) << 7 | ||
| 65 | d[1] |= ((channel >> 0) & 0x01) << 6 | ||
| 66 | return d | ||
| 67 | |||
| 68 | class MCP3204(MCP320X): | ||
| 69 | def __init__(self, chip=0): | ||
| 70 | MCP320X.__init__(self, chip, 4, "MCP3204") | ||
| 71 | |||
| 72 | class MCP3208(MCP320X): | ||
| 73 | def __init__(self, chip=0): | ||
| 74 | MCP320X.__init__(self, chip, 8, "MCP3208") | ||
| 75 | |||
diff --git a/python/webiopi/devices/analog/mcp4725.py b/python/webiopi/devices/analog/mcp4725.py new file mode 100644 index 0000000..a46337d --- /dev/null +++ b/python/webiopi/devices/analog/mcp4725.py | |||
| @@ -0,0 +1,38 @@ | |||
| 1 | # Copyright 2012-2013 Eric Ptak - trouch.com | ||
| 2 | # | ||
| 3 | # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 4 | # you may not use this file except in compliance with the License. | ||
| 5 | # You may obtain a copy of the License at | ||
| 6 | # | ||
| 7 | # http://www.apache.org/licenses/LICENSE-2.0 | ||
| 8 | # | ||
| 9 | # Unless required by applicable law or agreed to in writing, software | ||
| 10 | # distributed under the License is distributed on an "AS IS" BASIS, | ||
| 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 12 | # See the License for the specific language governing permissions and | ||
| 13 | # limitations under the License. | ||
| 14 | |||
| 15 | from webiopi.utils.types import toint | ||
| 16 | from webiopi.devices.i2c import I2C | ||
| 17 | from webiopi.devices.analog import DAC | ||
| 18 | |||
| 19 | |||
| 20 | class MCP4725(DAC, I2C): | ||
| 21 | def __init__(self, slave=0x60): | ||
| 22 | I2C.__init__(self, toint(slave)) | ||
| 23 | DAC.__init__(self, 1, 12, 3.3) | ||
| 24 | |||
| 25 | def __str__(self): | ||
| 26 | return "MCP4725(slave=0x%02X)" % self.slave | ||
| 27 | |||
| 28 | def __analogRead__(self, channel, diff=False): | ||
| 29 | d = self.readBytes(3) | ||
| 30 | value = (d[1] << 8 | d[2]) >> 4 | ||
| 31 | return value | ||
| 32 | |||
| 33 | |||
| 34 | def __analogWrite__(self, channel, value): | ||
| 35 | d = bytearray(2) | ||
| 36 | d[0] = (value >> 8) & 0x0F | ||
| 37 | d[1] = value & 0xFF | ||
| 38 | self.writeBytes(d) \ No newline at end of file | ||
diff --git a/python/webiopi/devices/analog/mcp492X.py b/python/webiopi/devices/analog/mcp492X.py new file mode 100644 index 0000000..4489149 --- /dev/null +++ b/python/webiopi/devices/analog/mcp492X.py | |||
| @@ -0,0 +1,53 @@ | |||
| 1 | # Copyright 2012-2013 Eric Ptak - trouch.com | ||
| 2 | # | ||
| 3 | # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 4 | # you may not use this file except in compliance with the License. | ||
| 5 | # You may obtain a copy of the License at | ||
| 6 | # | ||
| 7 | # http://www.apache.org/licenses/LICENSE-2.0 | ||
| 8 | # | ||
| 9 | # Unless required by applicable law or agreed to in writing, software | ||
| 10 | # distributed under the License is distributed on an "AS IS" BASIS, | ||
| 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 12 | # See the License for the specific language governing permissions and | ||
| 13 | # limitations under the License. | ||
| 14 | |||
| 15 | from webiopi.utils.types import toint | ||
| 16 | from webiopi.devices.spi import SPI | ||
| 17 | from webiopi.devices.analog import DAC | ||
| 18 | |||
| 19 | class MCP492X(SPI, DAC): | ||
| 20 | def __init__(self, chip, channelCount): | ||
| 21 | SPI.__init__(self, toint(chip), 0, 8, 10000000) | ||
| 22 | DAC.__init__(self, channelCount, 12, 3.3) | ||
| 23 | self.buffered=False | ||
| 24 | self.gain=False | ||
| 25 | self.shutdown=False | ||
| 26 | self.values = [0 for i in range(channelCount)] | ||
| 27 | |||
| 28 | def __str__(self): | ||
| 29 | return "MCP492%d(chip=%d)" % (self._analogCount, self.chip) | ||
| 30 | |||
| 31 | def __analogRead__(self, channel, diff=False): | ||
| 32 | return self.values[channel] | ||
| 33 | |||
| 34 | def __analogWrite__(self, channel, value): | ||
| 35 | d = bytearray(2) | ||
| 36 | d[0] = 0 | ||
| 37 | d[0] |= (channel & 0x01) << 7 | ||
| 38 | d[0] |= (self.buffered & 0x01) << 6 | ||
| 39 | d[0] |= (not self.gain & 0x01) << 5 | ||
| 40 | d[0] |= (not self.shutdown & 0x01) << 4 | ||
| 41 | d[0] |= (value >> 8) & 0x0F | ||
| 42 | d[1] = value & 0xFF | ||
| 43 | self.writeBytes(d) | ||
| 44 | self.values[channel] = value | ||
| 45 | |||
| 46 | class MCP4921(MCP492X): | ||
| 47 | def __init__(self, chip=0): | ||
| 48 | MCP492X.__init__(self, chip, 1) | ||
| 49 | |||
| 50 | class MCP4922(MCP492X): | ||
| 51 | def __init__(self, chip=0): | ||
| 52 | MCP492X.__init__(self, chip, 2) | ||
| 53 | |||
diff --git a/python/webiopi/devices/analog/pca9685.py b/python/webiopi/devices/analog/pca9685.py new file mode 100644 index 0000000..3b91121 --- /dev/null +++ b/python/webiopi/devices/analog/pca9685.py | |||
| @@ -0,0 +1,63 @@ | |||
| 1 | # Copyright 2012-2013 Eric Ptak - trouch.com | ||
| 2 | # | ||
| 3 | # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 4 | # you may not use this file except in compliance with the License. | ||
| 5 | # You may obtain a copy of the License at | ||
| 6 | # | ||
| 7 | # http://www.apache.org/licenses/LICENSE-2.0 | ||
| 8 | # | ||
| 9 | # Unless required by applicable law or agreed to in writing, software | ||
| 10 | # distributed under the License is distributed on an "AS IS" BASIS, | ||
| 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 12 | # See the License for the specific language governing permissions and | ||
| 13 | # limitations under the License. | ||
| 14 | |||
| 15 | import time | ||
| 16 | from webiopi.utils.types import toint | ||
| 17 | from webiopi.devices.i2c import I2C | ||
| 18 | from webiopi.devices.analog import PWM | ||
| 19 | |||
| 20 | class PCA9685(PWM, I2C): | ||
| 21 | MODE1 = 0x00 | ||
| 22 | PWM_BASE = 0x06 | ||
| 23 | PRESCALE = 0xFE | ||
| 24 | |||
| 25 | M1_SLEEP = 1<<4 | ||
| 26 | M1_AI = 1<<5 | ||
| 27 | M1_RESTART = 1<<7 | ||
| 28 | |||
| 29 | def __init__(self, slave=0x40, frequency=50): | ||
| 30 | I2C.__init__(self, toint(slave)) | ||
| 31 | PWM.__init__(self, 16, 12, toint(frequency)) | ||
| 32 | self.VREF = 0 | ||
| 33 | |||
| 34 | self.prescale = int(25000000.0/((2**12)*self.frequency)) | ||
| 35 | self.mode1 = self.M1_RESTART | self.M1_AI | ||
| 36 | |||
| 37 | self.writeRegister(self.MODE1, self.M1_SLEEP) | ||
| 38 | self.writeRegister(self.PRESCALE, self.prescale) | ||
| 39 | time.sleep(0.01) | ||
| 40 | |||
| 41 | self.writeRegister(self.MODE1, self.mode1) | ||
| 42 | |||
| 43 | def __str__(self): | ||
| 44 | return "PCA9685(slave=0x%02X)" % self.slave | ||
| 45 | |||
| 46 | def getChannelAddress(self, channel): | ||
| 47 | return int(channel * 4 + self.PWM_BASE) | ||
| 48 | |||
| 49 | def __pwmRead__(self, channel): | ||
| 50 | addr = self.getChannelAddress(channel) | ||
| 51 | d = self.readRegisters(addr, 4) | ||
| 52 | start = d[1] << 8 | d[0] | ||
| 53 | end = d[3] << 8 | d[2] | ||
| 54 | return end-start | ||
| 55 | |||
| 56 | def __pwmWrite__(self, channel, value): | ||
| 57 | addr = self.getChannelAddress(channel) | ||
| 58 | d = bytearray(4) | ||
| 59 | d[0] = 0 | ||
| 60 | d[1] = 0 | ||
| 61 | d[2] = (value & 0x0FF) | ||
| 62 | d[3] = (value & 0xF00) >> 8 | ||
| 63 | self.writeRegisters(addr, d) | ||
diff --git a/python/webiopi/devices/bus.py b/python/webiopi/devices/bus.py new file mode 100644 index 0000000..cd271e0 --- /dev/null +++ b/python/webiopi/devices/bus.py | |||
| @@ -0,0 +1,117 @@ | |||
| 1 | # Copyright 2012-2013 Eric Ptak - trouch.com | ||
| 2 | # | ||
| 3 | # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 4 | # you may not use this file except in compliance with the License. | ||
| 5 | # You may obtain a copy of the License at | ||
| 6 | # | ||
| 7 | # http://www.apache.org/licenses/LICENSE-2.0 | ||
| 8 | # | ||
| 9 | # Unless required by applicable law or agreed to in writing, software | ||
| 10 | # distributed under the License is distributed on an "AS IS" BASIS, | ||
| 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 12 | # See the License for the specific language governing permissions and | ||
| 13 | # limitations under the License. | ||
| 14 | |||
| 15 | import os | ||
| 16 | import time | ||
| 17 | import subprocess | ||
| 18 | |||
| 19 | from webiopi.utils.logger import debug, info | ||
| 20 | |||
| 21 | BUSLIST = { | ||
| 22 | "I2C": {"enabled": False, "gpio": {0:"SDA", 1:"SCL", 2:"SDA", 3:"SCL"}, "modules": ["i2c-bcm2708", "i2c-dev"]}, | ||
| 23 | "SPI": {"enabled": False, "gpio": {7:"CE1", 8:"CE0", 9:"MISO", 10:"MOSI", 11:"SCLK"}, "modules": ["spi-bcm2708", "spidev"]}, | ||
| 24 | "UART": {"enabled": False, "gpio": {14:"TX", 15:"RX"}}, | ||
| 25 | "ONEWIRE": {"enabled": False, "gpio": {4:"DATA"}, "modules": ["w1-gpio"], "wait": 2} | ||
| 26 | } | ||
| 27 | |||
| 28 | def loadModule(module): | ||
| 29 | debug("Loading module : %s" % module) | ||
| 30 | subprocess.call(["modprobe", module]) | ||
| 31 | |||
| 32 | def unloadModule(module): | ||
| 33 | subprocess.call(["modprobe", "-r", module]) | ||
| 34 | |||
| 35 | def loadModules(bus): | ||
| 36 | if BUSLIST[bus]["enabled"] == False and not modulesLoaded(bus): | ||
| 37 | info("Loading %s modules" % bus) | ||
| 38 | for module in BUSLIST[bus]["modules"]: | ||
| 39 | loadModule(module) | ||
| 40 | if "wait" in BUSLIST[bus]: | ||
| 41 | info("Sleeping %ds to let %s modules load" % (BUSLIST[bus]["wait"], bus)) | ||
| 42 | time.sleep(BUSLIST[bus]["wait"]) | ||
| 43 | |||
| 44 | BUSLIST[bus]["enabled"] = True | ||
| 45 | |||
| 46 | def unloadModules(bus): | ||
| 47 | info("Unloading %s modules" % bus) | ||
| 48 | for module in BUSLIST[bus]["modules"]: | ||
| 49 | unloadModule(module) | ||
| 50 | BUSLIST[bus]["enabled"] = False | ||
| 51 | |||
| 52 | def __modulesLoaded__(modules, lines): | ||
| 53 | if len(modules) == 0: | ||
| 54 | return True | ||
| 55 | for line in lines: | ||
| 56 | if modules[0].replace("-", "_") == line.split(" ")[0]: | ||
| 57 | return __modulesLoaded__(modules[1:], lines) | ||
| 58 | return False | ||
| 59 | |||
| 60 | def modulesLoaded(bus): | ||
| 61 | if not bus in BUSLIST or not "modules" in BUSLIST[bus]: | ||
| 62 | return True | ||
| 63 | |||
| 64 | try: | ||
| 65 | with open("/proc/modules") as f: | ||
| 66 | c = f.read() | ||
| 67 | f.close() | ||
| 68 | lines = c.split("\n") | ||
| 69 | return __modulesLoaded__(BUSLIST[bus]["modules"], lines) | ||
| 70 | except: | ||
| 71 | return False | ||
| 72 | |||
| 73 | def checkAllBus(): | ||
| 74 | for bus in BUSLIST: | ||
| 75 | if modulesLoaded(bus): | ||
| 76 | BUSLIST[bus]["enabled"] = True | ||
| 77 | |||
| 78 | class Bus(): | ||
| 79 | def __init__(self, busName, device, flag=os.O_RDWR): | ||
| 80 | loadModules(busName) | ||
| 81 | self.busName = busName | ||
| 82 | self.device = device | ||
| 83 | self.flag = flag | ||
| 84 | self.fd = 0 | ||
| 85 | self.open() | ||
| 86 | |||
| 87 | def open(self): | ||
| 88 | self.fd = os.open(self.device, self.flag) | ||
| 89 | if self.fd < 0: | ||
| 90 | raise Exception("Cannot open %s" % self.device) | ||
| 91 | |||
| 92 | def close(self): | ||
| 93 | if self.fd > 0: | ||
| 94 | os.close(self.fd) | ||
| 95 | |||
| 96 | def read(self, size=1): | ||
| 97 | if self.fd > 0: | ||
| 98 | return os.read(self.fd, size) | ||
| 99 | raise Exception("Device %s not open" % self.device) | ||
| 100 | |||
| 101 | def readBytes(self, size=1): | ||
| 102 | return bytearray(self.read(size)) | ||
| 103 | |||
| 104 | def readByte(self): | ||
| 105 | return self.readBytes()[0] | ||
| 106 | |||
| 107 | def write(self, string): | ||
| 108 | if self.fd > 0: | ||
| 109 | return os.write(self.fd, string) | ||
| 110 | raise Exception("Device %s not open" % self.device) | ||
| 111 | |||
| 112 | def writeBytes(self, data): | ||
| 113 | return self.write(bytearray(data)) | ||
| 114 | |||
| 115 | def writeByte(self, value): | ||
| 116 | self.writeBytes([value]) | ||
| 117 | |||
diff --git a/python/webiopi/devices/digital/__init__.py b/python/webiopi/devices/digital/__init__.py new file mode 100644 index 0000000..2c992b3 --- /dev/null +++ b/python/webiopi/devices/digital/__init__.py | |||
| @@ -0,0 +1,144 @@ | |||
| 1 | # Copyright 2012-2013 Eric Ptak - trouch.com | ||
| 2 | # | ||
| 3 | # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 4 | # you may not use this file except in compliance with the License. | ||
| 5 | # You may obtain a copy of the License at | ||
| 6 | # | ||
| 7 | # http://www.apache.org/licenses/LICENSE-2.0 | ||
| 8 | # | ||
| 9 | # Unless required by applicable law or agreed to in writing, software | ||
| 10 | # distributed under the License is distributed on an "AS IS" BASIS, | ||
| 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 12 | # See the License for the specific language governing permissions and | ||
| 13 | # limitations under the License. | ||
| 14 | |||
| 15 | from webiopi.decorators.rest import request, response | ||
| 16 | from webiopi.utils.types import M_JSON | ||
| 17 | |||
| 18 | class GPIOPort(): | ||
| 19 | IN = 0 | ||
| 20 | OUT = 1 | ||
| 21 | |||
| 22 | LOW = False | ||
| 23 | HIGH = True | ||
| 24 | |||
| 25 | def __init__(self, channelCount): | ||
| 26 | self.digitalChannelCount = channelCount | ||
| 27 | |||
| 28 | def checkDigitalChannel(self, channel): | ||
| 29 | if not 0 <= channel < self.digitalChannelCount: | ||
| 30 | raise ValueError("Channel %d out of range [%d..%d]" % (channel, 0, self.digitalChannelCount-1)) | ||
| 31 | |||
| 32 | def checkDigitalValue(self, value): | ||
| 33 | if not (value == 0 or value == 1): | ||
| 34 | raise ValueError("Value %d not in {0, 1}") | ||
| 35 | |||
| 36 | |||
| 37 | @request("GET", "count") | ||
| 38 | @response("%d") | ||
| 39 | def digitalCount(self): | ||
| 40 | return self.digitalChannelCount | ||
| 41 | |||
| 42 | def __family__(self): | ||
| 43 | return "GPIOPort" | ||
| 44 | |||
| 45 | def __getFunction__(self, channel): | ||
| 46 | raise NotImplementedError | ||
| 47 | |||
| 48 | def __setFunction__(self, channel, func): | ||
| 49 | raise NotImplementedError | ||
| 50 | |||
| 51 | def __digitalRead__(self, chanel): | ||
| 52 | raise NotImplementedError | ||
| 53 | |||
| 54 | def __portRead__(self): | ||
| 55 | raise NotImplementedError | ||
| 56 | |||
| 57 | def __digitalWrite__(self, chanel, value): | ||
| 58 | raise NotImplementedError | ||
| 59 | |||
| 60 | def __portWrite__(self, value): | ||
| 61 | raise NotImplementedError | ||
| 62 | |||
| 63 | def getFunction(self, channel): | ||
| 64 | self.checkDigitalChannel(channel) | ||
| 65 | return self.__getFunction__(channel) | ||
| 66 | |||
| 67 | @request("GET", "%(channel)d/function") | ||
| 68 | def getFunctionString(self, channel): | ||
| 69 | func = self.getFunction(channel) | ||
| 70 | if func == self.IN: | ||
| 71 | return "IN" | ||
| 72 | elif func == self.OUT: | ||
| 73 | return "OUT" | ||
| 74 | # elif func == GPIO.PWM: | ||
| 75 | # return "PWM" | ||
| 76 | else: | ||
| 77 | return "UNKNOWN" | ||
| 78 | |||
| 79 | def setFunction(self, channel, value): | ||
| 80 | self.checkDigitalChannel(channel) | ||
| 81 | self.__setFunction__(channel, value) | ||
| 82 | return self.getFunction(channel) | ||
| 83 | |||
| 84 | @request("POST", "%(channel)d/function/%(value)s") | ||
| 85 | def setFunctionString(self, channel, value): | ||
| 86 | value = value.lower() | ||
| 87 | if value == "in": | ||
| 88 | self.setFunction(channel, self.IN) | ||
| 89 | elif value == "out": | ||
| 90 | self.setFunction(channel, self.OUT) | ||
| 91 | # elif value == "pwm": | ||
| 92 | # self.setFunction(channel, GPIO.PWM) | ||
| 93 | else: | ||
| 94 | raise ValueError("Bad Function") | ||
| 95 | return self.getFunctionString(channel) | ||
| 96 | |||
| 97 | @request("GET", "%(channel)d/value") | ||
| 98 | @response("%d") | ||
| 99 | def digitalRead(self, channel): | ||
| 100 | self.checkDigitalChannel(channel) | ||
| 101 | return self.__digitalRead__(channel) | ||
| 102 | |||
| 103 | @request("GET", "*") | ||
| 104 | @response(contentType=M_JSON) | ||
| 105 | def wildcard(self, compact=False): | ||
| 106 | if compact: | ||
| 107 | f = "f" | ||
| 108 | v = "v" | ||
| 109 | else: | ||
| 110 | f = "function" | ||
| 111 | v = "value" | ||
| 112 | |||
| 113 | values = {} | ||
| 114 | for i in range(self.digitalChannelCount): | ||
| 115 | if compact: | ||
| 116 | func = self.getFunction(i) | ||
| 117 | else: | ||
| 118 | func = self.getFunctionString(i) | ||
| 119 | values[i] = {f: func, v: int(self.digitalRead(i))} | ||
| 120 | return values | ||
| 121 | |||
| 122 | @request("GET", "*/integer") | ||
| 123 | @response("%d") | ||
| 124 | def portRead(self): | ||
| 125 | return self.__portRead__() | ||
| 126 | |||
| 127 | @request("POST", "%(channel)d/value/%(value)d") | ||
| 128 | @response("%d") | ||
| 129 | def digitalWrite(self, channel, value): | ||
| 130 | self.checkDigitalChannel(channel) | ||
| 131 | self.checkDigitalValue(value) | ||
| 132 | self.__digitalWrite__(channel, value) | ||
| 133 | return self.digitalRead(channel) | ||
| 134 | |||
| 135 | @request("POST", "*/integer/%(value)d") | ||
| 136 | @response("%d") | ||
| 137 | def portWrite(self, value): | ||
| 138 | self.__portWrite__(value) | ||
| 139 | return self.portRead() | ||
| 140 | |||
| 141 | DRIVERS = {} | ||
| 142 | DRIVERS["mcp23XXX"] = ["MCP23008", "MCP23009", "MCP23017", "MCP23018", "MCP23S08", "MCP23S09", "MCP23S17", "MCP23S18"] | ||
| 143 | DRIVERS["pcf8574" ] = ["PCF8574", "PCF8574A"] | ||
| 144 | DRIVERS["ds2408" ] = ["DS2408"] | ||
diff --git a/python/webiopi/devices/digital/ds2408.py b/python/webiopi/devices/digital/ds2408.py new file mode 100644 index 0000000..4784593 --- /dev/null +++ b/python/webiopi/devices/digital/ds2408.py | |||
| @@ -0,0 +1,84 @@ | |||
| 1 | # Copyright 2013 Stuart Marsden | ||
| 2 | # | ||
| 3 | # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 4 | # you may not use this file except in compliance with the License. | ||
| 5 | # You may obtain a copy of the License at | ||
| 6 | # | ||
| 7 | # http://www.apache.org/licenses/LICENSE-2.0 | ||
| 8 | # | ||
| 9 | # Unless required by applicable law or agreed to in writing, software | ||
| 10 | # distributed under the License is distributed on an "AS IS" BASIS, | ||
| 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 12 | # See the License for the specific language governing permissions and | ||
| 13 | # limitations under the License. | ||
| 14 | |||
| 15 | from webiopi.devices.onewire import OneWire | ||
| 16 | from webiopi.devices.digital import GPIOPort | ||
| 17 | |||
| 18 | class DS2408(OneWire, GPIOPort): | ||
| 19 | FUNCTIONS = [GPIOPort.IN for i in range(8)] | ||
| 20 | |||
| 21 | def __init__(self, slave=None): | ||
| 22 | OneWire.__init__(self, slave, 0x29, "2408") | ||
| 23 | GPIOPort.__init__(self, 8) | ||
| 24 | self.portWrite(0x00) | ||
| 25 | |||
| 26 | def __str__(self): | ||
| 27 | return "DS2408(slave=%s)" % self.slave | ||
| 28 | |||
| 29 | def __getFunction__(self, channel): | ||
| 30 | return self.FUNCTIONS[channel] | ||
| 31 | |||
| 32 | def __setFunction__(self, channel, value): | ||
| 33 | if not value in [self.IN, self.OUT]: | ||
| 34 | raise ValueError("Requested function not supported") | ||
| 35 | self.FUNCTIONS[channel] = value | ||
| 36 | if value == self.IN: | ||
| 37 | self.__output__(channel, 0) | ||
| 38 | |||
| 39 | def __digitalRead__(self, channel): | ||
| 40 | mask = 1 << channel | ||
| 41 | d = self.readState() | ||
| 42 | if d != None: | ||
| 43 | return (d & mask) == mask | ||
| 44 | |||
| 45 | |||
| 46 | def __digitalWrite__(self, channel, value): | ||
| 47 | mask = 1 << channel | ||
| 48 | b = self.readByte() | ||
| 49 | if value: | ||
| 50 | b |= mask | ||
| 51 | else: | ||
| 52 | b &= ~mask | ||
| 53 | self.writeByte(b) | ||
| 54 | |||
| 55 | def __portWrite__(self, value): | ||
| 56 | self.writeByte(value) | ||
| 57 | |||
| 58 | def __portRead__(self): | ||
| 59 | return self.readByte() | ||
| 60 | |||
| 61 | def readState(self): | ||
| 62 | try: | ||
| 63 | with open("/sys/bus/w1/devices/%s/state" % self.slave, "rb") as f: | ||
| 64 | data = f.read(1) | ||
| 65 | return ord(data) | ||
| 66 | except IOError: | ||
| 67 | return -1 | ||
| 68 | |||
| 69 | def readByte(self): | ||
| 70 | try: | ||
| 71 | with open("/sys/bus/w1/devices/%s/output" % self.slave, "rb") as f: | ||
| 72 | data = f.read(1) | ||
| 73 | return bytearray(data)[0] | ||
| 74 | except IOError: | ||
| 75 | return -1 | ||
| 76 | |||
| 77 | def writeByte(self, value): | ||
| 78 | try: | ||
| 79 | with open("/sys/bus/w1/devices/%s/output" % self.slave, "wb") as f: | ||
| 80 | f.write(bytearray([value])) | ||
| 81 | except IOError: | ||
| 82 | pass | ||
| 83 | |||
| 84 | |||
diff --git a/python/webiopi/devices/digital/gpio.py b/python/webiopi/devices/digital/gpio.py new file mode 100644 index 0000000..5da6cc5 --- /dev/null +++ b/python/webiopi/devices/digital/gpio.py | |||
| @@ -0,0 +1,189 @@ | |||
| 1 | # Copyright 2012-2013 Eric Ptak - trouch.com | ||
| 2 | # | ||
| 3 | # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 4 | # you may not use this file except in compliance with the License. | ||
| 5 | # You may obtain a copy of the License at | ||
| 6 | # | ||
| 7 | # http://www.apache.org/licenses/LICENSE-2.0 | ||
| 8 | # | ||
| 9 | # Unless required by applicable law or agreed to in writing, software | ||
| 10 | # distributed under the License is distributed on an "AS IS" BASIS, | ||
| 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 12 | # See the License for the specific language governing permissions and | ||
| 13 | # limitations under the License. | ||
| 14 | |||
| 15 | from webiopi.utils.types import M_JSON | ||
| 16 | from webiopi.utils.logger import debug | ||
| 17 | from webiopi.devices.digital import GPIOPort | ||
| 18 | from webiopi.decorators.rest import request, response | ||
| 19 | try: | ||
| 20 | import _webiopi.GPIO as GPIO | ||
| 21 | except: | ||
| 22 | pass | ||
| 23 | |||
| 24 | EXPORT = [] | ||
| 25 | |||
| 26 | class NativeGPIO(GPIOPort): | ||
| 27 | def __init__(self): | ||
| 28 | GPIOPort.__init__(self, 54) | ||
| 29 | self.export = range(54) | ||
| 30 | self.post_value = True | ||
| 31 | self.post_function = True | ||
| 32 | self.gpio_setup = [] | ||
| 33 | self.gpio_reset = [] | ||
| 34 | |||
| 35 | def __str__(self): | ||
| 36 | return "GPIO" | ||
| 37 | |||
| 38 | def addGPIO(self, lst, gpio, params): | ||
| 39 | gpio = int(gpio) | ||
| 40 | params = params.split(" ") | ||
| 41 | func = params[0].lower() | ||
| 42 | if func == "in": | ||
| 43 | func = GPIO.IN | ||
| 44 | elif func == "out": | ||
| 45 | func = GPIO.OUT | ||
| 46 | else: | ||
| 47 | raise Exception("Unknown function") | ||
| 48 | |||
| 49 | value = -1 | ||
| 50 | if len(params) > 1: | ||
| 51 | value = int(params[1]) | ||
| 52 | lst.append({"gpio": gpio, "func": func, "value": value}) | ||
| 53 | |||
| 54 | def addGPIOSetup(self, gpio, params): | ||
| 55 | self.addGPIO(self.gpio_setup, gpio, params) | ||
| 56 | |||
| 57 | def addGPIOReset(self, gpio, params): | ||
| 58 | self.addGPIO(self.gpio_reset, gpio, params) | ||
| 59 | |||
| 60 | def addSetups(self, gpios): | ||
| 61 | for (gpio, params) in gpios: | ||
| 62 | self.addGPIOSetup(gpio, params) | ||
| 63 | |||
| 64 | def addResets(self, gpios): | ||
| 65 | for (gpio, params) in gpios: | ||
| 66 | self.addGPIOReset(gpio, params) | ||
| 67 | |||
| 68 | def setup(self): | ||
| 69 | for g in self.gpio_setup: | ||
| 70 | gpio = g["gpio"] | ||
| 71 | debug("Setup GPIO %d" % gpio) | ||
| 72 | GPIO.setFunction(gpio, g["func"]) | ||
| 73 | if g["value"] >= 0 and GPIO.getFunction(gpio) == GPIO.OUT: | ||
| 74 | GPIO.digitalWrite(gpio, g["value"]) | ||
| 75 | |||
| 76 | def close(self): | ||
| 77 | for g in self.gpio_reset: | ||
| 78 | gpio = g["gpio"] | ||
| 79 | debug("Reset GPIO %d" % gpio) | ||
| 80 | GPIO.setFunction(gpio, g["func"]) | ||
| 81 | if g["value"] >= 0 and GPIO.getFunction(gpio) == GPIO.OUT: | ||
| 82 | GPIO.digitalWrite(gpio, g["value"]) | ||
| 83 | |||
| 84 | def checkDigitalChannelExported(self, channel): | ||
| 85 | if not channel in self.export: | ||
| 86 | raise GPIO.InvalidChannelException("Channel %d is not allowed" % channel) | ||
| 87 | |||
| 88 | def checkPostingFunctionAllowed(self): | ||
| 89 | if not self.post_function: | ||
| 90 | raise ValueError("POSTing function to native GPIO not allowed") | ||
| 91 | |||
| 92 | def checkPostingValueAllowed(self): | ||
| 93 | if not self.post_value: | ||
| 94 | raise ValueError("POSTing value to native GPIO not allowed") | ||
| 95 | |||
| 96 | def __digitalRead__(self, channel): | ||
| 97 | self.checkDigitalChannelExported(channel) | ||
| 98 | return GPIO.digitalRead(channel) | ||
| 99 | |||
| 100 | def __digitalWrite__(self, channel, value): | ||
| 101 | self.checkDigitalChannelExported(channel) | ||
| 102 | self.checkPostingValueAllowed() | ||
| 103 | GPIO.digitalWrite(channel, value) | ||
| 104 | |||
| 105 | def __getFunction__(self, channel): | ||
| 106 | self.checkDigitalChannelExported(channel) | ||
| 107 | return GPIO.getFunction(channel) | ||
| 108 | |||
| 109 | def __setFunction__(self, channel, value): | ||
| 110 | self.checkDigitalChannelExported(channel) | ||
| 111 | self.checkPostingFunctionAllowed() | ||
| 112 | GPIO.setFunction(channel, value) | ||
| 113 | |||
| 114 | def __portRead__(self): | ||
| 115 | value = 0 | ||
| 116 | for i in self.export: | ||
| 117 | value |= GPIO.digitalRead(i) << i | ||
| 118 | return value | ||
| 119 | |||
| 120 | def __portWrite__(self, value): | ||
| 121 | if len(self.export) < 54: | ||
| 122 | for i in self.export: | ||
| 123 | if GPIO.getFunction(i) == GPIO.OUT: | ||
| 124 | GPIO.digitalWrite(i, (value >> i) & 1) | ||
| 125 | else: | ||
| 126 | raise Exception("Please limit exported GPIO to write integers") | ||
| 127 | |||
| 128 | @request("GET", "*") | ||
| 129 | @response(contentType=M_JSON) | ||
| 130 | def wildcard(self, compact=False): | ||
| 131 | if compact: | ||
| 132 | f = "f" | ||
| 133 | v = "v" | ||
| 134 | else: | ||
| 135 | f = "function" | ||
| 136 | v = "value" | ||
| 137 | |||
| 138 | values = {} | ||
| 139 | print(self.export) | ||
| 140 | for i in self.export: | ||
| 141 | if compact: | ||
| 142 | func = GPIO.getFunction(i) | ||
| 143 | else: | ||
| 144 | func = GPIO.getFunctionString(i) | ||
| 145 | values[i] = {f: func, v: int(GPIO.digitalRead(i))} | ||
| 146 | return values | ||
| 147 | |||
| 148 | |||
| 149 | @request("GET", "%(channel)d/pulse", "%s") | ||
| 150 | def getPulse(self, channel): | ||
| 151 | self.checkDigitalChannelExported(channel) | ||
| 152 | self.checkDigitalChannel(channel) | ||
| 153 | return GPIO.getPulse(channel) | ||
| 154 | |||
| 155 | @request("POST", "%(channel)d/sequence/%(args)s") | ||
| 156 | @response("%d") | ||
| 157 | def outputSequence(self, channel, args): | ||
| 158 | self.checkDigitalChannelExported(channel) | ||
| 159 | self.checkPostingValueAllowed() | ||
| 160 | self.checkDigitalChannel(channel) | ||
| 161 | (period, sequence) = args.split(",") | ||
| 162 | period = int(period) | ||
| 163 | GPIO.outputSequence(channel, period, sequence) | ||
| 164 | return int(sequence[-1]) | ||
| 165 | |||
| 166 | @request("POST", "%(channel)d/pulse/") | ||
| 167 | def pulse(self, channel): | ||
| 168 | self.checkDigitalChannelExported(channel) | ||
| 169 | self.checkPostingValueAllowed() | ||
| 170 | self.checkDigitalChannel(channel) | ||
| 171 | GPIO.pulse(channel) | ||
| 172 | return "OK" | ||
| 173 | |||
| 174 | @request("POST", "%(channel)d/pulseRatio/%(value)f") | ||
| 175 | def pulseRatio(self, channel, value): | ||
| 176 | self.checkDigitalChannelExported(channel) | ||
| 177 | self.checkPostingValueAllowed() | ||
| 178 | self.checkDigitalChannel(channel) | ||
| 179 | GPIO.pulseRatio(channel, value) | ||
| 180 | return GPIO.getPulse(channel) | ||
| 181 | |||
| 182 | @request("POST", "%(channel)d/pulseAngle/%(value)f") | ||
| 183 | def pulseAngle(self, channel, value): | ||
| 184 | self.checkDigitalChannelExported(channel) | ||
| 185 | self.checkPostingValueAllowed() | ||
| 186 | self.checkDigitalChannel(channel) | ||
| 187 | GPIO.pulseAngle(channel, value) | ||
| 188 | return GPIO.getPulse(channel) | ||
| 189 | |||
diff --git a/python/webiopi/devices/digital/mcp23XXX.py b/python/webiopi/devices/digital/mcp23XXX.py new file mode 100644 index 0000000..99edd61 --- /dev/null +++ b/python/webiopi/devices/digital/mcp23XXX.py | |||
| @@ -0,0 +1,153 @@ | |||
| 1 | # Copyright 2012-2013 Eric Ptak - trouch.com | ||
| 2 | # | ||
| 3 | # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 4 | # you may not use this file except in compliance with the License. | ||
| 5 | # You may obtain a copy of the License at | ||
| 6 | # | ||
| 7 | # http://www.apache.org/licenses/LICENSE-2.0 | ||
| 8 | # | ||
| 9 | # Unless required by applicable law or agreed to in writing, software | ||
| 10 | # distributed under the License is distributed on an "AS IS" BASIS, | ||
| 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 12 | # See the License for the specific language governing permissions and | ||
| 13 | # limitations under the License. | ||
| 14 | |||
| 15 | from webiopi.utils.types import toint | ||
| 16 | from webiopi.devices.i2c import I2C | ||
| 17 | from webiopi.devices.spi import SPI | ||
| 18 | from webiopi.devices.digital import GPIOPort | ||
| 19 | |||
| 20 | class MCP23XXX(GPIOPort): | ||
| 21 | IODIR = 0x00 | ||
| 22 | IPOL = 0x01 | ||
| 23 | GPINTEN = 0x02 | ||
| 24 | DEFVAL = 0x03 | ||
| 25 | INTCON = 0x04 | ||
| 26 | IOCON = 0x05 | ||
| 27 | GPPU = 0x06 | ||
| 28 | INTF = 0x07 | ||
| 29 | INTCAP = 0x08 | ||
| 30 | GPIO = 0x09 | ||
| 31 | OLAT = 0x0A | ||
| 32 | |||
| 33 | def __init__(self, channelCount): | ||
| 34 | GPIOPort.__init__(self, channelCount) | ||
| 35 | self.banks = int(channelCount / 8) | ||
| 36 | |||
| 37 | def getAddress(self, register, channel=0): | ||
| 38 | return register * self.banks + int(channel / 8) | ||
| 39 | |||
| 40 | def getChannel(self, register, channel): | ||
| 41 | self.checkDigitalChannel(channel) | ||
| 42 | addr = self.getAddress(register, channel) | ||
| 43 | mask = 1 << (channel % 8) | ||
| 44 | return (addr, mask) | ||
| 45 | |||
| 46 | def __digitalRead__(self, channel): | ||
| 47 | (addr, mask) = self.getChannel(self.GPIO, channel) | ||
| 48 | d = self.readRegister(addr) | ||
| 49 | return (d & mask) == mask | ||
| 50 | |||
| 51 | def __digitalWrite__(self, channel, value): | ||
| 52 | (addr, mask) = self.getChannel(self.GPIO, channel) | ||
| 53 | d = self.readRegister(addr) | ||
| 54 | if value: | ||
| 55 | d |= mask | ||
| 56 | else: | ||
| 57 | d &= ~mask | ||
| 58 | self.writeRegister(addr, d) | ||
| 59 | |||
| 60 | def __getFunction__(self, channel): | ||
| 61 | (addr, mask) = self.getChannel(self.IODIR, channel) | ||
| 62 | d = self.readRegister(addr) | ||
| 63 | return self.IN if (d & mask) == mask else self.OUT | ||
| 64 | |||
| 65 | def __setFunction__(self, channel, value): | ||
| 66 | if not value in [self.IN, self.OUT]: | ||
| 67 | raise ValueError("Requested function not supported") | ||
| 68 | |||
| 69 | (addr, mask) = self.getChannel(self.IODIR, channel) | ||
| 70 | d = self.readRegister(addr) | ||
| 71 | if value == self.IN: | ||
| 72 | d |= mask | ||
| 73 | else: | ||
| 74 | d &= ~mask | ||
| 75 | self.writeRegister(addr, d) | ||
| 76 | |||
| 77 | def __portRead__(self): | ||
| 78 | value = 0 | ||
| 79 | for i in range(self.banks): | ||
| 80 | value |= self.readRegister(self.banks*self.GPIO+i) << 8*i | ||
| 81 | return value | ||
| 82 | |||
| 83 | def __portWrite__(self, value): | ||
| 84 | for i in range(self.banks): | ||
| 85 | self.writeRegister(self.banks*self.GPIO+i, (value >> 8*i) & 0xFF) | ||
| 86 | |||
| 87 | class MCP230XX(MCP23XXX, I2C): | ||
| 88 | def __init__(self, slave, channelCount, name): | ||
| 89 | I2C.__init__(self, toint(slave)) | ||
| 90 | MCP23XXX.__init__(self, channelCount) | ||
| 91 | self.name = name | ||
| 92 | |||
| 93 | def __str__(self): | ||
| 94 | return "%s(slave=0x%02X)" % (self.name, self.slave) | ||
| 95 | |||
| 96 | class MCP23008(MCP230XX): | ||
| 97 | def __init__(self, slave=0x20): | ||
| 98 | MCP230XX.__init__(self, slave, 8, "MCP23008") | ||
| 99 | |||
| 100 | class MCP23009(MCP230XX): | ||
| 101 | def __init__(self, slave=0x20): | ||
| 102 | MCP230XX.__init__(self, slave, 8, "MCP23009") | ||
| 103 | |||
| 104 | class MCP23017(MCP230XX): | ||
| 105 | def __init__(self, slave=0x20): | ||
| 106 | MCP230XX.__init__(self, slave, 16, "MCP23017") | ||
| 107 | |||
| 108 | class MCP23018(MCP230XX): | ||
| 109 | def __init__(self, slave=0x20): | ||
| 110 | MCP230XX.__init__(self, slave, 16, "MCP23018") | ||
| 111 | |||
| 112 | class MCP23SXX(MCP23XXX, SPI): | ||
| 113 | SLAVE = 0x20 | ||
| 114 | |||
| 115 | WRITE = 0x00 | ||
| 116 | READ = 0x01 | ||
| 117 | |||
| 118 | def __init__(self, chip, slave, channelCount, name): | ||
| 119 | SPI.__init__(self, toint(chip), 0, 8, 10000000) | ||
| 120 | MCP23XXX.__init__(self, channelCount) | ||
| 121 | self.slave = self.SLAVE | ||
| 122 | iocon_value = 0x08 # Hardware Address Enable | ||
| 123 | iocon_addr = self.getAddress(self.IOCON) | ||
| 124 | self.writeRegister(iocon_addr, iocon_value) | ||
| 125 | self.slave = toint(slave) | ||
| 126 | self.name = name | ||
| 127 | |||
| 128 | def __str__(self): | ||
| 129 | return "%s(chip=%d, slave=0x%02X)" % (self.name, self.chip, self.slave) | ||
| 130 | |||
| 131 | def readRegister(self, addr): | ||
| 132 | d = self.xfer([(self.slave << 1) | self.READ, addr, 0x00]) | ||
| 133 | return d[2] | ||
| 134 | |||
| 135 | def writeRegister(self, addr, value): | ||
| 136 | self.writeBytes([(self.slave << 1) | self.WRITE, addr, value]) | ||
| 137 | |||
| 138 | class MCP23S08(MCP23SXX): | ||
| 139 | def __init__(self, chip=0, slave=0x20): | ||
| 140 | MCP23SXX.__init__(self, chip, slave, 8, "MCP23S08") | ||
| 141 | |||
| 142 | class MCP23S09(MCP23SXX): | ||
| 143 | def __init__(self, chip=0, slave=0x20): | ||
| 144 | MCP23SXX.__init__(self, chip, slave, 8, "MCP23S09") | ||
| 145 | |||
| 146 | class MCP23S17(MCP23SXX): | ||
| 147 | def __init__(self, chip=0, slave=0x20): | ||
| 148 | MCP23SXX.__init__(self, chip, slave, 16, "MCP23S17") | ||
| 149 | |||
| 150 | class MCP23S18(MCP23SXX): | ||
| 151 | def __init__(self, chip=0, slave=0x20): | ||
| 152 | MCP23SXX.__init__(self, chip, slave, 16, "MCP23S18") | ||
| 153 | |||
diff --git a/python/webiopi/devices/digital/pcf8574.py b/python/webiopi/devices/digital/pcf8574.py new file mode 100644 index 0000000..146e8f4 --- /dev/null +++ b/python/webiopi/devices/digital/pcf8574.py | |||
| @@ -0,0 +1,70 @@ | |||
| 1 | # Copyright 2012-2013 Eric Ptak - trouch.com | ||
| 2 | # | ||
| 3 | # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 4 | # you may not use this file except in compliance with the License. | ||
| 5 | # You may obtain a copy of the License at | ||
| 6 | # | ||
| 7 | # http://www.apache.org/licenses/LICENSE-2.0 | ||
| 8 | # | ||
| 9 | # Unless required by applicable law or agreed to in writing, software | ||
| 10 | # distributed under the License is distributed on an "AS IS" BASIS, | ||
| 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 12 | # See the License for the specific language governing permissions and | ||
| 13 | # limitations under the License. | ||
| 14 | |||
| 15 | from webiopi.utils.types import toint | ||
| 16 | from webiopi.devices.i2c import I2C | ||
| 17 | from webiopi.devices.digital import GPIOPort | ||
| 18 | |||
| 19 | class PCF8574(I2C, GPIOPort): | ||
| 20 | FUNCTIONS = [GPIOPort.IN for i in range(8)] | ||
| 21 | |||
| 22 | def __init__(self, slave=0x20): | ||
| 23 | slave = toint(slave) | ||
| 24 | if slave in range(0x20, 0x28): | ||
| 25 | self.name = "PCF8574" | ||
| 26 | elif slave in range(0x38, 0x40): | ||
| 27 | self.name = "PCF8574A" | ||
| 28 | else: | ||
| 29 | raise ValueError("Bad slave address for PCF8574(A) : 0x%02X not in range [0x20..0x27, 0x38..0x3F]" % slave) | ||
| 30 | |||
| 31 | I2C.__init__(self, slave) | ||
| 32 | GPIOPort.__init__(self, 8) | ||
| 33 | self.portWrite(0xFF) | ||
| 34 | self.portRead() | ||
| 35 | |||
| 36 | def __str__(self): | ||
| 37 | return "%s(slave=0x%02X)" % (self.name, self.slave) | ||
| 38 | |||
| 39 | def __getFunction__(self, channel): | ||
| 40 | return self.FUNCTIONS[channel] | ||
| 41 | |||
| 42 | def __setFunction__(self, channel, value): | ||
| 43 | if not value in [self.IN, self.OUT]: | ||
| 44 | raise ValueError("Requested function not supported") | ||
| 45 | self.FUNCTIONS[channel] = value | ||
| 46 | |||
| 47 | def __digitalRead__(self, channel): | ||
| 48 | mask = 1 << channel | ||
| 49 | d = self.readByte() | ||
| 50 | return (d & mask) == mask | ||
| 51 | |||
| 52 | def __portRead__(self): | ||
| 53 | return self.readByte() | ||
| 54 | |||
| 55 | def __digitalWrite__(self, channel, value): | ||
| 56 | mask = 1 << channel | ||
| 57 | b = self.readByte() | ||
| 58 | if value: | ||
| 59 | b |= mask | ||
| 60 | else: | ||
| 61 | b &= ~mask | ||
| 62 | self.writeByte(b) | ||
| 63 | |||
| 64 | def __portWrite__(self, value): | ||
| 65 | self.writeByte(value) | ||
| 66 | |||
| 67 | class PCF8574A(PCF8574): | ||
| 68 | def __init__(self, slave=0x38): | ||
| 69 | PCF8574.__init__(self, slave) | ||
| 70 | |||
diff --git a/python/webiopi/devices/i2c.py b/python/webiopi/devices/i2c.py new file mode 100644 index 0000000..1b3196a --- /dev/null +++ b/python/webiopi/devices/i2c.py | |||
| @@ -0,0 +1,75 @@ | |||
| 1 | # Copyright 2012-2013 Eric Ptak - trouch.com | ||
| 2 | # | ||
| 3 | # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 4 | # you may not use this file except in compliance with the License. | ||
| 5 | # You may obtain a copy of the License at | ||
| 6 | # | ||
| 7 | # http://www.apache.org/licenses/LICENSE-2.0 | ||
| 8 | # | ||
| 9 | # Unless required by applicable law or agreed to in writing, software | ||
| 10 | # distributed under the License is distributed on an "AS IS" BASIS, | ||
| 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 12 | # See the License for the specific language governing permissions and | ||
| 13 | # limitations under the License. | ||
| 14 | |||
| 15 | import fcntl | ||
| 16 | |||
| 17 | from webiopi.utils.version import BOARD_REVISION | ||
| 18 | from webiopi.devices.bus import Bus | ||
| 19 | |||
| 20 | # /dev/i2c-X ioctl commands. The ioctl's parameter is always an | ||
| 21 | # unsigned long, except for: | ||
| 22 | # - I2C_FUNCS, takes pointer to an unsigned long | ||
| 23 | # - I2C_RDWR, takes pointer to struct i2c_rdwr_ioctl_data | ||
| 24 | # - I2C_SMBUS, takes pointer to struct i2c_smbus_ioctl_data | ||
| 25 | |||
| 26 | I2C_RETRIES = 0x0701 # number of times a device address should | ||
| 27 | # be polled when not acknowledging | ||
| 28 | I2C_TIMEOUT = 0x0702 # set timeout in units of 10 ms | ||
| 29 | |||
| 30 | # NOTE: Slave address is 7 or 10 bits, but 10-bit addresses | ||
| 31 | # are NOT supported! (due to code brokenness) | ||
| 32 | |||
| 33 | I2C_SLAVE = 0x0703 # Use this slave address | ||
| 34 | I2C_SLAVE_FORCE = 0x0706 # Use this slave address, even if it | ||
| 35 | # is already in use by a driver! | ||
| 36 | I2C_TENBIT = 0x0704 # 0 for 7 bit addrs, != 0 for 10 bit | ||
| 37 | |||
| 38 | I2C_FUNCS = 0x0705 # Get the adapter functionality mask | ||
| 39 | |||
| 40 | I2C_RDWR = 0x0707 # Combined R/W transfer (one STOP only) | ||
| 41 | |||
| 42 | I2C_PEC = 0x0708 # != 0 to use PEC with SMBus | ||
| 43 | I2C_SMBUS = 0x0720 # SMBus transfer */ | ||
| 44 | |||
| 45 | |||
| 46 | class I2C(Bus): | ||
| 47 | def __init__(self, slave): | ||
| 48 | self.channel = 0 | ||
| 49 | if BOARD_REVISION > 1: | ||
| 50 | self.channel = 1 | ||
| 51 | |||
| 52 | Bus.__init__(self, "I2C", "/dev/i2c-%d" % self.channel) | ||
| 53 | self.slave = slave | ||
| 54 | if fcntl.ioctl(self.fd, I2C_SLAVE, self.slave): | ||
| 55 | raise Exception("Error binding I2C slave 0x%02X" % self.slave) | ||
| 56 | |||
| 57 | def __str__(self): | ||
| 58 | return "I2C(slave=0x%02X)" % self.slave | ||
| 59 | |||
| 60 | def readRegister(self, addr): | ||
| 61 | self.writeByte(addr) | ||
| 62 | return self.readByte() | ||
| 63 | |||
| 64 | def readRegisters(self, addr, count): | ||
| 65 | self.writeByte(addr) | ||
| 66 | return self.readBytes(count) | ||
| 67 | |||
| 68 | def writeRegister(self, addr, byte): | ||
| 69 | self.writeBytes([addr, byte]) | ||
| 70 | |||
| 71 | def writeRegisters(self, addr, buff): | ||
| 72 | d = bytearray(len(buff)+1) | ||
| 73 | d[0] = addr | ||
| 74 | d[1:] = buff | ||
| 75 | self.writeBytes(d) | ||
diff --git a/python/webiopi/devices/instance.py b/python/webiopi/devices/instance.py new file mode 100644 index 0000000..633a8c3 --- /dev/null +++ b/python/webiopi/devices/instance.py | |||
| @@ -0,0 +1,6 @@ | |||
| 1 | DEVICES = {} | ||
| 2 | def deviceInstance(name): | ||
| 3 | if name in DEVICES: | ||
| 4 | return DEVICES[name]["device"] | ||
| 5 | else: | ||
| 6 | return None | ||
diff --git a/python/webiopi/devices/manager.py b/python/webiopi/devices/manager.py new file mode 100644 index 0000000..0f1e521 --- /dev/null +++ b/python/webiopi/devices/manager.py | |||
| @@ -0,0 +1,77 @@ | |||
| 1 | import imp | ||
| 2 | from webiopi.utils import logger | ||
| 3 | from webiopi.utils import types | ||
| 4 | from webiopi.devices.instance import DEVICES | ||
| 5 | |||
| 6 | from webiopi.devices import serial, digital, analog, sensor, shield | ||
| 7 | |||
| 8 | PACKAGES = [serial, digital, analog, sensor, shield] | ||
| 9 | def findDeviceClass(name): | ||
| 10 | for package in PACKAGES: | ||
| 11 | if hasattr(package, name): | ||
| 12 | return getattr(package, name) | ||
| 13 | if hasattr(package, "DRIVERS"): | ||
| 14 | for driver in package.DRIVERS: | ||
| 15 | if name in package.DRIVERS[driver]: | ||
| 16 | (fp, pathname, stuff) = imp.find_module(package.__name__.replace(".", "/") + "/" + driver) | ||
| 17 | module = imp.load_module(driver, fp, pathname, stuff) | ||
| 18 | return getattr(module, name) | ||
| 19 | return None | ||
| 20 | |||
| 21 | def addDevice(name, device, args): | ||
| 22 | devClass = findDeviceClass(device) | ||
| 23 | if devClass == None: | ||
| 24 | raise Exception("Device driver not found for %s" % device) | ||
| 25 | if len(args) > 0: | ||
| 26 | dev = devClass(**args) | ||
| 27 | else: | ||
| 28 | dev = devClass() | ||
| 29 | addDeviceInstance(name, dev, args) | ||
| 30 | |||
| 31 | def addDeviceInstance(name, dev, args): | ||
| 32 | funcs = {"GET": {}, "POST": {}} | ||
| 33 | for att in dir(dev): | ||
| 34 | func = getattr(dev, att) | ||
| 35 | if callable(func) and hasattr(func, "routed"): | ||
| 36 | if name == "GPIO": | ||
| 37 | logger.debug("Mapping %s.%s to REST %s /GPIO/%s" % (dev, att, func.method, func.path)) | ||
| 38 | else: | ||
| 39 | logger.debug("Mapping %s.%s to REST %s /devices/%s/%s" % (dev, att, func.method, name, func.path)) | ||
| 40 | funcs[func.method][func.path] = func | ||
| 41 | |||
| 42 | DEVICES[name] = {'device': dev, 'functions': funcs} | ||
| 43 | if name == "GPIO": | ||
| 44 | logger.info("GPIO - Native mapped to REST API /GPIO") | ||
| 45 | else: | ||
| 46 | logger.info("%s - %s mapped to REST API /devices/%s" % (dev.__family__(), dev, name)) | ||
| 47 | |||
| 48 | def closeDevices(): | ||
| 49 | devices = [k for k in DEVICES.keys()] | ||
| 50 | for name in devices: | ||
| 51 | device = DEVICES[name]["device"] | ||
| 52 | logger.debug("Closing device %s - %s" % (name, device)) | ||
| 53 | del DEVICES[name] | ||
| 54 | device.close() | ||
| 55 | |||
| 56 | def getDevicesJSON(compact=False): | ||
| 57 | devname = "name" | ||
| 58 | devtype = "type" | ||
| 59 | |||
| 60 | devices = [] | ||
| 61 | for devName in DEVICES: | ||
| 62 | if devName == "GPIO": | ||
| 63 | continue | ||
| 64 | instance = DEVICES[devName]["device"] | ||
| 65 | if hasattr(instance, "__family__"): | ||
| 66 | family = instance.__family__() | ||
| 67 | if isinstance(family, str): | ||
| 68 | devices.append({devname: devName, devtype:family}) | ||
| 69 | else: | ||
| 70 | for fam in family: | ||
| 71 | devices.append({devname: devName, devtype:fam}) | ||
| 72 | |||
| 73 | else: | ||
| 74 | devices.append({devname: devName, type:instance.__str__()}) | ||
| 75 | |||
| 76 | return types.jsonDumps(sorted(devices, key=lambda dev: dev[devname])) | ||
| 77 | |||
diff --git a/python/webiopi/devices/onewire.py b/python/webiopi/devices/onewire.py new file mode 100644 index 0000000..b012c90 --- /dev/null +++ b/python/webiopi/devices/onewire.py | |||
| @@ -0,0 +1,74 @@ | |||
| 1 | # Copyright 2012-2013 Eric Ptak - trouch.com | ||
| 2 | # | ||
| 3 | # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 4 | # you may not use this file except in compliance with the License. | ||
| 5 | # You may obtain a copy of the License at | ||
| 6 | # | ||
| 7 | # http://www.apache.org/licenses/LICENSE-2.0 | ||
| 8 | # | ||
| 9 | # Unless required by applicable law or agreed to in writing, software | ||
| 10 | # distributed under the License is distributed on an "AS IS" BASIS, | ||
| 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 12 | # See the License for the specific language governing permissions and | ||
| 13 | # limitations under the License. | ||
| 14 | |||
| 15 | import os | ||
| 16 | from webiopi.devices.bus import Bus, loadModule | ||
| 17 | |||
| 18 | EXTRAS = { | ||
| 19 | "TEMP": {"loaded": False, "module": "w1-therm"}, | ||
| 20 | "2408": {"loaded": False, "module": "w1_ds2408"} | ||
| 21 | |||
| 22 | } | ||
| 23 | |||
| 24 | def loadExtraModule(name): | ||
| 25 | if EXTRAS[name]["loaded"] == False: | ||
| 26 | loadModule(EXTRAS[name]["module"]) | ||
| 27 | EXTRAS[name]["loaded"] = True | ||
| 28 | |||
| 29 | class OneWire(Bus): | ||
| 30 | def __init__(self, slave=None, family=0, extra=None): | ||
| 31 | Bus.__init__(self, "ONEWIRE", "/sys/bus/w1/devices/w1_bus_master1/w1_master_slaves", os.O_RDONLY) | ||
| 32 | if self.fd > 0: | ||
| 33 | os.close(self.fd) | ||
| 34 | self.fd = 0 | ||
| 35 | |||
| 36 | self.family = family | ||
| 37 | if slave != None: | ||
| 38 | addr = slave.split("-") | ||
| 39 | if len(addr) == 1: | ||
| 40 | self.slave = "%02x-%s" % (family, slave) | ||
| 41 | elif len(addr) == 2: | ||
| 42 | prefix = int(addr[0], 16) | ||
| 43 | if family > 0 and family != prefix: | ||
| 44 | raise Exception("1-Wire slave address %s does not match family %02x" % (slave, family)) | ||
| 45 | self.slave = slave | ||
| 46 | else: | ||
| 47 | devices = self.deviceList() | ||
| 48 | if len(devices) == 0: | ||
| 49 | raise Exception("No device match family %02x" % family) | ||
| 50 | self.slave = devices[0] | ||
| 51 | |||
| 52 | loadExtraModule(extra) | ||
| 53 | |||
| 54 | def __str__(self): | ||
| 55 | return "1-Wire(slave=%s)" % self.slave | ||
| 56 | |||
| 57 | def deviceList(self): | ||
| 58 | devices = [] | ||
| 59 | with open(self.device) as f: | ||
| 60 | lines = f.read().split("\n") | ||
| 61 | if self.family > 0: | ||
| 62 | prefix = "%02x-" % self.family | ||
| 63 | for line in lines: | ||
| 64 | if line.startswith(prefix): | ||
| 65 | devices.append(line) | ||
| 66 | else: | ||
| 67 | devices = lines | ||
| 68 | return devices; | ||
| 69 | |||
| 70 | def read(self): | ||
| 71 | with open("/sys/bus/w1/devices/%s/w1_slave" % self.slave) as f: | ||
| 72 | data = f.read() | ||
| 73 | return data | ||
| 74 | |||
diff --git a/python/webiopi/devices/sensor/__init__.py b/python/webiopi/devices/sensor/__init__.py new file mode 100644 index 0000000..598a82f --- /dev/null +++ b/python/webiopi/devices/sensor/__init__.py | |||
| @@ -0,0 +1,177 @@ | |||
| 1 | # Copyright 2012-2013 Eric Ptak - trouch.com | ||
| 2 | # | ||
| 3 | # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 4 | # you may not use this file except in compliance with the License. | ||
| 5 | # You may obtain a copy of the License at | ||
| 6 | # | ||
| 7 | # http://www.apache.org/licenses/LICENSE-2.0 | ||
| 8 | # | ||
| 9 | # Unless required by applicable law or agreed to in writing, software | ||
| 10 | # distributed under the License is distributed on an "AS IS" BASIS, | ||
| 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 12 | # See the License for the specific language governing permissions and | ||
| 13 | # limitations under the License. | ||
| 14 | |||
| 15 | from webiopi.utils.types import toint | ||
| 16 | from webiopi.utils.types import M_JSON | ||
| 17 | from webiopi.devices.instance import deviceInstance | ||
| 18 | from webiopi.decorators.rest import request, response | ||
| 19 | |||
| 20 | class Pressure(): | ||
| 21 | def __init__(self, altitude=0, external=None): | ||
| 22 | self.altitude = toint(altitude) | ||
| 23 | if isinstance(external, str): | ||
| 24 | self.external = deviceInstance(external) | ||
| 25 | else: | ||
| 26 | self.external = external | ||
| 27 | |||
| 28 | if self.external != None and not isinstance(self.external, Temperature): | ||
| 29 | raise Exception("external must be a Temperature sensor") | ||
| 30 | |||
| 31 | def __family__(self): | ||
| 32 | return "Pressure" | ||
| 33 | |||
| 34 | def __getPascal__(self): | ||
| 35 | raise NotImplementedError | ||
| 36 | |||
| 37 | def __getPascalAtSea__(self): | ||
| 38 | raise NotImplementedError | ||
| 39 | |||
| 40 | @request("GET", "sensor/pressure/pa") | ||
| 41 | @response("%d") | ||
| 42 | def getPascal(self): | ||
| 43 | return self.__getPascal__() | ||
| 44 | |||
| 45 | @request("GET", "sensor/pressure/hpa") | ||
| 46 | @response("%.2f") | ||
| 47 | def getHectoPascal(self): | ||
| 48 | return float(self.__getPascal__()) / 100.0 | ||
| 49 | |||
| 50 | @request("GET", "sensor/pressure/sea/pa") | ||
| 51 | @response("%d") | ||
| 52 | def getPascalAtSea(self): | ||
| 53 | pressure = self.__getPascal__() | ||
| 54 | if self.external != None: | ||
| 55 | k = self.external.getKelvin() | ||
| 56 | if k != 0: | ||
| 57 | return float(pressure) / (1.0 / (1.0 + 0.0065 / k * self.altitude)**5.255) | ||
| 58 | return float(pressure) / (1.0 - self.altitude / 44330.0)**5.255 | ||
| 59 | |||
| 60 | @request("GET", "sensor/pressure/sea/hpa") | ||
| 61 | @response("%.2f") | ||
| 62 | def getHectoPascalAtSea(self): | ||
| 63 | return self.getPascalAtSea() / 100.0 | ||
| 64 | |||
| 65 | class Temperature(): | ||
| 66 | def __family__(self): | ||
| 67 | return "Temperature" | ||
| 68 | |||
| 69 | def __getKelvin__(self): | ||
| 70 | raise NotImplementedError | ||
| 71 | |||
| 72 | def __getCelsius__(self): | ||
| 73 | raise NotImplementedError | ||
| 74 | |||
| 75 | def __getFahrenheit__(self): | ||
| 76 | raise NotImplementedError | ||
| 77 | |||
| 78 | def Kelvin2Celsius(self, value=None): | ||
| 79 | if value == None: | ||
| 80 | value = self.getKelvin() | ||
| 81 | return value - 273.15 | ||
| 82 | |||
| 83 | def Kelvin2Fahrenheit(self, value=None): | ||
| 84 | if value == None: | ||
| 85 | value = self.getKelvin() | ||
| 86 | return value * 1.8 - 459.67 | ||
| 87 | |||
| 88 | def Celsius2Kelvin(self, value=None): | ||
| 89 | if value == None: | ||
| 90 | value = self.getCelsius() | ||
| 91 | return value + 273.15 | ||
| 92 | |||
| 93 | def Celsius2Fahrenheit(self, value=None): | ||
| 94 | if value == None: | ||
| 95 | value = self.getCelsius() | ||
| 96 | return value * 1.8 + 32 | ||
| 97 | |||
| 98 | def Fahrenheit2Kelvin(self, value=None): | ||
| 99 | if value == None: | ||
| 100 | value = self.getFahrenheit() | ||
| 101 | return (value - 459.67) / 1.8 | ||
| 102 | |||
| 103 | def Fahrenheit2Celsius(self, value=None): | ||
| 104 | if value == None: | ||
| 105 | value = self.getFahrenheit() | ||
| 106 | return (value - 32) / 1.8 | ||
| 107 | |||
| 108 | @request("GET", "sensor/temperature/k") | ||
| 109 | @response("%.02f") | ||
| 110 | def getKelvin(self): | ||
| 111 | return self.__getKelvin__() | ||
| 112 | |||
| 113 | @request("GET", "sensor/temperature/c") | ||
| 114 | @response("%.02f") | ||
| 115 | def getCelsius(self): | ||
| 116 | return self.__getCelsius__() | ||
| 117 | |||
| 118 | @request("GET", "sensor/temperature/f") | ||
| 119 | @response("%.02f") | ||
| 120 | def getFahrenheit(self): | ||
| 121 | return self.__getFahrenheit__() | ||
| 122 | |||
| 123 | class Luminosity(): | ||
| 124 | def __family__(self): | ||
| 125 | return "Luminosity" | ||
| 126 | |||
| 127 | def __getLux__(self): | ||
| 128 | raise NotImplementedError | ||
| 129 | |||
| 130 | @request("GET", "sensor/luminosity/lx") | ||
| 131 | @response("%.02f") | ||
| 132 | def getLux(self): | ||
| 133 | return self.__getLux__() | ||
| 134 | |||
| 135 | class Distance(): | ||
| 136 | def __family__(self): | ||
| 137 | return "Distance" | ||
| 138 | |||
| 139 | def __getMillimeter__(self): | ||
| 140 | raise NotImplementedError | ||
| 141 | |||
| 142 | @request("GET", "sensor/distance/mm") | ||
| 143 | @response("%.02f") | ||
| 144 | def getMillimeter(self): | ||
| 145 | return self.__getMillimeter__() | ||
| 146 | |||
| 147 | @request("GET", "sensor/distance/cm") | ||
| 148 | @response("%.02f") | ||
| 149 | def getCentimeter(self): | ||
| 150 | return self.getMillimeter() / 10 | ||
| 151 | |||
| 152 | @request("GET", "sensor/distance/m") | ||
| 153 | @response("%.02f") | ||
| 154 | def getMeter(self): | ||
| 155 | return self.getMillimeter() / 1000 | ||
| 156 | |||
| 157 | @request("GET", "sensor/distance/in") | ||
| 158 | @response("%.02f") | ||
| 159 | def getInch(self): | ||
| 160 | return self.getMillimeter() / 0.254 | ||
| 161 | |||
| 162 | @request("GET", "sensor/distance/ft") | ||
| 163 | @response("%.02f") | ||
| 164 | def getFoot(self): | ||
| 165 | return self.getInch() / 12 | ||
| 166 | |||
| 167 | @request("GET", "sensor/distance/yd") | ||
| 168 | @response("%.02f") | ||
| 169 | def getYard(self): | ||
| 170 | return self.getInch() / 36 | ||
| 171 | |||
| 172 | DRIVERS = {} | ||
| 173 | DRIVERS["bmp085"] = ["BMP085"] | ||
| 174 | DRIVERS["onewiretemp"] = ["DS1822", "DS1825", "DS18B20", "DS18S20", "DS28EA00"] | ||
| 175 | DRIVERS["tmpXXX"] = ["TMP75", "TMP102", "TMP275"] | ||
| 176 | DRIVERS["tslXXXX"] = ["TSL2561", "TSL2561CS", "TSL2561T", "TSL4531", "TSL45311", "TSL45313", "TSL45315", "TSL45317"] | ||
| 177 | DRIVERS["vcnl4000"] = ["VCNL4000"] | ||
diff --git a/python/webiopi/devices/sensor/bmp085.py b/python/webiopi/devices/sensor/bmp085.py new file mode 100644 index 0000000..e21ab9a --- /dev/null +++ b/python/webiopi/devices/sensor/bmp085.py | |||
| @@ -0,0 +1,100 @@ | |||
| 1 | # Copyright 2012-2013 Eric Ptak - trouch.com | ||
| 2 | # | ||
| 3 | # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 4 | # you may not use this file except in compliance with the License. | ||
| 5 | # You may obtain a copy of the License at | ||
| 6 | # | ||
| 7 | # http://www.apache.org/licenses/LICENSE-2.0 | ||
| 8 | # | ||
| 9 | # Unless required by applicable law or agreed to in writing, software | ||
| 10 | # distributed under the License is distributed on an "AS IS" BASIS, | ||
| 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 12 | # See the License for the specific language governing permissions and | ||
| 13 | # limitations under the License. | ||
| 14 | |||
| 15 | import time | ||
| 16 | from webiopi.utils.types import signInteger | ||
| 17 | from webiopi.devices.i2c import I2C | ||
| 18 | from webiopi.devices.sensor import Temperature, Pressure | ||
| 19 | |||
| 20 | class BMP085(I2C, Temperature, Pressure): | ||
| 21 | def __init__(self, altitude=0, external=None): | ||
| 22 | I2C.__init__(self, 0x77) | ||
| 23 | Pressure.__init__(self, altitude, external) | ||
| 24 | |||
| 25 | self.ac1 = self.readSignedInteger(0xAA) | ||
| 26 | self.ac2 = self.readSignedInteger(0xAC) | ||
| 27 | self.ac3 = self.readSignedInteger(0xAE) | ||
| 28 | self.ac4 = self.readUnsignedInteger(0xB0) | ||
| 29 | self.ac5 = self.readUnsignedInteger(0xB2) | ||
| 30 | self.ac6 = self.readUnsignedInteger(0xB4) | ||
| 31 | self.b1 = self.readSignedInteger(0xB6) | ||
| 32 | self.b2 = self.readSignedInteger(0xB8) | ||
| 33 | self.mb = self.readSignedInteger(0xBA) | ||
| 34 | self.mc = self.readSignedInteger(0xBC) | ||
| 35 | self.md = self.readSignedInteger(0xBE) | ||
| 36 | |||
| 37 | def __str__(self): | ||
| 38 | return "BMP085" | ||
| 39 | |||
| 40 | def __family__(self): | ||
| 41 | return [Temperature.__family__(self), Pressure.__family__(self)] | ||
| 42 | |||
| 43 | def readUnsignedInteger(self, address): | ||
| 44 | d = self.readRegisters(address, 2) | ||
| 45 | return d[0] << 8 | d[1] | ||
| 46 | |||
| 47 | def readSignedInteger(self, address): | ||
| 48 | d = self.readUnsignedInteger(address) | ||
| 49 | return signInteger(d, 16) | ||
| 50 | |||
| 51 | def readUT(self): | ||
| 52 | self.writeRegister(0xF4, 0x2E) | ||
| 53 | time.sleep(0.01) | ||
| 54 | return self.readUnsignedInteger(0xF6) | ||
| 55 | |||
| 56 | def readUP(self): | ||
| 57 | self.writeRegister(0xF4, 0x34) | ||
| 58 | time.sleep(0.01) | ||
| 59 | return self.readUnsignedInteger(0xF6) | ||
| 60 | |||
| 61 | def getB5(self): | ||
| 62 | ut = self.readUT() | ||
| 63 | x1 = ((ut - self.ac6) * self.ac5) / 2**15 | ||
| 64 | x2 = (self.mc * 2**11) / (x1 + self.md) | ||
| 65 | return x1 + x2 | ||
| 66 | |||
| 67 | def __getKelvin__(self): | ||
| 68 | return self.Celsius2Kelvin() | ||
| 69 | |||
| 70 | def __getCelsius__(self): | ||
| 71 | t = (self.getB5() + 8) / 2**4 | ||
| 72 | return float(t) / 10.0 | ||
| 73 | |||
| 74 | def __getFahrenheit__(self): | ||
| 75 | return self.Celsius2Fahrenheit() | ||
| 76 | |||
| 77 | def __getPascal__(self): | ||
| 78 | b5 = self.getB5() | ||
| 79 | up = self.readUP() | ||
| 80 | b6 = b5 - 4000 | ||
| 81 | x1 = (self.b2 * (b6 * b6 / 2**12)) / 2**11 | ||
| 82 | x2 = self.ac2 * b6 / 2**11 | ||
| 83 | x3 = x1 + x2 | ||
| 84 | b3 = (self.ac1*4 + x3 + 2) / 4 | ||
| 85 | |||
| 86 | x1 = self.ac3 * b6 / 2**13 | ||
| 87 | x2 = (self.b1 * (b6 * b6 / 2**12)) / 2**16 | ||
| 88 | x3 = (x1 + x2 + 2) / 2**2 | ||
| 89 | b4 = self.ac4 * (x3 + 32768) / 2**15 | ||
| 90 | b7 = (up-b3) * 50000 | ||
| 91 | if b7 < 0x80000000: | ||
| 92 | p = (b7 * 2) / b4 | ||
| 93 | else: | ||
| 94 | p = (b7 / b4) * 2 | ||
| 95 | |||
| 96 | x1 = (p / 2**8) * (p / 2**8) | ||
| 97 | x1 = (x1 * 3038) / 2**16 | ||
| 98 | x2 = (-7357*p) / 2**16 | ||
| 99 | p = p + (x1 + x2 + 3791) / 2**4 | ||
| 100 | return int(p) | ||
diff --git a/python/webiopi/devices/sensor/onewiretemp.py b/python/webiopi/devices/sensor/onewiretemp.py new file mode 100644 index 0000000..703c32d --- /dev/null +++ b/python/webiopi/devices/sensor/onewiretemp.py | |||
| @@ -0,0 +1,58 @@ | |||
| 1 | # Copyright 2012-2013 Eric Ptak - trouch.com | ||
| 2 | # | ||
| 3 | # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 4 | # you may not use this file except in compliance with the License. | ||
| 5 | # You may obtain a copy of the License at | ||
| 6 | # | ||
| 7 | # http://www.apache.org/licenses/LICENSE-2.0 | ||
| 8 | # | ||
| 9 | # Unless required by applicable law or agreed to in writing, software | ||
| 10 | # distributed under the License is distributed on an "AS IS" BASIS, | ||
| 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 12 | # See the License for the specific language governing permissions and | ||
| 13 | # limitations under the License. | ||
| 14 | |||
| 15 | from webiopi.devices.onewire import OneWire | ||
| 16 | from webiopi.devices.sensor import Temperature | ||
| 17 | |||
| 18 | class OneWireTemp(OneWire, Temperature): | ||
| 19 | def __init__(self, slave=None, family=0, name="1-Wire"): | ||
| 20 | OneWire.__init__(self, slave, family, "TEMP") | ||
| 21 | self.name = name | ||
| 22 | |||
| 23 | def __str__(self): | ||
| 24 | return "%s(slave=%s)" % (self.name, self.slave) | ||
| 25 | |||
| 26 | def __getKelvin__(self): | ||
| 27 | return self.Celsius2Kelvin() | ||
| 28 | |||
| 29 | def __getCelsius__(self): | ||
| 30 | data = self.read() | ||
| 31 | lines = data.split("\n") | ||
| 32 | if lines[0].endswith("YES"): | ||
| 33 | i = lines[1].find("=") | ||
| 34 | temp = lines[1][i+1:] | ||
| 35 | return int(temp) / 1000.0 | ||
| 36 | |||
| 37 | def __getFahrenheit__(self): | ||
| 38 | return self.Celsius2Fahrenheit() | ||
| 39 | |||
| 40 | class DS18S20(OneWireTemp): | ||
| 41 | def __init__(self, slave=None): | ||
| 42 | OneWireTemp.__init__(self, slave, 0x10, "DS18S20") | ||
| 43 | |||
| 44 | class DS1822(OneWireTemp): | ||
| 45 | def __init__(self, slave=None): | ||
| 46 | OneWireTemp.__init__(self, slave, 0x22, "DS1822") | ||
| 47 | |||
| 48 | class DS18B20(OneWireTemp): | ||
| 49 | def __init__(self, slave=None): | ||
| 50 | OneWireTemp.__init__(self, slave, 0x28, "DS18B20") | ||
| 51 | |||
| 52 | class DS1825(OneWireTemp): | ||
| 53 | def __init__(self, slave=None): | ||
| 54 | OneWireTemp.__init__(self, slave, 0x3B, "DS1825") | ||
| 55 | |||
| 56 | class DS28EA00(OneWireTemp): | ||
| 57 | def __init__(self, slave=None): | ||
| 58 | OneWireTemp.__init__(self, slave, 0x42, "DS28EA00") | ||
diff --git a/python/webiopi/devices/sensor/tmpXXX.py b/python/webiopi/devices/sensor/tmpXXX.py new file mode 100644 index 0000000..ba41153 --- /dev/null +++ b/python/webiopi/devices/sensor/tmpXXX.py | |||
| @@ -0,0 +1,60 @@ | |||
| 1 | # Copyright 2012-2013 Eric Ptak - trouch.com | ||
| 2 | # | ||
| 3 | # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 4 | # you may not use this file except in compliance with the License. | ||
| 5 | # You may obtain a copy of the License at | ||
| 6 | # | ||
| 7 | # http://www.apache.org/licenses/LICENSE-2.0 | ||
| 8 | # | ||
| 9 | # Unless required by applicable law or agreed to in writing, software | ||
| 10 | # distributed under the License is distributed on an "AS IS" BASIS, | ||
| 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 12 | # See the License for the specific language governing permissions and | ||
| 13 | # limitations under the License. | ||
| 14 | |||
| 15 | from webiopi.utils.types import toint, signInteger | ||
| 16 | from webiopi.devices.i2c import I2C | ||
| 17 | from webiopi.devices.sensor import Temperature | ||
| 18 | |||
| 19 | class TMP102(I2C, Temperature): | ||
| 20 | def __init__(self, slave=0x48): | ||
| 21 | I2C.__init__(self, toint(slave)) | ||
| 22 | |||
| 23 | def __str__(self): | ||
| 24 | return "TMP102(slave=0x%02X)" % self.slave | ||
| 25 | |||
| 26 | def __getKelvin__(self): | ||
| 27 | return self.Celsius2Kelvin() | ||
| 28 | |||
| 29 | def __getCelsius__(self): | ||
| 30 | d = self.readBytes(2) | ||
| 31 | count = ((d[0] << 4) | (d[1] >> 4)) & 0xFFF | ||
| 32 | return signInteger(count, 12)*0.0625 | ||
| 33 | |||
| 34 | def __getFahrenheit__(self): | ||
| 35 | return self.Celsius2Fahrenheit() | ||
| 36 | |||
| 37 | class TMP75(TMP102): | ||
| 38 | def __init__(self, slave=0x48, resolution=12): | ||
| 39 | TMP102.__init__(self, slave) | ||
| 40 | resolution = toint(resolution) | ||
| 41 | if not resolution in range(9,13): | ||
| 42 | raise ValueError("%dbits resolution out of range [%d..%d]bits" % (resolution, 9, 12)) | ||
| 43 | self.resolution = resolution | ||
| 44 | |||
| 45 | config = self.readRegister(0x01) | ||
| 46 | config &= ~0x60 | ||
| 47 | config |= (self.resolution - 9) << 5 | ||
| 48 | self.writeRegister(0x01, config) | ||
| 49 | self.readRegisters(0x00, 2) | ||
| 50 | |||
| 51 | def __str__(self): | ||
| 52 | return "TMP75(slave=0x%02X, resolution=%d-bits)" % (self.slave, self.resolution) | ||
| 53 | |||
| 54 | class TMP275(TMP75): | ||
| 55 | def __init__(self, slave=0x48, resolution=12): | ||
| 56 | TMP75.__init__(self, slave, resolution) | ||
| 57 | |||
| 58 | def __str__(self): | ||
| 59 | return "TMP275(slave=0x%02X, resolution=%d-bits)" % (self.slave, self.resolution) | ||
| 60 | |||
diff --git a/python/webiopi/devices/sensor/tslXXXX.py b/python/webiopi/devices/sensor/tslXXXX.py new file mode 100644 index 0000000..1189d6f --- /dev/null +++ b/python/webiopi/devices/sensor/tslXXXX.py | |||
| @@ -0,0 +1,247 @@ | |||
| 1 | # Copyright 2013 Andreas Riegg | ||
| 2 | # | ||
| 3 | # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 4 | # you may not use this file except in compliance with the License. | ||
| 5 | # You may obtain a copy of the License at | ||
| 6 | # | ||
| 7 | # http://www.apache.org/licenses/LICENSE-2.0 | ||
| 8 | # | ||
| 9 | # Unless required by applicable law or agreed to in writing, software | ||
| 10 | # distributed under the License is distributed on an "AS IS" BASIS, | ||
| 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 12 | # See the License for the specific language governing permissions and | ||
| 13 | # limitations under the License. | ||
| 14 | # | ||
| 15 | # | ||
| 16 | # Changelog | ||
| 17 | # | ||
| 18 | # 1.0 2013/02/28 Initial release | ||
| 19 | # | ||
| 20 | |||
| 21 | from webiopi.utils.types import toint | ||
| 22 | from webiopi.devices.i2c import I2C | ||
| 23 | from webiopi.devices.sensor import Luminosity | ||
| 24 | |||
| 25 | class TSL_LIGHT_X(I2C, Luminosity): | ||
| 26 | VAL_COMMAND = 0x80 | ||
| 27 | REG_CONTROL = 0x00 | VAL_COMMAND | ||
| 28 | REG_CONFIG = 0x01 | VAL_COMMAND | ||
| 29 | |||
| 30 | VAL_PWON = 0x03 | ||
| 31 | VAL_PWOFF = 0x00 | ||
| 32 | VAL_INVALID = -1 | ||
| 33 | |||
| 34 | def __init__(self, slave, time, name="TSL_LIGHT_X"): | ||
| 35 | I2C.__init__(self, toint(slave)) | ||
| 36 | self.name = name | ||
| 37 | self.wake() # devices are powered down after power reset, wake them | ||
| 38 | self.setTime(toint(time)) | ||
| 39 | |||
| 40 | def __str__(self): | ||
| 41 | return "%s(slave=0x%02X)" % (self.name, self.slave) | ||
| 42 | |||
| 43 | def wake(self): | ||
| 44 | self.__wake__() | ||
| 45 | |||
| 46 | def __wake__(self): | ||
| 47 | self.writeRegister(self.REG_CONTROL, self.VAL_PWON) | ||
| 48 | |||
| 49 | def sleep(self): | ||
| 50 | self.__sleep__() | ||
| 51 | |||
| 52 | def __sleep__(self): | ||
| 53 | self.writeRegister(self.REG_CONTROL, self.VAL_PWOFF) | ||
| 54 | |||
| 55 | def setTime(self, time): | ||
| 56 | self.__setTime__(time) | ||
| 57 | |||
| 58 | def getTime(self): | ||
| 59 | return self.__getTime__() | ||
| 60 | |||
| 61 | class TSL2561X(TSL_LIGHT_X): | ||
| 62 | VAL_TIME_402_MS = 0x02 | ||
| 63 | VAL_TIME_101_MS = 0x01 | ||
| 64 | VAL_TIME_14_MS = 0x00 | ||
| 65 | |||
| 66 | REG_CHANNEL_0_LOW = 0x0C | TSL_LIGHT_X.VAL_COMMAND | ||
| 67 | REG_CHANNEL_1_LOW = 0x0E | TSL_LIGHT_X.VAL_COMMAND | ||
| 68 | |||
| 69 | MASK_GAIN = 0x10 | ||
| 70 | MASK_TIME = 0x03 | ||
| 71 | |||
| 72 | def __init__(self, slave, time, gain, name="TSL2561X"): | ||
| 73 | TSL_LIGHT_X.__init__(self, slave, time, name) | ||
| 74 | self.setGain(toint(gain)) | ||
| 75 | |||
| 76 | def __getLux__(self): | ||
| 77 | ch0_bytes = self.readRegisters(self.REG_CHANNEL_0_LOW, 2) | ||
| 78 | ch1_bytes = self.readRegisters(self.REG_CHANNEL_1_LOW, 2) | ||
| 79 | ch0_word = ch0_bytes[1] << 8 | ch0_bytes[0] | ||
| 80 | ch1_word = ch1_bytes[1] << 8 | ch1_bytes[0] | ||
| 81 | if ch0_word == 0 | ch1_word == 0: | ||
| 82 | return self.VAL_INVALID # Driver security, avoid crash in lux calculation | ||
| 83 | else: | ||
| 84 | scaling = self.time_multiplier * self.gain_multiplier | ||
| 85 | return self.__calculateLux__(scaling * ch0_word, scaling * ch1_word) | ||
| 86 | |||
| 87 | def setGain(self, gain): | ||
| 88 | if gain == 1: | ||
| 89 | bit_gain = 0 | ||
| 90 | self.gain_multiplier = 16 | ||
| 91 | elif gain == 16: | ||
| 92 | bit_gain = 1 | ||
| 93 | self.gain_multiplier = 1 | ||
| 94 | else: | ||
| 95 | raise ValueError("Gain %d out of range [%d,%d]" % (gain, 1, 16)) | ||
| 96 | new_byte_gain = (bit_gain << 4) & self.MASK_GAIN | ||
| 97 | |||
| 98 | current_byte_config = self.readRegister(self.REG_CONFIG) | ||
| 99 | new_byte_config = (current_byte_config & ~self.MASK_GAIN) | new_byte_gain | ||
| 100 | self.writeRegister(self.REG_CONFIG, new_byte_config) | ||
| 101 | |||
| 102 | def getGain(self): | ||
| 103 | current_byte_config = self.readRegister(self.REG_CONFIG) | ||
| 104 | if (current_byte_config & self.MASK_GAIN): | ||
| 105 | return 16 | ||
| 106 | else: | ||
| 107 | return 1 | ||
| 108 | |||
| 109 | def __setTime__(self, time): | ||
| 110 | if not time in [14, 101, 402]: | ||
| 111 | raise ValueError("Time %d out of range [%d,%d,%d]" % (time, 14, 101, 402)) | ||
| 112 | if time == 402: | ||
| 113 | bits_time = self.VAL_TIME_402_MS | ||
| 114 | self.time_multiplier = 1 | ||
| 115 | elif time == 101: | ||
| 116 | bits_time = self.VAL_TIME_101_MS | ||
| 117 | self.time_multiplier = 322 / 81 | ||
| 118 | elif time == 14: | ||
| 119 | bits_time = self.VAL_TIME_14_MS | ||
| 120 | self.time_multiplier = 322 / 11 | ||
| 121 | new_byte_time = bits_time & self.MASK_TIME | ||
| 122 | |||
| 123 | current_byte_config = self.readRegister(self.REG_CONFIG) | ||
| 124 | new_byte_config = (current_byte_config & ~self.MASK_TIME) | new_byte_time | ||
| 125 | self.writeRegister(self.REG_CONFIG, new_byte_config) | ||
| 126 | |||
| 127 | def __getTime__(self): | ||
| 128 | current_byte_config = self.readRegister(self.REG_CONFIG) | ||
| 129 | bits_time = (current_byte_config & self.MASK_TIME) | ||
| 130 | if bits_time == self.VAL_TIME_402_MS: | ||
| 131 | t = 402 | ||
| 132 | elif bits_time == self.VAL_TIME_101_MS: | ||
| 133 | t = 101 | ||
| 134 | elif bits_time == self.VAL_TIME_14_MS: | ||
| 135 | t = 14 | ||
| 136 | else: | ||
| 137 | t = TSL_LIGHT_X.VAL_INVALID # indicates undefined | ||
| 138 | return t | ||
| 139 | |||
| 140 | class TSL2561CS(TSL2561X): | ||
| 141 | # Package CS (Chipscale) chip version | ||
| 142 | def __init__(self, slave=0x39, time=402, gain=1): | ||
| 143 | TSL2561X.__init__(self, slave, time, gain, "TSL2561CS") | ||
| 144 | |||
| 145 | def __calculateLux__(self, channel0_value, channel1value): | ||
| 146 | channelRatio = channel1value / channel0_value | ||
| 147 | if 0 < channelRatio <= 0.52: | ||
| 148 | lux = 0.0315 * channel0_value - 0.0593 * channel0_value *(channelRatio**1.4) | ||
| 149 | elif 0.52 < channelRatio <= 0.65: | ||
| 150 | lux = 0.0229 * channel0_value - 0.0291 * channel1value | ||
| 151 | elif 0.65 < channelRatio <= 0.80: | ||
| 152 | lux = 0.0157 * channel0_value - 0.0180 * channel1value | ||
| 153 | elif 0.80 < channelRatio <= 1.30: | ||
| 154 | lux = 0.00338 * channel0_value - 0.00260 * channel1value | ||
| 155 | else: # if channelRatio > 1.30 | ||
| 156 | lux = 0 | ||
| 157 | return lux | ||
| 158 | |||
| 159 | class TSL2561T(TSL2561X): | ||
| 160 | # Package T (TMB-6) chip version | ||
| 161 | def __init__(self, slave=0x39, time=402, gain=1): | ||
| 162 | TSL2561X.__init__(self, slave, time, gain, "TSL2561T") | ||
| 163 | |||
| 164 | def __calculateLux__(self, channel0_value, channel1_value): | ||
| 165 | channel_ratio = channel1_value / channel0_value | ||
| 166 | if 0 < channel_ratio <= 0.50: | ||
| 167 | lux = 0.0304 * channel0_value - 0.062 * channel0_value * (channel_ratio**1.4) | ||
| 168 | elif 0.50 < channel_ratio <= 0.61: | ||
| 169 | lux = 0.0224 * channel0_value - 0.031 * channel1_value | ||
| 170 | elif 0.61 < channel_ratio <= 0.80: | ||
| 171 | lux = 0.0128 * channel0_value - 0.0153 * channel1_value | ||
| 172 | elif 0.80 < channel_ratio <= 1.30: | ||
| 173 | lux = 0.00146 * channel0_value - 0.00112 * channel1_value | ||
| 174 | else: # if channel_ratio > 1.30 | ||
| 175 | lux = 0 | ||
| 176 | return lux | ||
| 177 | |||
| 178 | class TSL2561(TSL2561T): | ||
| 179 | # Default version for unknown packages, uses T Package class lux calculation | ||
| 180 | def __init__(self, slave=0x39, time=402, gain=1): | ||
| 181 | TSL2561X.__init__(self, slave, time, gain, "TSL2561") | ||
| 182 | |||
| 183 | |||
| 184 | class TSL4531(TSL_LIGHT_X): | ||
| 185 | # Default version for unknown subtypes, uses 0x29 as slave address | ||
| 186 | VAL_TIME_400_MS = 0x00 | ||
| 187 | VAL_TIME_200_MS = 0x01 | ||
| 188 | VAL_TIME_100_MS = 0x02 | ||
| 189 | |||
| 190 | REG_DATA_LOW = 0x04 | TSL_LIGHT_X.VAL_COMMAND | ||
| 191 | |||
| 192 | MASK_TCNTRL = 0x03 | ||
| 193 | |||
| 194 | def __init__(self, slave=0x29, time=400, name="TSL4531"): | ||
| 195 | TSL_LIGHT_X.__init__(self, slave, time, name) | ||
| 196 | |||
| 197 | def __setTime__(self, time): | ||
| 198 | if not time in [100, 200, 400]: | ||
| 199 | raise ValueError("Time %d out of range [%d,%d,%d]" % (time, 100, 200, 400)) | ||
| 200 | if time == 400: | ||
| 201 | bits_time = self.VAL_TIME_400_MS | ||
| 202 | self.time_multiplier = 1 | ||
| 203 | elif time == 200: | ||
| 204 | bits_time = self.VAL_TIME_200_MS | ||
| 205 | self.time_multiplier = 2 | ||
| 206 | elif time == 100: | ||
| 207 | bits_time = self.VAL_TIME_100_MS | ||
| 208 | self.time_multiplier = 4 | ||
| 209 | new_byte_time = bits_time & self.MASK_TCNTRL | ||
| 210 | |||
| 211 | current_byte_config = self.readRegister(self.REG_CONFIG) | ||
| 212 | new_byte_config = (current_byte_config & ~self.MASK_TCNTRL) | new_byte_time | ||
| 213 | self.writeRegister(self.REG_CONFIG, new_byte_config) | ||
| 214 | |||
| 215 | def __getTime__(self): | ||
| 216 | current_byte_config = self.readRegister(self.REG_CONFIG) | ||
| 217 | bits_time = (current_byte_config & self.MASK_TCNTRL) | ||
| 218 | if bits_time == self.VAL_TIME_400_MS: | ||
| 219 | t = 400 | ||
| 220 | elif bits_time == self.VAL_TIME_200_MS: | ||
| 221 | t = 200 | ||
| 222 | elif bits_time == self.VAL_TIME_100_MS: | ||
| 223 | t = 100 | ||
| 224 | else: | ||
| 225 | t = TSL_LIGHT_X.VAL_INVALID # indicates undefined | ||
| 226 | return t | ||
| 227 | |||
| 228 | def __getLux__(self): | ||
| 229 | data_bytes = self.readRegisters(self.REG_DATA_LOW, 2) | ||
| 230 | return self.time_multiplier * (data_bytes[1] << 8 | data_bytes[0]) | ||
| 231 | |||
| 232 | class TSL45311(TSL4531): | ||
| 233 | def __init__(self, slave=0x39, time=400): | ||
| 234 | TSL4531.__init__(self, slave, time, "TSL45311") | ||
| 235 | |||
| 236 | class TSL45313(TSL4531): | ||
| 237 | def __init__(self, slave=0x39, time=400): | ||
| 238 | TSL4531.__init__(self, slave, time, "TSL45313") | ||
| 239 | |||
| 240 | class TSL45315(TSL4531): | ||
| 241 | def __init__(self, slave=0x29, time=400): | ||
| 242 | TSL4531.__init__(self, slave, time, "TSL45315") | ||
| 243 | |||
| 244 | class TSL45317(TSL4531): | ||
| 245 | def __init__(self, slave=0x29, time=400): | ||
| 246 | TSL4531.__init__(self, slave, time, "TSL45317") | ||
| 247 | |||
diff --git a/python/webiopi/devices/sensor/vcnl4000.py b/python/webiopi/devices/sensor/vcnl4000.py new file mode 100644 index 0000000..fb8b40b --- /dev/null +++ b/python/webiopi/devices/sensor/vcnl4000.py | |||
| @@ -0,0 +1,211 @@ | |||
| 1 | # Copyright 2013 Andreas Riegg | ||
| 2 | # | ||
| 3 | # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 4 | # you may not use this file except in compliance with the License. | ||
| 5 | # You may obtain a copy of the License at | ||
| 6 | # | ||
| 7 | # http://www.apache.org/licenses/LICENSE-2.0 | ||
| 8 | # | ||
| 9 | # Unless required by applicable law or agreed to in writing, software | ||
| 10 | # distributed under the License is distributed on an "AS IS" BASIS, | ||
| 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 12 | # See the License for the specific language governing permissions and | ||
| 13 | # limitations under the License. | ||
| 14 | # | ||
| 15 | # | ||
| 16 | # Changelog | ||
| 17 | # | ||
| 18 | # 1.0 2013/02/24 Initial release. Luminosity is final. Proximity is good beta | ||
| 19 | # and a working coarse estimation for distance value. | ||
| 20 | # | ||
| 21 | |||
| 22 | import time | ||
| 23 | from webiopi.devices.i2c import I2C | ||
| 24 | from webiopi.devices.sensor import Luminosity, Distance | ||
| 25 | from webiopi.utils.types import toint | ||
| 26 | from webiopi.utils.logger import debug | ||
| 27 | |||
| 28 | class VCNL4000(I2C, Luminosity, Distance): | ||
| 29 | REG_COMMAND = 0x80 | ||
| 30 | REG_IR_LED_CURRENT = 0x83 | ||
| 31 | REG_AMB_PARAMETERS = 0x84 | ||
| 32 | REG_AMB_RESULT_HIGH = 0x85 | ||
| 33 | REG_PROX_RESULT_HIGH = 0x87 | ||
| 34 | REG_PROX_FREQUENCY = 0x89 | ||
| 35 | REG_PROX_ADJUST = 0x8A | ||
| 36 | |||
| 37 | VAL_MOD_TIMING_DEF = 129 # default from data sheet | ||
| 38 | |||
| 39 | VAL_PR_FREQ_3M125HZ = 0 | ||
| 40 | VAL_PR_FREQ_1M5625HZ = 1 | ||
| 41 | VAL_PR_FREQ_781K25HZ = 2 | ||
| 42 | VAL_PR_FREQ_390K625HZ = 3 | ||
| 43 | |||
| 44 | VAL_START_AMB = 1 << 4 | ||
| 45 | VAL_START_PROX = 1 << 3 | ||
| 46 | |||
| 47 | VAL_INVALID = -1 | ||
| 48 | VAL_NO_PROXIMITY = -1 | ||
| 49 | |||
| 50 | MASK_PROX_FREQUENCY = 0b00111111 | ||
| 51 | MASK_IR_LED_CURRENT = 0b00111111 | ||
| 52 | MASK_PROX_READY = 0b00100000 | ||
| 53 | MASK_AMB_READY = 0b01000000 | ||
| 54 | |||
| 55 | def __init__(self, slave=0b0010011, current=20, frequency=781, prox_threshold=15, prox_cycles=10, cal_cycles= 5): | ||
| 56 | I2C.__init__(self, toint(slave)) | ||
| 57 | self.setCurrent(toint(current)) | ||
| 58 | self.setFrequency(toint(frequency)) | ||
| 59 | self.prox_threshold = toint(prox_threshold) | ||
| 60 | self.prox_cycles = toint(prox_cycles) | ||
| 61 | self.cal_cycles = toint(cal_cycles) | ||
| 62 | self.__setProximityTiming__() | ||
| 63 | self.__setAmbientMeasuringMode__() | ||
| 64 | time.sleep(0.001) | ||
| 65 | self.calibrate() # may have to be repeated from time to time or before every proximity measurement | ||
| 66 | |||
| 67 | def __str__(self): | ||
| 68 | return "VCNL4000(slave=0x%02X)" % self.slave | ||
| 69 | |||
| 70 | def __family__(self): | ||
| 71 | return [Luminosity.__family__(self), Distance.__family__(self)] | ||
| 72 | |||
| 73 | def __setProximityTiming__(self): | ||
| 74 | self.writeRegister(self.REG_PROX_ADJUST, self.VAL_MOD_TIMING_DEF) | ||
| 75 | |||
| 76 | def __setAmbientMeasuringMode__(self): | ||
| 77 | ambient_parameter_bytes = 1 << 7 | 1 << 3 | 5 | ||
| 78 | # Parameter is set to | ||
| 79 | # -continuous conversion mode (bit 7) | ||
| 80 | # -auto offset compensation (bit 3) | ||
| 81 | # -averaging 32 samples (5) | ||
| 82 | self.writeRegister(self.REG_AMB_PARAMETERS, ambient_parameter_bytes) | ||
| 83 | |||
| 84 | def calibrate(self): | ||
| 85 | self.offset = self.__measureOffset__() | ||
| 86 | debug ("VCNL4000: offset = %d" % (self.offset)) | ||
| 87 | return self.offset | ||
| 88 | |||
| 89 | |||
| 90 | def setCurrent(self, current): | ||
| 91 | self.current = current | ||
| 92 | self.__setCurrent__() | ||
| 93 | |||
| 94 | |||
| 95 | def getCurrent(self): | ||
| 96 | return self.__getCurrent__() | ||
| 97 | |||
| 98 | def setFrequency(self, frequency): | ||
| 99 | self.frequency = frequency | ||
| 100 | self.__setFrequency__() | ||
| 101 | |||
| 102 | def getFrequency(self): | ||
| 103 | return self.__getFrequency__() | ||
| 104 | |||
| 105 | def __setFrequency__(self): | ||
| 106 | if not self.frequency in [391, 781, 1563, 3125]: | ||
| 107 | raise ValueError("Frequency %d out of range [%d,%d,%d,,%d]" % (self.frequency, 391, 781, 1563, 3125)) | ||
| 108 | if self.frequency == 391: | ||
| 109 | bits_frequency = self.VAL_PR_FREQ_390K625HZ | ||
| 110 | elif self.frequency == 781: | ||
| 111 | bits_frequency = self.VAL_PR_FREQ_781K25HZ | ||
| 112 | elif self.frequency == 1563: | ||
| 113 | bits_frequency = self.VAL_PR_FREQ_1M5625HZ | ||
| 114 | elif self.frequency == 3125: | ||
| 115 | bits_frequency = self.VAL_PR_FREQ_3M125HZ | ||
| 116 | self.writeRegister(self.REG_PROX_FREQUENCY, bits_frequency) | ||
| 117 | debug ("VCNL4000: new freq = %d" % (self.readRegister(self.REG_PROX_FREQUENCY))) | ||
| 118 | |||
| 119 | def __getFrequency__(self): | ||
| 120 | bits_frequency = self.readRegister(self.REG_PROX_FREQUENCY) & self.MASK_PROX_FREQUENCY | ||
| 121 | if bits_frequency == self.VAL_PR_FREQ_390K625HZ: | ||
| 122 | f = 391 | ||
| 123 | elif bits_frequency == self.VAL_PR_FREQ_781K25HZ: | ||
| 124 | f = 781 | ||
| 125 | elif bits_frequency == self.VAL_PR_FREQ_1M5625HZ: | ||
| 126 | f = 1563 | ||
| 127 | elif bits_frequency == self.VAL_PR_FREQ_3M125HZ: | ||
| 128 | f = 3125 | ||
| 129 | else: | ||
| 130 | f = self.VAL_INVALID # indicates undefined | ||
| 131 | return f | ||
| 132 | |||
| 133 | def __setCurrent__(self): | ||
| 134 | if not self.current in range(0,201): | ||
| 135 | raise ValueError("%d mA LED current out of range [%d..%d] mA" % (self.current, 0, 201)) | ||
| 136 | self.writeRegister(self.REG_IR_LED_CURRENT, int(self.current / 10)) | ||
| 137 | debug ("VCNL4000: new curr = %d" % (self.readRegister(self.REG_IR_LED_CURRENT))) | ||
| 138 | |||
| 139 | def __getCurrent__(self): | ||
| 140 | bits_current = self.readRegister(self.REG_IR_LED_CURRENT) & self.MASK_IR_LED_CURRENT | ||
| 141 | return bits_current * 10 | ||
| 142 | |||
| 143 | def __getLux__(self): | ||
| 144 | self.writeRegister(self.REG_COMMAND, self.VAL_START_AMB) | ||
| 145 | while not (self.readRegister(self.REG_COMMAND) & self.MASK_AMB_READY): | ||
| 146 | time.sleep(0.001) | ||
| 147 | light_bytes = self.readRegisters(self.REG_AMB_RESULT_HIGH, 2) | ||
| 148 | light_word = light_bytes[0] << 8 | light_bytes[1] | ||
| 149 | return self.__calculateLux__(light_word) | ||
| 150 | |||
| 151 | def __calculateLux__(self, light_word): | ||
| 152 | return (light_word + 3) * 0.25 # From VISHAY application note | ||
| 153 | |||
| 154 | def __getMillimeter__(self): | ||
| 155 | success = 0 | ||
| 156 | fail = 0 | ||
| 157 | prox = 0 | ||
| 158 | match_cycles = self.prox_cycles | ||
| 159 | while (fail < match_cycles) & (success < match_cycles): | ||
| 160 | real_counts = self.__readProximityCounts__() - self.offset | ||
| 161 | if real_counts > self.prox_threshold: | ||
| 162 | success += 1 | ||
| 163 | prox += real_counts | ||
| 164 | else: | ||
| 165 | fail += 1 | ||
| 166 | if fail == match_cycles: | ||
| 167 | return self.VAL_NO_PROXIMITY | ||
| 168 | else: | ||
| 169 | return self.__calculateMillimeter__(prox // match_cycles) | ||
| 170 | |||
| 171 | def __calculateMillimeter__(self, raw_proximity_counts): | ||
| 172 | # According to chip spec the proximity counts are strong non-linear with distance and cannot be calculated | ||
| 173 | # with a direct formula. From experience found on web this chip is generally not suited for really exact | ||
| 174 | # distance calculations. This is a rough distance estimation lookup table for now. Maybe someone can | ||
| 175 | # provide a more exact approximation in the future. | ||
| 176 | |||
| 177 | debug ("VCNL4000: prox real raw counts = %d" % (raw_proximity_counts)) | ||
| 178 | if raw_proximity_counts >= 10000: | ||
| 179 | estimated_distance = 0 | ||
| 180 | elif raw_proximity_counts >= 3000: | ||
| 181 | estimated_distance = 5 | ||
| 182 | elif raw_proximity_counts >= 900: | ||
| 183 | estimated_distance = 10 | ||
| 184 | elif raw_proximity_counts >= 300: | ||
| 185 | estimated_distance = 20 | ||
| 186 | elif raw_proximity_counts >= 150: | ||
| 187 | estimated_distance = 30 | ||
| 188 | elif raw_proximity_counts >= 75: | ||
| 189 | estimated_distance = 40 | ||
| 190 | elif raw_proximity_counts >= 50: | ||
| 191 | estimated_distance = 50 | ||
| 192 | elif raw_proximity_counts >= 25: | ||
| 193 | estimated_distance = 70 | ||
| 194 | else: | ||
| 195 | estimated_distance = 100 | ||
| 196 | return estimated_distance | ||
| 197 | |||
| 198 | def __measureOffset__(self): | ||
| 199 | offset = 0 | ||
| 200 | for unused in range(self.cal_cycles): | ||
| 201 | offset += self.__readProximityCounts__() | ||
| 202 | return offset // self.cal_cycles | ||
| 203 | |||
| 204 | def __readProximityCounts__(self): | ||
| 205 | self.writeRegister(self.REG_COMMAND, self.VAL_START_PROX) | ||
| 206 | while not (self.readRegister(self.REG_COMMAND) & self.MASK_PROX_READY): | ||
| 207 | time.sleep(0.001) | ||
| 208 | proximity_bytes = self.readRegisters(self.REG_PROX_RESULT_HIGH, 2) | ||
| 209 | debug ("VCNL4000: prox raw value = %d" % (proximity_bytes[0] << 8 | proximity_bytes[1])) | ||
| 210 | return (proximity_bytes[0] << 8 | proximity_bytes[1]) | ||
| 211 | \ No newline at end of file | ||
diff --git a/python/webiopi/devices/serial.py b/python/webiopi/devices/serial.py new file mode 100644 index 0000000..800f96f --- /dev/null +++ b/python/webiopi/devices/serial.py | |||
| @@ -0,0 +1,86 @@ | |||
| 1 | # Copyright 2012-2013 Eric Ptak - trouch.com | ||
| 2 | # | ||
| 3 | # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 4 | # you may not use this file except in compliance with the License. | ||
| 5 | # You may obtain a copy of the License at | ||
| 6 | # | ||
| 7 | # http://www.apache.org/licenses/LICENSE-2.0 | ||
| 8 | # | ||
| 9 | # Unless required by applicable law or agreed to in writing, software | ||
| 10 | # distributed under the License is distributed on an "AS IS" BASIS, | ||
| 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 12 | # See the License for the specific language governing permissions and | ||
| 13 | # limitations under the License. | ||
| 14 | |||
| 15 | import os | ||
| 16 | import fcntl | ||
| 17 | import struct | ||
| 18 | import termios | ||
| 19 | |||
| 20 | from webiopi.devices.bus import Bus | ||
| 21 | from webiopi.decorators.rest import request | ||
| 22 | |||
| 23 | TIOCINQ = hasattr(termios, 'FIONREAD') and termios.FIONREAD or 0x541B | ||
| 24 | TIOCM_zero_str = struct.pack('I', 0) | ||
| 25 | |||
| 26 | class Serial(Bus): | ||
| 27 | def __init__(self, device="/dev/ttyAMA0", baudrate=9600): | ||
| 28 | if not device.startswith("/dev/"): | ||
| 29 | device = "/dev/%s" % device | ||
| 30 | |||
| 31 | if isinstance(baudrate, str): | ||
| 32 | baudrate = int(baudrate) | ||
| 33 | |||
| 34 | aname = "B%d" % baudrate | ||
| 35 | if not hasattr(termios, aname): | ||
| 36 | raise Exception("Unsupported baudrate") | ||
| 37 | self.baudrate = baudrate | ||
| 38 | |||
| 39 | Bus.__init__(self, "UART", device, os.O_RDWR | os.O_NOCTTY) | ||
| 40 | fcntl.fcntl(self.fd, fcntl.F_SETFL, os.O_NDELAY) | ||
| 41 | |||
| 42 | #backup = termios.tcgetattr(self.fd) | ||
| 43 | options = termios.tcgetattr(self.fd) | ||
| 44 | # iflag | ||
| 45 | options[0] = 0 | ||
| 46 | |||
| 47 | # oflag | ||
| 48 | options[1] = 0 | ||
| 49 | |||
| 50 | # cflag | ||
| 51 | options[2] |= (termios.CLOCAL | termios.CREAD) | ||
| 52 | options[2] &= ~termios.PARENB | ||
| 53 | options[2] &= ~termios.CSTOPB | ||
| 54 | options[2] &= ~termios.CSIZE | ||
| 55 | options[2] |= termios.CS8 | ||
| 56 | |||
| 57 | # lflag | ||
| 58 | options[3] = 0 | ||
| 59 | |||
| 60 | speed = getattr(termios, aname) | ||
| 61 | # input speed | ||
| 62 | options[4] = speed | ||
| 63 | # output speed | ||
| 64 | options[5] = speed | ||
| 65 | |||
| 66 | termios.tcsetattr(self.fd, termios.TCSADRAIN, options) | ||
| 67 | |||
| 68 | def __str__(self): | ||
| 69 | return "Serial(%s, %dbps)" % (self.device, self.baudrate) | ||
| 70 | |||
| 71 | def __family__(self): | ||
| 72 | return "Serial" | ||
| 73 | |||
| 74 | def available(self): | ||
| 75 | s = fcntl.ioctl(self.fd, TIOCINQ, TIOCM_zero_str) | ||
| 76 | return struct.unpack('I',s)[0] | ||
| 77 | |||
| 78 | @request("GET", "") | ||
| 79 | def read(self): | ||
| 80 | if self.available() > 0: | ||
| 81 | return Bus.read(self, self.available()).decode() | ||
| 82 | return "" | ||
| 83 | |||
| 84 | @request("POST", "", "data") | ||
| 85 | def write(self, data): | ||
| 86 | Bus.write(self, data) | ||
diff --git a/python/webiopi/devices/shield/__init__.py b/python/webiopi/devices/shield/__init__.py new file mode 100644 index 0000000..ef77597 --- /dev/null +++ b/python/webiopi/devices/shield/__init__.py | |||
| @@ -0,0 +1,16 @@ | |||
| 1 | # Copyright 2012-2013 Eric Ptak - trouch.com | ||
| 2 | # | ||
| 3 | # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 4 | # you may not use this file except in compliance with the License. | ||
| 5 | # You may obtain a copy of the License at | ||
| 6 | # | ||
| 7 | # http://www.apache.org/licenses/LICENSE-2.0 | ||
| 8 | # | ||
| 9 | # Unless required by applicable law or agreed to in writing, software | ||
| 10 | # distributed under the License is distributed on an "AS IS" BASIS, | ||
| 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 12 | # See the License for the specific language governing permissions and | ||
| 13 | # limitations under the License. | ||
| 14 | |||
| 15 | DRIVERS = {} | ||
| 16 | DRIVERS["piface"] = ["PiFaceDigital"] \ No newline at end of file | ||
diff --git a/python/webiopi/devices/shield/piface.py b/python/webiopi/devices/shield/piface.py new file mode 100644 index 0000000..ffe6215 --- /dev/null +++ b/python/webiopi/devices/shield/piface.py | |||
| @@ -0,0 +1,66 @@ | |||
| 1 | # Copyright 2012-2013 Eric Ptak - trouch.com | ||
| 2 | # | ||
| 3 | # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 4 | # you may not use this file except in compliance with the License. | ||
| 5 | # You may obtain a copy of the License at | ||
| 6 | # | ||
| 7 | # http://www.apache.org/licenses/LICENSE-2.0 | ||
| 8 | # | ||
| 9 | # Unless required by applicable law or agreed to in writing, software | ||
| 10 | # distributed under the License is distributed on an "AS IS" BASIS, | ||
| 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 12 | # See the License for the specific language governing permissions and | ||
| 13 | # limitations under the License. | ||
| 14 | |||
| 15 | from webiopi.utils.types import M_JSON | ||
| 16 | from webiopi.devices.digital.mcp23XXX import MCP23S17 | ||
| 17 | from webiopi.decorators.rest import request, response | ||
| 18 | |||
| 19 | |||
| 20 | class PiFaceDigital(): | ||
| 21 | def __init__(self, board=0): | ||
| 22 | mcp = MCP23S17(0, 0x20+board) | ||
| 23 | mcp.writeRegister(mcp.getAddress(mcp.IODIR, 0), 0x00) # Port A as output | ||
| 24 | mcp.writeRegister(mcp.getAddress(mcp.IODIR, 8), 0xFF) # Port B as input | ||
| 25 | mcp.writeRegister(mcp.getAddress(mcp.GPPU, 0), 0x00) # Port A PU OFF | ||
| 26 | mcp.writeRegister(mcp.getAddress(mcp.GPPU, 8), 0xFF) # Port B PU ON | ||
| 27 | self.mcp = mcp | ||
| 28 | self.board = board | ||
| 29 | |||
| 30 | def __str__(self): | ||
| 31 | return "PiFaceDigital(%d)" % self.board | ||
| 32 | |||
| 33 | def __family__(self): | ||
| 34 | return "PiFaceDigital" | ||
| 35 | |||
| 36 | def checkChannel(self, channel): | ||
| 37 | if not channel in range(8): | ||
| 38 | raise ValueError("Channel %d invalid" % channel) | ||
| 39 | |||
| 40 | @request("GET", "digital/input/%(channel)d") | ||
| 41 | @response("%d") | ||
| 42 | def digitalRead(self, channel): | ||
| 43 | self.checkChannel(channel) | ||
| 44 | return not self.mcp.digitalRead(channel+8) | ||
| 45 | |||
| 46 | @request("POST", "digital/output/%(channel)d/%(value)d") | ||
| 47 | @response("%d") | ||
| 48 | def digitalWrite(self, channel, value): | ||
| 49 | self.checkChannel(channel) | ||
| 50 | return self.mcp.digitalWrite(channel, value) | ||
| 51 | |||
| 52 | @request("GET", "digital/output/%(channel)d") | ||
| 53 | @response("%d") | ||
| 54 | def digitalReadOutput(self, channel): | ||
| 55 | self.checkChannel(channel) | ||
| 56 | return self.mcp.digitalRead(channel) | ||
| 57 | |||
| 58 | @request("GET", "digital/*") | ||
| 59 | @response(contentType=M_JSON) | ||
| 60 | def readAll(self): | ||
| 61 | inputs = {} | ||
| 62 | outputs = {} | ||
| 63 | for i in range(8): | ||
| 64 | inputs[i] = self.digitalRead(i) | ||
| 65 | outputs[i] = self.digitalReadOutput(i) | ||
| 66 | return {"input": inputs, "output": outputs} | ||
diff --git a/python/webiopi/devices/spi.py b/python/webiopi/devices/spi.py new file mode 100644 index 0000000..d19de79 --- /dev/null +++ b/python/webiopi/devices/spi.py | |||
| @@ -0,0 +1,145 @@ | |||
| 1 | # Copyright 2012-2013 Eric Ptak - trouch.com | ||
| 2 | # | ||
| 3 | # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 4 | # you may not use this file except in compliance with the License. | ||
| 5 | # You may obtain a copy of the License at | ||
| 6 | # | ||
| 7 | # http://www.apache.org/licenses/LICENSE-2.0 | ||
| 8 | # | ||
| 9 | # Unless required by applicable law or agreed to in writing, software | ||
| 10 | # distributed under the License is distributed on an "AS IS" BASIS, | ||
| 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 12 | # See the License for the specific language governing permissions and | ||
| 13 | # limitations under the License. | ||
| 14 | |||
| 15 | import fcntl | ||
| 16 | import array | ||
| 17 | import ctypes | ||
| 18 | import struct | ||
| 19 | |||
| 20 | from webiopi.utils.version import PYTHON_MAJOR | ||
| 21 | from webiopi.devices.bus import Bus | ||
| 22 | |||
| 23 | # from spi/spidev.h | ||
| 24 | _IOC_NRBITS = 8 | ||
| 25 | _IOC_TYPEBITS = 8 | ||
| 26 | _IOC_SIZEBITS = 14 | ||
| 27 | _IOC_DIRBITS = 2 | ||
| 28 | |||
| 29 | _IOC_NRSHIFT = 0 | ||
| 30 | _IOC_TYPESHIFT = (_IOC_NRSHIFT+_IOC_NRBITS) | ||
| 31 | _IOC_SIZESHIFT = (_IOC_TYPESHIFT+_IOC_TYPEBITS) | ||
| 32 | _IOC_DIRSHIFT = (_IOC_SIZESHIFT+_IOC_SIZEBITS) | ||
| 33 | |||
| 34 | _IOC_NONE = 0 | ||
| 35 | _IOC_WRITE = 1 | ||
| 36 | _IOC_READ = 2 | ||
| 37 | |||
| 38 | def _IOC(direction,t,nr,size): | ||
| 39 | return (((direction) << _IOC_DIRSHIFT) | | ||
| 40 | ((size) << _IOC_SIZESHIFT) | | ||
| 41 | ((t) << _IOC_TYPESHIFT) | | ||
| 42 | ((nr) << _IOC_NRSHIFT)) | ||
| 43 | def _IOR(t, number, size): | ||
| 44 | return _IOC(_IOC_READ, t, number, size) | ||
| 45 | def _IOW(t, number, size): | ||
| 46 | return _IOC(_IOC_WRITE, t, number, size) | ||
| 47 | |||
| 48 | SPI_CPHA = 0x01 | ||
| 49 | SPI_CPOL = 0x02 | ||
| 50 | |||
| 51 | SPI_MODE_0 = (0|0) | ||
| 52 | SPI_MODE_1 = (0|SPI_CPHA) | ||
| 53 | SPI_MODE_2 = (SPI_CPOL|0) | ||
| 54 | SPI_MODE_3 = (SPI_CPOL|SPI_CPHA) | ||
| 55 | |||
| 56 | # does not work | ||
| 57 | # SPI_CS_HIGH = 0x04 | ||
| 58 | # SPI_LSB_FIRST = 0x08 | ||
| 59 | # SPI_3WIRE = 0x10 | ||
| 60 | # SPI_LOOP = 0x20 | ||
| 61 | # SPI_NO_CS = 0x40 | ||
| 62 | # SPI_READY = 0x80 | ||
| 63 | |||
| 64 | SPI_IOC_MAGIC = ord('k') | ||
| 65 | |||
| 66 | def SPI_IOC_MESSAGE(count): | ||
| 67 | return _IOW(SPI_IOC_MAGIC, 0, count) | ||
| 68 | |||
| 69 | # Read / Write of SPI mode (SPI_MODE_0..SPI_MODE_3) | ||
| 70 | SPI_IOC_RD_MODE = _IOR(SPI_IOC_MAGIC, 1, 1) | ||
| 71 | SPI_IOC_WR_MODE = _IOW(SPI_IOC_MAGIC, 1, 1) | ||
| 72 | |||
| 73 | # Read / Write SPI bit justification | ||
| 74 | # does not work | ||
| 75 | # SPI_IOC_RD_LSB_FIRST = _IOR(SPI_IOC_MAGIC, 2, 1) | ||
| 76 | # SPI_IOC_WR_LSB_FIRST = _IOW(SPI_IOC_MAGIC, 2, 1) | ||
| 77 | |||
| 78 | # Read / Write SPI device word length (1..N) | ||
| 79 | SPI_IOC_RD_BITS_PER_WORD = _IOR(SPI_IOC_MAGIC, 3, 1) | ||
| 80 | SPI_IOC_WR_BITS_PER_WORD = _IOW(SPI_IOC_MAGIC, 3, 1) | ||
| 81 | |||
| 82 | # Read / Write SPI device default max speed hz | ||
| 83 | SPI_IOC_RD_MAX_SPEED_HZ = _IOR(SPI_IOC_MAGIC, 4, 4) | ||
| 84 | SPI_IOC_WR_MAX_SPEED_HZ = _IOW(SPI_IOC_MAGIC, 4, 4) | ||
| 85 | |||
| 86 | class SPI(Bus): | ||
| 87 | def __init__(self, chip=0, mode=0, bits=8, speed=0): | ||
| 88 | Bus.__init__(self, "SPI", "/dev/spidev0.%d" % chip) | ||
| 89 | self.chip = chip | ||
| 90 | |||
| 91 | val8 = array.array('B', [0]) | ||
| 92 | val8[0] = mode | ||
| 93 | if fcntl.ioctl(self.fd, SPI_IOC_WR_MODE, val8): | ||
| 94 | raise Exception("Cannot write SPI Mode") | ||
| 95 | if fcntl.ioctl(self.fd, SPI_IOC_RD_MODE, val8): | ||
| 96 | raise Exception("Cannot read SPI Mode") | ||
| 97 | self.mode = struct.unpack('B', val8)[0] | ||
| 98 | assert(self.mode == mode) | ||
| 99 | |||
| 100 | val8[0] = bits | ||
| 101 | if fcntl.ioctl(self.fd, SPI_IOC_WR_BITS_PER_WORD, val8): | ||
| 102 | raise Exception("Cannot write SPI Bits per word") | ||
| 103 | if fcntl.ioctl(self.fd, SPI_IOC_RD_BITS_PER_WORD, val8): | ||
| 104 | raise Exception("Cannot read SPI Bits per word") | ||
| 105 | self.bits = struct.unpack('B', val8)[0] | ||
| 106 | assert(self.bits == bits) | ||
| 107 | |||
| 108 | val32 = array.array('I', [0]) | ||
| 109 | if speed > 0: | ||
| 110 | val32[0] = speed | ||
| 111 | if fcntl.ioctl(self.fd, SPI_IOC_WR_MAX_SPEED_HZ, val32): | ||
| 112 | raise Exception("Cannot write SPI Max speed") | ||
| 113 | if fcntl.ioctl(self.fd, SPI_IOC_RD_MAX_SPEED_HZ, val32): | ||
| 114 | raise Exception("Cannot read SPI Max speed") | ||
| 115 | self.speed = struct.unpack('I', val32)[0] | ||
| 116 | assert((self.speed == speed) or (speed == 0)) | ||
| 117 | |||
| 118 | def __str__(self): | ||
| 119 | return "SPI(chip=%d, mode=%d, speed=%dHz)" % (self.chip, self.mode, self.speed) | ||
| 120 | |||
| 121 | def xfer(self, txbuff=None): | ||
| 122 | length = len(txbuff) | ||
| 123 | if PYTHON_MAJOR >= 3: | ||
| 124 | _txbuff = bytes(txbuff) | ||
| 125 | _txptr = ctypes.create_string_buffer(_txbuff) | ||
| 126 | else: | ||
| 127 | _txbuff = str(bytearray(txbuff)) | ||
| 128 | _txptr = ctypes.create_string_buffer(_txbuff) | ||
| 129 | _rxptr = ctypes.create_string_buffer(length) | ||
| 130 | |||
| 131 | data = struct.pack("QQLLHBBL", #64 64 32 32 16 8 8 32 b = 32B | ||
| 132 | ctypes.addressof(_txptr), | ||
| 133 | ctypes.addressof(_rxptr), | ||
| 134 | length, | ||
| 135 | self.speed, | ||
| 136 | 0, #delay | ||
| 137 | self.bits, | ||
| 138 | 0, # cs_change, | ||
| 139 | 0 # pad | ||
| 140 | ) | ||
| 141 | |||
| 142 | fcntl.ioctl(self.fd, SPI_IOC_MESSAGE(len(data)), data) | ||
| 143 | _rxbuff = ctypes.string_at(_rxptr, length) | ||
| 144 | return bytearray(_rxbuff) | ||
| 145 | \ No newline at end of file | ||
diff --git a/python/webiopi/protocols/__init__.py b/python/webiopi/protocols/__init__.py new file mode 100644 index 0000000..fc4ac9a --- /dev/null +++ b/python/webiopi/protocols/__init__.py | |||
| @@ -0,0 +1,14 @@ | |||
| 1 | # Copyright 2012-2013 Eric Ptak - trouch.com | ||
| 2 | # | ||
| 3 | # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 4 | # you may not use this file except in compliance with the License. | ||
| 5 | # You may obtain a copy of the License at | ||
| 6 | # | ||
| 7 | # http://www.apache.org/licenses/LICENSE-2.0 | ||
| 8 | # | ||
| 9 | # Unless required by applicable law or agreed to in writing, software | ||
| 10 | # distributed under the License is distributed on an "AS IS" BASIS, | ||
| 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 12 | # See the License for the specific language governing permissions and | ||
| 13 | # limitations under the License. | ||
| 14 | |||
diff --git a/python/webiopi/protocols/coap.py b/python/webiopi/protocols/coap.py new file mode 100644 index 0000000..3ed6b3e --- /dev/null +++ b/python/webiopi/protocols/coap.py | |||
| @@ -0,0 +1,537 @@ | |||
| 1 | # Copyright 2012-2013 Eric Ptak - trouch.com | ||
| 2 | # | ||
| 3 | # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 4 | # you may not use this file except in compliance with the License. | ||
| 5 | # You may obtain a copy of the License at | ||
| 6 | # | ||
| 7 | # http://www.apache.org/licenses/LICENSE-2.0 | ||
| 8 | # | ||
| 9 | # Unless required by applicable law or agreed to in writing, software | ||
| 10 | # distributed under the License is distributed on an "AS IS" BASIS, | ||
| 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 12 | # See the License for the specific language governing permissions and | ||
| 13 | # limitations under the License. | ||
| 14 | |||
| 15 | from webiopi.utils.version import PYTHON_MAJOR | ||
| 16 | from webiopi.utils.logger import info, exception | ||
| 17 | |||
| 18 | import socket | ||
| 19 | import struct | ||
| 20 | import logging | ||
| 21 | import threading | ||
| 22 | |||
| 23 | M_PLAIN = "text/plain" | ||
| 24 | M_JSON = "application/json" | ||
| 25 | |||
| 26 | if PYTHON_MAJOR >= 3: | ||
| 27 | from urllib.parse import urlparse | ||
| 28 | else: | ||
| 29 | from urlparse import urlparse | ||
| 30 | |||
| 31 | try : | ||
| 32 | import _webiopi.GPIO as GPIO | ||
| 33 | except: | ||
| 34 | pass | ||
| 35 | |||
| 36 | def HTTPCode2CoAPCode(code): | ||
| 37 | return int(code/100) * 32 + (code%100) | ||
| 38 | |||
| 39 | |||
| 40 | class COAPContentFormat(): | ||
| 41 | FORMATS = {0: "text/plain", | ||
| 42 | 40: "application/link-format", | ||
| 43 | 41: "application/xml", | ||
| 44 | 42: "application/octet-stream", | ||
| 45 | 47: "application/exi", | ||
| 46 | 50: "application/json" | ||
| 47 | } | ||
| 48 | |||
| 49 | @staticmethod | ||
| 50 | def getCode(fmt): | ||
| 51 | if fmt == None: | ||
| 52 | return None | ||
| 53 | for code in COAPContentFormat.FORMATS: | ||
| 54 | if COAPContentFormat.FORMATS[code] == fmt: | ||
| 55 | return code | ||
| 56 | return None | ||
| 57 | |||
| 58 | @staticmethod | ||
| 59 | def toString(code): | ||
| 60 | if code == None: | ||
| 61 | return None | ||
| 62 | |||
| 63 | if code in COAPContentFormat.FORMATS: | ||
| 64 | return COAPContentFormat.FORMATS[code] | ||
| 65 | |||
| 66 | raise Exception("Unknown content format %d" % code) | ||
| 67 | |||
| 68 | |||
| 69 | class COAPOption(): | ||
| 70 | OPTIONS = {1: "If-Match", | ||
| 71 | 3: "Uri-Host", | ||
| 72 | 4: "ETag", | ||
| 73 | 5: "If-None-Match", | ||
| 74 | 7: "Uri-Port", | ||
| 75 | 8: "Location-Path", | ||
| 76 | 11: "Uri-Path", | ||
| 77 | 12: "Content-Format", | ||
| 78 | 14: "Max-Age", | ||
| 79 | 15: "Uri-Query", | ||
| 80 | 16: "Accept", | ||
| 81 | 20: "Location-Query", | ||
| 82 | 35: "Proxy-Uri", | ||
| 83 | 39: "Proxy-Scheme" | ||
| 84 | } | ||
| 85 | |||
| 86 | IF_MATCH = 1 | ||
| 87 | URI_HOST = 3 | ||
| 88 | ETAG = 4 | ||
| 89 | IF_NONE_MATCH = 5 | ||
| 90 | URI_PORT = 7 | ||
| 91 | LOCATION_PATH = 8 | ||
| 92 | URI_PATH = 11 | ||
| 93 | CONTENT_FORMAT = 12 | ||
| 94 | MAX_AGE = 14 | ||
| 95 | URI_QUERY = 15 | ||
| 96 | ACCEPT = 16 | ||
| 97 | LOCATION_QUERY = 20 | ||
| 98 | PROXY_URI = 35 | ||
| 99 | PROXY_SCHEME = 39 | ||
| 100 | |||
| 101 | |||
| 102 | class COAPMessage(): | ||
| 103 | TYPES = ["CON", "NON", "ACK", "RST"] | ||
| 104 | CON = 0 | ||
| 105 | NON = 1 | ||
| 106 | ACK = 2 | ||
| 107 | RST = 3 | ||
| 108 | |||
| 109 | def __init__(self, msg_type=0, code=0, uri=None): | ||
| 110 | self.version = 1 | ||
| 111 | self.type = msg_type | ||
| 112 | self.code = code | ||
| 113 | self.id = 0 | ||
| 114 | self.token = None | ||
| 115 | self.options = [] | ||
| 116 | self.host = "" | ||
| 117 | self.port = 5683 | ||
| 118 | self.uri_path = "" | ||
| 119 | self.content_format = None | ||
| 120 | self.payload = None | ||
| 121 | |||
| 122 | if uri != None: | ||
| 123 | p = urlparse(uri) | ||
| 124 | self.host = p.hostname | ||
| 125 | if p.port: | ||
| 126 | self.port = int(p.port) | ||
| 127 | self.uri_path = p.path | ||
| 128 | |||
| 129 | def __getOptionHeader__(self, byte): | ||
| 130 | delta = (byte & 0xF0) >> 4 | ||
| 131 | length = byte & 0x0F | ||
| 132 | return (delta, length) | ||
| 133 | |||
| 134 | def __str__(self): | ||
| 135 | result = [] | ||
| 136 | result.append("Version: %s" % self.version) | ||
| 137 | result.append("Type: %s" % self.TYPES[self.type]) | ||
| 138 | result.append("Code: %s" % self.CODES[self.code]) | ||
| 139 | result.append("Id: %s" % self.id) | ||
| 140 | result.append("Token: %s" % self.token) | ||
| 141 | #result.append("Options: %s" % len(self.options)) | ||
| 142 | #for option in self.options: | ||
| 143 | # result.append("+ %d: %s" % (option["number"], option["value"])) | ||
| 144 | result.append("Uri-Path: %s" % self.uri_path) | ||
| 145 | result.append("Content-Format: %s" % (COAPContentFormat.toString(self.content_format) if self.content_format else M_PLAIN)) | ||
| 146 | result.append("Payload: %s" % self.payload) | ||
| 147 | result.append("") | ||
| 148 | return '\n'.join(result) | ||
| 149 | |||
| 150 | def getOptionHeaderValue(self, value): | ||
| 151 | if value > 268: | ||
| 152 | return 14 | ||
| 153 | if value > 12: | ||
| 154 | return 13 | ||
| 155 | return value | ||
| 156 | |||
| 157 | def getOptionHeaderExtension(self, value): | ||
| 158 | buff = bytearray() | ||
| 159 | v = self.getOptionHeaderValue(value) | ||
| 160 | |||
| 161 | if v == 14: | ||
| 162 | value -= 269 | ||
| 163 | buff.append((value & 0xFF00) >> 8) | ||
| 164 | buff.append(value & 0x00FF) | ||
| 165 | |||
| 166 | elif v == 13: | ||
| 167 | value -= 13 | ||
| 168 | buff.append(value) | ||
| 169 | |||
| 170 | return buff | ||
| 171 | |||
| 172 | def appendOption(self, buff, lastnumber, option, data): | ||
| 173 | delta = option - lastnumber | ||
| 174 | length = len(data) | ||
| 175 | |||
| 176 | d = self.getOptionHeaderValue(delta) | ||
| 177 | l = self.getOptionHeaderValue(length) | ||
| 178 | |||
| 179 | b = 0 | ||
| 180 | b |= (d << 4) & 0xF0 | ||
| 181 | b |= l & 0x0F | ||
| 182 | buff.append(b) | ||
| 183 | |||
| 184 | ext = self.getOptionHeaderExtension(delta); | ||
| 185 | for b in ext: | ||
| 186 | buff.append(b) | ||
| 187 | |||
| 188 | ext = self.getOptionHeaderExtension(length); | ||
| 189 | for b in ext: | ||
| 190 | buff.append(b) | ||
| 191 | |||
| 192 | for b in data: | ||
| 193 | buff.append(b) | ||
| 194 | |||
| 195 | return option | ||
| 196 | |||
| 197 | def getBytes(self): | ||
| 198 | buff = bytearray() | ||
| 199 | byte = (self.version & 0x03) << 6 | ||
| 200 | byte |= (self.type & 0x03) << 4 | ||
| 201 | if self.token: | ||
| 202 | token_len = min(len(self.token), 8); | ||
| 203 | else: | ||
| 204 | token_len = 0 | ||
| 205 | byte |= token_len | ||
| 206 | buff.append(byte) | ||
| 207 | buff.append(self.code) | ||
| 208 | buff.append((self.id & 0xFF00) >> 8) | ||
| 209 | buff.append(self.id & 0x00FF) | ||
| 210 | |||
| 211 | if self.token: | ||
| 212 | for c in self.token: | ||
| 213 | buff.append(c) | ||
| 214 | |||
| 215 | lastnumber = 0 | ||
| 216 | |||
| 217 | if len(self.uri_path) > 0: | ||
| 218 | paths = self.uri_path.split("/") | ||
| 219 | for p in paths: | ||
| 220 | if len(p) > 0: | ||
| 221 | if PYTHON_MAJOR >= 3: | ||
| 222 | data = p.encode() | ||
| 223 | else: | ||
| 224 | data = bytearray(p) | ||
| 225 | lastnumber = self.appendOption(buff, lastnumber, COAPOption.URI_PATH, data) | ||
| 226 | |||
| 227 | if self.content_format != None: | ||
| 228 | data = bytearray() | ||
| 229 | fmt_code = self.content_format | ||
| 230 | if fmt_code > 0xFF: | ||
| 231 | data.append((fmt_code & 0xFF00) >> 8) | ||
| 232 | data.append(fmt_code & 0x00FF) | ||
| 233 | lastnumber = self.appendOption(buff, lastnumber, COAPOption.CONTENT_FORMAT, data) | ||
| 234 | |||
| 235 | buff.append(0xFF) | ||
| 236 | |||
| 237 | if self.payload: | ||
| 238 | if PYTHON_MAJOR >= 3: | ||
| 239 | data = self.payload.encode() | ||
| 240 | else: | ||
| 241 | data = bytearray(self.payload) | ||
| 242 | for c in data: | ||
| 243 | buff.append(c) | ||
| 244 | |||
| 245 | return buff | ||
| 246 | |||
| 247 | def parseByteArray(self, buff): | ||
| 248 | self.version = (buff[0] & 0xC0) >> 6 | ||
| 249 | self.type = (buff[0] & 0x30) >> 4 | ||
| 250 | token_length = buff[0] & 0x0F | ||
| 251 | index = 4 | ||
| 252 | if token_length > 0: | ||
| 253 | self.token = buff[index:index+token_length] | ||
| 254 | |||
| 255 | index += token_length | ||
| 256 | self.code = buff[1] | ||
| 257 | self.id = (buff[2] << 8) | buff[3] | ||
| 258 | |||
| 259 | number = 0 | ||
| 260 | |||
| 261 | # process options | ||
| 262 | while index < len(buff) and buff[index] != 0xFF: | ||
| 263 | (delta, length) = self.__getOptionHeader__(buff[index]) | ||
| 264 | offset = 1 | ||
| 265 | |||
| 266 | # delta extended with 1 byte | ||
| 267 | if delta == 13: | ||
| 268 | delta += buff[index+offset] | ||
| 269 | offset += 1 | ||
| 270 | # delta extended with 2 buff | ||
| 271 | elif delta == 14: | ||
| 272 | delta += 255 + ((buff[index+offset] << 8) | buff[index+offset+1]) | ||
| 273 | offset += 2 | ||
| 274 | |||
| 275 | # length extended with 1 byte | ||
| 276 | if length == 13: | ||
| 277 | length += buff[index+offset] | ||
| 278 | offset += 1 | ||
| 279 | |||
| 280 | # length extended with 2 buff | ||
| 281 | elif length == 14: | ||
| 282 | length += 255 + ((buff[index+offset] << 8) | buff[index+offset+1]) | ||
| 283 | offset += 2 | ||
| 284 | |||
| 285 | number += delta | ||
| 286 | valueBytes = buff[index+offset:index+offset+length] | ||
| 287 | # opaque option value | ||
| 288 | if number in [COAPOption.IF_MATCH, COAPOption.ETAG]: | ||
| 289 | value = valueBytes | ||
| 290 | # integer option value | ||
| 291 | elif number in [COAPOption.URI_PORT, COAPOption.CONTENT_FORMAT, COAPOption.MAX_AGE, COAPOption.ACCEPT]: | ||
| 292 | value = 0 | ||
| 293 | for b in valueBytes: | ||
| 294 | value <<= 8 | ||
| 295 | value |= b | ||
| 296 | # string option value | ||
| 297 | else: | ||
| 298 | if PYTHON_MAJOR >= 3: | ||
| 299 | value = valueBytes.decode() | ||
| 300 | else: | ||
| 301 | value = str(valueBytes) | ||
| 302 | self.options.append({'number': number, 'value': value}) | ||
| 303 | index += offset + length | ||
| 304 | |||
| 305 | index += 1 # skip 0xFF / end-of-options | ||
| 306 | |||
| 307 | if len(buff) > index: | ||
| 308 | self.payload = buff[index:] | ||
| 309 | else: | ||
| 310 | self.payload = "" | ||
| 311 | |||
| 312 | for option in self.options: | ||
| 313 | (number, value) = option.values() | ||
| 314 | if number == COAPOption.URI_PATH: | ||
| 315 | self.uri_path += "/%s" % value | ||
| 316 | |||
| 317 | |||
| 318 | class COAPRequest(COAPMessage): | ||
| 319 | CODES = {0: None, | ||
| 320 | 1: "GET", | ||
| 321 | 2: "POST", | ||
| 322 | 3: "PUT", | ||
| 323 | 4: "DELETE" | ||
| 324 | } | ||
| 325 | |||
| 326 | GET = 1 | ||
| 327 | POST = 2 | ||
| 328 | PUT = 3 | ||
| 329 | DELETE = 4 | ||
| 330 | |||
| 331 | def __init__(self, msg_type=0, code=0, uri=None): | ||
| 332 | COAPMessage.__init__(self, msg_type, code, uri) | ||
| 333 | |||
| 334 | class COAPGet(COAPRequest): | ||
| 335 | def __init__(self, uri): | ||
| 336 | COAPRequest.__init__(self, COAPMessage.CON, COAPRequest.GET, uri) | ||
| 337 | |||
| 338 | class COAPPost(COAPRequest): | ||
| 339 | def __init__(self, uri): | ||
| 340 | COAPRequest.__init__(self, COAPMessage.CON, COAPRequest.POST, uri) | ||
| 341 | |||
| 342 | class COAPPut(COAPRequest): | ||
| 343 | def __init__(self, uri): | ||
| 344 | COAPRequest.__init__(self, COAPMessage.CON, COAPRequest.PUT, uri) | ||
| 345 | |||
| 346 | class COAPDelete(COAPRequest): | ||
| 347 | def __init__(self, uri): | ||
| 348 | COAPRequest.__init__(self, COAPMessage.CON, COAPRequest.DELETE, uri) | ||
| 349 | |||
| 350 | class COAPResponse(COAPMessage): | ||
| 351 | CODES = {0: None, | ||
| 352 | 64: "2.00 OK", | ||
| 353 | 65: "2.01 Created", | ||
| 354 | 66: "2.02 Deleted", | ||
| 355 | 67: "2.03 Valid", | ||
| 356 | 68: "2.04 Changed", | ||
| 357 | 69: "2.05 Content", | ||
| 358 | 128: "4.00 Bad Request", | ||
| 359 | 129: "4.01 Unauthorized", | ||
| 360 | 130: "4.02 Bad Option", | ||
| 361 | 131: "4.03 Forbidden", | ||
| 362 | 132: "4.04 Not Found", | ||
| 363 | 133: "4.05 Method Not Allowed", | ||
| 364 | 134: "4.06 Not Acceptable", | ||
| 365 | 140: "4.12 Precondition Failed", | ||
| 366 | 141: "4.13 Request Entity Too Large", | ||
| 367 | 143: "4.15 Unsupported Content-Format", | ||
| 368 | 160: "5.00 Internal Server Error", | ||
| 369 | 161: "5.01 Not Implemented", | ||
| 370 | 162: "5.02 Bad Gateway", | ||
| 371 | 163: "5.03 Service Unavailable", | ||
| 372 | 164: "5.04 Gateway Timeout", | ||
| 373 | 165: "5.05 Proxying Not Supported" | ||
| 374 | } | ||
| 375 | |||
| 376 | # 2.XX | ||
| 377 | OK = 64 | ||
| 378 | CREATED = 65 | ||
| 379 | DELETED = 66 | ||
| 380 | VALID = 67 | ||
| 381 | CHANGED = 68 | ||
| 382 | CONTENT = 69 | ||
| 383 | |||
| 384 | # 4.XX | ||
| 385 | BAD_REQUEST = 128 | ||
| 386 | UNAUTHORIZED = 129 | ||
| 387 | BAD_OPTION = 130 | ||
| 388 | FORBIDDEN = 131 | ||
| 389 | NOT_FOUND = 132 | ||
| 390 | NOT_ALLOWED = 133 | ||
| 391 | NOT_ACCEPTABLE = 134 | ||
| 392 | PRECONDITION_FAILED = 140 | ||
| 393 | ENTITY_TOO_LARGE = 141 | ||
| 394 | UNSUPPORTED_CONTENT = 143 | ||
| 395 | |||
| 396 | # 5.XX | ||
| 397 | INTERNAL_ERROR = 160 | ||
| 398 | NOT_IMPLEMENTED = 161 | ||
| 399 | BAD_GATEWAY = 162 | ||
| 400 | SERVICE_UNAVAILABLE = 163 | ||
| 401 | GATEWAY_TIMEOUT = 164 | ||
| 402 | PROXYING_NOT_SUPPORTED = 165 | ||
| 403 | |||
| 404 | def __init__(self): | ||
| 405 | COAPMessage.__init__(self) | ||
| 406 | |||
| 407 | class COAPClient(): | ||
| 408 | def __init__(self): | ||
| 409 | self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) | ||
| 410 | self.socket.settimeout(1.0) | ||
| 411 | self.socket.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 2) | ||
| 412 | |||
| 413 | def sendRequest(self, message): | ||
| 414 | data = message.getBytes(); | ||
| 415 | sent = 0 | ||
| 416 | while sent<4: | ||
| 417 | try: | ||
| 418 | self.socket.sendto(data, (message.host, message.port)) | ||
| 419 | data = self.socket.recv(1500) | ||
| 420 | response = COAPResponse() | ||
| 421 | response.parseByteArray(bytearray(data)) | ||
| 422 | return response | ||
| 423 | except socket.timeout: | ||
| 424 | sent+=1 | ||
| 425 | return None | ||
| 426 | |||
| 427 | class COAPServer(threading.Thread): | ||
| 428 | logger = logging.getLogger("CoAP") | ||
| 429 | |||
| 430 | def __init__(self, host, port, handler): | ||
| 431 | threading.Thread.__init__(self, name="COAPThread") | ||
| 432 | self.handler = COAPHandler(handler) | ||
| 433 | self.host = host | ||
| 434 | self.port = port | ||
| 435 | self.multicast_ip = '224.0.1.123' | ||
| 436 | self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) | ||
| 437 | self.socket.bind(('', port)) | ||
| 438 | self.socket.settimeout(1) | ||
| 439 | self.running = True | ||
| 440 | self.start() | ||
| 441 | |||
| 442 | def run(self): | ||
| 443 | info("CoAP Server binded on coap://%s:%s/" % (self.host, self.port)) | ||
| 444 | while self.running == True: | ||
| 445 | try: | ||
| 446 | (request, client) = self.socket.recvfrom(1500) | ||
| 447 | requestBytes = bytearray(request) | ||
| 448 | coapRequest = COAPRequest() | ||
| 449 | coapRequest.parseByteArray(requestBytes) | ||
| 450 | coapResponse = COAPResponse() | ||
| 451 | #self.logger.debug("Received Request:\n%s" % coapRequest) | ||
| 452 | self.processMessage(coapRequest, coapResponse) | ||
| 453 | #self.logger.debug("Sending Response:\n%s" % coapResponse) | ||
| 454 | responseBytes = coapResponse.getBytes() | ||
| 455 | self.socket.sendto(responseBytes, client) | ||
| 456 | self.logger.debug('"%s %s CoAP/%.1f" %s -' % (coapRequest.CODES[coapRequest.code], coapRequest.uri_path, coapRequest.version, coapResponse.CODES[coapResponse.code])) | ||
| 457 | |||
| 458 | except socket.timeout as e: | ||
| 459 | continue | ||
| 460 | except Exception as e: | ||
| 461 | if self.running == True: | ||
| 462 | exception(e) | ||
| 463 | |||
| 464 | info("CoAP Server stopped") | ||
| 465 | |||
| 466 | def enableMulticast(self): | ||
| 467 | while not self.running: | ||
| 468 | pass | ||
| 469 | mreq = struct.pack("4sl", socket.inet_aton(self.multicast_ip), socket.INADDR_ANY) | ||
| 470 | self.socket.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq) | ||
| 471 | info("CoAP Server binded on coap://%s:%s/ (MULTICAST)" % (self.multicast_ip, self.port)) | ||
| 472 | |||
| 473 | def stop(self): | ||
| 474 | self.running = False | ||
| 475 | self.socket.close() | ||
| 476 | |||
| 477 | def processMessage(self, request, response): | ||
| 478 | if request.type == COAPMessage.CON: | ||
| 479 | response.type = COAPMessage.ACK | ||
| 480 | else: | ||
| 481 | response.type = COAPMessage.NON | ||
| 482 | |||
| 483 | if request.token: | ||
| 484 | response.token = request.token | ||
| 485 | |||
| 486 | response.id = request.id | ||
| 487 | response.uri_path = request.uri_path | ||
| 488 | |||
| 489 | if request.code == COAPRequest.GET: | ||
| 490 | self.handler.do_GET(request, response) | ||
| 491 | elif request.code == COAPRequest.POST: | ||
| 492 | self.handler.do_POST(request, response) | ||
| 493 | elif request.code / 32 == 0: | ||
| 494 | response.code = COAPResponse.NOT_IMPLEMENTED | ||
| 495 | else: | ||
| 496 | exception(Exception("Received CoAP Response : %s" % response)) | ||
| 497 | |||
| 498 | class COAPHandler(): | ||
| 499 | def __init__(self, handler): | ||
| 500 | self.handler = handler | ||
| 501 | |||
| 502 | def do_GET(self, request, response): | ||
| 503 | try: | ||
| 504 | (code, body, contentType) = self.handler.do_GET(request.uri_path[1:], True) | ||
| 505 | if code == 0: | ||
| 506 | response.code = COAPResponse.NOT_FOUND | ||
| 507 | elif code == 200: | ||
| 508 | response.code = COAPResponse.CONTENT | ||
| 509 | else: | ||
| 510 | response.code = HTTPCode2CoAPCode(code) | ||
| 511 | response.payload = body | ||
| 512 | response.content_format = COAPContentFormat.getCode(contentType) | ||
| 513 | except (GPIO.InvalidDirectionException, GPIO.InvalidChannelException, GPIO.SetupException) as e: | ||
| 514 | response.code = COAPResponse.FORBIDDEN | ||
| 515 | response.payload = "%s" % e | ||
| 516 | except Exception as e: | ||
| 517 | response.code = COAPResponse.INTERNAL_ERROR | ||
| 518 | raise e | ||
| 519 | |||
| 520 | def do_POST(self, request, response): | ||
| 521 | try: | ||
| 522 | (code, body, contentType) = self.handler.do_POST(request.uri_path[1:], request.payload, True) | ||
| 523 | if code == 0: | ||
| 524 | response.code = COAPResponse.NOT_FOUND | ||
| 525 | elif code == 200: | ||
| 526 | response.code = COAPResponse.CHANGED | ||
| 527 | else: | ||
| 528 | response.code = HTTPCode2CoAPCode(code) | ||
| 529 | response.payload = body | ||
| 530 | response.content_format = COAPContentFormat.getCode(contentType) | ||
| 531 | except (GPIO.InvalidDirectionException, GPIO.InvalidChannelException, GPIO.SetupException) as e: | ||
| 532 | response.code = COAPResponse.FORBIDDEN | ||
| 533 | response.payload = "%s" % e | ||
| 534 | except Exception as e: | ||
| 535 | response.code = COAPResponse.INTERNAL_ERROR | ||
| 536 | raise e | ||
| 537 | |||
diff --git a/python/webiopi/protocols/http.py b/python/webiopi/protocols/http.py new file mode 100644 index 0000000..aea6d82 --- /dev/null +++ b/python/webiopi/protocols/http.py | |||
| @@ -0,0 +1,249 @@ | |||
| 1 | # Copyright 2012-2013 Eric Ptak - trouch.com | ||
| 2 | # | ||
| 3 | # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 4 | # you may not use this file except in compliance with the License. | ||
| 5 | # You may obtain a copy of the License at | ||
| 6 | # | ||
| 7 | # http://www.apache.org/licenses/LICENSE-2.0 | ||
| 8 | # | ||
| 9 | # Unless required by applicable law or agreed to in writing, software | ||
| 10 | # distributed under the License is distributed on an "AS IS" BASIS, | ||
| 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 12 | # See the License for the specific language governing permissions and | ||
| 13 | # limitations under the License. | ||
| 14 | |||
| 15 | import os | ||
| 16 | import threading | ||
| 17 | import codecs | ||
| 18 | import mimetypes as mime | ||
| 19 | import logging | ||
| 20 | |||
| 21 | from webiopi.utils.version import VERSION_STRING, PYTHON_MAJOR | ||
| 22 | from webiopi.utils.logger import info, exception | ||
| 23 | from webiopi.utils.crypto import encrypt | ||
| 24 | from webiopi.utils.types import str2bool | ||
| 25 | |||
| 26 | if PYTHON_MAJOR >= 3: | ||
| 27 | import http.server as BaseHTTPServer | ||
| 28 | else: | ||
| 29 | import BaseHTTPServer | ||
| 30 | |||
| 31 | try : | ||
| 32 | import _webiopi.GPIO as GPIO | ||
| 33 | except: | ||
| 34 | pass | ||
| 35 | |||
| 36 | WEBIOPI_DOCROOT = "/usr/share/webiopi/htdocs" | ||
| 37 | |||
| 38 | class HTTPServer(BaseHTTPServer.HTTPServer, threading.Thread): | ||
| 39 | def __init__(self, host, port, handler, context, docroot, index, auth=None): | ||
| 40 | BaseHTTPServer.HTTPServer.__init__(self, ("", port), HTTPHandler) | ||
| 41 | threading.Thread.__init__(self, name="HTTPThread") | ||
| 42 | self.host = host | ||
| 43 | self.port = port | ||
| 44 | |||
| 45 | if context: | ||
| 46 | self.context = context | ||
| 47 | if not self.context.startswith("/"): | ||
| 48 | self.context = "/" + self.context | ||
| 49 | if not self.context.endswith("/"): | ||
| 50 | self.context += "/" | ||
| 51 | else: | ||
| 52 | self.context = "/" | ||
| 53 | |||
| 54 | self.docroot = docroot | ||
| 55 | |||
| 56 | if index: | ||
| 57 | self.index = index | ||
| 58 | else: | ||
| 59 | self.index = "index.html" | ||
| 60 | |||
| 61 | self.handler = handler | ||
| 62 | self.auth = auth | ||
| 63 | |||
| 64 | self.running = True | ||
| 65 | self.start() | ||
| 66 | |||
| 67 | def get_request(self): | ||
| 68 | sock, addr = self.socket.accept() | ||
| 69 | sock.settimeout(10.0) | ||
| 70 | return (sock, addr) | ||
| 71 | |||
| 72 | def run(self): | ||
| 73 | info("HTTP Server binded on http://%s:%s%s" % (self.host, self.port, self.context)) | ||
| 74 | try: | ||
| 75 | self.serve_forever() | ||
| 76 | except Exception as e: | ||
| 77 | if self.running == True: | ||
| 78 | exception(e) | ||
| 79 | info("HTTP Server stopped") | ||
| 80 | |||
| 81 | def stop(self): | ||
| 82 | self.running = False | ||
| 83 | self.server_close() | ||
| 84 | |||
| 85 | class HTTPHandler(BaseHTTPServer.BaseHTTPRequestHandler): | ||
| 86 | logger = logging.getLogger("HTTP") | ||
| 87 | |||
| 88 | def log_message(self, fmt, *args): | ||
| 89 | self.logger.debug(fmt % args) | ||
| 90 | |||
| 91 | def log_error(self, fmt, *args): | ||
| 92 | pass | ||
| 93 | |||
| 94 | def version_string(self): | ||
| 95 | return VERSION_STRING | ||
| 96 | |||
| 97 | def checkAuthentication(self): | ||
| 98 | if self.server.auth == None or len(self.server.auth) == 0: | ||
| 99 | return True | ||
| 100 | |||
| 101 | authHeader = self.headers.get('Authorization') | ||
| 102 | if authHeader == None: | ||
| 103 | return False | ||
| 104 | |||
| 105 | if not authHeader.startswith("Basic "): | ||
| 106 | return False | ||
| 107 | |||
| 108 | auth = authHeader.replace("Basic ", "") | ||
| 109 | if PYTHON_MAJOR >= 3: | ||
| 110 | auth_hash = encrypt(auth.encode()) | ||
| 111 | else: | ||
| 112 | auth_hash = encrypt(auth) | ||
| 113 | |||
| 114 | if auth_hash == self.server.auth: | ||
| 115 | return True | ||
| 116 | return False | ||
| 117 | |||
| 118 | def requestAuthentication(self): | ||
| 119 | self.send_response(401) | ||
| 120 | self.send_header("WWW-Authenticate", 'Basic realm="webiopi"') | ||
| 121 | self.end_headers(); | ||
| 122 | |||
| 123 | def sendResponse(self, code, body=None, contentType="text/plain"): | ||
| 124 | if code >= 400: | ||
| 125 | if body != None: | ||
| 126 | self.send_error(code, body) | ||
| 127 | else: | ||
| 128 | self.send_error(code) | ||
| 129 | else: | ||
| 130 | self.send_response(code) | ||
| 131 | self.send_header("Cache-Control", "no-cache") | ||
| 132 | if body != None: | ||
| 133 | self.send_header("Content-Type", contentType); | ||
| 134 | self.end_headers(); | ||
| 135 | self.wfile.write(body.encode()) | ||
| 136 | |||
| 137 | def findFile(self, filepath): | ||
| 138 | if os.path.exists(filepath): | ||
| 139 | if os.path.isdir(filepath): | ||
| 140 | filepath += "/" + self.server.index | ||
| 141 | if os.path.exists(filepath): | ||
| 142 | return filepath | ||
| 143 | else: | ||
| 144 | return filepath | ||
| 145 | return None | ||
| 146 | |||
| 147 | |||
| 148 | def serveFile(self, relativePath): | ||
| 149 | if self.server.docroot != None: | ||
| 150 | path = self.findFile(self.server.docroot + "/" + relativePath) | ||
| 151 | if path == None: | ||
| 152 | path = self.findFile("./" + relativePath) | ||
| 153 | |||
| 154 | else: | ||
| 155 | path = self.findFile("./" + relativePath) | ||
| 156 | if path == None: | ||
| 157 | path = self.findFile(WEBIOPI_DOCROOT + "/" + relativePath) | ||
| 158 | |||
| 159 | if path == None and (relativePath.startswith("webiopi.") or relativePath.startswith("jquery")): | ||
| 160 | path = self.findFile(WEBIOPI_DOCROOT + "/" + relativePath) | ||
| 161 | |||
| 162 | if path == None: | ||
| 163 | return self.sendResponse(404, "Not Found") | ||
| 164 | |||
| 165 | realPath = os.path.realpath(path) | ||
| 166 | |||
| 167 | if realPath.endswith(".py"): | ||
| 168 | return self.sendResponse(403, "Not Authorized") | ||
| 169 | |||
| 170 | if not (realPath.startswith(os.getcwd()) | ||
| 171 | or (self.server.docroot and realPath.startswith(self.server.docroot)) | ||
| 172 | or realPath.startswith(WEBIOPI_DOCROOT)): | ||
| 173 | return self.sendResponse(403, "Not Authorized") | ||
| 174 | |||
| 175 | (contentType, encoding) = mime.guess_type(path) | ||
| 176 | f = codecs.open(path, encoding=encoding) | ||
| 177 | data = f.read() | ||
| 178 | f.close() | ||
| 179 | self.send_response(200) | ||
| 180 | self.send_header("Content-Type", contentType); | ||
| 181 | self.send_header("Content-Length", os.path.getsize(realPath)) | ||
| 182 | self.end_headers() | ||
| 183 | self.wfile.write(data) | ||
| 184 | |||
| 185 | def processRequest(self): | ||
| 186 | self.request.settimeout(None) | ||
| 187 | if not self.checkAuthentication(): | ||
| 188 | return self.requestAuthentication() | ||
| 189 | |||
| 190 | request = self.path.replace(self.server.context, "/").split('?') | ||
| 191 | relativePath = request[0] | ||
| 192 | if relativePath[0] == "/": | ||
| 193 | relativePath = relativePath[1:] | ||
| 194 | |||
| 195 | if relativePath == "webiopi" or relativePath == "webiopi/": | ||
| 196 | self.send_response(301) | ||
| 197 | self.send_header("Location", "/") | ||
| 198 | self.end_headers() | ||
| 199 | return | ||
| 200 | |||
| 201 | params = {} | ||
| 202 | if len(request) > 1: | ||
| 203 | for s in request[1].split('&'): | ||
| 204 | if s.find('=') > 0: | ||
| 205 | (name, value) = s.split('=') | ||
| 206 | params[name] = value | ||
| 207 | else: | ||
| 208 | params[s] = None | ||
| 209 | |||
| 210 | compact = False | ||
| 211 | if 'compact' in params: | ||
| 212 | compact = str2bool(params['compact']) | ||
| 213 | |||
| 214 | try: | ||
| 215 | result = (None, None, None) | ||
| 216 | if self.command == "GET": | ||
| 217 | result = self.server.handler.do_GET(relativePath, compact) | ||
| 218 | elif self.command == "POST": | ||
| 219 | length = 0 | ||
| 220 | length_header = 'content-length' | ||
| 221 | if length_header in self.headers: | ||
| 222 | length = int(self.headers[length_header]) | ||
| 223 | result = self.server.handler.do_POST(relativePath, self.rfile.read(length), compact) | ||
| 224 | else: | ||
| 225 | result = (405, None, None) | ||
| 226 | |||
| 227 | (code, body, contentType) = result | ||
| 228 | |||
| 229 | if code > 0: | ||
| 230 | self.sendResponse(code, body, contentType) | ||
| 231 | else: | ||
| 232 | if self.command == "GET": | ||
| 233 | self.serveFile(relativePath) | ||
| 234 | else: | ||
| 235 | self.sendResponse(404) | ||
| 236 | |||
| 237 | except (GPIO.InvalidDirectionException, GPIO.InvalidChannelException, GPIO.SetupException) as e: | ||
| 238 | self.sendResponse(403, "%s" % e) | ||
| 239 | except ValueError as e: | ||
| 240 | self.sendResponse(403, "%s" % e) | ||
| 241 | except Exception as e: | ||
| 242 | self.sendResponse(500) | ||
| 243 | raise e | ||
| 244 | |||
| 245 | def do_GET(self): | ||
| 246 | self.processRequest() | ||
| 247 | |||
| 248 | def do_POST(self): | ||
| 249 | self.processRequest() | ||
diff --git a/python/webiopi/protocols/rest.py b/python/webiopi/protocols/rest.py new file mode 100644 index 0000000..6010604 --- /dev/null +++ b/python/webiopi/protocols/rest.py | |||
| @@ -0,0 +1,254 @@ | |||
| 1 | # Copyright 2012-2013 Eric Ptak - trouch.com | ||
| 2 | # | ||
| 3 | # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 4 | # you may not use this file except in compliance with the License. | ||
| 5 | # You may obtain a copy of the License at | ||
| 6 | # | ||
| 7 | # http://www.apache.org/licenses/LICENSE-2.0 | ||
| 8 | # | ||
| 9 | # Unless required by applicable law or agreed to in writing, software | ||
| 10 | # distributed under the License is distributed on an "AS IS" BASIS, | ||
| 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 12 | # See the License for the specific language governing permissions and | ||
| 13 | # limitations under the License. | ||
| 14 | |||
| 15 | from webiopi.utils import types | ||
| 16 | from webiopi.utils import logger | ||
| 17 | from webiopi.utils.types import M_JSON, M_PLAIN | ||
| 18 | from webiopi.utils.version import BOARD_REVISION, VERSION_STRING, MAPPING | ||
| 19 | from webiopi.devices import manager | ||
| 20 | from webiopi.devices import instance | ||
| 21 | from webiopi.devices.bus import BUSLIST | ||
| 22 | |||
| 23 | try: | ||
| 24 | import _webiopi.GPIO as GPIO | ||
| 25 | except: | ||
| 26 | pass | ||
| 27 | |||
| 28 | MACROS = {} | ||
| 29 | |||
| 30 | class RESTHandler(): | ||
| 31 | def __init__(self): | ||
| 32 | self.device_mapping = True | ||
| 33 | self.export = [] | ||
| 34 | self.routes = {} | ||
| 35 | self.macros = {} | ||
| 36 | |||
| 37 | |||
| 38 | def addMacro(self, macro): | ||
| 39 | self.macros[macro.__name__] = macro | ||
| 40 | |||
| 41 | def addRoute(self, source, destination): | ||
| 42 | if source[0] == "/": | ||
| 43 | source = source[1:] | ||
| 44 | if destination[0] == "/": | ||
| 45 | destination = destination[1:] | ||
| 46 | self.routes[source] = destination | ||
| 47 | logger.info("Added Route /%s => /%s" % (source, destination)) | ||
| 48 | |||
| 49 | def findRoute(self, path): | ||
| 50 | for source in self.routes: | ||
| 51 | if path.startswith(source): | ||
| 52 | route = path.replace(source, self.routes[source]) | ||
| 53 | logger.info("Routing /%s => /%s" % (path, route)) | ||
| 54 | return route | ||
| 55 | return path | ||
| 56 | |||
| 57 | def extract(self, fmtArray, pathArray, args): | ||
| 58 | if len(fmtArray) != len(pathArray): | ||
| 59 | return False | ||
| 60 | if len(fmtArray) == 0: | ||
| 61 | return True | ||
| 62 | fmt = fmtArray[0] | ||
| 63 | path = pathArray[0] | ||
| 64 | if fmt == path: | ||
| 65 | return self.extract(fmtArray[1:], pathArray[1:], args) | ||
| 66 | if fmt.startswith("%"): | ||
| 67 | |||
| 68 | fmt = fmt[1:] | ||
| 69 | t = 's' | ||
| 70 | if fmt[0] == '(': | ||
| 71 | if fmt[-1] == ')': | ||
| 72 | name = fmt[1:-1] | ||
| 73 | elif fmt[-2] == ')': | ||
| 74 | name = fmt[1:-2] | ||
| 75 | t = fmt[-1] | ||
| 76 | else: | ||
| 77 | raise Exception("Missing closing brace") | ||
| 78 | else: | ||
| 79 | name = fmt | ||
| 80 | |||
| 81 | if t == 's': | ||
| 82 | args[name] = path | ||
| 83 | elif t == 'b': | ||
| 84 | args[name] = types.str2bool(path) | ||
| 85 | elif t == 'd': | ||
| 86 | args[name] = types.toint(path) | ||
| 87 | elif t == 'x': | ||
| 88 | args[name] = int(path, 16) | ||
| 89 | elif t == 'f': | ||
| 90 | args[name] = float(path) | ||
| 91 | else: | ||
| 92 | raise Exception("Unknown format type : %s" % t) | ||
| 93 | |||
| 94 | return self.extract(fmtArray[1:], pathArray[1:], args) | ||
| 95 | |||
| 96 | return False | ||
| 97 | |||
| 98 | def getDeviceRoute(self, method, path): | ||
| 99 | pathArray = path.split("/") | ||
| 100 | deviceName = pathArray[0] | ||
| 101 | device = instance.DEVICES[deviceName] | ||
| 102 | if device == None: | ||
| 103 | return (None, deviceName + " Not Found") | ||
| 104 | pathArray = pathArray[1:] | ||
| 105 | funcs = device["functions"][method] | ||
| 106 | functionName = "/".join(pathArray) | ||
| 107 | if functionName in funcs: | ||
| 108 | return (funcs[functionName], {}) | ||
| 109 | |||
| 110 | for fname in funcs: | ||
| 111 | func = funcs[fname] | ||
| 112 | funcPathArray = func.path.split("/") | ||
| 113 | args = {} | ||
| 114 | if self.extract(funcPathArray, pathArray, args): | ||
| 115 | return (func, args) | ||
| 116 | |||
| 117 | return (None, functionName + " Not Found") | ||
| 118 | |||
| 119 | def callDeviceFunction(self, method, path, data=None): | ||
| 120 | (func, args) = self.getDeviceRoute(method, path) | ||
| 121 | if func == None: | ||
| 122 | return (404, args, M_PLAIN) | ||
| 123 | |||
| 124 | if func.data != None: | ||
| 125 | args[func.data] = data | ||
| 126 | |||
| 127 | result = func(**args) | ||
| 128 | response = None | ||
| 129 | contentType = None | ||
| 130 | if result != None: | ||
| 131 | if hasattr(func, "contentType"): | ||
| 132 | contentType = func.contentType | ||
| 133 | if contentType == M_JSON: | ||
| 134 | response = types.jsonDumps(result) | ||
| 135 | else: | ||
| 136 | response = func.format % result | ||
| 137 | else: | ||
| 138 | response = result | ||
| 139 | |||
| 140 | return (200, response, contentType) | ||
| 141 | |||
| 142 | def do_GET(self, relativePath, compact=False): | ||
| 143 | relativePath = self.findRoute(relativePath) | ||
| 144 | |||
| 145 | # JSON full state | ||
| 146 | if relativePath == "*": | ||
| 147 | return (200, self.getJSON(compact), M_JSON) | ||
| 148 | |||
| 149 | # RPi header map | ||
| 150 | elif relativePath == "map": | ||
| 151 | json = "%s" % MAPPING | ||
| 152 | json = json.replace("'", '"') | ||
| 153 | return (200, json, M_JSON) | ||
| 154 | |||
| 155 | # server version | ||
| 156 | elif relativePath == "version": | ||
| 157 | return (200, VERSION_STRING, M_PLAIN) | ||
| 158 | |||
| 159 | # board revision | ||
| 160 | elif relativePath == "revision": | ||
| 161 | revision = "%s" % BOARD_REVISION | ||
| 162 | return (200, revision, M_PLAIN) | ||
| 163 | |||
| 164 | # Single GPIO getter | ||
| 165 | elif relativePath.startswith("GPIO/"): | ||
| 166 | return self.callDeviceFunction("GET", relativePath) | ||
| 167 | |||
| 168 | elif relativePath == "devices/*": | ||
| 169 | return (200, manager.getDevicesJSON(compact), M_JSON) | ||
| 170 | |||
| 171 | elif relativePath.startswith("devices/"): | ||
| 172 | if not self.device_mapping: | ||
| 173 | return (404, None, None) | ||
| 174 | path = relativePath.replace("devices/", "") | ||
| 175 | return self.callDeviceFunction("GET", path) | ||
| 176 | |||
| 177 | else: | ||
| 178 | return (0, None, None) | ||
| 179 | |||
| 180 | def do_POST(self, relativePath, data, compact=False): | ||
| 181 | relativePath = self.findRoute(relativePath) | ||
| 182 | |||
| 183 | if relativePath.startswith("GPIO/"): | ||
| 184 | return self.callDeviceFunction("POST", relativePath) | ||
| 185 | |||
| 186 | elif relativePath.startswith("macros/"): | ||
| 187 | paths = relativePath.split("/") | ||
| 188 | mname = paths[1] | ||
| 189 | if len(paths) > 2: | ||
| 190 | value = paths[2] | ||
| 191 | else: | ||
| 192 | value = "" | ||
| 193 | |||
| 194 | if mname in self.macros: | ||
| 195 | macro = self.macros[mname] | ||
| 196 | |||
| 197 | if ',' in value: | ||
| 198 | args = value.split(',') | ||
| 199 | result = macro(*args) | ||
| 200 | elif len(value) > 0: | ||
| 201 | result = macro(value) | ||
| 202 | else: | ||
| 203 | result = macro() | ||
| 204 | |||
| 205 | response = "" | ||
| 206 | if result: | ||
| 207 | response = "%s" % result | ||
| 208 | return (200, response, M_PLAIN) | ||
| 209 | |||
| 210 | else: | ||
| 211 | return (404, mname + " Not Found", M_PLAIN) | ||
| 212 | |||
| 213 | elif relativePath.startswith("devices/"): | ||
| 214 | if not self.device_mapping: | ||
| 215 | return (404, None, None) | ||
| 216 | path = relativePath.replace("devices/", "") | ||
| 217 | return self.callDeviceFunction("POST", path, data) | ||
| 218 | |||
| 219 | else: # path unknowns | ||
| 220 | return (0, None, None) | ||
| 221 | |||
| 222 | def getJSON(self, compact=False): | ||
| 223 | if compact: | ||
| 224 | f = 'f' | ||
| 225 | v = 'v' | ||
| 226 | else: | ||
| 227 | f = 'function' | ||
| 228 | v = 'value' | ||
| 229 | |||
| 230 | json = {} | ||
| 231 | for (bus, value) in BUSLIST.items(): | ||
| 232 | json[bus] = int(value["enabled"]) | ||
| 233 | |||
| 234 | gpios = {} | ||
| 235 | if len(self.export) > 0: | ||
| 236 | export = self.export | ||
| 237 | else: | ||
| 238 | export = range(GPIO.GPIO_COUNT) | ||
| 239 | |||
| 240 | for gpio in export: | ||
| 241 | gpios[gpio] = {} | ||
| 242 | if compact: | ||
| 243 | gpios[gpio][f] = GPIO.getFunction(gpio) | ||
| 244 | else: | ||
| 245 | gpios[gpio][f] = GPIO.getFunctionString(gpio) | ||
| 246 | gpios[gpio][v] = int(GPIO.input(gpio)) | ||
| 247 | |||
| 248 | if GPIO.getFunction(gpio) == GPIO.PWM: | ||
| 249 | (pwmType, value) = GPIO.getPulse(gpio).split(':') | ||
| 250 | gpios[gpio][pwmType] = value | ||
| 251 | |||
| 252 | json['GPIO'] = gpios | ||
| 253 | return types.jsonDumps(json) | ||
| 254 | |||
diff --git a/python/webiopi/server/__init__.py b/python/webiopi/server/__init__.py new file mode 100644 index 0000000..68fdbe6 --- /dev/null +++ b/python/webiopi/server/__init__.py | |||
| @@ -0,0 +1,139 @@ | |||
| 1 | # Copyright 2012-2013 Eric Ptak - trouch.com | ||
| 2 | # | ||
| 3 | # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 4 | # you may not use this file except in compliance with the License. | ||
| 5 | # You may obtain a copy of the License at | ||
| 6 | # | ||
| 7 | # http://www.apache.org/licenses/LICENSE-2.0 | ||
| 8 | # | ||
| 9 | # Unless required by applicable law or agreed to in writing, software | ||
| 10 | # distributed under the License is distributed on an "AS IS" BASIS, | ||
| 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 12 | # See the License for the specific language governing permissions and | ||
| 13 | # limitations under the License. | ||
| 14 | |||
| 15 | import os | ||
| 16 | import time | ||
| 17 | import socket | ||
| 18 | |||
| 19 | from webiopi.utils.config import Config | ||
| 20 | from webiopi.utils import loader | ||
| 21 | from webiopi.utils import logger | ||
| 22 | from webiopi.utils import crypto | ||
| 23 | from webiopi.devices import manager | ||
| 24 | from webiopi.protocols import rest | ||
| 25 | from webiopi.protocols import http | ||
| 26 | from webiopi.protocols import coap | ||
| 27 | from webiopi.devices.digital.gpio import NativeGPIO | ||
| 28 | |||
| 29 | def getLocalIP(): | ||
| 30 | s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) | ||
| 31 | try: | ||
| 32 | s.connect(('8.8.8.8', 53)) | ||
| 33 | host = s.getsockname()[0] | ||
| 34 | s.close() | ||
| 35 | return host | ||
| 36 | except: | ||
| 37 | return "localhost" | ||
| 38 | |||
| 39 | class Server(): | ||
| 40 | def __init__(self, port=8000, coap_port=5683, login=None, password=None, passwdfile=None, configfile=None): | ||
| 41 | self.host = getLocalIP() | ||
| 42 | self.gpio = NativeGPIO() | ||
| 43 | self.restHandler = rest.RESTHandler() | ||
| 44 | manager.addDeviceInstance("GPIO", self.gpio, []) | ||
| 45 | |||
| 46 | if configfile != None: | ||
| 47 | logger.info("Loading configuration from %s" % configfile) | ||
| 48 | config = Config(configfile) | ||
| 49 | else: | ||
| 50 | config = Config() | ||
| 51 | |||
| 52 | self.gpio.addSetups(config.items("GPIO")) | ||
| 53 | self.gpio.addResets(config.items("~GPIO")) | ||
| 54 | self.gpio.setup() | ||
| 55 | |||
| 56 | devices = config.items("DEVICES") | ||
| 57 | for (name, params) in devices: | ||
| 58 | values = params.split(" ") | ||
| 59 | driver = values[0]; | ||
| 60 | args = {} | ||
| 61 | i = 1 | ||
| 62 | while i < len(values): | ||
| 63 | (arg, val) = values[i].split(":") | ||
| 64 | args[arg] = val | ||
| 65 | i+=1 | ||
| 66 | manager.addDevice(name, driver, args) | ||
| 67 | |||
| 68 | scripts = config.items("SCRIPTS") | ||
| 69 | for (name, source) in scripts: | ||
| 70 | loader.loadScript(name, source, self.restHandler) | ||
| 71 | |||
| 72 | self.restHandler.device_mapping = config.getboolean("REST", "device-mapping", True) | ||
| 73 | self.gpio.post_value = config.getboolean("REST", "gpio-post-value", True) | ||
| 74 | self.gpio.post_function = config.getboolean("REST", "gpio-post-function", True) | ||
| 75 | exports = config.get("REST", "gpio-export", None) | ||
| 76 | if exports != None: | ||
| 77 | self.gpio.export = [int(s) for s in exports.split(",")] | ||
| 78 | self.restHandler.export = self.gpio.export | ||
| 79 | |||
| 80 | http_port = config.getint("HTTP", "port", port) | ||
| 81 | http_enabled = config.getboolean("HTTP", "enabled", http_port > 0) | ||
| 82 | http_passwdfile = config.get("HTTP", "passwd-file", passwdfile) | ||
| 83 | context = config.get("HTTP", "context", None) | ||
| 84 | docroot = config.get("HTTP", "doc-root", None) | ||
| 85 | index = config.get("HTTP", "welcome-file", None) | ||
| 86 | |||
| 87 | coap_port = config.getint("COAP", "port", coap_port) | ||
| 88 | coap_enabled = config.getboolean("COAP", "enabled", coap_port > 0) | ||
| 89 | coap_multicast = config.getboolean("COAP", "multicast", coap_enabled) | ||
| 90 | |||
| 91 | routes = config.items("ROUTES") | ||
| 92 | for (source, destination) in routes: | ||
| 93 | self.restHandler.addRoute(source, destination) | ||
| 94 | |||
| 95 | auth = None | ||
| 96 | if http_passwdfile != None: | ||
| 97 | if os.path.exists(http_passwdfile): | ||
| 98 | f = open(http_passwdfile) | ||
| 99 | auth = f.read().strip(" \r\n") | ||
| 100 | f.close() | ||
| 101 | if len(auth) > 0: | ||
| 102 | logger.info("Access protected using %s" % http_passwdfile) | ||
| 103 | else: | ||
| 104 | logger.info("Passwd file %s is empty" % http_passwdfile) | ||
| 105 | else: | ||
| 106 | logger.error("Passwd file %s not found" % http_passwdfile) | ||
| 107 | |||
| 108 | elif login != None or password != None: | ||
| 109 | auth = crypto.encryptCredentials(login, password) | ||
| 110 | logger.info("Access protected using login/password") | ||
| 111 | |||
| 112 | if auth == None or len(auth) == 0: | ||
| 113 | logger.warn("Access unprotected") | ||
| 114 | |||
| 115 | if http_enabled: | ||
| 116 | self.http_server = http.HTTPServer(self.host, http_port, self.restHandler, context, docroot, index, auth) | ||
| 117 | else: | ||
| 118 | self.http_server = None | ||
| 119 | |||
| 120 | if coap_enabled: | ||
| 121 | self.coap_server = coap.COAPServer(self.host, coap_port, self.restHandler) | ||
| 122 | if coap_multicast: | ||
| 123 | self.coap_server.enableMulticast() | ||
| 124 | else: | ||
| 125 | self.coap_server = None | ||
| 126 | |||
| 127 | def addMacro(self, macro): | ||
| 128 | self.restHandler.addMacro(macro) | ||
| 129 | |||
| 130 | def stop(self): | ||
| 131 | if self.http_server: | ||
| 132 | self.http_server.stop() | ||
| 133 | if self.coap_server: | ||
| 134 | self.coap_server.stop() | ||
| 135 | loader.unloadScripts() | ||
| 136 | manager.closeDevices() | ||
| 137 | |||
| 138 | |||
| 139 | |||
diff --git a/python/webiopi/utils/__init__.py b/python/webiopi/utils/__init__.py new file mode 100644 index 0000000..ad86c93 --- /dev/null +++ b/python/webiopi/utils/__init__.py | |||
| @@ -0,0 +1,16 @@ | |||
| 1 | # Copyright 2012-2013 Eric Ptak - trouch.com | ||
| 2 | # | ||
| 3 | # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 4 | # you may not use this file except in compliance with the License. | ||
| 5 | # You may obtain a copy of the License at | ||
| 6 | # | ||
| 7 | # http://www.apache.org/licenses/LICENSE-2.0 | ||
| 8 | # | ||
| 9 | # Unless required by applicable law or agreed to in writing, software | ||
| 10 | # distributed under the License is distributed on an "AS IS" BASIS, | ||
| 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 12 | # See the License for the specific language governing permissions and | ||
| 13 | # limitations under the License. | ||
| 14 | |||
| 15 | |||
| 16 | |||
diff --git a/python/webiopi/utils/config.py b/python/webiopi/utils/config.py new file mode 100644 index 0000000..72302e5 --- /dev/null +++ b/python/webiopi/utils/config.py | |||
| @@ -0,0 +1,35 @@ | |||
| 1 | from webiopi.utils.version import PYTHON_MAJOR | ||
| 2 | |||
| 3 | if PYTHON_MAJOR >= 3: | ||
| 4 | import configparser as parser | ||
| 5 | else: | ||
| 6 | import ConfigParser as parser | ||
| 7 | |||
| 8 | class Config(): | ||
| 9 | |||
| 10 | def __init__(self, configfile=None): | ||
| 11 | config = parser.ConfigParser() | ||
| 12 | config.optionxform = str | ||
| 13 | if configfile != None: | ||
| 14 | config.read(configfile) | ||
| 15 | self.config = config | ||
| 16 | |||
| 17 | def get(self, section, key, default): | ||
| 18 | if self.config.has_option(section, key): | ||
| 19 | return self.config.get(section, key) | ||
| 20 | return default | ||
| 21 | |||
| 22 | def getboolean(self, section, key, default): | ||
| 23 | if self.config.has_option(section, key): | ||
| 24 | return self.config.getboolean(section, key) | ||
| 25 | return default | ||
| 26 | |||
| 27 | def getint(self, section, key, default): | ||
| 28 | if self.config.has_option(section, key): | ||
| 29 | return self.config.getint(section, key) | ||
| 30 | return default | ||
| 31 | |||
| 32 | def items(self, section): | ||
| 33 | if self.config.has_section(section): | ||
| 34 | return self.config.items(section) | ||
| 35 | return {} | ||
diff --git a/python/webiopi/utils/crypto.py b/python/webiopi/utils/crypto.py new file mode 100644 index 0000000..aa48a82 --- /dev/null +++ b/python/webiopi/utils/crypto.py | |||
| @@ -0,0 +1,17 @@ | |||
| 1 | import base64 | ||
| 2 | import hashlib | ||
| 3 | from webiopi.utils.version import PYTHON_MAJOR | ||
| 4 | |||
| 5 | def encodeCredentials(login, password): | ||
| 6 | abcd = "%s:%s" % (login, password) | ||
| 7 | if PYTHON_MAJOR >= 3: | ||
| 8 | b = base64.b64encode(abcd.encode()) | ||
| 9 | else: | ||
| 10 | b = base64.b64encode(abcd) | ||
| 11 | return b | ||
| 12 | |||
| 13 | def encrypt(value): | ||
| 14 | return hashlib.sha256(value).hexdigest() | ||
| 15 | |||
| 16 | def encryptCredentials(login, password): | ||
| 17 | return encrypt(encodeCredentials(login, password)) | ||
diff --git a/python/webiopi/utils/loader.py b/python/webiopi/utils/loader.py new file mode 100644 index 0000000..bf7c693 --- /dev/null +++ b/python/webiopi/utils/loader.py | |||
| @@ -0,0 +1,26 @@ | |||
| 1 | import imp | ||
| 2 | import webiopi.utils.logger as logger | ||
| 3 | import webiopi.utils.thread as thread | ||
| 4 | SCRIPTS = {} | ||
| 5 | |||
| 6 | def loadScript(name, source, handler = None): | ||
| 7 | logger.info("Loading %s from %s" % (name, source)) | ||
| 8 | script = imp.load_source(name, source) | ||
| 9 | SCRIPTS[name] = script | ||
| 10 | |||
| 11 | if hasattr(script, "setup"): | ||
| 12 | script.setup() | ||
| 13 | if handler: | ||
| 14 | for aname in dir(script): | ||
| 15 | attr = getattr(script, aname) | ||
| 16 | if callable(attr) and hasattr(attr, "macro"): | ||
| 17 | handler.addMacro(attr) | ||
| 18 | if hasattr(script, "loop"): | ||
| 19 | thread.runLoop(script.loop, True) | ||
| 20 | |||
| 21 | def unloadScripts(): | ||
| 22 | for name in SCRIPTS: | ||
| 23 | script = SCRIPTS[name] | ||
| 24 | if hasattr(script, "destroy"): | ||
| 25 | script.destroy() | ||
| 26 | \ No newline at end of file | ||
diff --git a/python/webiopi/utils/logger.py b/python/webiopi/utils/logger.py new file mode 100644 index 0000000..fb92ec5 --- /dev/null +++ b/python/webiopi/utils/logger.py | |||
| @@ -0,0 +1,45 @@ | |||
| 1 | import logging | ||
| 2 | |||
| 3 | LOG_FORMATTER = logging.Formatter(fmt='%(asctime)s - %(name)s - %(levelname)s - %(message)s', datefmt="%Y-%m-%d %H:%M:%S") | ||
| 4 | ROOT_LOGGER = logging.getLogger() | ||
| 5 | ROOT_LOGGER.setLevel(logging.WARN) | ||
| 6 | |||
| 7 | CONSOLE_HANDLER = logging.StreamHandler() | ||
| 8 | CONSOLE_HANDLER.setFormatter(LOG_FORMATTER) | ||
| 9 | ROOT_LOGGER.addHandler(CONSOLE_HANDLER) | ||
| 10 | |||
| 11 | LOGGER = logging.getLogger("WebIOPi") | ||
| 12 | |||
| 13 | def setInfo(): | ||
| 14 | ROOT_LOGGER.setLevel(logging.INFO) | ||
| 15 | |||
| 16 | def setDebug(): | ||
| 17 | ROOT_LOGGER.setLevel(logging.DEBUG) | ||
| 18 | |||
| 19 | def debugEnabled(): | ||
| 20 | return ROOT_LOGGER.level == logging.DEBUG | ||
| 21 | |||
| 22 | def logToFile(filename): | ||
| 23 | FILE_HANDLER = logging.FileHandler(filename) | ||
| 24 | FILE_HANDLER.setFormatter(LOG_FORMATTER) | ||
| 25 | ROOT_LOGGER.addHandler(FILE_HANDLER) | ||
| 26 | |||
| 27 | def debug(message): | ||
| 28 | LOGGER.debug(message) | ||
| 29 | |||
| 30 | def info(message): | ||
| 31 | LOGGER.info(message) | ||
| 32 | |||
| 33 | def warn(message): | ||
| 34 | LOGGER.warn(message) | ||
| 35 | |||
| 36 | def error(message): | ||
| 37 | LOGGER.error(message) | ||
| 38 | |||
| 39 | def exception(message): | ||
| 40 | LOGGER.exception(message) | ||
| 41 | |||
| 42 | def printBytes(buff): | ||
| 43 | for i in range(0, len(buff)): | ||
| 44 | print("%03d: 0x%02X %03d %c" % (i, buff[i], buff[i], buff[i])) | ||
| 45 | |||
diff --git a/python/webiopi/utils/thread.py b/python/webiopi/utils/thread.py new file mode 100644 index 0000000..98ed752 --- /dev/null +++ b/python/webiopi/utils/thread.py | |||
| @@ -0,0 +1,50 @@ | |||
| 1 | import time | ||
| 2 | import signal | ||
| 3 | import threading | ||
| 4 | from webiopi.utils import logger | ||
| 5 | |||
| 6 | RUNNING = False | ||
| 7 | TASKS = [] | ||
| 8 | |||
| 9 | class Task(threading.Thread): | ||
| 10 | def __init__(self, func, loop=False): | ||
| 11 | threading.Thread.__init__(self) | ||
| 12 | self.func = func | ||
| 13 | self.loop = loop | ||
| 14 | self.running = True | ||
| 15 | self.start() | ||
| 16 | |||
| 17 | def stop(self): | ||
| 18 | self.running = False | ||
| 19 | |||
| 20 | def run(self): | ||
| 21 | if self.loop: | ||
| 22 | while self.running == True: | ||
| 23 | self.func() | ||
| 24 | else: | ||
| 25 | self.func() | ||
| 26 | |||
| 27 | def stop(signum=0, frame=None): | ||
| 28 | global RUNNING | ||
| 29 | if RUNNING: | ||
| 30 | logger.info("Stopping...") | ||
| 31 | RUNNING = False | ||
| 32 | for task in TASKS: | ||
| 33 | task.stop() | ||
| 34 | |||
| 35 | |||
| 36 | def runLoop(func=None, async=False): | ||
| 37 | global RUNNING | ||
| 38 | RUNNING = True | ||
| 39 | signal.signal(signal.SIGINT, stop) | ||
| 40 | signal.signal(signal.SIGTERM, stop) | ||
| 41 | |||
| 42 | if func != None: | ||
| 43 | if async: | ||
| 44 | TASKS.append(Task(func, True)) | ||
| 45 | else: | ||
| 46 | while RUNNING: | ||
| 47 | func() | ||
| 48 | else: | ||
| 49 | while RUNNING: | ||
| 50 | time.sleep(1) | ||
diff --git a/python/webiopi/utils/types.py b/python/webiopi/utils/types.py new file mode 100644 index 0000000..0afaa20 --- /dev/null +++ b/python/webiopi/utils/types.py | |||
| @@ -0,0 +1,30 @@ | |||
| 1 | import json | ||
| 2 | from webiopi.utils import logger | ||
| 3 | |||
| 4 | M_PLAIN = "text/plain" | ||
| 5 | M_JSON = "application/json" | ||
| 6 | |||
| 7 | def jsonDumps(obj): | ||
| 8 | if logger.debugEnabled(): | ||
| 9 | return json.dumps(obj, sort_keys=True, indent=4, separators=(',', ': ')) | ||
| 10 | else: | ||
| 11 | return json.dumps(obj) | ||
| 12 | |||
| 13 | def str2bool(value): | ||
| 14 | return (value == "1") or (value == "true") or (value == "True") or (value == "yes") or (value == "Yes") | ||
| 15 | |||
| 16 | def toint(value): | ||
| 17 | if isinstance(value, str): | ||
| 18 | if value.startswith("0b"): | ||
| 19 | return int(value, 2) | ||
| 20 | elif value.startswith("0x"): | ||
| 21 | return int(value, 16) | ||
| 22 | else: | ||
| 23 | return int(value) | ||
| 24 | return value | ||
| 25 | |||
| 26 | |||
| 27 | def signInteger(value, bitcount): | ||
| 28 | if value & (1<<(bitcount-1)): | ||
| 29 | return value - (1<<bitcount) | ||
| 30 | return value | ||
diff --git a/python/webiopi/utils/version.py b/python/webiopi/utils/version.py new file mode 100644 index 0000000..b5e32de --- /dev/null +++ b/python/webiopi/utils/version.py | |||
| @@ -0,0 +1,29 @@ | |||
| 1 | import re | ||
| 2 | import sys | ||
| 3 | |||
| 4 | VERSION = '0.6.2' | ||
| 5 | VERSION_STRING = "WebIOPi/%s/Python%d.%d" % (VERSION, sys.version_info.major, sys.version_info.minor) | ||
| 6 | PYTHON_MAJOR = sys.version_info.major | ||
| 7 | BOARD_REVISION = 0 | ||
| 8 | |||
| 9 | _MAPPING = [[], [], []] | ||
| 10 | _MAPPING[1] = ["V33", "V50", 0, "V50", 1, "GND", 4, 14, "GND", 15, 17, 18, 21, "GND", 22, 23, "V33", 24, 10, "GND", 9, 25, 11, 8, "GND", 7] | ||
| 11 | _MAPPING[2] = ["V33", "V50", 2, "V50", 3, "GND", 4, 14, "GND", 15, 17, 18, 27, "GND", 22, 23, "V33", 24, 10, "GND", 9, 25, 11, 8, "GND", 7] | ||
| 12 | |||
| 13 | |||
| 14 | try: | ||
| 15 | with open("/proc/cpuinfo") as f: | ||
| 16 | rc = re.compile("Revision\s*:\s(.*)\n") | ||
| 17 | info = f.read() | ||
| 18 | result = rc.search(info) | ||
| 19 | if result != None: | ||
| 20 | hex_cpurev = result.group(1) | ||
| 21 | if hex_cpurev.startswith("1000"): | ||
| 22 | hex_cpurev = hex_cpurev[-4:] | ||
| 23 | cpurev = int(hex_cpurev, 16) | ||
| 24 | BOARD_REVISION = 1 if (cpurev < 4) else 2 | ||
| 25 | |||
| 26 | except: | ||
| 27 | pass | ||
| 28 | |||
| 29 | MAPPING = _MAPPING[BOARD_REVISION] | ||
