summaryrefslogtreecommitdiffstats
path: root/python
diff options
context:
space:
mode:
Diffstat (limited to 'python')
-rw-r--r--python/config136
-rw-r--r--python/native/bridge.c720
-rw-r--r--python/native/cpuinfo.c65
-rw-r--r--python/native/cpuinfo.h23
-rw-r--r--python/native/gpio.c329
-rw-r--r--python/native/gpio.h73
-rw-r--r--python/passwd1
-rw-r--r--python/setup.py37
-rwxr-xr-xpython/webiopi-passwd.py57
-rwxr-xr-xpython/webiopi.init.sh155
-rwxr-xr-xpython/webiopi.sh2
-rw-r--r--python/webiopi/__init__.py34
-rw-r--r--python/webiopi/__main__.py79
-rw-r--r--python/webiopi/clients/__init__.py209
-rw-r--r--python/webiopi/decorators/__init__.py0
-rw-r--r--python/webiopi/decorators/rest.py19
-rw-r--r--python/webiopi/devices/__init__.py13
-rw-r--r--python/webiopi/devices/analog/__init__.py267
-rw-r--r--python/webiopi/devices/analog/ads1x1x.py82
-rw-r--r--python/webiopi/devices/analog/mcp3x0x.py75
-rw-r--r--python/webiopi/devices/analog/mcp4725.py38
-rw-r--r--python/webiopi/devices/analog/mcp492X.py53
-rw-r--r--python/webiopi/devices/analog/pca9685.py63
-rw-r--r--python/webiopi/devices/bus.py117
-rw-r--r--python/webiopi/devices/digital/__init__.py144
-rw-r--r--python/webiopi/devices/digital/ds2408.py84
-rw-r--r--python/webiopi/devices/digital/gpio.py189
-rw-r--r--python/webiopi/devices/digital/mcp23XXX.py153
-rw-r--r--python/webiopi/devices/digital/pcf8574.py70
-rw-r--r--python/webiopi/devices/i2c.py75
-rw-r--r--python/webiopi/devices/instance.py6
-rw-r--r--python/webiopi/devices/manager.py77
-rw-r--r--python/webiopi/devices/onewire.py74
-rw-r--r--python/webiopi/devices/sensor/__init__.py177
-rw-r--r--python/webiopi/devices/sensor/bmp085.py100
-rw-r--r--python/webiopi/devices/sensor/onewiretemp.py58
-rw-r--r--python/webiopi/devices/sensor/tmpXXX.py60
-rw-r--r--python/webiopi/devices/sensor/tslXXXX.py247
-rw-r--r--python/webiopi/devices/sensor/vcnl4000.py211
-rw-r--r--python/webiopi/devices/serial.py86
-rw-r--r--python/webiopi/devices/shield/__init__.py16
-rw-r--r--python/webiopi/devices/shield/piface.py66
-rw-r--r--python/webiopi/devices/spi.py145
-rw-r--r--python/webiopi/protocols/__init__.py14
-rw-r--r--python/webiopi/protocols/coap.py537
-rw-r--r--python/webiopi/protocols/http.py249
-rw-r--r--python/webiopi/protocols/rest.py254
-rw-r--r--python/webiopi/server/__init__.py139
-rw-r--r--python/webiopi/utils/__init__.py16
-rw-r--r--python/webiopi/utils/config.py35
-rw-r--r--python/webiopi/utils/crypto.py17
-rw-r--r--python/webiopi/utils/loader.py26
-rw-r--r--python/webiopi/utils/logger.py45
-rw-r--r--python/webiopi/utils/thread.py50
-rw-r--r--python/webiopi/utils/types.py30
-rw-r--r--python/webiopi/utils/version.py29
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
31enabled = true
32port = 8000
33
34# File containing sha256(base64("user:password"))
35# Use webiopi-passwd command to generate it
36passwd-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
48enabled = true
49port = 5683
50# Enable CoAP multicast
51multicast = 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/*
2Copyright (c) 2012 Ben Croston / 2012-2013 Eric PTAK
3
4Permission is hereby granted, free of charge, to any person obtaining a copy of
5this software and associated documentation files (the "Software"), to deal in
6the Software without restriction, including without limitation the rights to
7use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
8of the Software, and to permit persons to whom the Software is furnished to do
9so, subject to the following conditions:
10
11The above copyright notice and this permission notice shall be included in all
12copies or substantial portions of the Software.
13
14THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20SOFTWARE.
21*/
22
23#include "Python.h"
24#include "gpio.h"
25#include "cpuinfo.h"
26
27static PyObject *_SetupException;
28static PyObject *_InvalidDirectionException;
29static PyObject *_InvalidChannelException;
30static PyObject *_InvalidPullException;
31
32static PyObject *_gpioCount;
33
34
35static PyObject *_low;
36static PyObject *_high;
37
38static PyObject *_in;
39static PyObject *_out;
40static PyObject *_alt0;
41static PyObject *_alt1;
42static PyObject *_alt2;
43static PyObject *_alt3;
44static PyObject *_alt4;
45static PyObject *_alt5;
46static PyObject *_pwm;
47
48static PyObject *_pud_off;
49static PyObject *_pud_up;
50static PyObject *_pud_down;
51
52static PyObject *_board_revision;
53
54static char* FUNCTIONS[] = {"IN", "OUT", "ALT5", "ALT4", "ALT0", "ALT1", "ALT2", "ALT3", "PWM"};
55static char* PWM_MODES[] = {"none", "ratio", "angle"};
56
57static int module_state = -1;
58
59// setup function run on import of the RPi.GPIO module
60static 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)
80static 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)
102static 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)
126static 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)
167static 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)
191static 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)
222static 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
254static 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
286static 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
319static 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
350static 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
382static 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
414static 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
446static 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
467static 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
496static 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
517static 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
540static 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
563PyMethodDef 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
605static 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
616PyMODINIT_FUNC PyInit_GPIO(void)
617#else
618PyMODINIT_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
714exit:
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/*
2Copyright (c) 2012 Ben Croston
3
4Permission is hereby granted, free of charge, to any person obtaining a copy of
5this software and associated documentation files (the "Software"), to deal in
6the Software without restriction, including without limitation the rights to
7use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
8of the Software, and to permit persons to whom the Software is furnished to do
9so, subject to the following conditions:
10
11The above copyright notice and this permission notice shall be included in all
12copies or substantial portions of the Software.
13
14THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20SOFTWARE.
21*/
22
23#include <stdio.h>
24#include <string.h>
25#include "cpuinfo.h"
26
27char *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
51int 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/*
2Copyright (c) 2012 Ben Croston
3
4Permission is hereby granted, free of charge, to any person obtaining a copy of
5this software and associated documentation files (the "Software"), to deal in
6the Software without restriction, including without limitation the rights to
7use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
8of the Software, and to permit persons to whom the Software is furnished to do
9so, subject to the following conditions:
10
11The above copyright notice and this permission notice shall be included in all
12copies or substantial portions of the Software.
13
14THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20SOFTWARE.
21*/
22
23int 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/*
2Copyright (c) 2012 Ben Croston / 2012-2013 Eric PTAK
3
4Permission is hereby granted, free of charge, to any person obtaining a copy of
5this software and associated documentation files (the "Software"), to deal in
6the Software without restriction, including without limitation the rights to
7use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
8of the Software, and to permit persons to whom the Software is furnished to do
9so, subject to the following conditions:
10
11The above copyright notice and this permission notice shall be included in all
12copies or substantial portions of the Software.
13
14THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20SOFTWARE.
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
49static volatile uint32_t *gpio_map;
50
51struct tspair {
52 struct timespec up;
53 struct timespec down;
54};
55
56static struct pulse gpio_pulses[GPIO_COUNT];
57static struct tspair gpio_tspairs[GPIO_COUNT];
58static pthread_t *gpio_threads[GPIO_COUNT];
59
60void 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
70int 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
94void 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
114void 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
132int 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
146int 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
156void 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
171void 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
189void 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
200void 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
213void 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
224void 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
236void 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
243void 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
255void 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
262void 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
271void 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
279struct pulse* getPulse(int gpio) {
280 return &gpio_pulses[gpio];
281}
282
283//added Eric PTAK - trouch.com
284void* 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
293void 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
307void 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
320int isPWMEnabled(int gpio) {
321 return gpio_threads[gpio] != NULL;
322}
323
324
325void 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/*
2Copyright (c) 2012 Ben Croston / 2012-2013 Eric PTAK
3
4Permission is hereby granted, free of charge, to any person obtaining a copy of
5this software and associated documentation files (the "Software"), to deal in
6the Software without restriction, including without limitation the rights to
7use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
8of the Software, and to permit persons to whom the Software is furnished to do
9so, subject to the following conditions:
10
11The above copyright notice and this permission notice shall be included in all
12copies or substantial portions of the Software.
13
14THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20SOFTWARE.
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
50struct pulse {
51 int type;
52 float value;
53};
54
55int setup(void);
56int get_function(int gpio);
57void set_function(int gpio, int function, int pud);
58int input(int gpio);
59void output(int gpio, int value);
60void outputSequence(int gpio, int period, char* sequence);
61struct pulse* getPulse(int gpio);
62void pulseMilli(int gpio, int up, int down);
63void pulseMilliRatio(int gpio, int width, float ratio);
64void pulseMicro(int gpio, int up, int down);
65void pulseMicroRatio(int gpio, int width, float ratio);
66void pulseAngle(int gpio, float angle);
67void pulseRatio(int gpio, float ratio);
68void enablePWM(int gpio);
69void disablePWM(int gpio);
70int isPWMEnabled(int gpio);
71
72void 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 @@
1from setuptools import setup, Extension
2
3classifiers = ['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
14setup(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
16import sys
17file = None
18
19print("WebIOPi passwd file generator")
20if 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()
30else:
31 file = "/etc/webiopi/passwd"
32
33f = open(file, "w")
34_LOGIN = "Enter Login: "
35_PASSWORD = "Enter Password: "
36_CONFIRM = "Confirm password: "
37_DONTMATCH = "Passwords don't match !"
38
39import getpass
40try:
41 login = raw_input(_LOGIN)
42except NameError:
43 login = input(_LOGIN)
44password = getpass.getpass(_PASSWORD)
45password2 = getpass.getpass(_CONFIRM)
46while password != password2:
47 print(_DONTMATCH)
48 password = getpass.getpass(_PASSWORD)
49 password2 = getpass.getpass(_CONFIRM)
50
51from webiopi.utils.crypto import encryptCredentials
52auth = encryptCredentials(login, password)
53print("\nHash: %s" % auth)
54if 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>
13LOG_FILE=/var/log/webiopi
14CONFIG_FILE=/etc/webiopi/config
15
16PATH=/sbin:/usr/sbin:/bin:/usr/bin
17DESC="WebIOPi"
18NAME=webiopi
19HOME=/usr/share/webiopi/htdocs
20DAEMON=/usr/bin/python
21DAEMON_ARGS="-m webiopi -l $LOG_FILE -c $CONFIG_FILE"
22PIDFILE=/var/run/$NAME.pid
23SCRIPTNAME=/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#
42do_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#
61do_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#
87do_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
97case "$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 ;;
153esac
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
2python -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
16from time import sleep
17
18from webiopi.utils.version import BOARD_REVISION, VERSION
19from webiopi.utils.logger import setInfo, setDebug, info, debug, warn, error, exception
20from webiopi.utils.thread import runLoop
21from webiopi.server import Server
22from webiopi.devices.instance import deviceInstance
23from webiopi.decorators.rest import macro
24
25from webiopi.devices import bus as _bus
26
27try:
28 import _webiopi.GPIO as GPIO
29except:
30 pass
31
32
33setInfo()
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
15import sys
16from webiopi.server import Server
17from webiopi.utils.loader import loadScript
18from webiopi.utils.logger import exception, setDebug, info, logToFile
19from webiopi.utils.version import VERSION_STRING
20from webiopi.utils.thread import runLoop, stop
21
22def 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
37def 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
74if __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
15from webiopi.utils.logger import LOGGER
16from webiopi.utils.version import PYTHON_MAJOR
17from webiopi.utils.crypto import encodeCredentials
18from webiopi.protocols.coap import COAPClient, COAPGet, COAPPost, COAPPut, COAPDelete
19
20if PYTHON_MAJOR >= 3:
21 import http.client as httplib
22else:
23 import httplib
24
25class 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
79class PiHttpClient(PiMixedClient):
80 def __init__(self, host, port=8000):
81 PiMixedClient.__init__(self, host, port, -1)
82
83class PiCoapClient(PiMixedClient):
84 def __init__(self, host, port=5683):
85 PiMixedClient.__init__(self, host, -1, port)
86
87class PiMulticastClient(PiMixedClient):
88 def __init__(self, port=5683):
89 PiMixedClient.__init__(self, "224.0.1.123", -1, port)
90
91class 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
99class 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
109class Device(RESTAPI):
110 def __init__(self, client, name, category):
111 RESTAPI.__init__(self, client, "/devices/" + name + "/" + category)
112
113class 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
135class NativeGPIO(GPIO):
136 def __init__(self, client):
137 RESTAPI.__init__(self, client, "/GPIO")
138
139class 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
152class 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
165class 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
175class Sensor(Device):
176 def __init__(self, client, name):
177 Device.__init__(self, client, name, "sensor")
178
179class 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
189class 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
196class Luminosity(Sensor):
197 def getLux(self):
198 return float(self.sendRequest("GET", "/luminosity/lux"))
199
200class 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 @@
1def 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
10def 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
17def 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
15from webiopi.decorators.rest import request, response
16from webiopi.utils.types import M_JSON
17
18class 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
101class 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
132class 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
262DRIVERS = {}
263DRIVERS["ads1x1x"] = ["ADS1014", "ADS1015", "ADS1114", "ADS1115"]
264DRIVERS["mcp3x0x"] = ["MCP3004", "MCP3008", "MCP3204", "MCP3208"]
265DRIVERS["mcp4725"] = ["MCP4725"]
266DRIVERS["mcp492X"] = ["MCP4921", "MCP4922"]
267DRIVERS["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
15from time import sleep
16from webiopi.utils.types import toint, signInteger
17from webiopi.devices.i2c import I2C
18from webiopi.devices.analog import ADC
19
20
21class 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
67class ADS1014(ADS1X1X):
68 def __init__(self, slave=0x48):
69 ADS1X1X.__init__(self, slave, 1, 12, "ADS1014")
70
71class ADS1015(ADS1X1X):
72 def __init__(self, slave=0x48):
73 ADS1X1X.__init__(self, slave, 4, 12, "ADS1015")
74
75class ADS1114(ADS1X1X):
76 def __init__(self, slave=0x48):
77 ADS1X1X.__init__(self, slave, 1, 16, "ADS1114")
78
79class 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
15from webiopi.utils.types import toint
16from webiopi.devices.spi import SPI
17from webiopi.devices.analog import ADC
18
19class 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
34class 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
47class MCP3004(MCP300X):
48 def __init__(self, chip=0):
49 MCP300X.__init__(self, chip, 4, "MCP3004")
50
51class MCP3008(MCP300X):
52 def __init__(self, chip=0):
53 MCP300X.__init__(self, chip, 8, "MCP3008")
54
55class 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
68class MCP3204(MCP320X):
69 def __init__(self, chip=0):
70 MCP320X.__init__(self, chip, 4, "MCP3204")
71
72class 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
15from webiopi.utils.types import toint
16from webiopi.devices.i2c import I2C
17from webiopi.devices.analog import DAC
18
19
20class 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
15from webiopi.utils.types import toint
16from webiopi.devices.spi import SPI
17from webiopi.devices.analog import DAC
18
19class 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
46class MCP4921(MCP492X):
47 def __init__(self, chip=0):
48 MCP492X.__init__(self, chip, 1)
49
50class 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
15import time
16from webiopi.utils.types import toint
17from webiopi.devices.i2c import I2C
18from webiopi.devices.analog import PWM
19
20class 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
15import os
16import time
17import subprocess
18
19from webiopi.utils.logger import debug, info
20
21BUSLIST = {
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
28def loadModule(module):
29 debug("Loading module : %s" % module)
30 subprocess.call(["modprobe", module])
31
32def unloadModule(module):
33 subprocess.call(["modprobe", "-r", module])
34
35def 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
46def unloadModules(bus):
47 info("Unloading %s modules" % bus)
48 for module in BUSLIST[bus]["modules"]:
49 unloadModule(module)
50 BUSLIST[bus]["enabled"] = False
51
52def __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
60def 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
73def checkAllBus():
74 for bus in BUSLIST:
75 if modulesLoaded(bus):
76 BUSLIST[bus]["enabled"] = True
77
78class 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
15from webiopi.decorators.rest import request, response
16from webiopi.utils.types import M_JSON
17
18class 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
141DRIVERS = {}
142DRIVERS["mcp23XXX"] = ["MCP23008", "MCP23009", "MCP23017", "MCP23018", "MCP23S08", "MCP23S09", "MCP23S17", "MCP23S18"]
143DRIVERS["pcf8574" ] = ["PCF8574", "PCF8574A"]
144DRIVERS["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
15from webiopi.devices.onewire import OneWire
16from webiopi.devices.digital import GPIOPort
17
18class 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
15from webiopi.utils.types import M_JSON
16from webiopi.utils.logger import debug
17from webiopi.devices.digital import GPIOPort
18from webiopi.decorators.rest import request, response
19try:
20 import _webiopi.GPIO as GPIO
21except:
22 pass
23
24EXPORT = []
25
26class 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
15from webiopi.utils.types import toint
16from webiopi.devices.i2c import I2C
17from webiopi.devices.spi import SPI
18from webiopi.devices.digital import GPIOPort
19
20class 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
87class 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
96class MCP23008(MCP230XX):
97 def __init__(self, slave=0x20):
98 MCP230XX.__init__(self, slave, 8, "MCP23008")
99
100class MCP23009(MCP230XX):
101 def __init__(self, slave=0x20):
102 MCP230XX.__init__(self, slave, 8, "MCP23009")
103
104class MCP23017(MCP230XX):
105 def __init__(self, slave=0x20):
106 MCP230XX.__init__(self, slave, 16, "MCP23017")
107
108class MCP23018(MCP230XX):
109 def __init__(self, slave=0x20):
110 MCP230XX.__init__(self, slave, 16, "MCP23018")
111
112class 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
138class MCP23S08(MCP23SXX):
139 def __init__(self, chip=0, slave=0x20):
140 MCP23SXX.__init__(self, chip, slave, 8, "MCP23S08")
141
142class MCP23S09(MCP23SXX):
143 def __init__(self, chip=0, slave=0x20):
144 MCP23SXX.__init__(self, chip, slave, 8, "MCP23S09")
145
146class MCP23S17(MCP23SXX):
147 def __init__(self, chip=0, slave=0x20):
148 MCP23SXX.__init__(self, chip, slave, 16, "MCP23S17")
149
150class 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
15from webiopi.utils.types import toint
16from webiopi.devices.i2c import I2C
17from webiopi.devices.digital import GPIOPort
18
19class 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
67class 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
15import fcntl
16
17from webiopi.utils.version import BOARD_REVISION
18from 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
26I2C_RETRIES = 0x0701 # number of times a device address should
27 # be polled when not acknowledging
28I2C_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
33I2C_SLAVE = 0x0703 # Use this slave address
34I2C_SLAVE_FORCE = 0x0706 # Use this slave address, even if it
35 # is already in use by a driver!
36I2C_TENBIT = 0x0704 # 0 for 7 bit addrs, != 0 for 10 bit
37
38I2C_FUNCS = 0x0705 # Get the adapter functionality mask
39
40I2C_RDWR = 0x0707 # Combined R/W transfer (one STOP only)
41
42I2C_PEC = 0x0708 # != 0 to use PEC with SMBus
43I2C_SMBUS = 0x0720 # SMBus transfer */
44
45
46class 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 @@
1DEVICES = {}
2def 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 @@
1import imp
2from webiopi.utils import logger
3from webiopi.utils import types
4from webiopi.devices.instance import DEVICES
5
6from webiopi.devices import serial, digital, analog, sensor, shield
7
8PACKAGES = [serial, digital, analog, sensor, shield]
9def 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
21def 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
31def 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
48def 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
56def 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
15import os
16from webiopi.devices.bus import Bus, loadModule
17
18EXTRAS = {
19 "TEMP": {"loaded": False, "module": "w1-therm"},
20 "2408": {"loaded": False, "module": "w1_ds2408"}
21
22}
23
24def loadExtraModule(name):
25 if EXTRAS[name]["loaded"] == False:
26 loadModule(EXTRAS[name]["module"])
27 EXTRAS[name]["loaded"] = True
28
29class 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
15from webiopi.utils.types import toint
16from webiopi.utils.types import M_JSON
17from webiopi.devices.instance import deviceInstance
18from webiopi.decorators.rest import request, response
19
20class 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
65class 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
123class 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
135class 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
172DRIVERS = {}
173DRIVERS["bmp085"] = ["BMP085"]
174DRIVERS["onewiretemp"] = ["DS1822", "DS1825", "DS18B20", "DS18S20", "DS28EA00"]
175DRIVERS["tmpXXX"] = ["TMP75", "TMP102", "TMP275"]
176DRIVERS["tslXXXX"] = ["TSL2561", "TSL2561CS", "TSL2561T", "TSL4531", "TSL45311", "TSL45313", "TSL45315", "TSL45317"]
177DRIVERS["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
15import time
16from webiopi.utils.types import signInteger
17from webiopi.devices.i2c import I2C
18from webiopi.devices.sensor import Temperature, Pressure
19
20class 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
15from webiopi.devices.onewire import OneWire
16from webiopi.devices.sensor import Temperature
17
18class 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
40class DS18S20(OneWireTemp):
41 def __init__(self, slave=None):
42 OneWireTemp.__init__(self, slave, 0x10, "DS18S20")
43
44class DS1822(OneWireTemp):
45 def __init__(self, slave=None):
46 OneWireTemp.__init__(self, slave, 0x22, "DS1822")
47
48class DS18B20(OneWireTemp):
49 def __init__(self, slave=None):
50 OneWireTemp.__init__(self, slave, 0x28, "DS18B20")
51
52class DS1825(OneWireTemp):
53 def __init__(self, slave=None):
54 OneWireTemp.__init__(self, slave, 0x3B, "DS1825")
55
56class 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
15from webiopi.utils.types import toint, signInteger
16from webiopi.devices.i2c import I2C
17from webiopi.devices.sensor import Temperature
18
19class 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
37class 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
54class 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
21from webiopi.utils.types import toint
22from webiopi.devices.i2c import I2C
23from webiopi.devices.sensor import Luminosity
24
25class 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
61class 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
140class 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
159class 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
178class 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
184class 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
232class TSL45311(TSL4531):
233 def __init__(self, slave=0x39, time=400):
234 TSL4531.__init__(self, slave, time, "TSL45311")
235
236class TSL45313(TSL4531):
237 def __init__(self, slave=0x39, time=400):
238 TSL4531.__init__(self, slave, time, "TSL45313")
239
240class TSL45315(TSL4531):
241 def __init__(self, slave=0x29, time=400):
242 TSL4531.__init__(self, slave, time, "TSL45315")
243
244class 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
22import time
23from webiopi.devices.i2c import I2C
24from webiopi.devices.sensor import Luminosity, Distance
25from webiopi.utils.types import toint
26from webiopi.utils.logger import debug
27
28class 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
15import os
16import fcntl
17import struct
18import termios
19
20from webiopi.devices.bus import Bus
21from webiopi.decorators.rest import request
22
23TIOCINQ = hasattr(termios, 'FIONREAD') and termios.FIONREAD or 0x541B
24TIOCM_zero_str = struct.pack('I', 0)
25
26class 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
15DRIVERS = {}
16DRIVERS["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
15from webiopi.utils.types import M_JSON
16from webiopi.devices.digital.mcp23XXX import MCP23S17
17from webiopi.decorators.rest import request, response
18
19
20class 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
15import fcntl
16import array
17import ctypes
18import struct
19
20from webiopi.utils.version import PYTHON_MAJOR
21from 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
38def _IOC(direction,t,nr,size):
39 return (((direction) << _IOC_DIRSHIFT) |
40 ((size) << _IOC_SIZESHIFT) |
41 ((t) << _IOC_TYPESHIFT) |
42 ((nr) << _IOC_NRSHIFT))
43def _IOR(t, number, size):
44 return _IOC(_IOC_READ, t, number, size)
45def _IOW(t, number, size):
46 return _IOC(_IOC_WRITE, t, number, size)
47
48SPI_CPHA = 0x01
49SPI_CPOL = 0x02
50
51SPI_MODE_0 = (0|0)
52SPI_MODE_1 = (0|SPI_CPHA)
53SPI_MODE_2 = (SPI_CPOL|0)
54SPI_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
64SPI_IOC_MAGIC = ord('k')
65
66def 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)
70SPI_IOC_RD_MODE = _IOR(SPI_IOC_MAGIC, 1, 1)
71SPI_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)
79SPI_IOC_RD_BITS_PER_WORD = _IOR(SPI_IOC_MAGIC, 3, 1)
80SPI_IOC_WR_BITS_PER_WORD = _IOW(SPI_IOC_MAGIC, 3, 1)
81
82# Read / Write SPI device default max speed hz
83SPI_IOC_RD_MAX_SPEED_HZ = _IOR(SPI_IOC_MAGIC, 4, 4)
84SPI_IOC_WR_MAX_SPEED_HZ = _IOW(SPI_IOC_MAGIC, 4, 4)
85
86class 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
15from webiopi.utils.version import PYTHON_MAJOR
16from webiopi.utils.logger import info, exception
17
18import socket
19import struct
20import logging
21import threading
22
23M_PLAIN = "text/plain"
24M_JSON = "application/json"
25
26if PYTHON_MAJOR >= 3:
27 from urllib.parse import urlparse
28else:
29 from urlparse import urlparse
30
31try :
32 import _webiopi.GPIO as GPIO
33except:
34 pass
35
36def HTTPCode2CoAPCode(code):
37 return int(code/100) * 32 + (code%100)
38
39
40class 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
69class 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
102class 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
318class 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
334class COAPGet(COAPRequest):
335 def __init__(self, uri):
336 COAPRequest.__init__(self, COAPMessage.CON, COAPRequest.GET, uri)
337
338class COAPPost(COAPRequest):
339 def __init__(self, uri):
340 COAPRequest.__init__(self, COAPMessage.CON, COAPRequest.POST, uri)
341
342class COAPPut(COAPRequest):
343 def __init__(self, uri):
344 COAPRequest.__init__(self, COAPMessage.CON, COAPRequest.PUT, uri)
345
346class COAPDelete(COAPRequest):
347 def __init__(self, uri):
348 COAPRequest.__init__(self, COAPMessage.CON, COAPRequest.DELETE, uri)
349
350class 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
407class 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
427class 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
498class 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
15import os
16import threading
17import codecs
18import mimetypes as mime
19import logging
20
21from webiopi.utils.version import VERSION_STRING, PYTHON_MAJOR
22from webiopi.utils.logger import info, exception
23from webiopi.utils.crypto import encrypt
24from webiopi.utils.types import str2bool
25
26if PYTHON_MAJOR >= 3:
27 import http.server as BaseHTTPServer
28else:
29 import BaseHTTPServer
30
31try :
32 import _webiopi.GPIO as GPIO
33except:
34 pass
35
36WEBIOPI_DOCROOT = "/usr/share/webiopi/htdocs"
37
38class 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
85class 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
15from webiopi.utils import types
16from webiopi.utils import logger
17from webiopi.utils.types import M_JSON, M_PLAIN
18from webiopi.utils.version import BOARD_REVISION, VERSION_STRING, MAPPING
19from webiopi.devices import manager
20from webiopi.devices import instance
21from webiopi.devices.bus import BUSLIST
22
23try:
24 import _webiopi.GPIO as GPIO
25except:
26 pass
27
28MACROS = {}
29
30class 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
15import os
16import time
17import socket
18
19from webiopi.utils.config import Config
20from webiopi.utils import loader
21from webiopi.utils import logger
22from webiopi.utils import crypto
23from webiopi.devices import manager
24from webiopi.protocols import rest
25from webiopi.protocols import http
26from webiopi.protocols import coap
27from webiopi.devices.digital.gpio import NativeGPIO
28
29def 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
39class 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 @@
1from webiopi.utils.version import PYTHON_MAJOR
2
3if PYTHON_MAJOR >= 3:
4 import configparser as parser
5else:
6 import ConfigParser as parser
7
8class 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 @@
1import base64
2import hashlib
3from webiopi.utils.version import PYTHON_MAJOR
4
5def 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
13def encrypt(value):
14 return hashlib.sha256(value).hexdigest()
15
16def 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 @@
1import imp
2import webiopi.utils.logger as logger
3import webiopi.utils.thread as thread
4SCRIPTS = {}
5
6def 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
21def 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 @@
1import logging
2
3LOG_FORMATTER = logging.Formatter(fmt='%(asctime)s - %(name)s - %(levelname)s - %(message)s', datefmt="%Y-%m-%d %H:%M:%S")
4ROOT_LOGGER = logging.getLogger()
5ROOT_LOGGER.setLevel(logging.WARN)
6
7CONSOLE_HANDLER = logging.StreamHandler()
8CONSOLE_HANDLER.setFormatter(LOG_FORMATTER)
9ROOT_LOGGER.addHandler(CONSOLE_HANDLER)
10
11LOGGER = logging.getLogger("WebIOPi")
12
13def setInfo():
14 ROOT_LOGGER.setLevel(logging.INFO)
15
16def setDebug():
17 ROOT_LOGGER.setLevel(logging.DEBUG)
18
19def debugEnabled():
20 return ROOT_LOGGER.level == logging.DEBUG
21
22def logToFile(filename):
23 FILE_HANDLER = logging.FileHandler(filename)
24 FILE_HANDLER.setFormatter(LOG_FORMATTER)
25 ROOT_LOGGER.addHandler(FILE_HANDLER)
26
27def debug(message):
28 LOGGER.debug(message)
29
30def info(message):
31 LOGGER.info(message)
32
33def warn(message):
34 LOGGER.warn(message)
35
36def error(message):
37 LOGGER.error(message)
38
39def exception(message):
40 LOGGER.exception(message)
41
42def 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 @@
1import time
2import signal
3import threading
4from webiopi.utils import logger
5
6RUNNING = False
7TASKS = []
8
9class 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
27def 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
36def 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 @@
1import json
2from webiopi.utils import logger
3
4M_PLAIN = "text/plain"
5M_JSON = "application/json"
6
7def 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
13def str2bool(value):
14 return (value == "1") or (value == "true") or (value == "True") or (value == "yes") or (value == "Yes")
15
16def 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
27def 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 @@
1import re
2import sys
3
4VERSION = '0.6.2'
5VERSION_STRING = "WebIOPi/%s/Python%d.%d" % (VERSION, sys.version_info.major, sys.version_info.minor)
6PYTHON_MAJOR = sys.version_info.major
7BOARD_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
14try:
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
26except:
27 pass
28
29MAPPING = _MAPPING[BOARD_REVISION]