Sunteți pe pagina 1din 276

Sandeep Tara: smbusmodule

/*
* smbusmodule.c - Python bindings for Linux SMBus access through i2c-dev
* Copyright (C) 2005-2007 Mark M. Hoffman <mhoffman@xxxxxxxxxxxxx>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
* forked from https://gist.github.com/sebastianludwig/c648a9e06c0dc2264fbd
*/
#include
#include
#include
#include
#include
#include

<Python.h>
"structmember.h"
<stdlib.h>
<stdio.h>
<fcntl.h>
<linux/i2c-dev.h>

/*
** These are required to build this module against Linux older than 2.6.23.
*/
#ifndef I2C_SMBUS_I2C_BLOCK_BROKEN
#undef I2C_SMBUS_I2C_BLOCK_DATA
#define I2C_SMBUS_I2C_BLOCK_BROKEN
6
#define I2C_SMBUS_I2C_BLOCK_DATA
8
#endif
/*yDoc_STRVAR(SMBus_module_doc,
"This module defines an object type that allows SMBus transactions\n"
"on hosts running the Linux kernel. The host kernel must have I2C\n"
"support, I2C device interface support, and a bus adapter driver.\n"
"All of these can be either built-in to the kernel, or loaded from\n"
"modules.\n"
"\n"
"Because the I2C device interface is opened R/W, users of this\n"
"module usually must have root permissions.\n");
*/
typedef struct {
PyObject_HEAD
int fd;
/* open file descriptor: /dev/i2c-?, or -1 */
int addr; /* current client SMBus address */
int pec; /* !0 => Packet Error Codes enabled */
} SMBus;
static PyObject *
SMBus_new(PyTypeObject *type, PyObject *args, PyObject *kwds)

SMBus *self;
if ((self = (SMBus *)type->tp_alloc(type, 0)) == NULL)
return NULL;
self->fd = -1;
self->addr = -1;
self->pec = 0;

return (PyObject *)self;

PyDoc_STRVAR(SMBus_close_doc,
"close()\n\n"
"Disconnects the object from the bus.\n");
static PyObject *
SMBus_close(SMBus *self)
{
if ((self->fd != -1) && (close(self->fd) == -1)) {
PyErr_SetFromErrno(PyExc_IOError);
return NULL;
}
self->fd = -1;
self->addr = -1;
self->pec = 0;

Py_INCREF(Py_None);
return Py_None;

static void
SMBus_dealloc(SMBus *self)
{
PyObject *ref = SMBus_close(self);
Py_XDECREF(ref);
/*old python 2.7 declaration */
/*self->ob_type->tp_free((PyObject *)self);*/
Py_TYPE(self)->tp_free((PyObject*)self);
}
#define MAXPATH 16
PyDoc_STRVAR(SMBus_open_doc,
"open(bus)\n\n"
"Connects the object to the specified SMBus.\n");
static PyObject *
SMBus_open(SMBus *self, PyObject *args, PyObject *kwds)
{
int bus;
char path[MAXPATH];
static char *kwlist[] = {"bus", NULL};

if (!PyArg_ParseTupleAndKeywords(args, kwds, "i:open", kwlist, &bus))


return NULL;
if (snprintf(path, MAXPATH, "/dev/i2c-%d", bus) >= MAXPATH) {
PyErr_SetString(PyExc_OverflowError,
"Bus number is invalid.");
return NULL;
}
if ((self->fd = open(path, O_RDWR, 0)) == -1) {
PyErr_SetFromErrno(PyExc_IOError);
return NULL;
}

Py_INCREF(Py_None);
return Py_None;

static int
SMBus_init(SMBus *self, PyObject *args, PyObject *kwds)
{
int bus = -1;
static char *kwlist[] = {"bus", NULL};
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|i:__init__",
kwlist, &bus))
return -1;
if (bus >= 0) {
SMBus_open(self, args, kwds);
if (PyErr_Occurred())
return -1;
}
}

return 0;

/*
* private helper function, 0 => success, !0 => error
*/
static int
SMBus_set_addr(SMBus *self, int addr)
{
int ret = 0;
if (self->addr != addr) {
ret = ioctl(self->fd, I2C_SLAVE, addr);
self->addr = addr;
}
}

return ret;

#define SMBus_SET_ADDR(self, addr) do { \


if (SMBus_set_addr(self, addr)) { \

PyErr_SetFromErrno(PyExc_IOError); \
return NULL; \
}\
} while(0)
PyDoc_STRVAR(SMBus_write_quick_doc,
"write_quick(addr)\n\n"
"Perform SMBus Quick transaction.\n");
static PyObject *
SMBus_write_quick(SMBus *self, PyObject *args)
{
int addr;
__s32 result;
if (!PyArg_ParseTuple(args, "i:write_quick", &addr))
return NULL;
SMBus_SET_ADDR(self, addr);
if ((result = i2c_smbus_write_quick(self->fd, I2C_SMBUS_WRITE))) {
PyErr_SetFromErrno(PyExc_IOError);
return NULL;
}

Py_INCREF(Py_None);
return Py_None;

PyDoc_STRVAR(SMBus_read_byte_doc,
"read_byte(addr) -> result\n\n"
"Perform SMBus Read Byte transaction.\n");
static PyObject *
SMBus_read_byte(SMBus *self, PyObject *args)
{
int addr;
__s32 result;
if (!PyArg_ParseTuple(args, "i:read_byte", &addr))
return NULL;
SMBus_SET_ADDR(self, addr);
if ((result = i2c_smbus_read_byte(self->fd)) == -1) {
PyErr_SetFromErrno(PyExc_IOError);
return NULL;
}
return Py_BuildValue("l", (long)result);
}
PyDoc_STRVAR(SMBus_write_byte_doc,
"write_byte(addr, val)\n\n"
"Perform SMBus Write Byte transaction.\n");
static PyObject *

SMBus_write_byte(SMBus *self, PyObject *args)


{
int addr, val;
__s32 result;
if (!PyArg_ParseTuple(args, "ii:write_byte", &addr, &val))
return NULL;
SMBus_SET_ADDR(self, addr);
if ((result = i2c_smbus_write_byte(self->fd, (__u8)val)) == -1) {
PyErr_SetFromErrno(PyExc_IOError);
return NULL;
}

Py_INCREF(Py_None);
return Py_None;

PyDoc_STRVAR(SMBus_read_byte_data_doc,
"read_byte_data(addr, cmd) -> result\n\n"
"Perform SMBus Read Byte Data transaction.\n");
static PyObject *
SMBus_read_byte_data(SMBus *self, PyObject *args)
{
int addr, cmd;
__s32 result;
if (!PyArg_ParseTuple(args, "ii:read_byte_data", &addr, &cmd))
return NULL;
SMBus_SET_ADDR(self, addr);
if ((result = i2c_smbus_read_byte_data(self->fd, (__u8)cmd)) == -1) {
PyErr_SetFromErrno(PyExc_IOError);
return NULL;
}
return Py_BuildValue("l", (long)result);
}
PyDoc_STRVAR(SMBus_write_byte_data_doc,
"write_byte_data(addr, cmd, val)\n\n"
"Perform SMBus Write Byte Data transaction.\n");
static PyObject *
SMBus_write_byte_data(SMBus *self, PyObject *args)
{
int addr, cmd, val;
__s32 result;
if (!PyArg_ParseTuple(args, "iii:write_byte_data", &addr, &cmd, &val))
return NULL;
SMBus_SET_ADDR(self, addr);

if ((result = i2c_smbus_write_byte_data(self->fd,
(__u8)cmd, (__u8)val)) == -1) {
PyErr_SetFromErrno(PyExc_IOError);
return NULL;
}
Py_INCREF(Py_None);
return Py_None;
}
PyDoc_STRVAR(SMBus_read_word_data_doc,
"read_word_data(addr, cmd) -> result\n\n"
"Perform SMBus Read Word Data transaction.\n");
static PyObject *
SMBus_read_word_data(SMBus *self, PyObject *args)
{
int addr, cmd;
__s32 result;
if (!PyArg_ParseTuple(args, "ii:read_word_data", &addr, &cmd))
return NULL;
SMBus_SET_ADDR(self, addr);
if ((result = i2c_smbus_read_word_data(self->fd, (__u8)cmd)) == -1) {
PyErr_SetFromErrno(PyExc_IOError);
return NULL;
}
}

return Py_BuildValue("l", (long)result);

PyDoc_STRVAR(SMBus_write_word_data_doc,
"write_word_data(addr, cmd, val)\n\n"
"Perform SMBus Write Word Data transaction.\n");
static PyObject *
SMBus_write_word_data(SMBus *self, PyObject *args)
{
int addr, cmd, val;
__s32 result;
if (!PyArg_ParseTuple(args, "iii:write_word_data", &addr, &cmd, &val))
return NULL;
SMBus_SET_ADDR(self, addr);
if ((result = i2c_smbus_write_word_data(self->fd,
(__u8)cmd, (__u16)val)) == -1) {
PyErr_SetFromErrno(PyExc_IOError);
return NULL;
}
Py_INCREF(Py_None);
return Py_None;
}

PyDoc_STRVAR(SMBus_process_call_doc,
"process_call(addr, cmd, val)\n\n"
"Perform SMBus Process Call transaction.\n");
static PyObject *
SMBus_process_call(SMBus *self, PyObject *args)
{
int addr, cmd, val;
__s32 result;
if (!PyArg_ParseTuple(args, "iii:process_call", &addr, &cmd, &val))
return NULL;
SMBus_SET_ADDR(self, addr);
if ((result = i2c_smbus_process_call(self->fd,
(__u8)cmd, (__u16)val)) == -1) {
PyErr_SetFromErrno(PyExc_IOError);
return NULL;
}
Py_INCREF(Py_None);
return Py_None;
}
/*
* private helper function; returns a new list of integers
*/
static PyObject *
SMBus_buf_to_list(__u8 const *buf, int len)
{
PyObject *list = PyList_New(len);
int ii;
if (list == NULL)
return NULL;

for (ii = 0; ii < len; ii++) {


PyObject *val = Py_BuildValue("l", (long)buf[ii]);
PyList_SET_ITEM(list, ii, val);
}
return list;

PyDoc_STRVAR(SMBus_read_block_data_doc,
"read_block_data(addr, cmd) -> results\n\n"
"Perform SMBus Read Block Data transaction.\n");
static PyObject *
SMBus_read_block_data(SMBus *self, PyObject *args)
{
int addr, cmd;
union i2c_smbus_data data;
if (!PyArg_ParseTuple(args, "ii:read_block_data", &addr, &cmd))
return NULL;

SMBus_SET_ADDR(self, addr);
/* save a bit of code by calling the access function directly */
if (i2c_smbus_access(self->fd, I2C_SMBUS_READ, (__u8)cmd,
I2C_SMBUS_BLOCK_DATA, &data)) {
PyErr_SetFromErrno(PyExc_IOError);
return NULL;
}
/* first byte of the block contains (remaining) data length */
return SMBus_buf_to_list(&data.block[1], data.block[0]);
}
/*
* private helper function: convert an integer list to union i2c_smbus_data
*/
static int
SMBus_list_to_data(PyObject *list, union i2c_smbus_data *data)
{
static char *msg = "Third argument must be a list of at least one, "
"but not more than 32 integers";
int ii, len;
if (!PyList_Check(list)) {
PyErr_SetString(PyExc_TypeError, msg);
return 0; /* fail */
}
if ((len = PyList_GET_SIZE(list)) > 32) {
PyErr_SetString(PyExc_OverflowError, msg);
return 0; /* fail */
}
/* first byte is the length */
data->block[0] = (__u8)len;
for (ii = 0; ii < len; ii++) {
PyObject *val = PyList_GET_ITEM(list, ii);
if (!PyLong_Check(val)) {
PyErr_SetString(PyExc_TypeError, msg);
return 0; /* fail */
}
data->block[ii+1] = (__u8)PyLong_AS_LONG(val);
}
}

return 1; /* success */

PyDoc_STRVAR(SMBus_write_block_data_doc,
"write_block_data(addr, cmd, [vals])\n\n"
"Perform SMBus Write Block Data transaction.\n");
static PyObject *
SMBus_write_block_data(SMBus *self, PyObject *args)
{
int addr, cmd;

union i2c_smbus_data data;


if (!PyArg_ParseTuple(args, "iiO&:write_block_data", &addr, &cmd,
SMBus_list_to_data, &data))
return NULL;
SMBus_SET_ADDR(self, addr);
/* save a bit of code by calling the access function directly */
if (i2c_smbus_access(self->fd, I2C_SMBUS_WRITE, (__u8)cmd,
I2C_SMBUS_BLOCK_DATA, &data)) {
PyErr_SetFromErrno(PyExc_IOError);
return NULL;
}

Py_INCREF(Py_None);
return Py_None;

PyDoc_STRVAR(SMBus_block_process_call_doc,
"block_process_call(addr, cmd, [vals]) -> results\n\n"
"Perform SMBus Block Process Call transaction.\n");
static PyObject *
SMBus_block_process_call(SMBus *self, PyObject *args)
{
int addr, cmd;
union i2c_smbus_data data;
if (!PyArg_ParseTuple(args, "iiO&:block_process_call", &addr, &cmd,
SMBus_list_to_data, &data))
return NULL;
SMBus_SET_ADDR(self, addr);
/* save a bit of code by calling the access function directly */
if (i2c_smbus_access(self->fd, I2C_SMBUS_WRITE, (__u8)cmd,
I2C_SMBUS_BLOCK_PROC_CALL, &data)) {
PyErr_SetFromErrno(PyExc_IOError);
return NULL;
}
/* first byte of the block contains (remaining) data length */
return SMBus_buf_to_list(&data.block[1], data.block[0]);
}
PyDoc_STRVAR(SMBus_read_i2c_block_data_doc,
"read_i2c_block_data(addr, cmd, len=32) -> results\n\n"
"Perform I2C Block Read transaction.\n");
static PyObject *
SMBus_read_i2c_block_data(SMBus *self, PyObject *args)
{
int addr, cmd, len=32;
union i2c_smbus_data data;
if (!PyArg_ParseTuple(args, "ii|i:read_i2c_block_data", &addr, &cmd,

&len))
return NULL;
SMBus_SET_ADDR(self, addr);
data.block[0] = len;
/* save a bit of code by calling the access function directly */
if (i2c_smbus_access(self->fd, I2C_SMBUS_READ, (__u8)cmd,
len == 32 ? I2C_SMBUS_I2C_BLOCK_BROKEN:
I2C_SMBUS_I2C_BLOCK_DATA, &data)) {
PyErr_SetFromErrno(PyExc_IOError);
return NULL;
}
/* first byte of the block contains (remaining) data length */
return SMBus_buf_to_list(&data.block[1], data.block[0]);
}
PyDoc_STRVAR(SMBus_write_i2c_block_data_doc,
"write_i2c_block_data(addr, cmd, [vals])\n\n"
"Perform I2C Block Write transaction.\n");
static PyObject *
SMBus_write_i2c_block_data(SMBus *self, PyObject *args)
{
int addr, cmd;
union i2c_smbus_data data;
if (!PyArg_ParseTuple(args, "iiO&:write_i2c_block_data", &addr, &cmd,
SMBus_list_to_data, &data))
return NULL;
SMBus_SET_ADDR(self, addr);
/* save a bit of code by calling the access function directly */
if (i2c_smbus_access(self->fd, I2C_SMBUS_WRITE, (__u8)cmd,
I2C_SMBUS_I2C_BLOCK_BROKEN, &data)) {
PyErr_SetFromErrno(PyExc_IOError);
return NULL;
}

Py_INCREF(Py_None);
return Py_None;

PyDoc_STRVAR(SMBus_type_doc,
"SMBus([bus]) -> SMBus\n\n"
"Return a new SMBus object that is (optionally) connected to the\n"
"specified I2C device interface.\n");
static PyMethodDef SMBus_methods[] = {
{"open", (PyCFunction)SMBus_open, METH_VARARGS | METH_KEYWORDS,
SMBus_open_doc},
{"close", (PyCFunction)SMBus_close, METH_NOARGS,
SMBus_close_doc},
{"write_quick", (PyCFunction)SMBus_write_quick, METH_VARARGS,
SMBus_write_quick_doc},

};

{"read_byte", (PyCFunction)SMBus_read_byte, METH_VARARGS,


SMBus_read_byte_doc},
{"write_byte", (PyCFunction)SMBus_write_byte, METH_VARARGS,
SMBus_write_byte_doc},
{"read_byte_data", (PyCFunction)SMBus_read_byte_data, METH_VARARGS,
SMBus_read_byte_data_doc},
{"write_byte_data", (PyCFunction)SMBus_write_byte_data, METH_VARARGS,
SMBus_write_byte_data_doc},
{"read_word_data", (PyCFunction)SMBus_read_word_data, METH_VARARGS,
SMBus_read_word_data_doc},
{"write_word_data", (PyCFunction)SMBus_write_word_data, METH_VARARGS,
SMBus_write_word_data_doc},
{"process_call", (PyCFunction)SMBus_process_call, METH_VARARGS,
SMBus_process_call_doc},
{"read_block_data", (PyCFunction)SMBus_read_block_data, METH_VARARGS,
SMBus_read_block_data_doc},
{"write_block_data", (PyCFunction)SMBus_write_block_data, METH_VARARGS,
SMBus_write_block_data_doc},
{"block_process_call", (PyCFunction)SMBus_block_process_call,
METH_VARARGS, SMBus_block_process_call_doc},
{"read_i2c_block_data", (PyCFunction)SMBus_read_i2c_block_data,
METH_VARARGS, SMBus_read_i2c_block_data_doc},
{"write_i2c_block_data", (PyCFunction)SMBus_write_i2c_block_data,
METH_VARARGS, SMBus_write_i2c_block_data_doc},
{NULL},

static PyObject *
SMBus_get_pec(SMBus *self, void *closure)
{
PyObject *result = self->pec ? Py_True : Py_False;
Py_INCREF(result);
return result;
}
static int
SMBus_set_pec(SMBus *self, PyObject *val, void *closure)
{
int pec;
pec = PyObject_IsTrue(val);
if (val == NULL) {
PyErr_SetString(PyExc_TypeError,
"Cannot delete attribute");
return -1;
}
else if (pec == -1) {
PyErr_SetString(PyExc_TypeError,
"The pec attribute must be a boolean.");
return -1;
}
if (self->pec != pec) {
if (ioctl(self->fd, I2C_PEC, pec)) {
PyErr_SetFromErrno(PyExc_IOError);
return -1;

}
self->pec = pec;
}
}

return 0;

static PyGetSetDef SMBus_getset[] = {


{"pec", (getter)SMBus_get_pec, (setter)SMBus_set_pec,
"True if Packet Error Codes (PEC) are enabled"},
{NULL},
};
/* old Python 2.7 declaration */
static PyTypeObject SMBus_type = {
/*tatic struct PyModuleDef SMBus_type = {*/
/* old Python 2.7 declaration */
/* PyObject_HEAD_INIT(NULL) */
/*0,
ob_size */
PyVarObject_HEAD_INIT(NULL, 0)
"SMBus",
/* tp_name */
sizeof(SMBus),
/* tp_basicsize */
0,
/* tp_itemsize */
(destructor)SMBus_dealloc,
/* tp_dealloc */
0,
/* tp_print */
0,
/* tp_getattr */
0,
/* tp_setattr */
0,
/* tp_compare */
0,
/* tp_repr */
0,
/* tp_as_number */
0,
/* tp_as_sequence */
0,
/* tp_as_mapping */
0,
/* tp_hash */
0,
/* tp_call */
0,
/* tp_str */
0,
/* tp_getattro */
0,
/* tp_setattro */
0,
/* tp_as_buffer */
Py_TPFLAGS_DEFAULT, /* tp_flags */
SMBus_type_doc,
/* tp_doc */
0,
/* tp_traverse */
0,
/* tp_clear */
0,
/* tp_richcompare */
0,
/* tp_weaklistoffset */
0,
/* tp_iter */
0,
/* tp_iternext */
SMBus_methods, /* tp_methods */
0,
/* tp_members */
SMBus_getset,
/* tp_getset */
0,
/* tp_base */
0,
/* tp_dict */
0,
/* tp_descr_get */
0,
/* tp_descr_set */
0,
/* tp_dictoffset */
(initproc)SMBus_init, /* tp_init */
0,
/* tp_alloc */
SMBus_new,
/* tp_new */

};
/*static PyMethodDef SMBus_module_methods[] = {
{NULL}
};*/
static struct PyModuleDef SMBusModule = {
PyModuleDef_HEAD_INIT,
"SMBus",
/* m_name */
"This module defines an object type that allows SMBus transactions\n"
"on hosts running the Linux kernel. The host kernel must have I2C\n"
"support, I2C device interface support, and a bus adapter driver.\n"
"All of these can be either built-in to the kernel, or loaded from\n"
"modules.\n"
"\n"
"Because the I2C device interface is opened R/W, users of this\n"
"module usually must have root permissions.\n", /* m_doc */
-1,
/* m_size */
NULL, /* m_methods */
NULL,
/* m_reload */
NULL,
/* m_traverse */
NULL,
/* m_clear */
NULL,
/* m_free */
};
#ifndef PyMODINIT_FUNC /* declarations for DLL import/export */
#define PyMODINIT_FUNC void
#endif
PyMODINIT_FUNC
PyInit_smbus(void)
{
PyObject* m;
if (PyType_Ready(&SMBus_type) < 0)
return NULL;
/* old Python 2.7 declaration */
/*m = Py_InitModule3("smbus", SMBus_module_methods, SMBus_module_doc);*/
m = PyModule_Create(&SMBusModule);
if (m == NULL)
return NULL;
Py_INCREF(&SMBus_type);
PyModule_AddObject(m, "SMBus", (PyObject *)&SMBus_type);
}

return m;

Sandeep Tara:
AB Electronics UK Servo Pi Python 3 Library
=====
Python 3 Library to use with Servo Pi Raspberry Pi expansion board from http://www.abelectronics.co.uk
Install
====

To download to your Raspberry Pi type in terminal:


```
git clone https://github.com/abelectronicsuk/ABElectronics_Python3_Libraries.git
```
The Servo Pi library is located in the ServoPi directory
Add the location where you downloaded the python libraries into PYTHONPATH e.g. download location is Desktop
```
export PYTHONPATH=${PYTHONPATH}:~/Desktop/ABElectronics_Python3_Libraries/ServoPi/
```
The library requires i2c to be enabled and python3-smbus to be installed.
Follow the tutorial at [https://www.abelectronics.co.uk/kb/article/1/i2c--smbus-and-raspbian-linux](https://www.abelectronics.co.uk/kb/article/1/i2c--smbus-and-raspbian-linux) to
enable i2c and install python-smbus for python 3.
The example python files in /ABElectronics_Python3_Libraries/ServoPi/ will now run from the terminal.
Functions:
---------```
set_pwm_freq(freq)
```
Set the PWM frequency
**Parameters:** freq - required frequency
**Returns:** null
```
set_pwm(channel, on, off)
```
Set the output on single channels
**Parameters:** channel - 1 to 16, on - time period, off - time period
**Returns:** null
```
set_all_pwm( on, off)
```
Set the output on all channels
**Parameters:** on - time period, off - time period
**Returns:** null
```
output_disable()
```
Disable the output via OE pin
**Parameters:** null
**Returns:** null
```
output_enable()
```
Enable the output via OE pin
**Parameters:** null

**Returns:** null
```
set_allcall_address(address)
```
Set the I2C address for the All Call function
**Parameters:** address
**Returns:** null
```
enable_allcall_address()
```
Enable the I2C address for the All Call function
**Parameters:** null
**Returns:** null
```
disable_allcall_address()
```
Disable the I2C address for the All Call function
**Parameters:** null
**Returns:** null
Usage
====
To use the Servo Pi library in your code you must first import the library:
```
from ABE_ServoPi import PWM
```
Next you must initialise the ServoPi object:
```
pwm = PWM(0x40)
```
Set PWM frequency to 60 Hz
```
pwm.set_pwm_freq(60)
pwm.output_enable()
```
Set three variables for pulse length
```
servoMin = 250 # Min pulse length out of 4096
servoMed = 400 # Min pulse length out of 4096
servoMax = 500 # Max pulse length out of 4096
```
Loop to move the servo on port 0 between three points
```
while (True):
pwm.set_pwm(0, 0, servoMin)
time.sleep(0.5)
pwm.set_pwm(0, 0, servoMed)
time.sleep(0.5)
pwm.set_pwm(0, 0, servoMax)
time.sleep(0.5)
# use set_all_pwm to set PWM on all outputs
```
#!/usr/bin/python3

from ABE_ServoPi import PWM


import time
from ABE_helpers import ABEHelpers
"""
================================================
ABElectronics Servo Pi pwm controller | PWM servo controller demo
Version 1.0 Created 29/02/2015
Requires python 3 smbus to be installed
run with: sudo python3 demo-servomove.py
================================================
This demo shows how to set the limits of movement on a servo
and then move between those positions
"""
i2c_helper = ABEHelpers()
bus = i2c_helper.get_smbus()
pwm = PWM(bus, 0x40)
# set the servo minimum, centre and maximum limits
servoMin = 250 # Min pulse length out of 4096
servoMed = 400 # Min pulse length out of 4096
servoMax = 500 # Max pulse length out of 4096
# Set PWM frequency to 60 Hz
pwm.set_pwm_freq(60)
pwm.output_enable()
while (True):
# Move servo on port 0 between three points
pwm.set_pwm(0, 0, servoMin)
time.sleep(0.5)
pwm.set_pwm(0, 0, servoMed)
time.sleep(0.5)
pwm.set_pwm(0, 0, servoMax)
time.sleep(0.5)
# use set_all_pwm to set PWM on all outputs
#!/usr/bin/python3
from ABE_ServoPi import PWM
import time
from ABE_helpers import ABEHelpers
"""
================================================
ABElectronics Servo Pi pwm controller | PWM output demo
Version 1.0 Created 29/02/2015
Requires python 3 smbus to be installed
run with: sudo python3 demo-pwm.py

================================================
This demo shows how to set a 1KHz output frequency and change the pulse width
between the minimum and maximum values
"""
# create an instance of the ABEHelpers class and use it
# to find the correct i2c bus
i2c_helper = ABEHelpers()
bus = i2c_helper.get_smbus()
# create an instance of the PWM class on i2c address 0x40
pwm = PWM(bus, 0x40)
# Set PWM frequency to 1 Khz and enable the output
pwm.set_pwm_freq(1000)
pwm.output_enable()
while (True):
for x in range(1, 4095, 5):
pwm.set_pwm(0, 0, x)
#time.sleep(0.01)
for x in range(4095, 1, -5):
pwm.set_pwm(0, 0, x)
#time.sleep(0.01)
#!/usr/bin/python3
import
import
import
import

time
math
re
RPi.GPIO as GPIO

"""
================================================
ABElectronics ServoPi 16-Channel PWM Servo Driver
Version 1.0 Created 29/02/2015
Requires python 3 smbus to be installed
================================================
"""
class PWM:
# Define registers values from datasheet
MODE1 = 0x00
MODE2 = 0x01
SUBADR1 = 0x02
SUBADR2 = 0x03
SUBADR3 = 0x04
ALLCALLADR = 0x05
LED0_ON_L = 0x06
LED0_ON_H = 0x07
LED0_OFF_L = 0x08
LED0_OFF_H = 0x09
ALL_LED_ON_L = 0xFA
ALL_LED_ON_H = 0xFB
ALL_LED_OFF_L = 0xFC

ALL_LED_OFF_H = 0xFD
PRE_SCALE = 0xFE
global _bus
def __init__(self, bus, address=0x40):
"""
init object with i2c address, default is 0x40 for ServoPi board
"""
self.address = address
self._bus = bus
self.write(self.address, self.MODE1, 0x00)
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BOARD)
GPIO.setup(7, GPIO.OUT)
def set_pwm_freq(self, freq):
"""
Set the PWM frequency
"""
scaleval = 25000000.0 # 25MHz
scaleval /= 4096.0
# 12-bit
scaleval /= float(freq)
scaleval -= 1.0
prescale = math.floor(scaleval + 0.5)
oldmode = self.read(self.address, self.MODE1)
newmode = (oldmode & 0x7F) | 0x10
self.write(self.address, self.MODE1, newmode)
self.write(self.address, self.PRE_SCALE, int(math.floor(prescale)))
self.write(self.address, self.MODE1, oldmode)
time.sleep(0.005)
self.write(self.address, self.MODE1, oldmode | 0x80)
def set_pwm(self, channel, on, off):
"""
set the output on a single channel
"""
self.write(self.address,
self.write(self.address,
self.write(self.address,
self.write(self.address,

self.LED0_ON_L + 4 * channel, on & 0xFF)


self.LED0_ON_H + 4 * channel, on >> 8)
self.LED0_OFF_L + 4 * channel, off & 0xFF)
self.LED0_OFF_H + 4 * channel, off >> 8)

def set_all_pwm(self, on, off):


"""
set the output on all channels
"""
self.write(self.address,
self.write(self.address,
self.write(self.address,
self.write(self.address,
def output_disable(self):
"""

self.ALL_LED_ON_L, on & 0xFF)


self.ALL_LED_ON_H, on >> 8)
self.ALL_LED_OFF_L, off & 0xFF)
self.ALL_LED_OFF_H, off >> 8)

disable output via OE pin


"""
GPIO.output(7, True)
def output_enable(self):
"""
enable output via OE pin
"""
GPIO.output(7, False)
def set_allcall_address(self, i2caddress):
"""
Set the I2C address for the All Call function
"""
oldmode = self.read(self.address, self.MODE1)
newmode = oldmode | (1 << 0)
self.write(self.address, self.MODE1, newmode)
self.write(self.address, self.ALLCALLADR, i2caddress << 1)
def enable_allcall_address(self):
"""
Enable the I2C address for the All Call function
"""
oldmode = self.read(self.address, self.MODE1)
newmode = oldmode | (1 << 0)
self.write(self.address, self.MODE1, newmode)
def disable_allcall_address(self):
"""
Disable the I2C address for the All Call function
"""
oldmode = self.read(self.address, self.MODE1)
newmode = oldmode & ~(1 << 0)
self.write(self.address, self.MODE1, newmode)
def write(self, address, reg, value):
"""
Write data to I2C bus
"""
try:
self._bus.write_byte_data(address, reg, value)
except IOError as err:
return err

def read(self, address, reg):


"""
Read data from I2C bus
"""
try:

result = self._bus.read_byte_data(address, reg)


return result
except IOError as err:
return err
#!/usr/bin/python3
import
import
import
import

time
math
re
RPi.GPIO as GPIO

"""
================================================
ABElectronics ServoPi 16-Channel PWM Servo Driver
Version 1.0 Created 29/02/2015
Requires python 3 smbus to be installed
================================================
"""
class PWM:
# Define registers values from datasheet
MODE1 = 0x00
MODE2 = 0x01
SUBADR1 = 0x02
SUBADR2 = 0x03
SUBADR3 = 0x04
ALLCALLADR = 0x05
LED0_ON_L = 0x06
LED0_ON_H = 0x07
LED0_OFF_L = 0x08
LED0_OFF_H = 0x09
ALL_LED_ON_L = 0xFA
ALL_LED_ON_H = 0xFB
ALL_LED_OFF_L = 0xFC
ALL_LED_OFF_H = 0xFD
PRE_SCALE = 0xFE
global _bus
def __init__(self, bus, address=0x40):
"""
init object with i2c address, default is 0x40 for ServoPi board
"""
self.address = address
self._bus = bus
self.write(self.address, self.MODE1, 0x00)
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BOARD)
GPIO.setup(7, GPIO.OUT)
def set_pwm_freq(self, freq):
"""
Set the PWM frequency
"""

scaleval = 25000000.0 # 25MHz


scaleval /= 4096.0
# 12-bit
scaleval /= float(freq)
scaleval -= 1.0
prescale = math.floor(scaleval + 0.5)
oldmode = self.read(self.address, self.MODE1)
newmode = (oldmode & 0x7F) | 0x10
self.write(self.address, self.MODE1, newmode)
self.write(self.address, self.PRE_SCALE, int(math.floor(prescale)))
self.write(self.address, self.MODE1, oldmode)
time.sleep(0.005)
self.write(self.address, self.MODE1, oldmode | 0x80)
def set_pwm(self, channel, on, off):
"""
set the output on a single channel
"""
self.write(self.address,
self.write(self.address,
self.write(self.address,
self.write(self.address,

self.LED0_ON_L + 4 * channel, on & 0xFF)


self.LED0_ON_H + 4 * channel, on >> 8)
self.LED0_OFF_L + 4 * channel, off & 0xFF)
self.LED0_OFF_H + 4 * channel, off >> 8)

def set_all_pwm(self, on, off):


"""
set the output on all channels
"""
self.write(self.address,
self.write(self.address,
self.write(self.address,
self.write(self.address,

self.ALL_LED_ON_L, on & 0xFF)


self.ALL_LED_ON_H, on >> 8)
self.ALL_LED_OFF_L, off & 0xFF)
self.ALL_LED_OFF_H, off >> 8)

def output_disable(self):
"""
disable output via OE pin
"""
GPIO.output(7, True)
def output_enable(self):
"""
enable output via OE pin
"""
GPIO.output(7, False)
def set_allcall_address(self, i2caddress):
"""
Set the I2C address for the All Call function
"""
oldmode = self.read(self.address, self.MODE1)
newmode = oldmode | (1 << 0)
self.write(self.address, self.MODE1, newmode)
self.write(self.address, self.ALLCALLADR, i2caddress << 1)

def enable_allcall_address(self):
"""
Enable the I2C address for the All Call function
"""
oldmode = self.read(self.address, self.MODE1)
newmode = oldmode | (1 << 0)
self.write(self.address, self.MODE1, newmode)
def disable_allcall_address(self):
"""
Disable the I2C address for the All Call function
"""
oldmode = self.read(self.address, self.MODE1)
newmode = oldmode & ~(1 << 0)
self.write(self.address, self.MODE1, newmode)
def write(self, address, reg, value):
"""
Write data to I2C bus
"""
try:
self._bus.write_byte_data(address, reg, value)
except IOError as err:
return err

def read(self, address, reg):


"""
Read data from I2C bus
"""
try:
result = self._bus.read_byte_data(address, reg)
return result
except IOError as err:
return err
#!/usr/bin/python3
try:
import smbus
except ImportError:
raise ImportError("python-smbus not found Install with 'sudo apt-get install python3-smbus'")
import re
"""
================================================
ABElectronics Python Helper Functions
Version 1.0 Created 29/02/2015
Python 3 only
Requires python 3 smbus to be installed. For more information
about enabling i2c and installing smbus visit
https://www.abelectronics.co.uk/i2c-raspbian-wheezy/info.aspx
================================================
"""

class ABEHelpers:
def get_smbus(self):
# detect i2C port number and assign to i2c_bus
i2c_bus = 0
for line in open('/proc/cpuinfo').readlines():
m = re.match('(.*?)\s*:\s*(.*)', line)
if m:
(name, value) = (m.group(1), m.group(2))
if name == "Revision":
if value[-4:] in ('0002', '0003'):
i2c_bus = 0
else:
i2c_bus = 1
break
try:
return smbus.SMBus(i2c_bus)
except IOError:
print ("Could not open the i2c bus.")
print ("Please check that i2c is enabled and python-smbus and i2c-tools are installed.")
print ("Visit https://www.abelectronics.co.uk/i2c-raspbian-wheezy/info.aspx for more information.")
AB Electronics UK RTC Pi Python 3 Library
=====
Python 3 Library to use with RTC Pi Raspberry Pi real-time clock board from http://www.abelectronics.co.uk
Install
====
To download to your Raspberry Pi type in terminal:
```
git clone https://github.com/abelectronicsuk/ABElectronics_Python3_Libraries.git
```
The RTC Pi library is located in the RTCPi directory
Add the location where you downloaded the python libraries into PYTHONPATH e.g. download location is Desktop
```
export PYTHONPATH=${PYTHONPATH}:~/Desktop/ABElectronics_Python_Libraries/RTCPi/
```
The library requires i2c to be enabled and python-smbus to be installed.
Follow the tutorial at [https://www.abelectronics.co.uk/i2c-raspbian-wheezy/info.aspx](https://www.abelectronics.co.uk/i2c-raspbian-wheezy/info.aspx) to enable i2c and install
python-smbus for python 3.
The example python files in /ABElectronics_Python3_Libraries/RTCPi/ will now run from the terminal.
Functions:
---------```
set_date(date)
```

Set the date and time on the RTC in ISO 8601 format - YYYY-MM-DDTHH:MM:SS
**Parameters:** date
**Returns:** null
```
read_date()
```
Returns the date from the RTC in ISO 8601 format - YYYY-MM-DDTHH:MM:SS
**Returns:** date
```
enable_output()
```
Enable the square-wave output on the SQW pin.
**Returns:** null
```
disable_output()
```
Disable the square-wave output on the SQW pin.
**Returns:** null
```
set_frequency()
```
Set the frequency for the square-wave output on the SQW pin.
**Parameters:** frequency - options are: 1 = 1Hz, 2 = 4.096KHz, 3 = 8.192KHz, 4 = 32.768KHz
**Returns:** null
Usage
====
To use the RTC Pi library in your code you must first import the library:
```
from ABE_RTCPi import RTC
```
Now import the helper class
```
from ABE_helpers import ABEHelpers
```
Next you must initialise the RTC object with the smbus object using the helpers:
```
i2c_helper = ABEHelpers()
bus = i2c_helper.get_smbus()
rtc = RTC()
```
Set the current time in ISO 8601 format:
```
rtc.set_date("2013-04-23T12:32:11")
```
Enable the square-wave output at 8.192KHz on the SQW pin:
```
rtc.set_frequency(3)
rtc.enable_output()

```
Read the current date and time from the RTC at 1 second intervals:
```
while (True):
print rtc.read_date()
time.sleep(1)
```
#!/usr/bin/python3
from ABE_RTCPi import RTC
from ABE_helpers import ABEHelpers
import time
"""
================================================
ABElectronics RTC Pi real-time clock | Set Time Demo
Version 1.0 Created 29/02/2015
Requires python 3 smbus to be installed
run with: python3 demo-rtcsettime.py
================================================
This demo shows how to set the time on the RTC Pi and then read the
current time at 1 second intervals
"""
i2c_helper = ABEHelpers()
bus = i2c_helper.get_smbus()
rtc = RTC(bus) # create a new instance of the RTC class
# set the date using ISO 8601 format - YYYY-MM-DDTHH:MM:SS
rtc.set_date("2013-04-23T12:32:11")
while True:
# read the date from the RTC in ISO 8601 format and print it to the screen
print (rtc.read_date())
time.sleep(1) # wait 1 second
#!/usr/bin/python3
from ABE_RTCPi import RTC
from ABE_helpers import ABEHelpers
import time
"""
================================================
ABElectronics RTC Pi real-time clock | RTC clock output demo
Version 1.0 Created 29/02/2015
Requires python 3 smbus to be installed
run with: python3 demo-rtcout.py
================================================
This demo shows how to enable the clock square wave output on the RTC Pi

and set the frequency


"""
i2c_helper = ABEHelpers()
bus = i2c_helper.get_smbus()
rtc = RTC(bus) # create a new instance of the RTC class
# set the frequency of the output square-wave, options are: 1 = 1Hz, 2 =
# 4.096KHz, 3 = 8.192KHz, 4 = 32.768KHz
rtc.set_frequency(3)
rtc.enable_output() # enable the square-wave
#!/usr/bin/python
import datetime
import re
"""
================================================
ABElectronics RTC Pi Real-time clock
Version 1.0 Created 29/02/2015
Requires python 3 smbus to be installed
================================================
"""
class RTC:
# Define registers values from datasheet
SECONDS = 0x00
MINUTES = 0x01
HOURS = 0x02
DAYOFWEEK = 0x03
DAY = 0x04
MONTH = 0x05
YEAR = 0x06
CONTROL = 0x07
# variables
__rtcAddress = 0x68 # I2C address
# initial configuration - square wave and output disabled, frequency set
# to 32.768KHz.
__config = 0x03
# the DS1307 does not store the current century so that has to be added on
# manually.
__century = 2000
global _bus
# local methods
def __init__(self, bus):
self._bus = bus
self._bus.write_byte_data(self.__rtcAddress, self.CONTROL, self.__config)
return
def __bcd_to_dec(self,bcd):

"""
internal method for converting BCD formatted number to decimal
"""
dec = 0
for a in (bcd >> 4, bcd):
for b in (1, 2, 4 ,8):
if a & 1:
dec += b
a >>= 1
dec *= 10
return dec / 10
def __dec_to_bcd(self,dec):
"""
internal method for converting decimal formatted number to BCD
"""
bcd = 0
for a in (dec // 10, dec % 10):
for b in (8, 4, 2, 1):
if a >= b:
bcd += 1
a -= b
bcd <<= 1
return bcd >> 1
def __get_century(self, val):
if len(val) > 2:
y = val[0] + val[1]
self.__century = int(y) * 100
return
def __updatebyte(self, byte, bit, value):
"""
internal method for setting the value of a single bit within a byte
"""
if value == 0:
return byte & ~(1 << bit)
elif value == 1:
return byte | (1 << bit)
# public methods
def set_date(self, date):
"""
set the date and time on the RTC
date must be in ISO 8601 format - YYYY-MM-DDTHH:MM:SS
"""
d = datetime.datetime.strptime(date, "%Y-%m-%dT%H:%M:%S")
self.__get_century(date)
self._bus.write_byte_data(
self.__rtcAddress,
self.SECONDS,
self.__dec_to_bcd(
d.second))
self._bus.write_byte_data(
self.__rtcAddress,

self.MINUTES,
self.__dec_to_bcd(
d.minute))
self._bus.write_byte_data(
self.__rtcAddress,
self.HOURS,
self.__dec_to_bcd(
d.hour))
self._bus.write_byte_data(
self.__rtcAddress,
self.DAYOFWEEK,
self.__dec_to_bcd(
d.weekday()))
self._bus.write_byte_data(
self.__rtcAddress,
self.DAY,
self.__dec_to_bcd(
d.day))
self._bus.write_byte_data(
self.__rtcAddress,
self.MONTH,
self.__dec_to_bcd(
d.month))
self._bus.write_byte_data(
self.__rtcAddress,
self.YEAR,
self.__dec_to_bcd(
d.year self.__century))
return
def read_date(self):
"""
read the date and time from the RTC in ISO 8601 format YYYY-MM-DDTHH:MM:SS
"""
seconds, minutes, hours, dayofweek, day, month,\
year = self._bus.read_i2c_block_data(self.__rtcAddress, 0, 7)
date = (
"%02d-%02d-%02dT%02d:%02d:%02d " %
(self.__bcd_to_dec(year) +
self.__century,
self.__bcd_to_dec(month),
self.__bcd_to_dec(day),
self.__bcd_to_dec(hours),
self.__bcd_to_dec(minutes),
self.__bcd_to_dec(seconds)))
return date
def enable_output(self):
"""
Enable the output pin
"""
self.__config = self.__updatebyte(self.__config, 7, 1)
self.__config = self.__updatebyte(self.__config, 4, 1)

self._bus.write_byte_data(self.__rtcAddress, self.CONTROL, self.__config)


return
def disable_output(self):
"""
Disable the output pin
"""
self.__config = self.__updatebyte(self.__config, 7, 0)
self.__config = self.__updatebyte(self.__config, 4, 0)
self._bus.write_byte_data(self.__rtcAddress, self.CONTROL, self.__config)
return
def set_frequency(self, frequency):
"""
set the frequency of the output pin square-wave
options are: 1 = 1Hz, 2 = 4.096KHz, 3 = 8.192KHz, 4 = 32.768KHz
"""
if frequency == 1:
self.__config = self.__updatebyte(self.__config, 0, 0)
self.__config = self.__updatebyte(self.__config, 1, 0)
if frequency == 2:
self.__config = self.__updatebyte(self.__config, 0, 1)
self.__config = self.__updatebyte(self.__config, 1, 0)
if frequency == 3:
self.__config = self.__updatebyte(self.__config, 0, 0)
self.__config = self.__updatebyte(self.__config, 1, 1)
if frequency == 4:
self.__config = self.__updatebyte(self.__config, 0, 1)
self.__config = self.__updatebyte(self.__config, 1, 1)
self._bus.write_byte_data(self.__rtcAddress, self.CONTROL, self.__config)
return
#!/usr/bin/python3
try:
import smbus
except ImportError:
raise ImportError("python-smbus not found Install with 'sudo apt-get install python3-smbus'")
import re
"""
================================================
ABElectronics Python Helper Functions
Version 1.0 Created 29/02/2015
Python 3 only
Requires python 3 smbus to be installed. For more information
about enabling i2c and installing smbus visit
https://www.abelectronics.co.uk/i2c-raspbian-wheezy/info.aspx
================================================
"""
class ABEHelpers:
def get_smbus(self):
# detect i2C port number and assign to i2c_bus

i2c_bus = 0
for line in open('/proc/cpuinfo').readlines():
m = re.match('(.*?)\s*:\s*(.*)', line)
if m:
(name, value) = (m.group(1), m.group(2))
if name == "Revision":
if value[-4:] in ('0002', '0003'):
i2c_bus = 0
else:
i2c_bus = 1
break
try:
return smbus.SMBus(i2c_bus)
except IOError:
print ("Could not open the i2c bus.")
print ("Please check that i2c is enabled and python-smbus and i2c-tools are installed.")
print ("Visit https://www.abelectronics.co.uk/i2c-raspbian-wheezy/info.aspx for more information.")
#!/usr/bin/python3
"""
================================================
ABElectronics IO Pi 32-Channel Port Expander - Tutorial 1
Version 1.0 Created 29/02/2015
Requires python 3 smbus to be installed
run with: python3 tutorial2.py
================================================
This example uses the write_pin and writePort methods to switch pin 1 on
and off on the IO Pi.
"""
from ABE_helpers import ABEHelpers
from ABE_IoPi import IoPi
import time
i2c_helper = ABEHelpers()
i2c_bus = i2c_helper.get_smbus()
bus = IoPi(i2c_bus, 0x20)
bus.set_pin_direction(1, 1) # set pin 1 as an input
bus.set_pin_direction(8, 0) # set pin 8 as an output
bus.write_pin(8, 0) # turn off pin 8
bus.set_pin_pullup(1, 1) # enable the internal pull-up resistor on pin 1
bus.invert_pin(1, 1) # invert pin 1 so a button press will register as 1
while True:
if bus.read_pin(1) == 1: # check to see if the button is pressed
print ('button pressed') # print a message to the screen
bus.write_pin(8, 1) # turn on the led on pin 8
time.sleep(2) # wait 2 seconds
else:

bus.write_pin(8, 0) # turn off the led on pin 8


#!/usr/bin/python3
"""
================================================
ABElectronics IO Pi 32-Channel Port Expander - Tutorial 1a
Version 1.0 Created 29/02/2015
Requires python 3 smbus to be installed
run with: python3 tutorial1a.py
================================================
This example uses the write_port method to count in binary using 8 LEDs
"""
from ABE_helpers import ABEHelpers
from ABE_IoPi import IoPi
import time
i2c_helper = ABEHelpers()
i2c_bus = i2c_helper.get_smbus()
bus = IoPi(i2c_bus, 0x20)
bus.set_port_direction(0, 0x00)
bus.write_port(0, 0x00)
while True:
for x in range(0, 255):
bus.write_port(0, x)
time.sleep(0.5)
bus.write_port(0, 0x00)
#!/usr/bin/python3
"""
================================================
ABElectronics IO Pi 32-Channel Port Expander - Tutorial 1
Version 1.0 Created 29/02/2015
Requires python 3 smbus to be installed
run with: python3 tutorial1.py
================================================
This example uses the write_pin and write_port methods to switch pin 1 on
and off on the IO Pi.
"""
from ABE_helpers import ABEHelpers
from ABE_IoPi import IoPi
import time
i2c_helper = ABEHelpers()
i2c_bus = i2c_helper.get_smbus()
bus = IoPi(i2c_bus, 0x21)
bus.set_port_direction(0, 0x00)
bus.write_port(0, 0x00)

while True:
bus.write_pin(1, 1)
time.sleep(1)
bus.write_pin(1, 0)
time.sleep(1)
AB Electronics UK IO Pi Python 3 Library
=====
Python 3 Library to use with IO Pi Raspberry Pi expansion board from http://www.abelectronics.co.uk
Install
====
To download to your Raspberry Pi type in terminal:
```
git clone https://github.com/abelectronicsuk/ABElectronics_Python3_Libraries.git
```
The IO Pi library is located in the IOPi directory
The library requires i2c to be enabled and python-smbus to be installed.
Follow the tutorial at [https://www.abelectronics.co.uk/i2c-raspbian-wheezy/info.aspx](https://www.abelectronics.co.uk/i2c-raspbian-wheezy/info.aspx) to enable i2c and install
python-smbus for python 3.
Add the location where you downloaded the python libraries into PYTHONPATH e.g. download location is Desktop
```
export PYTHONPATH=${PYTHONPATH}:~/Desktop/ABElectronics_Python3_Libraries/IOPi/
```
The example python files in /ABElectronics_Python3_Libraries/IOPi/ will now run from the terminal.
Functions:
---------```
set_pin_direction(pin, direction):
```
Sets the IO direction for an individual pin
**Parameters:** pin - 1 to 16, direction - 1 = input, 0 = output
**Returns:** null
```
set_port_direction(port, direction):
```
Sets the IO direction for the specified IO port
**Parameters:** port - 0 = pins 1 to 8, port 1 = pins 9 to 16, direction - 1 = input, 0 = output
**Returns:** null
```
set_port_pullups(self, port, value)
```
Set the internal 100K pull-up resistors for the selected IO port
**Parameters:** port - 1 to 16, value: 1 = Enabled, 0 = Disabled
**Returns:** null
```

write_pin(pin, value)
```
Write to an individual pin 1 - 16
**Parameters:** pin - 1 to 16, value - 1 = Enabled, 0 = Disabled
**Returns:** null
```
write_port(self, port, value)
```
Write to all pins on the selected port
**Parameters:** port - 0 = pins 1 to 8, port 1 = pins 9 to 16, value - number between 0 and 255 or 0x00 and 0xFF
**Returns:** null
```
read_pin(pin)
```
Read the value of an individual pin 1 - 16
**Parameters:** pin: 1 to 16
**Returns:** 0 = logic level low, 1 = logic level high
```
read_port(port)
```
Read all pins on the selected port
**Parameters:** port - 0 = pins 1 to 8, port 1 = pins 9 to 16
**Returns:** number between 0 and 255 or 0x00 and 0xFF
```
invert_port(port, polarity)
```
Invert the polarity of the pins on a selected port
**Parameters:** port - 0 = pins 1 to 8, port 1 = pins 9 to 16, polarity - 0 = same logic state of the input pin, 1 = inverted logic state of the input pin
**Returns:** null
```
invert_pin(pin, polarity)
```
Invert the polarity of the selected pin
**Parameters:** pin - 1 to 16, polarity - 0 = same logic state of the input pin, 1 = inverted logic state of the input pin
**Returns:** null
```
mirror_interrupts(value)
```
Mirror Interrupts
**Parameters:** value - 1 = The INT pins are internally connected, 0 = The INT pins are not connected. INTA is associated with PortA and INTB is associated with PortB
**Returns:** null
```
set_interrupt_type(port, value)
```
Sets the type of interrupt for each pin on the selected port
**Parameters:** port 0 = pins 1 to 8, port 1 = pins 9 to 16, value: 1 = interrupt is fired when the pin matches the default value, 0 = the interrupt is fired on state change
**Returns:** null
```
set_interrupt_defaults(port, value)
```
These bits set the compare value for pins configured for interrupt-on-change on the selected port.
If the associated pin level is the opposite from the register bit, an interrupt occurs.
**Parameters:** port 0 = pins 1 to 8, port 1 = pins 9 to 16, value: compare value
**Returns:** null
```
set_interrupt_on_port(port, value)

```
Enable interrupts for the pins on the selected port
**Parameters:** port 0 = pins 1 to 8, port 1 = pins 9 to 16, value: number between 0 and 255 or 0x00 and 0xFF
**Returns:** null
```
set_interrupt_on_pin(pin, value)
```
Enable interrupts for the selected pin
**Parameters:** pin - 1 to 16, value - 0 = interrupt disabled, 1 = interrupt enabled
**Returns:** null
```
read_interrupt_status(port)
```
Read the interrupt status for the pins on the selected port
**Parameters:** port 0 = pins 1 to 8, port 1 = pins 9 to 16
**Returns:** status
```
read_interrupt_capture(port)
```
Read the value from the selected port at the time of the last interrupt trigger
**Parameters:** port 0 = pins 1 to 8, port 1 = pins 9 to 16
**Returns:** status
```
reset_interrupts()
```
Set the interrupts A and B to 0
**Parameters:** null
**Returns:** null
Usage
====
To use the IO Pi library in your code you must first import the library:
```
from ABE_IOPi import IOPI
```
Now import the helper class
```
from ABE_helpers import ABEHelpers
```
Next you must initialise the IO object with the smbus object using the helpers:
```
i2c_helper = ABEHelpers()
bus = i2c_helper.get_smbus()
bus1 = IOPI(bus,0x20)
```
We will read the inputs 1 to 8 from bus 2 so set port 0 to be inputs and enable the internal pull-up resistors
```
bus1.set_port_direction(0, 0xFF)
bus1.set_port_pullups(0, 0xFF)
```
You can now read the pin 1 with:

```
print 'Pin 1: ' + str(bus1.read_pin(1))
```
#!/usr/bin/python3
"""
================================================
ABElectronics IO Pi 32-Channel Port Expander - Output Write Demo
Version 1.0 Created 29/02/2015
Requires python 3 smbus to be installed
run with: python3 demo-iopiwrite.py
================================================
This example uses the write_pin and writeBank methods to switch the pins
on and off on the IO Pi.
Initialise the IOPi device using the default addresses, you will need to
change the addresses if you have changed the jumpers on the IO Pi
"""
from ABE_helpers import ABEHelpers
from ABE_IoPi import IoPi
import time
i2c_helper = ABEHelpers()
newbus = i2c_helper.get_smbus()
bus1 = IoPi(newbus, 0x20)
# We will write to the pins 9 to 16 from bus 1 so set port 1 to be outputs
# turn off the pins
bus1.set_port_direction(1, 0x00)
bus1.write_port(1, 0x00)
while True:
# count to 255 and display the value on pins 9 to 16 in binary format
for x in range(0, 255):
time.sleep(0.05)
bus1.write_port(1, x)
# turn off all of the pins on bank 1
bus1.write_port(1, 0x00)
# now turn on all of the leds in turn by writing to one pin at a time
bus1.write_pin(9, 1)
time.sleep(0.1)
bus1.write_pin(10, 1)
time.sleep(0.1)
bus1.write_pin(11, 1)
time.sleep(0.1)
bus1.write_pin(12, 1)
time.sleep(0.1)
bus1.write_pin(13, 1)
time.sleep(0.1)
bus1.write_pin(14, 1)
time.sleep(0.1)
bus1.write_pin(15, 1)
time.sleep(0.1)

bus1.write_pin(16, 1)
# and turn off all of the leds in turn by writing to one pin at a time
bus1.write_pin(9, 0)
time.sleep(0.1)
bus1.write_pin(10, 0)
time.sleep(0.1)
bus1.write_pin(11, 0)
time.sleep(0.1)
bus1.write_pin(12, 0)
time.sleep(0.1)
bus1.write_pin(13, 0)
time.sleep(0.1)
bus1.write_pin(14, 0)
time.sleep(0.1)
bus1.write_pin(15, 0)
time.sleep(0.1)
bus1.write_pin(16, 0)
# repeat until the program ends
#!/usr/bin/python3
from ABE_helpers import ABEHelpers
from ABE_IoPi import IoPi
import time
import os
"""
================================================
ABElectronics IO Pi 32-Channel Port Expander - Read Write Demo
Version 1.0 Created 29/02/2015
Requires python 3 smbus to be installed
run with: python3 demo-iopireadwrite.py
================================================
This example reads pin 1 of bus 1 on the IO Pi board and sets pin 1 of bus 2 to match.
The internal pull-up resistors are enabled so the input pin will read as 1 unless
the pin is connected to ground.
Initialise the IOPi device using the default addresses, you will need to
change the addresses if you have changed the jumpers on the IO Pi
"""
i2c_helper = ABEHelpers()
i2c_bus = i2c_helper.get_smbus()
# create two instances of the IoPi class called bus1 and bus2 and set the default i2c addresses
bus1 = IoPi(i2c_bus, 0x20) # bus 1 will be inputs
bus2 = IoPi(i2c_bus, 0x21) # bus 2 will be outputs
# Each bus is divided up two 8 bit ports. Port 0 controls pins 1 to 8, Port 1 controls pins 9 to 16.
# We will read the inputs on pin 1 of bus 1 so set port 0 to be inputs and
# enable the internal pull-up resistors
bus1.set_port_direction(0, 0xFF)
bus1.set_port_pullups(0, 0xFF)

# We will write to the output pin 1 on bus 2 so set port 0 to be outputs and
# turn off the pins on port 0
bus2.set_port_direction(0, 0x00)
bus2.write_port(0, 0x00)
while True:
# read pin 1 on bus 1. If pin 1 is high set the output on bus 2 pin 1 to high, otherwise set it to low.
# connect pin 1 on bus 1 to ground to see the output on bus 2 pin 1 change state.
if (bus1.read_pin(1) == 1):
bus2.write_pin(1, 1)
else:
bus2.write_pin(1, 0)

# wait 0.1 seconds before reading the pins again


time.sleep(0.1)
#!/usr/bin/python3
from ABE_helpers import ABEHelpers
from ABE_IoPi import IoPi
import time
import os
"""
================================================
ABElectronics IO Pi 32-Channel Port Expander - Input Read Demo
Version 1.0 Created 29/02/2015
Requires python 3 smbus to be installed
run with: python3 demo-iopiread.py
================================================
This example reads the first 8 pins of bus 1 on the IO Pi board. The
internal pull-up resistors are enabled so each pin will read as 1 unless
the pin is connected to ground.
Initialise the IOPi device using the default addresses, you will need to
change the addresses if you have changed the jumpers on the IO Pi
"""
i2c_helper = ABEHelpers()
i2c_bus = i2c_helper.get_smbus()
bus = IoPi(i2c_bus, 0x20)
# We will read the inputs 1 to 8 from bus 2 so set port 0 to be inputs and
# enable the internal pull-up resistors
bus.set_port_direction(0, 0xFF)
bus.set_port_pullups(0, 0xFF)
while True:
# clear the console
os.system('clear')

# read the pins 1 to 8 and print the results


print ('Pin 1: ' + str(bus.read_pin(1)))
print ('Pin 2: ' + str(bus.read_pin(2)))
print ('Pin 3: ' + str(bus.read_pin(3)))
print ('Pin 4: ' + str(bus.read_pin(4)))
print ('Pin 5: ' + str(bus.read_pin(5)))
print ('Pin 6: ' + str(bus.read_pin(6)))
print ('Pin 7: ' + str(bus.read_pin(7)))
print ('Pin 8: ' + str(bus.read_pin(8)))
# wait 0.5 seconds before reading the pins again
time.sleep(0.1)
#!/usr/bin/python3
"""
================================================
ABElectronics IO Pi V2 32-Channel Port Expander
Version 1.0 Created 29/02/2015
Requires python 3 smbus to be installed
run with: python3 demo-iointerrupts.py
================================================
This example shows how to use the interrupt methods on the IO Pi.
The interrupts will be enabled and set so that a voltage applied to
pins 1 and 16 will trigger INT A and B respectively.
using the read_interrupt_capture or read_port methods will
reset the interrupts
Initialise the IOPi device using the default addresses and set the
output of bank 1 on IC1 to the input of bank 1 on IC2
"""
from ABE_helpers import ABEHelpers
from ABE_IoPi import IoPi
import time
i2c_helper = ABEHelpers()
newbus = i2c_helper.get_smbus()
bus1 = IoPi(newbus, 0x20)
bus2 = IoPi(newbus, 0x21)
# Set all pins on bus 2 to be inputs with internal pull-ups disabled.
bus2.set_port_pullups(0, 0x00)
bus2.set_port_pullups(1, 0x00)
bus2.set_port_direction(0, 0xFF)
bus2.set_port_direction(1, 0xFF)
# Set the interrupt polarity to be active high and mirroring disabled, so
# pins 1 to 8 trigger INT A and pins 9 to 16 trigger INT B
bus2.set_interrupt_polarity(1)
bus2.mirror_interrupts(0)
# Set the interrupts default value to trigger when 5V is applied to pins 1
# and 16

bus2.set_interrupt_defaults(0, 0x01)
bus2.set_interrupt_defaults(0, 0x80)
# Set the interrupt type to be 1 for ports A and B so an interrupt is
# fired when the pin matches the default value
bus2.set_interrupt_type(0, 1)
bus2.set_interrupt_type(1, 1)
# Enable interrupts for pins 1 and 16
bus2.set_interrupt_on_pin(1, 1)
bus2.set_interrupt_on_pin(16, 1)
while True:
# read the port value from the last capture for ports 0 and 1. This will
# reset the interrupts
print (bus2.read_interrupt_capture(0))
print (bus2.read_interrupt_capture(1))
time.sleep(2)
#!/usr/bin/python3
"""
================================================
ABElectronics IO Pi 32-Channel Port Expander
Version 1.0 Created 29/02/2015
Requires python 3 smbus to be installed with: sudo apt-get install python-smbus
================================================
Each MCP23017 chip is split into two 8-bit ports. port 0 controls
pins 1 to 8 while port 1 controls pins 9 to 16.
When writing to or reading from a port the least significant bit represents
the lowest numbered pin on the selected port.
"""
class IoPi(object):
# Define registers values from datasheet
IODIRA = 0x00 # IO direction A - 1= input 0 = output
IODIRB = 0x01 # IO direction B - 1= input 0 = output
# Input polarity A - If a bit is set, the corresponding GPIO register bit
# will reflect the inverted value on the pin.
IPOLA = 0x02
# Input polarity B - If a bit is set, the corresponding GPIO register bit
# will reflect the inverted value on the pin.
IPOLB = 0x03
# The GPINTEN register controls the interrupt-onchange feature for each
# pin on port A.
GPINTENA = 0x04
# The GPINTEN register controls the interrupt-onchange feature for each
# pin on port B.
GPINTENB = 0x05
# Default value for port A - These bits set the compare value for pins
# configured for interrupt-on-change. If the associated pin level is the
# opposite from the register bit, an interrupt occurs.

DEFVALA = 0x06
# Default value for port B - These bits set the compare value for pins
# configured for interrupt-on-change. If the associated pin level is the
# opposite from the register bit, an interrupt occurs.
DEFVALB = 0x07
# Interrupt control register for port A. If 1 interrupt is fired when the
# pin matches the default value, if 0 the interrupt is fired on state
# change
INTCONA = 0x08
# Interrupt control register for port B. If 1 interrupt is fired when the
# pin matches the default value, if 0 the interrupt is fired on state
# change
INTCONB = 0x09
IOCON = 0x0A # see datasheet for configuration register
GPPUA = 0x0C # pull-up resistors for port A
GPPUB = 0x0D # pull-up resistors for port B
# The INTF register reflects the interrupt condition on the port A pins of
# any pin that is enabled for interrupts. A set bit indicates that the
# associated pin caused the interrupt.
INTFA = 0x0E
# The INTF register reflects the interrupt condition on the port B pins of
# any pin that is enabled for interrupts. A set bit indicates that the
# associated pin caused the interrupt.
INTFB = 0x0F
# The INTCAP register captures the GPIO port A value at the time the
# interrupt occurred.
INTCAPA = 0x10
# The INTCAP register captures the GPIO port B value at the time the
# interrupt occurred.
INTCAPB = 0x11
GPIOA = 0x12 # data port A
GPIOB = 0x13 # data port B
OLATA = 0x14 # output latches A
OLATB = 0x15 # output latches B
# variables
address = 0x20 # I2C address
port_a_dir = 0x00 # port a direction
port_b_dir = 0x00 # port b direction
portaval = 0x00 # port a value
portbval = 0x00 # port b value
porta_pullup = 0x00 # port a pull-up resistors
portb_pullup = 0x00 # port a pull-up resistors
porta_polarity = 0x00 # input polarity for port a
portb_polarity = 0x00 # input polarity for port b
intA = 0x00 # interrupt control for port a
intB = 0x00 # interrupt control for port a
# initial configuration - see IOCON page in the MCP23017 datasheet for
# more information.
config = 0x22
global _bus
def __init__(self, bus, address=0x20):
"""
init object with smbus object, i2c address, default is 0x20, 0x21 for
IOPi board,
Load default configuration, all pins are inputs with pull-ups disabled

"""
self._bus = bus
self.address = address
self._bus.write_byte_data(self.address, self.IOCON, self.config)
self.portaval = self._bus.read_byte_data(self.address, self.GPIOA)
self.portbval = self._bus.read_byte_data(self.address, self.GPIOB)
self._bus.write_byte_data(self.address, self.IODIRA, 0xFF)
self._bus.write_byte_data(self.address, self.IODIRB, 0xFF)
self.set_port_pullups(0,0x00)
self.set_port_pullups(1,0x00)
self.invert_port(0, 0x00)
self.invert_port(1, 0x00)
return
# local methods
def __updatebyte(self, byte, bit, value):
"""
internal method for setting the value of a single bit within a byte
"""
if value == 0:
return byte & ~(1 << bit)
elif value == 1:
return byte | (1 << bit)
def __checkbit(self, byte, bit):
"""
internal method for reading the value of a single bit within a byte
"""
if byte & (1 << bit):
return 1
else:
return 0
# public methods
def set_pin_direction(self, pin, direction):
"""
set IO direction for an individual pin
pins 1 to 16
direction 1 = input, 0 = output
"""
pin = pin - 1
if pin < 8:
self.port_a_dir = self.__updatebyte(self.port_a_dir, pin, direction)
self._bus.write_byte_data(self.address, self.IODIRA, self.port_a_dir)
else:
self.port_b_dir = self.__updatebyte(self.port_b_dir, pin - 8, direction)
self._bus.write_byte_data(self.address, self.IODIRB, self.port_b_dir)
return
def set_port_direction(self, port, direction):
"""
set direction for an IO port
port 0 = pins 1 to 8, port 1 = pins 8 to 16
1 = input, 0 = output
"""

if port == 1:
self._bus.write_byte_data(self.address, self.IODIRB, direction)
self.port_b_dir = direction
else:
self._bus.write_byte_data(self.address, self.IODIRA, direction)
self.port_a_dir = direction
return
def set_pin_pullup(self, pinval, value):
"""
set the internal 100K pull-up resistors for an individual pin
pins 1 to 16
value 1 = enabled, 0 = disabled
"""
pin = pinval - 1
if pin < 8:
self.porta_pullup = self.__updatebyte(self.porta_pullup, pin, value)
self._bus.write_byte_data(self.address, self.GPPUA, self.porta_pullup)
else:
self.portb_pullup = self.__updatebyte(self.portb_pullup,pin - 8,value)
self._bus.write_byte_data(self.address, self.GPPUB, self.portb_pullup)
return
def set_port_pullups(self, port, value):
"""
set the internal 100K pull-up resistors for the selected IO port
"""
if port == 1:
self.portb_pullup = value
self._bus.write_byte_data(self.address, self.GPPUB, value)
else:
self.porta_pullup = value
self._bus.write_byte_data(self.address, self.GPPUA, value)
return
def write_pin(self, pin, value):
"""
write to an individual pin 1 - 16
"""
pin = pin - 1
if pin < 8:
self.portaval = self.__updatebyte(self.portaval, pin, value)
self._bus.write_byte_data(self.address, self.GPIOA, self.portaval)
else:
self.portbval = self.__updatebyte(self.portbval, pin - 8, value)
self._bus.write_byte_data(self.address, self.GPIOB, self.portbval)
return
def write_port(self, port, value):
"""
write to all pins on the selected port
port 0 = pins 1 to 8, port 1 = pins 8 to 16
value = number between 0 and 255 or 0x00 and 0xFF
"""
if port == 1:
self._bus.write_byte_data(self.address, self.GPIOB, value)
self.portbval = value

else:
self._bus.write_byte_data(self.address, self.GPIOA, value)
self.portaval = value
return
def read_pin(self, pinval):
"""
read the value of an individual pin 1 - 16
returns 0 = logic level low, 1 = logic level high
"""
pin = pinval - 1
if pin < 8:
self.portaval = self._bus.read_byte_data(self.address, self.GPIOA)
return self.__checkbit(self.portaval, pin)
else:
pin = pin - 8
self.portbval = self._bus.read_byte_data(self.address, self.GPIOB)
return self.__checkbit(self.portbval, pin)
def read_port(self, port):
"""
read all pins on the selected port
port 0 = pins 1 to 8, port 1 = pins 8 to 16
returns number between 0 and 255 or 0x00 and 0xFF
"""
if port == 1:
self.portbval = self._bus.read_byte_data(self.address, self.GPIOB)
return self.portbval
else:
self.portaval = self._bus.read_byte_data(self.address, self.GPIOA)
return self.portaval
def invert_port(self, port, polarity):
"""
invert the polarity of the pins on a selected port
port 0 = pins 1 to 8, port 1 = pins 8 to 16
polarity 0 = same logic state of the input pin, 1 = inverted logic
state of the input pin
"""
if port == 1:
self._bus.write_byte_data(self.address, self.IPOLB, polarity)
self.portb_polarity = polarity
else:
self._bus.write_byte_data(self.address, self.IPOLA, polarity)
self.porta_polarity = polarity
return
def invert_pin(self, pin, polarity):
"""
invert the polarity of the selected pin
pins 1 to 16
polarity 0 = same logic state of the input pin, 1 = inverted logic
state of the input pin
"""
pin = pin - 1
if pin < 8:
self.porta_polarity = self.__updatebyte(

self.porta_polarity,
pin,
polarity)
self._bus.write_byte_data(self.address, self.IPOLA, self.porta_polarity)
else:
self.portb_polarity = self.__updatebyte(
self.portb_polarity,
pin 8,
polarity)
self._bus.write_byte_data(self.address, self.IPOLB, self.portb_polarity)
return
def mirror_interrupts(self, value):
"""
1 = The INT pins are internally connected, 0 = The INT pins are not
connected. INTA is associated with PortA and INTB is associated with
PortB
"""
if value == 0:
self.config = self.__updatebyte(self.config, 6, 0)
self._bus.write_byte_data(self.address, self.IOCON, self.config)
if value == 1:
self.config = self.__updatebyte(self.config, 6, 1)
self._bus.write_byte_data(self.address, self.IOCON, self.config)
return
def set_interrupt_polarity(self, value):
"""
This sets the polarity of the INT output pins - 1 = Active-high.
0 = Active-low.
"""
if value == 0:
self.config = self.__updatebyte(self.config, 1, 0)
self._bus.write_byte_data(self.address, self.IOCON, self.config)
if value == 1:
self.config = self.__updatebyte(self.config, 1, 1)
self._bus.write_byte_data(self.address, self.IOCON, self.config)
return
return
def set_interrupt_type(self, port, value):
"""
Sets the type of interrupt for each pin on the selected port
1 = interrupt is fired when the pin matches the default value
0 = the interrupt is fired on state change
"""
if port == 0:
self._bus.write_byte_data(self.address, self.INTCONA, value)
else:
self._bus.write_byte_data(self.address, self.INTCONB, value)
return
def set_interrupt_defaults(self, port, value):
"""
These bits set the compare value for pins configured for
interrupt-on-change on the selected port.

If the associated pin level is the opposite from the register bit, an
interrupt occurs.
"""
if port == 0:
self._bus.write_byte_data(self.address, self.DEFVALA, value)
else:
self._bus.write_byte_data(self.address, self.DEFVALB, value)
return
def set_interrupt_on_port(self, port, value):
"""
Enable interrupts for the pins on the selected port
port 0 = pins 1 to 8, port 1 = pins 8 to 16
value = number between 0 and 255 or 0x00 and 0xFF
"""
if port == 0:
self._bus.write_byte_data(self.address, self.GPINTENA, value)
self.intA = value
else:
self._bus.write_byte_data(self.address, self.GPINTENB, value)
self.intB = value
return
def set_interrupt_on_pin(self, pin, value):
"""
Enable interrupts for the selected pin
Pin = 1 to 16
Value 0 = interrupt disabled, 1 = interrupt enabled
"""
pin = pin - 1
if pin < 8:
self.intA = self.__updatebyte(self.intA, pin, value)
self._bus.write_byte_data(self.address, self.GPINTENA, self.intA)
else:
self.intB = self.__updatebyte(self.intB, pin - 8, value)
self._bus.write_byte_data(self.address, self.GPINTENB, self.intB)
return
def read_interrupt_status(self, port):
"""
read the interrupt status for the pins on the selected port
port 0 = pins 1 to 8, port 1 = pins 8 to 16
"""
if port == 0:
return self._bus.read_byte_data(self.address, self.INTFA)
else:
return self._bus.read_byte_data(self.address, self.INTFB)
def read_interrupt_capture(self, port):
"""
read the value from the selected port at the time of the last
interrupt trigger
port 0 = pins 1 to 8, port 1 = pins 8 to 16
"""
if port == 0:
return self._bus.read_byte_data(self.address, self.INTCAPA)
else:

return self._bus.read_byte_data(self.address, self.INTCAPB)


def reset_interrupts(self):
"""
set the interrupts A and B to 0
"""
self.read_interrupt_capture(0)
self.read_interrupt_capture(1)
return
#!/usr/bin/python3
try:
import smbus
except ImportError:
raise ImportError("python-smbus not found Install with 'sudo apt-get install python3-smbus'")
import re
"""
================================================
ABElectronics Python Helper Functions
Version 1.0 Created 29/02/2015
Python 3 only
Requires python 3 smbus to be installed. For more information
about enabling i2c and installing smbus visit
https://www.abelectronics.co.uk/i2c-raspbian-wheezy/info.aspx
================================================
"""
class ABEHelpers:
def get_smbus(self):
# detect i2C port number and assign to i2c_bus
i2c_bus = 0
for line in open('/proc/cpuinfo').readlines():
m = re.match('(.*?)\s*:\s*(.*)', line)
if m:
(name, value) = (m.group(1), m.group(2))
if name == "Revision":
if value[-4:] in ('0002', '0003'):
i2c_bus = 0
else:
i2c_bus = 1
break
try:
return smbus.SMBus(i2c_bus)
except IOError:
print ("Could not open the i2c bus.")
print ("Please check that i2c is enabled and python-smbus and i2c-tools are installed.")
print ("Visit https://www.abelectronics.co.uk/i2c-raspbian-wheezy/info.aspx for more information.")
#!/usr/bin/python3
from
from
from
from
from

ABE_helpers import ABEHelpers


ABE_ExpanderPi import RTC
ABE_ExpanderPi import ADC
ABE_ExpanderPi import IO
ABE_ExpanderPi import DAC

import time
import os
#
#
#
#
#
#

================================================
ABElectronics Expander Pi | Tester
Version 1.0 Created 29/03/2015
run with: python3 tester.py
================================================

# This script tests the various functionality of the Expander Pi


i2c_helper = ABEHelpers()
bus = i2c_helper.get_smbus()
rtc = RTC(bus) # create a new instance of the RTC class
adc = ADC() # create an instance of the ADC class
io = IO(bus) # create an instance of the IO class
dac = DAC() # create an instance of the DAC class
# set the date using ISO 8601 format - YYYY-MM-DDTHH:MM:SS
rtc.set_date("2014-01-01T00:00:00")
dac.set_dac_voltage(1, 1.5) # set the voltage on channel 1 to 1.5V
dac.set_dac_voltage(2, 1.0) # set the voltage on channel 2 to 1.0V
# set the reference voltage. this should be set to the exact voltage
# measured on the Expander Pi Vref pin.
adc.set_adc_refvoltage(4.096)
while True:
# clear the console
os.system('clear')
# read the date from the RTC in ISO 8601 format and print it to the screen
print ('Date: ' + rtc.read_date())
print ('')
print ('ADC: ')
print ('Channel 1: ' + str(adc.read_adc_voltage(1)))
print ('Channel 2: ' + str(adc.read_adc_voltage(2)))
print ('Channel 3: ' + str(adc.read_adc_voltage(3)))
print ('Channel 4: ' + str(adc.read_adc_voltage(4)))
print ('Channel 5: ' + str(adc.read_adc_voltage(5)))
print ('Channel 6: ' + str(adc.read_adc_voltage(6)))
print ('Channel 7: ' + str(adc.read_adc_voltage(7)))
print ('Channel 8: ' + str(adc.read_adc_voltage(8)))
print ('')
print ('IO: ')
print ('Pin 1: ' + str(io.read_pin(1)))
print ('Pin 2: ' + str(io.read_pin(2)))
print ('Pin 3: ' + str(io.read_pin(3)))
print ('Pin 4: ' + str(io.read_pin(4)))
print ('Pin 5: ' + str(io.read_pin(5)))
print ('Pin 6: ' + str(io.read_pin(6)))
print ('Pin 7: ' + str(io.read_pin(7)))
print ('Pin 8: ' + str(io.read_pin(8)))
time.sleep(0.2)
AB Electronics UK Expander Pi Python 3 Library
=====

Python 3 Library to use with Expander Pi board from http://www.abelectronics.co.uk


The Expander Pi contains separate classes for the real-time clock, analogue to digital converter, digital to analogue converter and the digital I/O pins. Examples are included to
show how each of the classes can be used.
Install
====
To download to your Raspberry Pi type in terminal:
```
git clone https://github.com/abelectronicsuk/ABElectronics_Python3_Libraries.git
```
The Expander Pi library is located in the ExpanderPi directory
Add the location where you downloaded the python libraries into PYTHONPATH e.g. download location is Desktop
```
export PYTHONPATH=${PYTHONPATH}:~/Desktop/ABElectronics_Python3_Libraries/ExpanderPi/
```
The library requires i2c to be enabled and python-smbus to be installed.
Follow the tutorial at [https://www.abelectronics.co.uk/i2c-raspbian-wheezy/info.aspx](https://www.abelectronics.co.uk/i2c-raspbian-wheezy/info.aspx) to enable i2c and install
python-smbus for python 3.
The example python files in /ABElectronics_Python3_Libraries/ExpanderPi/ will now run from the terminal.
# Class: ADC #
The ADC class controls the functions on the 12 bit 8 channel Analogue to Digital converter. The Expander Pi comes with an on board 4.096 voltage reference. To use an external
voltage reference remover the solder bridge from jumper J1 and connect the external voltage reference to the Vref pin.

Functions:
---------```
read_adc_voltage(channel)
```
Read the voltage from the selected channel on the ADC
**Parameters:** channel = options are: 1 to 8
**Returns:** voltage
```
read_adc_raw(channel)
```
Read the raw value from the selected channel on the ADC
**Parameters:** channel = options are: 1 to 8
**Returns:** raw 12 bit value (0 to 4096)
```
set_adc_refvoltage(voltage)
```

set the reference voltage for the analogue to digital converter.


By default the ADC uses an on-board 4.096V voltage reference. If you choose to use an external voltage reference you will need to use this method to set the ADC reference
voltage to match the supplied reference voltage.
The reference voltage must be less than or equal to the voltage on the Raspberry Pi 5V rail.
**Parameters:** voltage (use a decimal number)
**Returns:** null

Usage
====
To use the ADC class in your code you must first import the library:
```
from ABE_ExpanderPi import ADC
```
Next you must initialise the ADC object:
```
adc = ADC()
```
If you are using an external voltage reference set the voltage using:
```
adc.set_adc_refvoltage(4.096)
```
Read the voltage from the ADC channel 1 at 1 second intervals:
```
while (True):
print adc.read_adc_voltage(1)
time.sleep(1)
```
# Class: DAC #
The DAC class controls the 2 channel 12 bit digital to analogue converter. The DAC uses an internal voltage reference and can output a voltage between 0 and 2.048V.
Functions:
---------```
set_dac_voltage(channel, voltage)
```
Set the voltage for the selected channel on the DAC
**Parameters:** channel - 1 or 2, voltage can be between 0 and 2.047 volts
**Returns:** null
```
set_dac_raw(channel, value)
```

Set the raw value from the selected channel on the DAC
**Parameters:** channel - 1 or 2,value int between 0 and 4095
**Returns:** null
Usage
====
To use the DAC class in your code you must first import the library:
```
from ABE_ExpanderPi import DAC
```
Next you must initialise the DAC object:
```
dac = DAC()
```
Set the channel and voltage for the DAC output.
```
dac.set_dac_voltage(1, 1.5)
```
# Class: IO #
The IO class controls the 16 digital I/O channels on the Expander Pi. The MCP23017 chip is split into two 8-bit ports. Port 0 controls pins 1 to 8 while Port 1 controls pins 9 to 16.
When writing to or reading from a port the least significant bit represents the lowest numbered pin on the selected port.
Functions:
---------```
set_pin_direction(pin, direction):
```
Sets the IO direction for an individual pin
**Parameters:** pin - 1 to 16, direction - 1 = input, 0 = output
**Returns:** null
```
set_port_direction(port, direction):
```
Sets the IO direction for the specified IO port
**Parameters:** port - 0 = pins 1 to 8, port 1 = pins 8 to 16, direction - 1 = input, 0 = output
**Returns:** null
```
set_port_pullups(self, port, value)
```
Set the internal 100K pull-up resistors for the selected IO port
**Parameters:** port - 1 to 16, value: 1 = Enabled, 0 = Disabled
**Returns:** null

```
write_pin(pin, value)
```
Write to an individual pin 1 - 16
**Parameters:** pin - 1 to 16, value - 1 = Enabled, 0 = Disabled
**Returns:** null
```
write_port(self, port, value)
```
Write to all pins on the selected port
**Parameters:** port - 0 = pins 1 to 8, port 1 = pins 8 to 16, value - number between 0 and 255 or 0x00 and 0xFF
**Returns:** null
```
read_pin(pin)
```
Read the value of an individual pin 1 - 16
**Parameters:** pin: 1 to 16
**Returns:** 0 = logic level low, 1 = logic level high
```
read_port(port)
```
Read all pins on the selected port
**Parameters:** port - 0 = pins 1 to 8, port 1 = pins 8 to 16
**Returns:** number between 0 and 255 or 0x00 and 0xFF
```
invert_port(port, polarity)
```
Invert the polarity of the pins on a selected port
**Parameters:** port - 0 = pins 1 to 8, port 1 = pins 8 to 16, polarity - 0 = same logic state of the input pin, 1 = inverted logic state of the input pin
**Returns:** null
```
invert_pin(pin, polarity)
```
Invert the polarity of the selected pin
**Parameters:** pin - 1 to 16, polarity - 0 = same logic state of the input pin, 1 = inverted logic state of the input pin
**Returns:** null
```
mirror_interrupts(value)
```
Mirror Interrupts
**Parameters:** value - 1 = The INT pins are internally connected, 0 = The INT pins are not connected. INTA is associated with PortA and INTB is associated with PortB
**Returns:** null
```

set_interrupt_type(port, value)
```
Sets the type of interrupt for each pin on the selected port
**Parameters:** port 0 = pins 1 to 8, port 1 = pins 8 to 16, value: 1 = interrupt is fired when the pin matches the default value, 0 = the interrupt is fired on state change
**Returns:** null
```
set_interrupt_defaults(port, value)
```
These bits set the compare value for pins configured for interrupt-on-change on the selected port.
If the associated pin level is the opposite from the register bit, an interrupt occurs.
**Parameters:** port 0 = pins 1 to 8, port 1 = pins 8 to 16, value: compare value
**Returns:** null
```
set_interrupt_on_port(port, value)
```
Enable interrupts for the pins on the selected port
**Parameters:** port 0 = pins 1 to 8, port 1 = pins 8 to 16, value: number between 0 and 255 or 0x00 and 0xFF
**Returns:** null
```
set_interrupt_on_pin(pin, value)
```
Enable interrupts for the selected pin
**Parameters:** pin - 1 to 16, value - 0 = interrupt disabled, 1 = interrupt enabled
**Returns:** null
```
read_interrupt_status(port)
```
Enable interrupts for the selected pin
**Parameters:** port 0 = pins 1 to 8, port 1 = pins 8 to 16
**Returns:** status
```
read_interrupt_capture(port)
```
Read the value from the selected port at the time of the last interrupt trigger
**Parameters:** port 0 = pins 1 to 8, port 1 = pins 8 to 16
**Returns:** status
```
reset_interrupts()
```
Set the interrupts A and B to 0
**Parameters:** null
**Returns:** null
Usage

====
To use the IO Pi library in your code you must first import the library:
```
from ABE_ExpanderPi import IO
```
Now import the helper class
```
from ABE_helpers import ABEHelpers
```
Next you must initialise the IO object with the smbus object using the helpers:
```
i2c_helper = ABEHelpers()
bus = i2c_helper.get_smbus()
io = IO(bus)
```
We will read the inputs 1 to 8 from the I/O bus so set port 0 to be inputs and enable the internal pull-up resistors
```
io.set_port_direction(0, 0xFF)
io.set_port_pullups(0, 0xFF)
```
You can now read the pin 1 with:
```
print 'Pin 1: ' + str(io.read_pin(1))
```
# Class: RTC #
The RTC class controls the DS1307 real-time clock on the Expander Pi. You can set and read the date and time from the clock as well as controlling the pulse output on the RTC
pin.
Functions:
---------```
set_date(date)
```
Set the date and time on the RTC in ISO 8601 format - YYYY-MM-DDTHH:MM:SS
**Parameters:** date
**Returns:** null
```
read_date()
```
Returns the date from the RTC in ISO 8601 format - YYYY-MM-DDTHH:MM:SS
**Returns:** date
```
enable_output()

```
Enable the square-wave output on the SQW pin.
**Returns:** null
```
disable_output()
```
Disable the square-wave output on the SQW pin.
**Returns:** null
```
set_frequency()
```
Set the frequency for the square-wave output on the SQW pin.
**Parameters:** frequency - options are: 1 = 1Hz, 2 = 4.096KHz, 3 = 8.192KHz, 4 = 32.768KHz
**Returns:** null
Usage
====
To use the RTC class in your code you must first import the library:
```
from ABE_ExpanderPi import RTC
```
Now import the helper class
```
from ABE_helpers import ABEHelpers
```
Next you must initialise the RTC object with the smbus object using the helpers:
```
i2c_helper = ABEHelpers()
bus = i2c_helper.get_smbus()
rtc = RTC(bus)
```
Set the date using ISO 8601 format - YYYY-MM-DDTHH:MM:SS :
```
rtc.set_date("2013-04-23T12:32:11")
```
Enable the square-wave output at 8.192KHz on the SQW pin:
```
rtc.set_frequency(3)
rtc.enable_output()
```
Read the current date and time from the RTC at 1 second intervals:
```
while (True):

print rtc.read_date()
time.sleep(1)
```
#!/usr/bin/python3
from ABE_ExpanderPi import RTC
from ABE_helpers import ABEHelpers
import time
"""
================================================
ABElectronics Expander Pi | Set Time Demo
Version 1.0 Created 29/03/2015
run with: python3 demo-rtcset_date.py
===============================================
This demo shows how to set the time on the Expander Pi real-time clock
and then read the current time at 1 second intervals
"""
i2c_helper = ABEHelpers()
bus = i2c_helper.get_smbus()
rtc = RTC(bus) # create a new instance of the RTC class
# set the date using ISO 8601 format - YYYY-MM-DDTHH:MM:SS
rtc.set_date("2013-04-23T12:32:11")
while True:
# read the date from the RTC in ISO 8601 format and print it to the screen
print (rtc.read_date())
time.sleep(1) # wait 1 second
#!/usr/bin/python3
from ABE_ExpanderPi import RTC
from ABE_helpers import ABEHelpers
import time
"""
================================================
ABElectronics Expander Pi | RTC clock output demo
Version 1.0 Created 29/03/2015
run with: python3 demo-rtcout.py
================================================
This demo shows how to enable the clock square wave output on the
Expander Pi real-time clock and set the frequency
"""
i2c_helper = ABEHelpers()
bus = i2c_helper.get_smbus()
rtc = RTC(bus) # create a new instance of the RTC class

# set the frequency of the output square-wave, options are: 1 = 1Hz, 2 =


# 4.096KHz, 3 = 8.192KHz, 4 = 32.768KHz
rtc.set_frequency(3)
rtc.enable_output() # enable the square-wave
#!/usr/bin/python3
from ABE_ExpanderPi import IO
from ABE_helpers import ABEHelpers
import time
"""
================================================
ABElectronics Expander Pi | Digital I/O Interrupts Demo
Version 1.0 Created 29/03/2015
run with: python3 demo-iowrite.py
================================================
This example uses the write_pin and writeBank methods to switch the pins
on and off on the I/O bus.
Initialise the IO class and create an instance called io.
"""
i2c_helper = ABEHelpers()
bus = i2c_helper.get_smbus()
io = IO(bus)
# We will write to the pins 9 to 16 so set port 1 to be outputs turn off
# the pins
io.set_port_direction(1, 0x00)
io.write_port(1, 0x00)
while True:
# count to 255 and display the value on pins 9 to 16 in binary format
for x in range(0, 255):
time.sleep(0.05)
io.write_port(1, x)
# turn off all of the pins on bank 1
io.write_port(1, 0x00)
# now turn on all of the leds in turn by writing to one pin at a time
io.write_pin(9, 1)
time.sleep(0.1)
io.write_pin(10, 1)
time.sleep(0.1)
io.write_pin(11, 1)
time.sleep(0.1)
io.write_pin(12, 1)
time.sleep(0.1)
io.write_pin(13, 1)
time.sleep(0.1)

io.write_pin(14, 1)
time.sleep(0.1)
io.write_pin(15, 1)
time.sleep(0.1)
io.write_pin(16, 1)
# and turn off all of the leds in turn by writing to one pin at a time
io.write_pin(9, 0)
time.sleep(0.1)
io.write_pin(10, 0)
time.sleep(0.1)
io.write_pin(11, 0)
time.sleep(0.1)
io.write_pin(12, 0)
time.sleep(0.1)
io.write_pin(13, 0)
time.sleep(0.1)
io.write_pin(14, 0)
time.sleep(0.1)
io.write_pin(15, 0)
time.sleep(0.1)
io.write_pin(16, 0)
# repeat until the program ends
#!/usr/bin/python3
from ABE_ExpanderPi import IO
from ABE_helpers import ABEHelpers
import time
import os
"""
================================================
ABElectronics Expander Pi | Digital I/O Interrupts Demo
Version 1.0 Created 29/03/2015
run with: python3 demo-ioread.py
================================================
This example reads the first 8 pins of on the Expander Pi Digital I/O
port. The internal pull-up resistors are enabled so each pin will read
as 1 unless the pin is connected to ground.
Initialise the IO class and create an instance called io.
"""
i2c_helper = ABEHelpers()
bus = i2c_helper.get_smbus()
io = IO(bus)
# We will read the inputs 1 to 8 from the I/O bus so set port 0 to be
# inputs and enable the internal pull-up resistors
io.set_port_direction(0, 0xFF)
io.set_port_pullups(0, 0xFF)
while True:
# clear the console

os.system('clear')
# read the pins 1 to 8 and print the results
print ('Pin 1: ' + str(io.read_pin(1)))
print ('Pin 2: ' + str(io.read_pin(2)))
print ('Pin 3: ' + str(io.read_pin(3)))
print ('Pin 4: ' + str(io.read_pin(4)))
print ('Pin 5: ' + str(io.read_pin(5)))
print ('Pin 6: ' + str(io.read_pin(6)))
print ('Pin 7: ' + str(io.read_pin(7)))
print ('Pin 8: ' + str(io.read_pin(8)))
# wait 0.5 seconds before reading the pins again
time.sleep(0.1)
#!/usr/bin/python3
from ABE_ExpanderPi import IO
from ABE_helpers import ABEHelpers
import time
"""
# ================================================
# ABElectronics Expander Pi | - IO Interrupts Demo
# Version 1.0 Created 29/03/2015
#
# run with: python3 demo-iointerrupts.py
# ================================================
#
#
#
#

This example shows how to use the interrupt methods on the Expander Pi IO port.
The interrupts will be enabled and set so that a voltage applied to pins 1 and 16 will trigger INT A and B respectively.
using the read_interrupt_capture or read_port methods will reset the
interrupts.

# Initialise the IOPi and create an instance called io.


"""
i2c_helper = ABEHelpers()
bus = i2c_helper.get_smbus()
io = IO(bus)
# Set all pins on the IO bus to be inputs with internal pull-ups disabled.
io.set_port_pullups(0, 0x00)
io.set_port_pullups(1, 0x00)
io.set_port_direction(0, 0xFF)
io.set_port_direction(1, 0xFF)
# Set the interrupt polarity to be active high and mirroring disabled, so
# pins 1 to 8 trigger INT A and pins 9 to 16 trigger INT B
io.set_interrupt_polarity(1)
io.mirror_interrupts(0)
# Set the interrupts default value to trigger when 5V is applied to pins 1
# and 16
io.set_interrupt_defaults(0, 0x01)
io.set_interrupt_defaults(0, 0x80)

# Set the interrupt type to be 1 for ports A and B so an interrupt is


# fired when the pin matches the default value
io.set_interrupt_type(0, 1)
io.set_interrupt_type(1, 1)
# Enable interrupts for pins 1 and 16
io.set_interrupt_on_pin(1, 1)
io.set_interrupt_on_pin(16, 1)
while True:
# read the port value from the last capture for ports 0 and 1. This will
# reset the interrupts
print (io.read_interrupt_capture(0))
print (io.read_interrupt_capture(1))
time.sleep(2)
#!/usr/bin/python3
from ABE_ExpanderPi import DAC
import time
"""
================================================
ABElectronics Expander Pi | DAC Write Demo
Version 1.0 Created 29/03/2015
run with: python3 demo-dacwrite.py
================================================
this demo will generate a 1.5V p-p square wave at 1Hz on channel 1
"""
dac = DAC()
while True:
dac.set_dac_voltage(1,
time.sleep(0.5) # wait
dac.set_dac_voltage(1,
time.sleep(0.5) # wait

1.5) # set the voltage on channel 1 to 1.5V


0.5 seconds
0) # set the voltage on channel 1 to 0V
0.5 seconds

#!/usr/bin/python3
from ABE_ExpanderPi import DAC
import time
import math
"""
================================================
ABElectronics Expander Pi | DAC sine wave generator demo
Version 1.0 Created 29/03/2015
run with: python3 demo-dacsinewave.py
================================================
this demo uses the set_dac_raw method to generate a sine wave from a
predefined set of values

"""
dac = DAC()
DACLookup_FullSine_12Bit = \
[2048, 2073, 2098, 2123, 2148, 2174, 2199, 2224,
2249, 2274, 2299, 2324, 2349, 2373, 2398, 2423,
2448, 2472, 2497, 2521, 2546, 2570, 2594, 2618,
2643, 2667, 2690, 2714, 2738, 2762, 2785, 2808,
2832, 2855, 2878, 2901, 2924, 2946, 2969, 2991,
3013, 3036, 3057, 3079, 3101, 3122, 3144, 3165,
3186, 3207, 3227, 3248, 3268, 3288, 3308, 3328,
3347, 3367, 3386, 3405, 3423, 3442, 3460, 3478,
3496, 3514, 3531, 3548, 3565, 3582, 3599, 3615,
3631, 3647, 3663, 3678, 3693, 3708, 3722, 3737,
3751, 3765, 3778, 3792, 3805, 3817, 3830, 3842,
3854, 3866, 3877, 3888, 3899, 3910, 3920, 3930,
3940, 3950, 3959, 3968, 3976, 3985, 3993, 4000,
4008, 4015, 4022, 4028, 4035, 4041, 4046, 4052,
4057, 4061, 4066, 4070, 4074, 4077, 4081, 4084,
4086, 4088, 4090, 4092, 4094, 4095, 4095, 4095,
4095, 4095, 4095, 4095, 4094, 4092, 4090, 4088,
4086, 4084, 4081, 4077, 4074, 4070, 4066, 4061,
4057, 4052, 4046, 4041, 4035, 4028, 4022, 4015,
4008, 4000, 3993, 3985, 3976, 3968, 3959, 3950,
3940, 3930, 3920, 3910, 3899, 3888, 3877, 3866,
3854, 3842, 3830, 3817, 3805, 3792, 3778, 3765,
3751, 3737, 3722, 3708, 3693, 3678, 3663, 3647,
3631, 3615, 3599, 3582, 3565, 3548, 3531, 3514,
3496, 3478, 3460, 3442, 3423, 3405, 3386, 3367,
3347, 3328, 3308, 3288, 3268, 3248, 3227, 3207,
3186, 3165, 3144, 3122, 3101, 3079, 3057, 3036,
3013, 2991, 2969, 2946, 2924, 2901, 2878, 2855,
2832, 2808, 2785, 2762, 2738, 2714, 2690, 2667,
2643, 2618, 2594, 2570, 2546, 2521, 2497, 2472,
2448, 2423, 2398, 2373, 2349, 2324, 2299, 2274,
2249, 2224, 2199, 2174, 2148, 2123, 2098, 2073,
2048, 2023, 1998, 1973, 1948, 1922, 1897, 1872,
1847, 1822, 1797, 1772, 1747, 1723, 1698, 1673,
1648, 1624, 1599, 1575, 1550, 1526, 1502, 1478,
1453, 1429, 1406, 1382, 1358, 1334, 1311, 1288,
1264, 1241, 1218, 1195, 1172, 1150, 1127, 1105,
1083, 1060, 1039, 1017, 995, 974, 952, 931,
910, 889, 869, 848, 828, 808, 788, 768,
749, 729, 710, 691, 673, 654, 636, 618,
600, 582, 565, 548, 531, 514, 497, 481,
465, 449, 433, 418, 403, 388, 374, 359,
345, 331, 318, 304, 291, 279, 266, 254,
242, 230, 219, 208, 197, 186, 176, 166,
156, 146, 137, 128, 120, 111, 103, 96,
88, 81, 74, 68, 61, 55, 50, 44,
39, 35, 30, 26, 22, 19, 15, 12,
10, 8, 6, 4, 2, 1, 1, 0,
0, 0, 1, 1, 2, 4, 6, 8,
10, 12, 15, 19, 22, 26, 30, 35,
39, 44, 50, 55, 61, 68, 74, 81,
88, 96, 103, 111, 120, 128, 137, 146,

156, 166, 176, 186, 197,


242, 254, 266, 279, 291,
345, 359, 374, 388, 403,
465, 481, 497, 514, 531,
600, 618, 636, 654, 673,
749, 768, 788, 808, 828,
910, 931, 952, 974, 995,
1083, 1105, 1127, 1150,
1264, 1288, 1311, 1334,
1453, 1478, 1502, 1526,
1648, 1673, 1698, 1723,
1847, 1872, 1897, 1922,

208, 219, 230,


304, 318, 331,
418, 433, 449,
548, 565, 582,
691, 710, 729,
848, 869, 889,
1017, 1039, 1060,
1172, 1195, 1218,
1358, 1382, 1406,
1550, 1575, 1599,
1747, 1772, 1797,
1948, 1973, 1998,

1241,
1429,
1624,
1822,
2023]

while True:
for val in DACLookup_FullSine_12Bit:
dac.set_dac_raw(1, val)
#!/usr/bin/python3
from ABE_ExpanderPi import ADC
import time
"""
================================================
ABElectronics Expander Pi | ADC Read Demo
Version 1.0 Created 29/03/2015
run with: python3 demo-adcread.py
================================================
this demo reads the voltage from channel 1 on the ADC inputs
"""
adc = ADC() # create an instance of the ADC
# set the reference voltage. this should be set to the exact voltage
# measured on the Expander Pi Vref pin.
adc.set_adc_refvoltage(4.096)
while True:
# read the voltage from channel 1 and display on the screen
print (adc.read_adc_voltage(1))
time.sleep(0.5)
#!/usr/bin/python3
try:
import smbus
except ImportError:
raise ImportError("python-smbus not found Install with 'sudo apt-get install python3-smbus'")
import re
"""
================================================
ABElectronics Python Helper Functions
Version 1.0 Created 29/02/2015
Python 3 only
Requires python 3 smbus to be installed. For more information

about enabling i2c and installing smbus visit


https://www.abelectronics.co.uk/i2c-raspbian-wheezy/info.aspx
================================================
"""
class ABEHelpers:
def get_smbus(self):
# detect i2C port number and assign to i2c_bus
i2c_bus = 0
for line in open('/proc/cpuinfo').readlines():
m = re.match('(.*?)\s*:\s*(.*)', line)
if m:
(name, value) = (m.group(1), m.group(2))
if name == "Revision":
if value[-4:] in ('0002', '0003'):
i2c_bus = 0
else:
i2c_bus = 1
break
try:
return smbus.SMBus(i2c_bus)
except IOError:
print ("Could not open the i2c bus.")
print ("Please check that i2c is enabled and python-smbus and i2c-tools are installed.")
print ("Visit https://www.abelectronics.co.uk/i2c-raspbian-wheezy/info.aspx for more information.")
#!/usr/bin/python3
import
import
import
import
import
import

spidev
time
datetime
sys
math
struct

"""
================================================
ABElectronics IO Pi V2 32-Channel Port Expander
Version 1.0 Created 29/03/2015
Requires python smbus to be installed
================================================
"""

class ADC:
"""
Based on the Microchip MCP3208
"""
# variables

__adcrefvoltage = 4.096 # reference voltage for the ADC chip.


# Define SPI bus and init
__spiADC = spidev.SpiDev()
__spiADC.open(0, 0)
__spiADC.max_speed_hz = (50000)
# public methods
def read_adc_voltage(self, channel):
"""
Read the voltage from the selected channel on the ADC
Channel = 1 to 8
"""
if ((channel > 8) or (channel < 1)):
print ('ADC channel needs to be 1 to 8')
raw = self.readADCraw(channel)
voltage = (self.__adcrefvoltage / 4096) * raw
return voltage
def readADCraw(self, channel):
"""
Read the raw value from the selected channel on the ADC
Channel = 1 to 8
"""
if ((channel > 8) or (channel < 1)):
print ('ADC channel needs to be 1 to 8')
return 0.0
channel = channel - 1
r = self.__spiADC.xfer2([4 + (channel >> 2), (channel & 3) << 6, 0])
ret = ((r[1] & 0x0F) << 8) + (r[2])
return ret
def set_adc_refvoltage(self, voltage):
"""
set the reference voltage for the analogue to digital converter.
By default the ADC uses an onboard 4.096V voltage reference. If you
choose to use an external voltage reference you will need to
use this method to set the ADC reference voltage to match the
supplied reference voltage.
The reference voltage must be less than or equal to the voltage on
the Raspberry Pi 5V rail.
"""
if (voltage >= 0.0) and (voltage <= 5.5):
self.__adcrefvoltage = voltage
else:
print ('reference voltage out of range')
return
class DAC:
"""
Based on the Microchip MCP4822

Define SPI bus and init


"""
__spiDAC = spidev.SpiDev()
__spiDAC.open(0, 1)
__spiDAC.max_speed_hz = (4000000)
def set_dac_voltage(self, channel, voltage):
"""
set the voltage for the selected channel on the DAC
voltage can be between 0 and 2.047 volts
"""
if ((channel > 2) or (channel < 1)):
print ('DAC channel needs to be 1 or 2')
if (voltage >= 0.0) and (voltage < 2.048):
rawval = (voltage / 2.048) * 4096
self.set_dac_raw(channel, int(rawval))
return
def set_dac_raw(self, channel, value):
"""
Set the raw value from the selected channel on the DAC
Channel = 1 or 2
Value between 0 and 4095
"""
lowByte = value & 0xff
highByte = (
(value >> 8) & 0xff) | (
channel 1) << 7 | 0x1 << 5 | 1 << 4
self.__spiDAC.xfer2([highByte, lowByte])
return
class IO:
"""
The MCP23017 chip is split into two 8-bit ports. port 0 controls pins
1 to 8 while port 1 controls pins 9 to 16.
When writing to or reading from a port the least significant bit
represents the lowest numbered pin on the selected port.
#
"""
# Define registers values from datasheet
IODIRA = 0x00 # IO direction A - 1= input 0 = output
IODIRB = 0x01 # IO direction B - 1= input 0 = output
# Input polarity A - If a bit is set, the corresponding GPIO register bit
# will reflect the inverted value on the pin.
IPOLA = 0x02
# Input polarity B - If a bit is set, the corresponding GPIO register bit
# will reflect the inverted value on the pin.
IPOLB = 0x03
# The GPINTEN register controls the interrupt-onchange feature for each

# pin on port A.
GPINTENA = 0x04
# The GPINTEN register controls the interrupt-onchange feature for each
# pin on port B.
GPINTENB = 0x05
# Default value for port A - These bits set the compare value for pins
# configured for interrupt-on-change. If the associated pin level is the
# opposite from the register bit, an interrupt occurs.
DEFVALA = 0x06
# Default value for port B - These bits set the compare value for pins
# configured for interrupt-on-change. If the associated pin level is the
# opposite from the register bit, an interrupt occurs.
DEFVALB = 0x07
# Interrupt control register for port A. If 1 interrupt is fired when the
# pin matches the default value, if 0 the interrupt is fired on state
# change
INTCONA = 0x08
# Interrupt control register for port B. If 1 interrupt is fired when the
# pin matches the default value, if 0 the interrupt is fired on state
# change
INTCONB = 0x09
IOCON = 0x0A # see datasheet for configuration register
GPPUA = 0x0C # pull-up resistors for port A
GPPUB = 0x0D # pull-up resistors for port B
# The INTF register reflects the interrupt condition on the port A pins of
# any pin that is enabled for interrupts. A set bit indicates that the
# associated pin caused the interrupt.
INTFA = 0x0E
# The INTF register reflects the interrupt condition on the port B pins of
# any pin that is enabled for interrupts. A set bit indicates that the
# associated pin caused the interrupt.
INTFB = 0x0F
# The INTCAP register captures the GPIO port A value at the time the
# interrupt occurred.
INTCAPA = 0x10
# The INTCAP register captures the GPIO port B value at the time the
# interrupt occurred.
INTCAPB = 0x11
GPIOA = 0x12 # data port A
GPIOB = 0x13 # data port B
OLATA = 0x14 # output latches A
OLATB = 0x15 # output latches B
# variables
__ioaddress = 0x20 # I2C address
__portA_dir = 0x00 # port a direction
__portB_dir = 0x00 # port b direction
__portA_val = 0x00 # port a value
__portB_val = 0x00 # port b value
__portA_pullup = 0x00 # port a pull-up resistors
__portB_pullup = 0x00 # port a pull-up resistors
__portA_polarity = 0x00 # input polarity for port a
__portB_polarity = 0x00 # input polarity for port b
__intA = 0x00 # interrupt control for port a
__intB = 0x00 # interrupt control for port a
# initial configuration - see IOCON page in the MCP23017 datasheet for
# more information.

__ioconfig = 0x22
global _bus
def __init__(self, bus):
"""
init object with i2c address, default is 0x20, 0x21 for IOPi board,
load default configuration
"""
self._bus = bus
self._bus.write_byte_data(self.__ioaddress, self.IOCON, self.__ioconfig)
self.__portA_val = self._bus.read_byte_data(self.__ioaddress, self.GPIOA)
self.__portB_val = self._bus.read_byte_data(self.__ioaddress, self.GPIOB)
self._bus.write_byte_data(self.__ioaddress, self.IODIRA, 0xFF)
self._bus.write_byte_data(self.__ioaddress, self.IODIRB, 0xFF)
return
# local methods
def __updatebyte(self, byte, bit, value):
""" internal method for setting the value of a single bit
within a byte """
if value == 0:
return byte & ~(1 << bit)
elif value == 1:
return byte | (1 << bit)
def __checkbit(self, byte, bit):
""" internal method for reading the value of a single bit
within a byte """
if byte & (1 << bit):
return 1
else:
return 0
# public methods
def set_pin_direction(self, pin, direction):
"""
set IO direction for an individual pin
pins 1 to 16
direction 1 = input, 0 = output
"""
pin = pin - 1
if pin < 8:
self.__portA_dir = self.__updatebyte(self.__portA_dir, pin, direction)
self._bus.write_byte_data(self.address, self.IODIRA, self.__portA_dir)
else:
self.__portB_dir = self.__updatebyte(self.__portB_dir, pin - 8, direction)
self._bus.write_byte_data(self.address, self.IODIRB, self.__portB_dir)
return
def set_port_direction(self, port, direction):
"""
set direction for an IO port
port 0 = pins 1 to 8, port 1 = pins 9 to 16

1 = input, 0 = output
"""
if port == 1:
self._bus.write_byte_data(self.__ioaddress, self.IODIRB, direction)
self.__portB_dir = direction
else:
self._bus.write_byte_data(self.__ioaddress, self.IODIRA, direction)
self.__portA_dir = direction
return
def set_pin_pullup(self, pin, value):
"""
set the internal 100K pull-up resistors for an individual pin
pins 1 to 16
value 1 = enabled, 0 = disabled
"""
pin = pin - 1
if pin < 8:
self.__portA_pullup = self.__updatebyte(self.__portA_pullup, pin, value)
self._bus.write_byte_data(self.address, self.GPPUA, self.__portA_pullup)
else:
self.__portB_pullup = self.__updatebyte(self.__portB_pullup,pin - 8,value)
self._bus.write_byte_data(self.address, self.GPPUB, self.__portB_pullup)
return
def set_port_pullups(self, port, value):
"""
set the internal 100K pull-up resistors for the selected IO port
"""
if port == 1:
self.__portA_pullup = value
self._bus.write_byte_data(self.__ioaddress, self.GPPUB, value)
else:
self.__portB_pullup = value
self._bus.write_byte_data(self.__ioaddress, self.GPPUA, value)
return
def write_pin(self, pin, value):
"""
write to an individual pin 1 - 16
"""
pin = pin - 1
if pin < 8:
self.__portA_val = self.__updatebyte(self.__portA_val, pin, value)
self._bus.write_byte_data(
self.__ioaddress,
self.GPIOA,
self.__portA_val)
else:
self.__portB_val = self.__updatebyte(
self.__portB_val,
pin 8,
value)

self._bus.write_byte_data(
self.__ioaddress,
self.GPIOB,
self.__portB_val)
return
def write_port(self, port, value):
"""
write to all pins on the selected port
port 0 = pins 1 to 8, port 1 = pins 9 to 16
value = number between 0 and 255 or 0x00 and 0xFF
"""
if port == 1:
self._bus.write_byte_data(self.__ioaddress, self.GPIOB, value)
self.__portB_val = value
else:
self._bus.write_byte_data(self.__ioaddress, self.GPIOA, value)
self.__portA_val = value
return
def read_pin(self, pin):
"""
read the value of an individual pin 1 - 16
returns 0 = logic level low, 1 = logic level high
"""
pin = pin - 1
if pin < 8:
self.__portA_val =self._bus.read_byte_data(
self.__ioaddress,
self.GPIOA)
return self.__checkbit(self.__portA_val, pin)
else:
pin = pin - 8
self.__portB_val =self._bus.read_byte_data(
self.__ioaddress,
self.GPIOB)
return self.__checkbit(self.__portB_val, pin)
def read_port(self, port):
"""
read all pins on the selected port
port 0 = pins 1 to 8, port 1 = pins 9 to 16
returns number between 0 and 255 or 0x00 and 0xFF
"""
if port == 1:
self.__portB_val =self._bus.read_byte_data(
self.__ioaddress,
self.GPIOB)
return self.__portB_val
else:
self.__portA_val =self._bus.read_byte_data(
self.__ioaddress,
self.GPIOA)
return self.__portA_val

def invert_port(self, port, polarity):


"""
invert the polarity of the pins on a selected port
port 0 = pins 1 to 8, port 1 = pins 9 to 16
polarity 0 = same logic state of the input pin, 1 = inverted logic
state of the input pin
"""
if port == 1:
self._bus.write_byte_data(self.__ioaddress, self.IPOLB, polarity)
self.__portB_polarity = polarity
else:
self._bus.write_byte_data(self.__ioaddress, self.IPOLA, polarity)
self.__portA_polarity = polarity
return
def invert_pin(self, pin, polarity):
"""
invert the polarity of the selected pin
pins 1 to 16
polarity 0 = same logic state of the input pin, 1 = inverted logic
state of the input pin
"""
pin = pin - 1
if pin < 8:
self.__portA_polarity = self.__updatebyte(
self.__portA_val,
pin,
polarity)
self._bus.write_byte_data(
self.__ioaddress,
self.IPOLA,
self.__portA_polarity)
else:
self.__portB_polarity = self.__updatebyte(
self.__portB_val,
pin 8,
polarity)
self._bus.write_byte_data(
self.__ioaddress,
self.IPOLB,
self.__portB_polarity)
return
def mirror_interrupts(self, value):
"""
1 = The INT pins are internally connected, 0 = The INT pins are not
connected. __intA is associated with PortA and __intB is associated
with PortB
"""
if value == 0:
self.config = self.__updatebyte(self.__ioconfig, 6, 0)
self._bus.write_byte_data(self.__ioaddress, self.IOCON, self.__ioconfig)

if value == 1:
self.config = self.__updatebyte(self.__ioconfig, 6, 1)
self._bus.write_byte_data(self.__ioaddress, self.IOCON, self.__ioconfig)
return
def set_interrupt_polarity(self, value):
"""
This sets the polarity of the INT output pins - 1 = Active-high. 0 =
Active-low.
"""
if value == 0:
self.config = self.__updatebyte(self.__ioconfig, 1, 0)
self._bus.write_byte_data(self.__ioaddress, self.IOCON, self.__ioconfig)
if value == 1:
self.config = self.__updatebyte(self.__ioconfig, 1, 1)
self._bus.write_byte_data(self.__ioaddress, self.IOCON, self.__ioconfig)
return
return
def set_interrupt_type(self, port, value):
"""
Sets the type of interrupt for each pin on the selected port
1 = interrupt is fired when the pin matches the default value, 0 =
the interrupt is fired on state change
"""
if port == 0:
self._bus.write_byte_data(self.__ioaddress, self.INTCONA, value)
else:
self._bus.write_byte_data(self.__ioaddress, self.INTCONB, value)
return
def set_interrupt_defaults(self, port, value):
"""
These bits set the compare value for pins configured for
interrupt-on-change on the selected port.
If the associated pin level is the opposite from the register bit, an
interrupt occurs.
"""
if port == 0:
self._bus.write_byte_data(self.__ioaddress, self.DEFVALA, value)
else:
self._bus.write_byte_data(self.__ioaddress, self.DEFVALB, value)
return
def set_interrupt_on_port(self, port, value):
"""
Enable interrupts for the pins on the selected port
port 0 = pins 1 to 8, port 1 = pins 9 to 16
value = number between 0 and 255 or 0x00 and 0xFF
"""
if port == 0:
self._bus.write_byte_data(self.__ioaddress, self.GPINTENA, value)
self.__intA = value

else:
self._bus.write_byte_data(self.__ioaddress, self.GPINTENB, value)
self.__intB = value
return
def set_interrupt_on_pin(self, pin, value):
"""
Enable interrupts for the selected pin
Pin = 1 to 16
Value 0 = interrupt disabled, 1 = interrupt enabled
"""
pin = pin - 1
if pin < 8:
self.__intA = self.__updatebyte(self.__intA, pin, value)
self._bus.write_byte_data(self.__ioaddress, self.GPINTENA, self.__intA)
else:
self.__intB = self.__updatebyte(self.__intB, pin - 8, value)
self._bus.write_byte_data(self.__ioaddress, self.GPINTENB, self.__intB)
return
def read_interrupt_status(self, port):
"""
read the interrupt status for the pins on the selected port
port 0 = pins 1 to 8, port 1 = pins 9 to 16
"""
if port == 0:
return self._bus.read_byte_data(self.__ioaddress, self.INTFA)
else:
return self._bus.read_byte_data(self.__ioaddress, self.INTFB)
def read_interrupt_capture(self, port):
"""
read the value from the selected port at the time of the last
interrupt trigger
port 0 = pins 1 to 8, port 1 = pins 9 to 16
"""
if port == 0:
return self._bus.read_byte_data(self.__ioaddress, self.INTCAPA)
else:
return self._bus.read_byte_data(self.__ioaddress, self.INTCAPB)
def reset_interrupts(self):
"""
Reset the interrupts A and B to 0
"""
self.read_interrupt_capture(0)
self.read_interrupt_capture(1)
return
class RTC:
"""

Based on the Maxim DS1307


Define registers values from datasheet
"""
SECONDS = 0x00
MINUTES = 0x01
HOURS = 0x02
DAYOFWEEK = 0x03
DAY = 0x04
MONTH = 0x05
YEAR = 0x06
CONTROL = 0x07
# variables
__rtcaddress = 0x68 # I2C address
# initial configuration - square wave and output disabled, frequency set
# to 32.768KHz.
__rtcconfig = 0x03
# the DS1307 does not store the current century so that has to be added on
# manually.
__century = 2000
# local methods
def __init__(self, bus):
self._bus = bus
self._bus.write_byte_data(self.__rtcaddress, self.CONTROL, self.__rtcconfig)
return
def __bcd_to_dec(self,bcd):
"""
internal method for converting BCD formatted number to decimal
"""
dec = 0
for a in (bcd >> 4, bcd):
for b in (1, 2, 4 ,8):
if a & 1:
dec += b
a >>= 1
dec *= 10
return dec / 10
def __dec_to_bcd(self,dec):
"""
internal method for converting decimal formatted number to BCD
"""
bcd = 0
for a in (dec // 10, dec % 10):
for b in (8, 4, 2, 1):
if a >= b:
bcd += 1
a -= b
bcd <<= 1
return bcd >> 1
def __get_century(self, val):
if len(val) > 2:

y = val[0] + val[1]
self.__century = int(y) * 100
return
def __updatebyte(self, byte, bit, value):
"""
internal method for setting the value of a single bit within a byte
"""
if value == 0:
return byte & ~(1 << bit)
elif value == 1:
return byte | (1 << bit)
# public methods
def set_date(self, date):
"""
set the date and time on the RTC
date must be in ISO 8601 format - YYYY-MM-DDTHH:MM:SS
"""
d = datetime.datetime.strptime(date, "%Y-%m-%dT%H:%M:%S")
self.__get_century(date)
self._bus.write_byte_data(
self.__rtcaddress,
self.SECONDS,
self.__dec_to_bcd(
d.second))
self._bus.write_byte_data(
self.__rtcaddress,
self.MINUTES,
self.__dec_to_bcd(
d.minute))
self._bus.write_byte_data(
self.__rtcaddress,
self.HOURS,
self.__dec_to_bcd(
d.hour))
self._bus.write_byte_data(
self.__rtcaddress,
self.DAYOFWEEK,
self.__dec_to_bcd(
d.weekday()))
self._bus.write_byte_data(
self.__rtcaddress,
self.DAY,
self.__dec_to_bcd(
d.day))
self._bus.write_byte_data(
self.__rtcaddress,
self.MONTH,
self.__dec_to_bcd(
d.month))
self._bus.write_byte_data(
self.__rtcaddress,
self.YEAR,
self.__dec_to_bcd(

d.year self.__century))
return
def read_date(self):
"""
read the date and time from the RTC in ISO 8601 format YYYY-MM-DDTHH:MM:SS
"""
seconds, minutes, hours, dayofweek, day, month, year \
=self._bus.read_i2c_block_data(self.__rtcaddress, 0, 7)
date = (
"%02d-%02d-%02dT%02d:%02d:%02d " %
(self.__bcd_to_dec(year) +
self.__century,
self.__bcd_to_dec(month),
self.__bcd_to_dec(day),
self.__bcd_to_dec(hours),
self.__bcd_to_dec(minutes),
self.__bcd_to_dec(seconds)))
return date
def enable_output(self):
"""
Enable the output pin
"""
self.__config = self.__updatebyte(self.__rtcconfig, 7, 1)
self.__config = self.__updatebyte(self.__rtcconfig, 4, 1)
self._bus.write_byte_data(self.__rtcaddress, self.CONTROL, self.__rtcconfig)
return
def disable_output(self):
"""
Disable the output pin
"""
self.__config = self.__updatebyte(self.__rtcconfig, 7, 0)
self.__config = self.__updatebyte(self.__rtcconfig, 4, 0)
self._bus.write_byte_data(self.__rtcaddress, self.CONTROL, self.__rtcconfig)
return
def set_frequency(self, frequency):
"""
set the frequency of the output pin square-wave
options are: 1 = 1Hz, 2 = 4.096KHz, 3 = 8.192KHz, 4 = 32.768KHz
"""
if frequency == 1:
self.__config = self.__updatebyte(self.__rtcconfig,
self.__config = self.__updatebyte(self.__rtcconfig,
if frequency == 2:
self.__config = self.__updatebyte(self.__rtcconfig,
self.__config = self.__updatebyte(self.__rtcconfig,
if frequency == 3:
self.__config = self.__updatebyte(self.__rtcconfig,

0, 0)
1, 0)
0, 1)
1, 0)
0, 0)

self.__config = self.__updatebyte(self.__rtcconfig, 1, 1)
if frequency == 4:
self.__config = self.__updatebyte(self.__rtcconfig, 0, 1)
self.__config = self.__updatebyte(self.__rtcconfig, 1, 1)
self._bus.write_byte_data(self.__rtcaddress, self.CONTROL, self.__rtcconfig)
return
AB Electronics UK Delta-Sigma Pi Python 3 Library
=====
Python 3 Library to use with Delta-Sigma Pi Raspberry Pi expansion board from http://www.abelectronics.co.uk
Install
====
To download to your Raspberry Pi type in terminal:
```
git clone https://github.com/abelectronicsuk/ABElectronics_Python3_Libraries.git
```
The Delta-Sigma Pi library is located in the DeltaSigmaPi directory
The library requires i2c to be enabled and python-smbus to be installed.
Follow the tutorial at [https://www.abelectronics.co.uk/i2c-raspbian-wheezy/info.aspx](https://www.abelectronics.co.uk/i2c-raspbian-wheezy/info.aspx) to enable i2c and install
python-smbus for python 3.
Add the location where you downloaded the python libraries into PYTHONPATH e.g. download location is Desktop
```
export PYTHONPATH=${PYTHONPATH}:~/Desktop/ABElectronics_Python3_Libraries/DeltaSigmaPi/
```
The example python files in /ABElectronics_Python3_Libraries/DeltaSigmaPi/ will now run from the terminal.
Functions:
---------```
read_voltage(channel)
```
Read the voltage from the selected channel
**Parameters:** channel - 1 to 8
**Returns:** number as float between 0 and 5.0
```
read_raw(channel)
```
Read the raw int value from the selected channel
**Parameters:** channel - 1 to 8
**Returns:** number as int
```
set_pga(gain)
```
Set the gain of the PDA on the chip
**Parameters:** gain - 1, 2, 4, 8
**Returns:** null
```
set_bit_rate(rate)
```

Set the sample bit rate of the adc


**Parameters:** rate - 12, 14, 16, 18
**Returns:** null
12 = 12 bit (240SPS max)
14 = 14 bit (60SPS max)
16 = 16 bit (15SPS max)
18 = 18 bit (3.75SPS max)
```
set_conversion_mode(mode)
```
Set the conversion mode for the adc
**Parameters:** mode - 0 = One-shot conversion, 1 = Continuous conversion
**Returns:** null
Usage
====
To use the Delta-Sigma library in your code you must first import the library:
```
from ABE_DeltaSigmaPi import DeltaSigma
```
Now import the helper class
```
from ABE_helpers import ABEHelpers
```
Next you must initialise the adc object and smbus:
```
i2c_helper = ABEHelpers()
bus = i2c_helper.get_smbus()
adc = DeltaSigma(bus, 0x68, 0x69, 18)
```
The first argument is the smbus object folled by the two I2C addresses of the ADC chips. The values shown are the default addresses of the ADC board.
The forth argument is the sample bit rate you want to use on the adc chips. Sample rate can be 12, 14, 16 or 18
You can now read the voltage from channel 1 with:
```
adc.read_voltage(1)
```
#!/usr/bin/python3
from ABE_DeltaSigmaPi import DeltaSigma
from ABE_helpers import ABEHelpers
import time
import os
"""
================================================
ABElectronics Delta-Sigma Pi 8-Channel ADC demo
Version 1.0 Created 29/02/2015
Requires python 3 smbus to be installed
run with: python3 demo-read_voltage.py
================================================

Initialise the ADC device using the default addresses and sample rate, change this value if you have changed the address selection jumpers
Sample rate can be 12,14, 16 or 18
"""
i2c_helper = ABEHelpers()
bus = i2c_helper.get_smbus()
adc = DeltaSigma(bus, 0x68, 0x69, 18)
while (True):
# clear the console
os.system('clear')
# read from adc channels and print to screen
print ("Channel 1: %02f" % adc.read_voltage(1))
print ("Channel 2: %02f" % adc.read_voltage(2))
print ("Channel 3: %02f" % adc.read_voltage(3))
print ("Channel 4: %02f" % adc.read_voltage(4))
print ("Channel 5: %02f" % adc.read_voltage(5))
print ("Channel 6: %02f" % adc.read_voltage(6))
print ("Channel 7: %02f" % adc.read_voltage(7))
print ("Channel 8: %02f" % adc.read_voltage(8))
# wait 0.5 seconds before reading the pins again
time.sleep(0.5)
#!/usr/bin/python3
try:
import smbus
except ImportError:
raise ImportError("python-smbus not found Install with 'sudo apt-get install python3-smbus'")
import re
"""
================================================
ABElectronics Python Helper Functions
Version 1.0 Created 29/02/2015
Python 3 only
Requires python 3 smbus to be installed. For more information
about enabling i2c and installing smbus visit
https://www.abelectronics.co.uk/i2c-raspbian-wheezy/info.aspx
================================================
"""
class ABEHelpers:
def get_smbus(self):
# detect i2C port number and assign to i2c_bus
i2c_bus = 0
for line in open('/proc/cpuinfo').readlines():
m = re.match('(.*?)\s*:\s*(.*)', line)
if m:
(name, value) = (m.group(1), m.group(2))
if name == "Revision":

if value[-4:] in ('0002', '0003'):


i2c_bus = 0
else:
i2c_bus = 1
break

try:
return smbus.SMBus(i2c_bus)
except IOError:
print ("Could not open the i2c bus.")
print ("Please check that i2c is enabled and python-smbus and i2c-tools are installed.")
print ("Visit https://www.abelectronics.co.uk/i2c-raspbian-wheezy/info.aspx for more information.")
#!/usr/bin/python3
"""
================================================
ABElectronics Delta-Sigma Pi V2 8-Channel ADC
Version 1.0 Created 29/02/2015
Requires python 3 smbus to be installed
================================================
"""
class DeltaSigma:
# internal variables
__address = 0x68 # default address for adc 1 on adc pi and delta-sigma pi
__address2 = 0x69 # default address for adc 2 on adc pi and delta-sigma pi
__config1 = 0x9C # PGAx1, 18 bit, one-shot conversion, channel 1
__currentchannel1 = 1 # channel variable for adc 1
__config2 = 0x9C # PGAx1, 18 bit, one-shot conversion, channel 1
__currentchannel2 = 1 # channel variable for adc2
__bitrate = 18 # current bitrate
__conversionmode = 1 # Conversion Mode
__signbit = False # signed bit checker
__pga = float(0.5) # current pga setting
__lsb = float(0.0000078125) # default lsb value for 18 bit
# create byte array and fill with initial values to define size
__adcreading = bytearray()
__adcreading.append(0x00)
__adcreading.append(0x00)
__adcreading.append(0x00)
__adcreading.append(0x00)
global _bus
# local methods
def __updatebyte(self, byte, bit, value):
# internal method for setting the value of a single bit within a
# byte
if value == 0:
return byte & ~(1 << bit)
elif value == 1:
return byte | (1 << bit)

def __checkbit(self, byte, bit):


# internal method for reading the value of a single bit within a
# byte
if byte & (1 << bit):
return 1
else:
return 0
def __twos_comp(self, val, bits):
if((val & (1 << (bits - 1))) != 0):
val = val - (1 << bits)
return val
def __setchannel(self, channel):
# internal method for updating the config to the selected channel
if channel < 5:
if channel != self.__currentchannel1:
if channel == 1:
self.__config1 = self.__updatebyte(self.__config1, 5, 0)
self.__config1 = self.__updatebyte(self.__config1, 6, 0)
self.__currentchannel1 = 1
if channel == 2:
self.__config1 = self.__updatebyte(self.__config1, 5, 1)
self.__config1 = self.__updatebyte(self.__config1, 6, 0)
self.__currentchannel1 = 2
if channel == 3:
self.__config1 = self.__updatebyte(self.__config1, 5, 0)
self.__config1 = self.__updatebyte(self.__config1, 6, 1)
self.__currentchannel1 = 3
if channel == 4:
self.__config1 = self.__updatebyte(self.__config1, 5, 1)
self.__config1 = self.__updatebyte(self.__config1, 6, 1)
self.__currentchannel1 = 4
else:
if channel != self.__currentchannel2:
if channel == 5:
self.__config2 = self.__updatebyte(self.__config2, 5, 0)
self.__config2 = self.__updatebyte(self.__config2, 6, 0)
self.__currentchannel2 = 5
if channel == 6:
self.__config2 = self.__updatebyte(self.__config2, 5, 1)
self.__config2 = self.__updatebyte(self.__config2, 6, 0)
self.__currentchannel2 = 6
if channel == 7:
self.__config2 = self.__updatebyte(self.__config2, 5, 0)
self.__config2 = self.__updatebyte(self.__config2, 6, 1)
self.__currentchannel2 = 7
if channel == 8:
self.__config2 = self.__updatebyte(self.__config2, 5, 1)
self.__config2 = self.__updatebyte(self.__config2, 6, 1)
self.__currentchannel2 = 8
return
# init object with i2caddress, default is 0x68, 0x69 for ADCoPi board
def __init__(self, bus, address=0x68, address2=0x69, rate=18):
self._bus = bus

self.__address = address
self.__address2 = address2
self.set_bit_rate(rate)
def read_voltage(self, channel):
# returns the voltage from the selected adc channel - channels 1 to
#8
raw = self.read_raw(channel)
if (self.__signbit):
voltage = (raw * (self.__lsb / self.__pga)) - (2.048 / (self.__pga * 2))
else:
voltage = (raw * (self.__lsb / self.__pga))
return float(voltage)
def read_raw(self, channel):
# reads the raw value from the selected adc channel - channels 1 to 8
h=0
l=0
m=0
s=0
# get the config and i2c address for the selected channel
self.__setchannel(channel)
if (channel < 5):
config = self.__config1
address = self.__address
else:
config = self.__config2
address = self.__address2
# if the conversion mode is set to one-shot update the ready bit to 1
if (self.__conversionmode == 0):
config = self.__updatebyte(config, 7, 1)
self._bus.write_byte(address, config)
config = self.__updatebyte(config, 7, 0)
# keep reading the adc data until the conversion result is ready
while True:
__adcreading = self._bus.read_i2c_block_data(address, config, 4)
if self.__bitrate == 18:
h = __adcreading[0]
m = __adcreading[1]
l = __adcreading[2]
s = __adcreading[3]
else:
h = __adcreading[0]
m = __adcreading[1]
s = __adcreading[2]
if self.__checkbit(s, 7) == 0:
break
self.__signbit = False
t = 0.0
# extract the returned bytes and combine in the correct order
if self.__bitrate == 18:
t = ((h & 0b00000011) << 16) | (m << 8) | l

self.__signbit = bool(self.__checkbit(t, 17))


if self.__signbit:
t = self.__updatebyte(t, 17, 0)
if self.__bitrate == 16:
t = (h << 8) | m
self.__signbit = bool(self.__checkbit(t, 15))
if self.__signbit:
t = self.__updatebyte(t, 15, 0)
if self.__bitrate == 14:
t = ((h & 0b00111111) << 8) | m
self.__signbit = self.__checkbit(t, 13)
if self.__signbit:
t = self.__updatebyte(t, 13, 0)
if self.__bitrate == 12:
t = ((h & 0b00001111) << 8) | m
self.__signbit = self.__checkbit(t, 11)
if self.__signbit:
t = self.__updatebyte(t, 11, 0)
return t
def set_pga(self, gain):
"""
PGA gain selection
1 = 1x
2 = 2x
4 = 4x
8 = 8x
"""
if gain == 1:
self.__config1 = self.__updatebyte(self.__config1,
self.__config1 = self.__updatebyte(self.__config1,
self.__config2 = self.__updatebyte(self.__config2,
self.__config2 = self.__updatebyte(self.__config2,
self.__pga = 0.5
if gain == 2:
self.__config1 = self.__updatebyte(self.__config1,
self.__config1 = self.__updatebyte(self.__config1,
self.__config2 = self.__updatebyte(self.__config2,
self.__config2 = self.__updatebyte(self.__config2,
self.__pga = 1
if gain == 4:
self.__config1 = self.__updatebyte(self.__config1,
self.__config1 = self.__updatebyte(self.__config1,
self.__config2 = self.__updatebyte(self.__config2,
self.__config2 = self.__updatebyte(self.__config2,
self.__pga = 2
if gain == 8:
self.__config1 = self.__updatebyte(self.__config1,
self.__config1 = self.__updatebyte(self.__config1,
self.__config2 = self.__updatebyte(self.__config2,
self.__config2 = self.__updatebyte(self.__config2,
self.__pga = 4

0,
1,
0,
1,

0)
0)
0)
0)

0,
1,
0,
1,

1)
0)
1)
0)

0,
1,
0,
1,

0)
1)
0)
1)

0,
1,
0,
1,

1)
1)
1)
1)

self._bus.write_byte(self.__address, self.__config1)
self._bus.write_byte(self.__address2, self.__config2)
return
def set_bit_rate(self, rate):
"""
sample rate and resolution
12 = 12 bit (240SPS max)
14 = 14 bit (60SPS max)
16 = 16 bit (15SPS max)
18 = 18 bit (3.75SPS max)
"""
if rate == 12:
self.__config1 = self.__updatebyte(self.__config1,
self.__config1 = self.__updatebyte(self.__config1,
self.__config2 = self.__updatebyte(self.__config2,
self.__config2 = self.__updatebyte(self.__config2,
self.__bitrate = 12
self.__lsb = 0.0005
if rate == 14:
self.__config1 = self.__updatebyte(self.__config1,
self.__config1 = self.__updatebyte(self.__config1,
self.__config2 = self.__updatebyte(self.__config2,
self.__config2 = self.__updatebyte(self.__config2,
self.__bitrate = 14
self.__lsb = 0.000125
if rate == 16:
self.__config1 = self.__updatebyte(self.__config1,
self.__config1 = self.__updatebyte(self.__config1,
self.__config2 = self.__updatebyte(self.__config2,
self.__config2 = self.__updatebyte(self.__config2,
self.__bitrate = 16
self.__lsb = 0.00003125
if rate == 18:
self.__config1 = self.__updatebyte(self.__config1,
self.__config1 = self.__updatebyte(self.__config1,
self.__config2 = self.__updatebyte(self.__config2,
self.__config2 = self.__updatebyte(self.__config2,
self.__bitrate = 18
self.__lsb = 0.0000078125

2,
3,
2,
3,

0)
0)
0)
0)

2,
3,
2,
3,

1)
0)
1)
0)

2,
3,
2,
3,

0)
1)
0)
1)

2,
3,
2,
3,

1)
1)
1)
1)

self._bus.write_byte(self.__address, self.__config1)
self._bus.write_byte(self.__address2, self.__config2)
return
def set_conversion_mode(self, mode):
"""
conversion mode for adc
0 = One shot conversion mode
1 = Continuous conversion mode
"""
if (mode == 0):
self.__config1 = self.__updatebyte(self.__config1, 4, 0)
self.__config2 = self.__updatebyte(self.__config2, 4, 0)
self.__conversionmode = 0

if (mode == 1):
self.__config1 = self.__updatebyte(self.__config1, 4, 1)
self.__config2 = self.__updatebyte(self.__config2, 4, 1)
self.__conversionmode = 1
#self._bus.write_byte(self.__address, self.__config1)
#self._bus.write_byte(self.__address2, self.__config2)
Return
AB Electronics UK ADC Pi Python 3 Library
=====
Python 3 Library to use with ADC Pi Raspberry Pi expansion board from http://www.abelectronics.co.uk
Install
====
To download to your Raspberry Pi type in terminal:
```
git clone https://github.com/abelectronicsuk/ABElectronics_Python3_Libraries.git
```
The ADC Pi library is located in the ADCPi directory
The library requires i2c to be enabled and python-smbus to be installed.
Follow the tutorial at [https://www.abelectronics.co.uk/i2c-raspbian-wheezy/info.aspx](https://www.abelectronics.co.uk/i2c-raspbian-wheezy/info.aspx) to enable i2c and install
python-smbus for python 3.
Add the location where you downloaded the python libraries into PYTHONPATH e.g. download location is Desktop
```
export PYTHONPATH=${PYTHONPATH}:~/Desktop/ABElectronics_Python3_Libraries/ADCPi/
```
The example python files in /ABElectronics_Python3_Libraries/ADCPi/ will now run from the terminal.
Functions:
---------```
read_voltage(channel)
```
Read the voltage from the selected channel
**Parameters:** channel - 1 to 8
**Returns:** number as float between 0 and 5.0
```
read_raw(channel)
```
Read the raw int value from the selected channel
**Parameters:** channel - 1 to 8
**Returns:** number as int
```
set_pga(gain)
```
Set the gain of the PDA on the chip
**Parameters:** gain - 1, 2, 4, 8
**Returns:** null
```

set_bit_rate(rate)
```
Set the sample bit rate of the adc
**Parameters:** rate - 12, 14, 16, 18
**Returns:** null
12 = 12 bit (240SPS max)
14 = 14 bit (60SPS max)
16 = 16 bit (15SPS max)
18 = 18 bit (3.75SPS max)
```
set_conversion_mode(mode)
```
Set the conversion mode for the adc
**Parameters:** mode - 0 = One-shot conversion, 1 = Continuous conversion
**Returns:** null
Usage
====
To use the ADC Pi library in your code you must first import the library:
```
from ABE_ADCPi import ADCPi
```
Now import the helper class
```
from ABE_helpers import ABEHelpers
```
Next you must initialise the adc object and smbus:
```
i2c_helper = ABEHelpers()
bus = i2c_helper.get_smbus()
adc = ADCPi(bus, 0x68, 0x69, 18)
```
The first argument is the smbus object folled by the two I2C addresses of the ADC chips. The values shown are the default addresses of the ADC board.
The forth argument is the sample bit rate you want to use on the adc chips. Sample rate can be 12, 14, 16 or 18
You can now read the voltage from channel 1 with:
```
adc.read_voltage(1)
```
#!/usr/bin/python3
from ABE_ADCPi import ADCPi
from ABE_helpers import ABEHelpers
import time
import os
"""
================================================
ABElectronics ADC Pi 8-Channel ADC demo
Version 1.0 Created 29/02/2015
Requires python 3 smbus to be installed
run with: python3 demo-read_voltage.py
================================================

Initialise the ADC device using the default addresses and sample rate,
change this value if you have changed the address selection jumpers
Sample rate can be 12,14, 16 or 18
"""
i2c_helper = ABEHelpers()
bus = i2c_helper.get_smbus()
adc = ADCPi(bus, 0x68, 0x69, 12)
while (True):
# clear the console
os.system('clear')
# read from adc channels and print to screen
print ("Channel 1: %02f" % adc.read_voltage(1))
print ("Channel 2: %02f" % adc.read_voltage(2))
print ("Channel 3: %02f" % adc.read_voltage(3))
print ("Channel 4: %02f" % adc.read_voltage(4))
print ("Channel 5: %02f" % adc.read_voltage(5))
print ("Channel 6: %02f" % adc.read_voltage(6))
print ("Channel 7: %02f" % adc.read_voltage(7))
print ("Channel 8: %02f" % adc.read_voltage(8))
# wait 0.5 seconds before reading the pins again
time.sleep(0.5)
#!/usr/bin/python3
from ABE_ADCPi import ADCPi
from ABE_helpers import ABEHelpers
import datetime
import time
"""
================================================
ABElectronics ADC Pi 8-Channel ADC data-logger demo
Version 1.0 Created 29/02/2015
Requires python 3 smbus to be installed
run with: python3 demo-read_voltage.py
================================================
Initialise the ADC device using the default addresses and sample rate, change
this value if you have changed the address selection jumpers
Sample rate can be 12,14, 16 or 18
"""
i2c_helper = ABEHelpers()
bus = i2c_helper.get_smbus()
adc = ADCPi(bus, 0x68, 0x69, 18)

def writetofile(texttowrtite):
f = open('adclog.txt', 'a')
f.write(str(datetime.datetime.now()) + " " + texttowrtite)
f.closed
while (True):
# read from adc channels and write to the log file
writetofile("Channel 1: %02f\n" % adc.read_voltage(1))
writetofile("Channel 2: %02f\n" % adc.read_voltage(2))
writetofile("Channel 3: %02f\n" % adc.read_voltage(3))
writetofile("Channel 4: %02f\n" % adc.read_voltage(4))
writetofile("Channel 5: %02f\n" % adc.read_voltage(5))
writetofile("Channel 6: %02f\n" % adc.read_voltage(6))
writetofile("Channel 7: %02f\n" % adc.read_voltage(7))
writetofile("Channel 8: %02f\n" % adc.read_voltage(8))
# wait 1 second before reading the pins again
time.sleep(1)
#!/usr/bin/python3
from ABE_ADCPi import ADCPi
from ABE_helpers import ABEHelpers
import time
import os
"""
================================================
ABElectronics ADC Pi ACS712 30 Amp current sensor demo
Version 1.0 Created 29/02/2015
Requires python 3 smbus to be installed
run with: python3 demo-acs712-30.py
================================================
Initialise the ADC device using the default addresses and sample rate,
change this value if you have changed the address selection jumpers
Sample rate can be 12,14, 16 or 18
"""
i2c_helper = ABEHelpers()
bus = i2c_helper.get_smbus()
adc = ADCPi(bus, 0x68, 0x69, 12)
# change the 2.5 value to be half of the supply voltage.
def calcCurrent(inval):
return ((inval) - 2.5) / 0.066
while (True):
# clear the console
os.system('clear')

# read from adc channels and print to screen


print ("Current on channel 1: %02f" % calcCurrent(adc.read_voltage(1)))
# wait 0.5 seconds before reading the pins again
time.sleep(0.5)
#!/usr/bin/python3
try:
import smbus
except ImportError:
raise ImportError("python-smbus not found Install with 'sudo apt-get install python3-smbus'")
import re
"""
================================================
ABElectronics Python Helper Functions
Version 1.0 Created 29/02/2015
Python 3 only
Requires python 3 smbus to be installed. For more information
about enabling i2c and installing smbus visit
https://www.abelectronics.co.uk/i2c-raspbian-wheezy/info.aspx
================================================
"""
class ABEHelpers:
def get_smbus(self):
# detect i2C port number and assign to i2c_bus
i2c_bus = 0
for line in open('/proc/cpuinfo').readlines():
m = re.match('(.*?)\s*:\s*(.*)', line)
if m:
(name, value) = (m.group(1), m.group(2))
if name == "Revision":
if value[-4:] in ('0002', '0003'):
i2c_bus = 0
else:
i2c_bus = 1
break
try:
return smbus.SMBus(i2c_bus)
except IOError:
print ("Could not open the i2c bus.")
print ("Please check that i2c is enabled and python-smbus and i2c-tools are installed.")
print ("Visit https://www.abelectronics.co.uk/i2c-raspbian-wheezy/info.aspx for more information.")
#!/usr/bin/python3
"""
================================================
ABElectronics ADC Pi V2 8-Channel ADC
Version 1.0 Created 29/02/2015
Requires python 3 smbus to be installed
================================================
"""

class ADCPi:
# internal variables
__address = 0x68 # default address for adc 1 on adc pi and delta-sigma pi
__address2 = 0x69 # default address for adc 2 on adc pi and delta-sigma pi
__config1 = 0x9C # PGAx1, 18 bit, continuous conversion, channel 1
__currentchannel1 = 1 # channel variable for adc 1
__config2 = 0x9C # PGAx1, 18 bit, continuous-shot conversion, channel 1
__currentchannel2 = 1 # channel variable for adc2
__bitrate = 18 # current bitrate
__conversionmode = 1 # Conversion Mode
__pga = float(0.5) # current pga setting
__lsb = float(0.0000078125) # default lsb value for 18 bit
# create byte array and fill with initial values to define size
__adcreading = bytearray()
__adcreading.append(0x00)
__adcreading.append(0x00)
__adcreading.append(0x00)
__adcreading.append(0x00)
global _bus
# local methods
def __updatebyte(self, byte, bit, value):
# internal method for setting the value of a single bit within a
# byte
if value == 0:
return byte & ~(1 << bit)
elif value == 1:
return byte | (1 << bit)
def __checkbit(self, byte, bit):
# internal method for reading the value of a single bit within a
# byte
bitval = ((byte & (1 << bit)) != 0)
if (bitval == 1):
return True
else:
return False
def __twos_comp(self, val, bits):
if((val & (1 << (bits - 1))) != 0):
val = val - (1 << bits)
return val
def __setchannel(self, channel):
# internal method for updating the config to the selected channel
if channel < 5:
if channel != self.__currentchannel1:
if channel == 1:
self.__config1 = self.__updatebyte(self.__config1, 5, 0)
self.__config1 = self.__updatebyte(self.__config1, 6, 0)
self.__currentchannel1 = 1
if channel == 2:

self.__config1 = self.__updatebyte(self.__config1,
self.__config1 = self.__updatebyte(self.__config1,
self.__currentchannel1 = 2
if channel == 3:
self.__config1 = self.__updatebyte(self.__config1,
self.__config1 = self.__updatebyte(self.__config1,
self.__currentchannel1 = 3
if channel == 4:
self.__config1 = self.__updatebyte(self.__config1,
self.__config1 = self.__updatebyte(self.__config1,
self.__currentchannel1 = 4

else:
if channel != self.__currentchannel2:
if channel == 5:
self.__config2 = self.__updatebyte(self.__config2,
self.__config2 = self.__updatebyte(self.__config2,
self.__currentchannel2 = 5
if channel == 6:
self.__config2 = self.__updatebyte(self.__config2,
self.__config2 = self.__updatebyte(self.__config2,
self.__currentchannel2 = 6
if channel == 7:
self.__config2 = self.__updatebyte(self.__config2,
self.__config2 = self.__updatebyte(self.__config2,
self.__currentchannel2 = 7
if channel == 8:
self.__config2 = self.__updatebyte(self.__config2,
self.__config2 = self.__updatebyte(self.__config2,
self.__currentchannel2 = 8
return

5, 1)
6, 0)
5, 0)
6, 1)
5, 1)
6, 1)

5, 0)
6, 0)
5, 1)
6, 0)
5, 0)
6, 1)
5, 1)
6, 1)

# init object with i2caddress, default is 0x68, 0x69 for ADCoPi board
def __init__(self, bus, address=0x68, address2=0x69, rate=18):
self._bus = bus
self.__address = address
self.__address2 = address2
self.set_bit_rate(rate)
def read_voltage(self, channel):
# returns the voltage from the selected adc channel - channels 1 to
#8
raw = self.read_raw(channel)
if (self.__signbit):
return float(0.0) # returned a negative voltage so return 0
else:
voltage = float(
(raw * (self.__lsb / self.__pga)) * 2.471)
return float(voltage)
def read_raw(self, channel):
# reads the raw value from the selected adc channel - channels 1 to 8
h=0
l=0
m=0
s=0
# get the config and i2c address for the selected channel

self.__setchannel(channel)
if (channel < 5):
config = self.__config1
address = self.__address
else:
config = self.__config2
address = self.__address2
# if the conversion mode is set to one-shot update the ready bit to 1
if (self.__conversionmode == 0):
config = self.__updatebyte(config, 7, 1)
self._bus.write_byte(address, config)
config = self.__updatebyte(config, 7, 0)
# keep reading the adc data until the conversion result is ready
while True:
__adcreading = self._bus.read_i2c_block_data(address, config, 4)
if self.__bitrate == 18:
h = __adcreading[0]
m = __adcreading[1]
l = __adcreading[2]
s = __adcreading[3]
else:
h = __adcreading[0]
m = __adcreading[1]
s = __adcreading[2]
if self.__checkbit(s, 7) == 0:
break
self.__signbit = False
t = 0.0
# extract the returned bytes and combine in the correct order
if self.__bitrate == 18:
t = ((h & 0b00000011) << 16) | (m << 8) | l
self.__signbit = bool(self.__checkbit(t, 17))
if self.__signbit:
t = self.__updatebyte(t, 17, 0)
if self.__bitrate == 16:
t = (h << 8) | m
self.__signbit = bool(self.__checkbit(t, 15))
if self.__signbit:
t = self.__updatebyte(t, 15, 0)
if self.__bitrate == 14:
t = ((h & 0b00111111) << 8) | m
self.__signbit = self.__checkbit(t, 13)
if self.__signbit:
t = self.__updatebyte(t, 13, 0)
if self.__bitrate == 12:
t = ((h & 0b00001111) << 8) | m
self.__signbit = self.__checkbit(t, 11)
if self.__signbit:
t = self.__updatebyte(t, 11, 0)
return t

def set_pga(self, gain):


"""
PGA gain selection
1 = 1x
2 = 2x
4 = 4x
8 = 8x
"""
if gain == 1:
self.__config1 = self.__updatebyte(self.__config1,
self.__config1 = self.__updatebyte(self.__config1,
self.__config2 = self.__updatebyte(self.__config2,
self.__config2 = self.__updatebyte(self.__config2,
self.__pga = 0.5
if gain == 2:
self.__config1 = self.__updatebyte(self.__config1,
self.__config1 = self.__updatebyte(self.__config1,
self.__config2 = self.__updatebyte(self.__config2,
self.__config2 = self.__updatebyte(self.__config2,
self.__pga = 1
if gain == 4:
self.__config1 = self.__updatebyte(self.__config1,
self.__config1 = self.__updatebyte(self.__config1,
self.__config2 = self.__updatebyte(self.__config2,
self.__config2 = self.__updatebyte(self.__config2,
self.__pga = 2
if gain == 8:
self.__config1 = self.__updatebyte(self.__config1,
self.__config1 = self.__updatebyte(self.__config1,
self.__config2 = self.__updatebyte(self.__config2,
self.__config2 = self.__updatebyte(self.__config2,
self.__pga = 4

0,
1,
0,
1,

0)
0)
0)
0)

0,
1,
0,
1,

1)
0)
1)
0)

0,
1,
0,
1,

0)
1)
0)
1)

0,
1,
0,
1,

1)
1)
1)
1)

2,
3,
2,
3,

0)
0)
0)
0)

self._bus.write_byte(self.__address, self.__config1)
self._bus.write_byte(self.__address2, self.__config2)
return
def set_bit_rate(self, rate):
"""
sample rate and resolution
12 = 12 bit (240SPS max)
14 = 14 bit (60SPS max)
16 = 16 bit (15SPS max)
18 = 18 bit (3.75SPS max)
"""
if rate == 12:
self.__config1 = self.__updatebyte(self.__config1,
self.__config1 = self.__updatebyte(self.__config1,
self.__config2 = self.__updatebyte(self.__config2,
self.__config2 = self.__updatebyte(self.__config2,
self.__bitrate = 12
self.__lsb = 0.0005
if rate == 14:
self.__config1 = self.__updatebyte(self.__config1,

2, 1)

self.__config1 = self.__updatebyte(self.__config1,
self.__config2 = self.__updatebyte(self.__config2,
self.__config2 = self.__updatebyte(self.__config2,
self.__bitrate = 14
self.__lsb = 0.000125
if rate == 16:
self.__config1 = self.__updatebyte(self.__config1,
self.__config1 = self.__updatebyte(self.__config1,
self.__config2 = self.__updatebyte(self.__config2,
self.__config2 = self.__updatebyte(self.__config2,
self.__bitrate = 16
self.__lsb = 0.00003125
if rate == 18:
self.__config1 = self.__updatebyte(self.__config1,
self.__config1 = self.__updatebyte(self.__config1,
self.__config2 = self.__updatebyte(self.__config2,
self.__config2 = self.__updatebyte(self.__config2,
self.__bitrate = 18
self.__lsb = 0.0000078125

3, 0)
2, 1)
3, 0)

2,
3,
2,
3,

0)
1)
0)
1)

2,
3,
2,
3,

1)
1)
1)
1)

self._bus.write_byte(self.__address, self.__config1)
self._bus.write_byte(self.__address2, self.__config2)
return
def set_conversion_mode(self, mode):
"""
conversion mode for adc
0 = One shot conversion mode
1 = Continuous conversion mode
"""
if (mode == 0):
self.__config1 = self.__updatebyte(self.__config1, 4,
self.__config2 = self.__updatebyte(self.__config2, 4,
self.__conversionmode = 0
if (mode == 1):
self.__config1 = self.__updatebyte(self.__config1, 4,
self.__config2 = self.__updatebyte(self.__config2, 4,
self.__conversionmode = 1
#self._bus.write_byte(self.__address, self.__config1)
#self._bus.write_byte(self.__address2, self.__config2)
Return

0)
0)
1)
1)

AB Electronics UK ADC Differential Pi Python 3 Library


=====
Python 3 Library to use with ADC Differential Pi Raspberry Pi expansion board from http://www.abelectronics.co.uk
Install
====
To download to your Raspberry Pi type in terminal:
```
git clone https://github.com/abelectronicsuk/ABElectronics_Python3_Libraries.git
```
The ADC Differential Pi library is located in the ADCDifferentialPi directory
The library requires i2c to be enabled and python-smbus to be installed.

Follow the tutorial at [https://www.abelectronics.co.uk/i2c-raspbian-wheezy/info.aspx](https://www.abelectronics.co.uk/i2c-raspbian-wheezy/info.aspx) to enable i2c and install


python-smbus for python 3.
Add the location where you downloaded the python libraries into PYTHONPATH e.g. download location is Desktop
```
export PYTHONPATH=${PYTHONPATH}:~/Desktop/ABElectronics_Python3_Libraries/ADCDifferentialPi/
```
The example python files in /ABElectronics_Python3_Libraries/ADCDifferentialPi/ will now run from the terminal.
Functions:
---------```
read_voltage(channel)
```
Read the voltage from the selected channel
**Parameters:** channel - 1 to 8
**Returns:** number as float between -2.048 and +2.048
```
read_raw(channel)
```
Read the raw int value from the selected channel
**Parameters:** channel - 1 to 8
**Returns:** number as int
```
set_pga(gain)
```
Set the gain of the PDA on the chip
**Parameters:** gain - 1, 2, 4, 8
**Returns:** null
```
set_bit_rate(rate)
```
Set the sample bit rate of the adc
**Parameters:** rate - 12, 14, 16, 18
**Returns:** null
12 = 12 bit (240SPS max)
14 = 14 bit (60SPS max)
16 = 16 bit (15SPS max)
18 = 18 bit (3.75SPS max)
```
set_conversion_mode(mode)
```
Set the conversion mode for the adc
**Parameters:** mode - 0 = One-shot conversion, 1 = Continuous conversion
**Returns:** null
Usage
====
To use the Delta-Sigma library in your code you must first import the library:
```
from ABE_DeltaSigmaPi import DeltaSigma

```
Now import the helper class
```
from ABE_helpers import ABEHelpers
```
Next you must initialise the adc object and smbus:
```
i2c_helper = ABEHelpers()
bus = i2c_helper.get_smbus()
adc = DeltaSigma(bus, 0x68, 0x69, 18)
```
The first argument is the smbus object folled by the two I2C addresses of the ADC chips. The values shown are the default addresses of the ADC board.
The forth argument is the sample bit rate you want to use on the adc chips. Sample rate can be 12, 14, 16 or 18
You can now read the voltage from channel 1 with:
```
adc.read_voltage(1)
```
#!/usr/bin/python3
from ABE_ADCDifferentialPi import ADCDifferentialPi
from ABE_helpers import ABEHelpers
import time
import os
import math
"""
================================================
ABElectronics ADC Differential Pi 8-Channel ADC read Resistance thermometer
using a Wheatstone bridge.
This demo uses a Semitec NTC (Negative Temperature Coefficient) Thermistors
10kohm 1%, Manufacturer Part No: 103AT-11
Purchased from Mouser Electronics, Part No: 954-103AT-11
The circuit is connected to the + and - inputs on channel 7 on the
ADC Differential Pi. This can also be used on the Delta Sigma Pi
The Wheatstone bridge is comprised of three 10K resistors and the
Resistance thermometer
Version 1.0 Created 30/10/2015
Requires python3 smbus to be installed
run with: python3 demo-resistance-thermometer.py
================================================

Initialise the ADC device using the default addresses and 18 bit sample rate,
change this value if you have changed the address selection jumpers
Bit rate can be 12,14, 16 or 18
"""

i2c_helper = ABEHelpers()
bus = i2c_helper.get_smbus()
adc = ADCDifferentialPi(bus, 0x68, 0x69, 18)
# the resistor values for the Wheatstone bridge are:
resistor1 = 10000
resistor2 = 10000
resistor3 = 10000
# Input voltage
voltin = 3.3
# Resistance thermometer values from datasheet
bResistance = 3435
t25Resistance = 10000
# Constants
t0 = 273.15;
t25 = t0 + 25;
def calcResistance(voltage):
return (resistor2*resistor3 + resistor3* (resistor1+resistor2)*voltage / voltin )/ (resistor1- (resistor1+resistor2)*voltage / voltin)
def calcTemp(resistance):
return 1 / ( (math.log(resistance / t25Resistance) / bResistance) + (1 / t25) ) - t0;
# loop forever reading the values and printing them to screen
while (True):
# read from adc channels and print to screen
bridgeVoltage = adc.read_voltage(1)
thermresistance = calcResistance(bridgeVoltage)
temperature = calcTemp(thermresistance)
# clear the console
os.system('clear')
# print values to screen
print ("Bridge Voltage: %02f volts" % bridgeVoltage)
print ("Resistance: %d ohms" % thermresistance)
print ("Temperature: %.2fC" % temperature)

# wait 0.5 seconds before reading the pins again


time.sleep(0.5)
#!/usr/bin/python3
from ABE_ADCDifferentialPi import ADCDifferentialPi
from ABE_helpers import ABEHelpers
import time
import os
"""
================================================
ABElectronics ADC Differential Pi 8-Channel ADC demo

Version 1.0 Created 30/09/2015


Requires python 3 smbus to be installed
run with: python3 demo-read_voltage.py
================================================
Initialise the ADC device using the default addresses and sample rate, change this value if you have changed the address selection jumpers
Sample rate can be 12,14, 16 or 18
"""
i2c_helper = ABEHelpers()
bus = i2c_helper.get_smbus()
adc = ADCDifferentialPi(bus, 0x68, 0x69, 18)
while (True):
# clear the console
os.system('clear')
# read from adc channels and print to screen
print ("Channel 1: %02f" % adc.read_voltage(1))
print ("Channel 2: %02f" % adc.read_voltage(2))
print ("Channel 3: %02f" % adc.read_voltage(3))
print ("Channel 4: %02f" % adc.read_voltage(4))
print ("Channel 5: %02f" % adc.read_voltage(5))
print ("Channel 6: %02f" % adc.read_voltage(6))
print ("Channel 7: %02f" % adc.read_voltage(7))
print ("Channel 8: %02f" % adc.read_voltage(8))
# wait 0.5 seconds before reading the pins again
time.sleep(0.5)
#!/usr/bin/python3
from ABE_ADCDifferentialPi import ADCDifferentialPi
from ABE_helpers import ABEHelpers
import time
import os
"""
================================================
ABElectronics ADC Differential Pi 8-Channel ADC read ADXL335
SparkFun Triple Axis Accelerometer Breakout
The ADXL335 outputs are connected to inputs 1, 2 and 3 on
the ADC Differential Pi and the negative inputs are connected to GND
The inputs must run via a voltage divider to avoid damaging the ADC chips
inputs. The demo uses a 18K and 27K divider with the 18K connected to the
sensor board and the lower side of the 27K connected to GND
Version 1.0 Created 04/10/2015
Requires python 3 smbus to be installed
run with: python3 demo-adxl335.py
================================================

Initialise the ADC device using the default addresses and 12 bit sample rate,
change this value if you have changed the address selection jumpers
Bit rate can be 12,14, 16 or 18
"""
i2c_helper = ABEHelpers()
bus = i2c_helper.get_smbus()
adc = ADCDifferentialPi(bus, 0x68, 0x69, 12)
# the conversion factor is the ratio of the voltage divider on the inputs
conversionfactor = 1.666
# setup these static values when the sensor is not moving
xStatic = 0
yStatic = 0
zStatic = 0
xMax = 0
xMin = 0
yMax = 0
yMin = 0
zMax = 0
zMin = 0
# get 50 samples from each adc channel and use that to get an average value for 0G.
# Keep the accelerometer still while this part of the code is running
for x in range(0, 50):
xStatic = xStatic + adc.read_voltage(1)
yStatic = yStatic + adc.read_voltage(2)
zStatic = zStatic + adc.read_voltage(3)
xStatic = (xStatic / 50) * conversionfactor
yStatic = (yStatic / 50) * conversionfactor
zStatic = (zStatic / 50) * conversionfactor
# loop forever reading the values and printing them to screen
while (True):
# read from adc channels and print to screen
xVoltage = (adc.read_voltage(1) * conversionfactor) - xStatic
yVoltage = (adc.read_voltage(2) * conversionfactor) - yStatic
zVoltage = (adc.read_voltage(3) * conversionfactor) - zStatic
xForce = xVoltage / 0.3
yForce = yVoltage / 0.3
zForce = zVoltage / 0.3
# Check values against max and min and update if needed
if xForce >= xMax:
xMax = xForce
if xForce <= xMin:
xMin = xForce

if yForce >= yMax:


yMax = yForce
if yForce <= yMin:
yMin = yForce
if zForce >= zMax:
zMax = zForce
if zForce <= zMin:
zMin = zForce

# clear the console


os.system('clear')
# print values to screen
print ("X: %02f" % xForce)
print ("Y: %02f" % yForce)
print ("Z: %02f" % zForce)
print ("Max X: %02f" % xMax)
print ("Min X: %02f" % xMin)
print ("Max Y: %02f" % yMax)
print ("Min Y: %02f" % yMin)
print ("Max Z: %02f" % zMax)
print ("Min Z: %02f" % zMin)
print ("X Static Voltage: %02f" % xStatic)
print ("Y Static Voltage: %02f" % yStatic)
print ("Z Static Voltage: %02f" % zStatic)

# wait 0.05 seconds before reading the pins again


time.sleep(0.05)
#!/usr/bin/python3
try:
import smbus
except ImportError:
raise ImportError("python-smbus not found Install with 'sudo apt-get install python3-smbus'")
import re
"""
================================================
ABElectronics Python Helper Functions
Version 1.0 Created 29/02/2015
Python 3 only
Requires python 3 smbus to be installed. For more information
about enabling i2c and installing smbus visit
https://www.abelectronics.co.uk/i2c-raspbian-wheezy/info.aspx
================================================
"""

class ABEHelpers:
def get_smbus(self):
# detect i2C port number and assign to i2c_bus
i2c_bus = 0
for line in open('/proc/cpuinfo').readlines():
m = re.match('(.*?)\s*:\s*(.*)', line)
if m:
(name, value) = (m.group(1), m.group(2))
if name == "Revision":
if value[-4:] in ('0002', '0003'):
i2c_bus = 0
else:
i2c_bus = 1
break
try:
return smbus.SMBus(i2c_bus)
except IOError:
print ("Could not open the i2c bus.")
print ("Please check that i2c is enabled and python-smbus and i2c-tools are installed.")
print ("Visit https://www.abelectronics.co.uk/i2c-raspbian-wheezy/info.aspx for more information.")
#!/usr/bin/python3
"""
================================================
ABElectronics ADC Differential Pi 8-Channel ADC
Version 1.0 Created 30/09/2015
Requires python 3 smbus to be installed
================================================
"""
class ADCDifferentialPi:
# internal variables
__address = 0x68 # default address for adc 1 on the ADC Differential Pi
__address2 = 0x69 # default address for adc 2 on the ADC Differential Pi
__config1 = 0x9C # PGAx1, 18 bit, one-shot conversion, channel 1
__currentchannel1 = 1 # channel variable for adc 1
__config2 = 0x9C # PGAx1, 18 bit, one-shot conversion, channel 1
__currentchannel2 = 1 # channel variable for adc2
__bitrate = 18 # current bitrate
__conversionmode = 1 # Conversion Mode
__signbit = False # signed bit checker
__pga = float(0.5) # current pga setting
__lsb = float(0.0000078125) # default lsb value for 18 bit
# create byte array and fill with initial values to define size
__adcreading = bytearray()
__adcreading.append(0x00)
__adcreading.append(0x00)
__adcreading.append(0x00)
__adcreading.append(0x00)

global _bus
# local methods
def __updatebyte(self, byte, bit, value):
# internal method for setting the value of a single bit within a
# byte
if value == 0:
return byte & ~(1 << bit)
elif value == 1:
return byte | (1 << bit)
def __checkbit(self, byte, bit):
# internal method for reading the value of a single bit within a
# byte
if byte & (1 << bit):
return 1
else:
return 0
def __twos_comp(self, val, bits):
if((val & (1 << (bits - 1))) != 0):
val = val - (1 << bits)
return val
def __setchannel(self, channel):
# internal method for updating the config to the selected channel
if channel < 5:
if channel != self.__currentchannel1:
if channel == 1:
self.__config1 = self.__updatebyte(self.__config1, 5, 0)
self.__config1 = self.__updatebyte(self.__config1, 6, 0)
self.__currentchannel1 = 1
if channel == 2:
self.__config1 = self.__updatebyte(self.__config1, 5, 1)
self.__config1 = self.__updatebyte(self.__config1, 6, 0)
self.__currentchannel1 = 2
if channel == 3:
self.__config1 = self.__updatebyte(self.__config1, 5, 0)
self.__config1 = self.__updatebyte(self.__config1, 6, 1)
self.__currentchannel1 = 3
if channel == 4:
self.__config1 = self.__updatebyte(self.__config1, 5, 1)
self.__config1 = self.__updatebyte(self.__config1, 6, 1)
self.__currentchannel1 = 4
else:
if channel != self.__currentchannel2:
if channel == 5:
self.__config2 = self.__updatebyte(self.__config2, 5, 0)
self.__config2 = self.__updatebyte(self.__config2, 6, 0)
self.__currentchannel2 = 5
if channel == 6:
self.__config2 = self.__updatebyte(self.__config2, 5, 1)
self.__config2 = self.__updatebyte(self.__config2, 6, 0)
self.__currentchannel2 = 6
if channel == 7:
self.__config2 = self.__updatebyte(self.__config2, 5, 0)

self.__config2 = self.__updatebyte(self.__config2, 6, 1)
self.__currentchannel2 = 7
if channel == 8:
self.__config2 = self.__updatebyte(self.__config2, 5, 1)
self.__config2 = self.__updatebyte(self.__config2, 6, 1)
self.__currentchannel2 = 8
return
# init object with i2caddress, default is 0x68, 0x69 for ADCoPi board
def __init__(self, bus, address=0x68, address2=0x69, rate=18):
self._bus = bus
self.__address = address
self.__address2 = address2
self.set_bit_rate(rate)
def read_voltage(self, channel):
# returns the voltage from the selected adc channel - channels 1 to
#8
raw = self.read_raw(channel)
if (self.__signbit):
voltage = (raw * (self.__lsb / self.__pga)) - (2.048 / (self.__pga * 2))
else:
voltage = (raw * (self.__lsb / self.__pga))
return float(voltage)
def read_raw(self, channel):
# reads the raw value from the selected adc channel - channels 1 to 8
h=0
l=0
m=0
s=0
# get the config and i2c address for the selected channel
self.__setchannel(channel)
if (channel < 5):
config = self.__config1
address = self.__address
else:
config = self.__config2
address = self.__address2
# if the conversion mode is set to one-shot update the ready bit to 1
if (self.__conversionmode == 0):
config = self.__updatebyte(config, 7, 1)
self._bus.write_byte(address, config)
config = self.__updatebyte(config, 7, 0)
# keep reading the adc data until the conversion result is ready
while True:
__adcreading = self._bus.read_i2c_block_data(address, config, 4)
if self.__bitrate == 18:
h = __adcreading[0]
m = __adcreading[1]
l = __adcreading[2]
s = __adcreading[3]
else:

h = __adcreading[0]
m = __adcreading[1]
s = __adcreading[2]
if self.__checkbit(s, 7) == 0:
break
self.__signbit = False
t = 0.0
# extract the returned bytes and combine in the correct order
if self.__bitrate == 18:
t = ((h & 0b00000011) << 16) | (m << 8) | l
self.__signbit = bool(self.__checkbit(t, 17))
if self.__signbit:
t = self.__updatebyte(t, 17, 0)
if self.__bitrate == 16:
t = (h << 8) | m
self.__signbit = bool(self.__checkbit(t, 15))
if self.__signbit:
t = self.__updatebyte(t, 15, 0)
if self.__bitrate == 14:
t = ((h & 0b00111111) << 8) | m
self.__signbit = self.__checkbit(t, 13)
if self.__signbit:
t = self.__updatebyte(t, 13, 0)
if self.__bitrate == 12:
t = ((h & 0b00001111) << 8) | m
self.__signbit = self.__checkbit(t, 11)
if self.__signbit:
t = self.__updatebyte(t, 11, 0)
return t
def set_pga(self, gain):
"""
PGA gain selection
1 = 1x
2 = 2x
4 = 4x
8 = 8x
"""
if gain == 1:
self.__config1 = self.__updatebyte(self.__config1,
self.__config1 = self.__updatebyte(self.__config1,
self.__config2 = self.__updatebyte(self.__config2,
self.__config2 = self.__updatebyte(self.__config2,
self.__pga = 0.5
if gain == 2:
self.__config1 = self.__updatebyte(self.__config1,
self.__config1 = self.__updatebyte(self.__config1,
self.__config2 = self.__updatebyte(self.__config2,
self.__config2 = self.__updatebyte(self.__config2,
self.__pga = 1
if gain == 4:

0,
1,
0,
1,

0)
0)
0)
0)

0,
1,
0,
1,

1)
0)
1)
0)

self.__config1 =
self.__config1 =
self.__config2 =
self.__config2 =
self.__pga = 2
if gain == 8:
self.__config1 =
self.__config1 =
self.__config2 =
self.__config2 =
self.__pga = 4

self.__updatebyte(self.__config1,
self.__updatebyte(self.__config1,
self.__updatebyte(self.__config2,
self.__updatebyte(self.__config2,

0,
1,
0,
1,

0)
1)
0)
1)

self.__updatebyte(self.__config1,
self.__updatebyte(self.__config1,
self.__updatebyte(self.__config2,
self.__updatebyte(self.__config2,

0,
1,
0,
1,

1)
1)
1)
1)

2,
3,
2,
3,

0)
0)
0)
0)

2,
3,
2,
3,

1)
0)
1)
0)

2,
3,
2,
3,

0)
1)
0)
1)

2,
3,
2,
3,

1)
1)
1)
1)

self._bus.write_byte(self.__address, self.__config1)
self._bus.write_byte(self.__address2, self.__config2)
return
def set_bit_rate(self, rate):
"""
sample rate and resolution
12 = 12 bit (240SPS max)
14 = 14 bit (60SPS max)
16 = 16 bit (15SPS max)
18 = 18 bit (3.75SPS max)
"""
if rate == 12:
self.__config1 = self.__updatebyte(self.__config1,
self.__config1 = self.__updatebyte(self.__config1,
self.__config2 = self.__updatebyte(self.__config2,
self.__config2 = self.__updatebyte(self.__config2,
self.__bitrate = 12
self.__lsb = 0.0005
if rate == 14:
self.__config1 = self.__updatebyte(self.__config1,
self.__config1 = self.__updatebyte(self.__config1,
self.__config2 = self.__updatebyte(self.__config2,
self.__config2 = self.__updatebyte(self.__config2,
self.__bitrate = 14
self.__lsb = 0.000125
if rate == 16:
self.__config1 = self.__updatebyte(self.__config1,
self.__config1 = self.__updatebyte(self.__config1,
self.__config2 = self.__updatebyte(self.__config2,
self.__config2 = self.__updatebyte(self.__config2,
self.__bitrate = 16
self.__lsb = 0.00003125
if rate == 18:
self.__config1 = self.__updatebyte(self.__config1,
self.__config1 = self.__updatebyte(self.__config1,
self.__config2 = self.__updatebyte(self.__config2,
self.__config2 = self.__updatebyte(self.__config2,
self.__bitrate = 18
self.__lsb = 0.0000078125
self._bus.write_byte(self.__address, self.__config1)
self._bus.write_byte(self.__address2, self.__config2)
return

def set_conversion_mode(self, mode):


"""
conversion mode for adc
0 = One shot conversion mode
1 = Continuous conversion mode
"""
if (mode == 0):
self.__config1 = self.__updatebyte(self.__config1, 4,
self.__config2 = self.__updatebyte(self.__config2, 4,
self.__conversionmode = 0
if (mode == 1):
self.__config1 = self.__updatebyte(self.__config1, 4,
self.__config2 = self.__updatebyte(self.__config2, 4,
self.__conversionmode = 1
#self._bus.write_byte(self.__address, self.__config1)
#self._bus.write_byte(self.__address2, self.__config2)
Return

0)
0)
1)
1)

AB Electronics UK ADCDAC Pi Python 3 Library


=====
Python 3 Library to use with ADCDAC Pi Raspberry Pi expansion board from http://www.abelectronics.co.uk
Install
====
To download to your Raspberry Pi type in terminal:
```
git clone https://github.com/abelectronicsuk/ABElectronics_Python3_Libraries.git
```
The ADCDAC Pi library is located in the ADCDACPi directory
```
The example python files in /ABElectronics_Python3_Libraries/ADCDACPi/ will now run from the terminal.
Functions:
---------```
read_adc_voltage(channel)
```
Read the voltage from the selected channel on the ADC
**Parameters:** channel - 1 or 2
**Returns:** number as float between 0 and 2.048
```
read_adc_raw(channel)
```
Read the raw value from the selected channel on the ADC
**Parameters:** channel - 1 or 2
**Returns:** int
```
set_adc_refvoltage(voltage)
```

Set the reference voltage for the analogue to digital converter.


The ADC uses the raspberry pi 3.3V power as a voltage reference so using this method to set the reference to match the exact output voltage from the 3.3V regulator will increase
the accuracy of the ADC readings.
**Parameters:** voltage - float between 0.0 and 7.0
**Returns:** null
```
set_dac_voltage(channel, voltage)
```
Set the voltage for the selected channel on the DAC. The DAC has two gain values, 1 or 2, which can be set when the ADCDAC object is created. A gain of 1 will give a voltage
between 0 and 2.047 volts. A gain of 2 will give a voltage between 0 and 3.3 volts.
**Parameters:** channel - 1 or 2, voltage can be between 0 and 2.047 volts
**Returns:** null
```
set_dac_raw(channel, value)
```
Set the raw value from the selected channel on the DAC
**Parameters:** channel - 1 or 2,value int between 0 and 4095
**Returns:** null
Usage
====
To use the ADCDAC Pi library in your code you must first import the library:
```
from ABE_ADCDACPi import ADCDACPi
```
Next you must initialise the adcdac object and set a gain of 1 or 2 for the DAC:
```
adcdac = ADCDACPi(1)
```
Set the reference voltage.
```
adcdac.set_adc_refvoltage(3.3)
```
Read the voltage from channel 2 and display on the screen
```
print adcdac.read_adc_voltage(2)
```
#!/usr/bin/python3
from ABE_ADCDACPi import ADCDACPi
import time
"""
================================================
ABElectronics ADCDAC Pi 2-Channel ADC, 2-Channel DAC | DAC Write Demo
Version 1.0 Created 29/02/2015
run with: python3 demo-dacwrite.py
================================================
this demo will generate a 1.5V p-p square wave at 1Hz
"""
adcdac = ADCDACPi(1) # create an instance of the ADCDAC Pi with a DAC gain set to 1

while True:
adcdac.set_dac_voltage(1, 1.5) # set the voltage on channel 1 to 1.5V
time.sleep(0.5) # wait 0.5 seconds
adcdac.set_dac_voltage(1, 0) # set the voltage on channel 1 to 0V
time.sleep(0.5) # wait 0.5 seconds
#!/usr/bin/python3
from ABE_ADCDACPi import ADCDACPi
import time
import math
"""
================================================
ABElectronics ADCDAC Pi 2-Channel ADC, 2-Channel DAC | DAC sine wave generator demo
Version 1.0 Created 29/02/2015
run with: python3 demo-dacsinewave.py
================================================
# this demo uses the set_dac_raw method to generate a sine wave from a
# predefined set of values
"""
adcdac = ADCDACPi(1) # create an instance of the ADCDAC Pi with a DAC gain set to 1
DACLookup_FullSine_12Bit = \
[2048, 2073, 2098, 2123, 2148, 2174, 2199, 2224,
2249, 2274, 2299, 2324, 2349, 2373, 2398, 2423,
2448, 2472, 2497, 2521, 2546, 2570, 2594, 2618,
2643, 2667, 2690, 2714, 2738, 2762, 2785, 2808,
2832, 2855, 2878, 2901, 2924, 2946, 2969, 2991,
3013, 3036, 3057, 3079, 3101, 3122, 3144, 3165,
3186, 3207, 3227, 3248, 3268, 3288, 3308, 3328,
3347, 3367, 3386, 3405, 3423, 3442, 3460, 3478,
3496, 3514, 3531, 3548, 3565, 3582, 3599, 3615,
3631, 3647, 3663, 3678, 3693, 3708, 3722, 3737,
3751, 3765, 3778, 3792, 3805, 3817, 3830, 3842,
3854, 3866, 3877, 3888, 3899, 3910, 3920, 3930,
3940, 3950, 3959, 3968, 3976, 3985, 3993, 4000,
4008, 4015, 4022, 4028, 4035, 4041, 4046, 4052,
4057, 4061, 4066, 4070, 4074, 4077, 4081, 4084,
4086, 4088, 4090, 4092, 4094, 4095, 4095, 4095,
4095, 4095, 4095, 4095, 4094, 4092, 4090, 4088,
4086, 4084, 4081, 4077, 4074, 4070, 4066, 4061,
4057, 4052, 4046, 4041, 4035, 4028, 4022, 4015,
4008, 4000, 3993, 3985, 3976, 3968, 3959, 3950,
3940, 3930, 3920, 3910, 3899, 3888, 3877, 3866,
3854, 3842, 3830, 3817, 3805, 3792, 3778, 3765,
3751, 3737, 3722, 3708, 3693, 3678, 3663, 3647,
3631, 3615, 3599, 3582, 3565, 3548, 3531, 3514,
3496, 3478, 3460, 3442, 3423, 3405, 3386, 3367,
3347, 3328, 3308, 3288, 3268, 3248, 3227, 3207,
3186, 3165, 3144, 3122, 3101, 3079, 3057, 3036,
3013, 2991, 2969, 2946, 2924, 2901, 2878, 2855,
2832, 2808, 2785, 2762, 2738, 2714, 2690, 2667,
2643, 2618, 2594, 2570, 2546, 2521, 2497, 2472,
2448, 2423, 2398, 2373, 2349, 2324, 2299, 2274,

2249, 2224, 2199, 2174, 2148, 2123, 2098, 2073,


2048, 2023, 1998, 1973, 1948, 1922, 1897, 1872,
1847, 1822, 1797, 1772, 1747, 1723, 1698, 1673,
1648, 1624, 1599, 1575, 1550, 1526, 1502, 1478,
1453, 1429, 1406, 1382, 1358, 1334, 1311, 1288,
1264, 1241, 1218, 1195, 1172, 1150, 1127, 1105,
1083, 1060, 1039, 1017, 995, 974, 952, 931,
910, 889, 869, 848, 828, 808, 788, 768,
749, 729, 710, 691, 673, 654, 636, 618,
600, 582, 565, 548, 531, 514, 497, 481,
465, 449, 433, 418, 403, 388, 374, 359,
345, 331, 318, 304, 291, 279, 266, 254,
242, 230, 219, 208, 197, 186, 176, 166,
156, 146, 137, 128, 120, 111, 103, 96,
88, 81, 74, 68, 61, 55, 50, 44,
39, 35, 30, 26, 22, 19, 15, 12,
10, 8, 6, 4, 2, 1, 1, 0,
0, 0, 1, 1, 2, 4, 6, 8,
10, 12, 15, 19, 22, 26, 30, 35,
39, 44, 50, 55, 61, 68, 74, 81,
88, 96, 103, 111, 120, 128, 137, 146,
156, 166, 176, 186, 197, 208, 219, 230,
242, 254, 266, 279, 291, 304, 318, 331,
345, 359, 374, 388, 403, 418, 433, 449,
465, 481, 497, 514, 531, 548, 565, 582,
600, 618, 636, 654, 673, 691, 710, 729,
749, 768, 788, 808, 828, 848, 869, 889,
910, 931, 952, 974, 995, 1017, 1039, 1060,
1083, 1105, 1127, 1150, 1172, 1195, 1218, 1241,
1264, 1288, 1311, 1334, 1358, 1382, 1406, 1429,
1453, 1478, 1502, 1526, 1550, 1575, 1599, 1624,
1648, 1673, 1698, 1723, 1747, 1772, 1797, 1822,
1847, 1872, 1897, 1922, 1948, 1973, 1998, 2023]
while True:
for val in DACLookup_FullSine_12Bit:
adcdac.set_dac_raw(1, val)
#!/usr/bin/python3
from ABE_ADCDACPi import ADCDACPi
import time
"""
================================================
ABElectronics ADCDAC Pi 2-Channel ADC, 2-Channel DAC | ADC Read Demo
Version 1.0 Created 29/02/2015
run with: python3 demo-adcread.py
================================================
this demo reads the voltage from channel 1 on the ADC inputs
"""
adcdac = ADCDACPi() # create an instance of ADCDACPi
# set the reference voltage. this should be set to the exact voltage
# measured on the raspberry pi 3.3V rail.

adcdac.set_adc_refvoltage(3.3)
while True:
# read the voltage from channel 1 and display on the screen
print (adcdac.read_adc_voltage(1))
time.sleep(0.5)
#!/usr/bin/python3
import RPi.GPIO as GPIO
import spidev
import ctypes
"""
================================================
ABElectronics ADCDAC Pi Analogue to Digital / Digital to Analogue Converter
Version 1.0 Created 29/02/2015
Version 1.1 Updated 15/04/2016 Added controllable gain factor
================================================
Based on the Microchip MCP3202 and MCP4822
"""
class Dac_bits(ctypes.LittleEndianStructure):
"""Class to define the DAC command register bitfields.
See Microchip mcp4822 datasheet for more information
"""
_fields_ = [
("data", ctypes.c_uint16, 12), #Bits 0:11
("shutdown", ctypes.c_uint16, 1), #Bit 12
("gain", ctypes.c_uint16, 1), #Bit 13
("reserved1", ctypes.c_uint16, 1), #Bit 14
("channel", ctypes.c_uint16, 1) #Bit 15
]
#GA field value lookup. <gainFactor>:<bitfield val>
__ga_field__ = {1:1, 2:0}
def gain_to_field_val(self, gainFactor):
"""Returns bitfield value based on desired gain"""
return self.__ga_field__[gainFactor]
class Dac_register(ctypes.Union):
"""Union to represent the DAC's command register
See Microchip mcp4822 datasheet for more information
"""
_fields_ = [
("bits", Dac_bits),
("bytes", ctypes.c_uint8 * 2),
("reg", ctypes.c_uint16)
]
class ADCDACPi:
# variables
__adcrefvoltage = 3.3 # reference voltage for the ADC chip.

# Define SPI bus and init


spiADC = spidev.SpiDev()
spiADC.open(0, 0)
spiADC.max_speed_hz = (900000)
spiDAC = spidev.SpiDev()
spiDAC.open(0, 1)
spiDAC.max_speed_hz = (4000000)
#Max DAC output voltage. Depends on gain factor
#The following table is in the form <gain factor>:<max voltage>
__dacMaxOutput__ = {
1:2.048, #This is Vref
2:3.3 #This is Vdd for ABE ADCDACPi board
}
# public methods
def __init__(self, gainFactor = 1):
"""Class Constructor
gainFactor -- Set the DAC's gain factor. The value should
be 1 or 2. Gain factor is used to determine output voltage
from the formula: Vout = G * Vref * D/4096
Where G is gain factor, Vref (for this chip) is 2.048 and
D is the 12-bit digital value
"""
if (gainFactor != 1) and (gainFactor != 2):
print ("Invalid gain factor. Must be 1 or 2")
self.gain = 1
else:
self.gain = gainFactor
self.maxDacVoltage = self.__dacMaxOutput__[self.gain]
def read_adc_voltage(self, channel):
"""
Read the voltage from the selected channel on the ADC
Channel = 1 or 2
"""
if ((channel > 2) or (channel < 1)):
print ("ADC channel needs to be 1 or 2")
raw = self.read_adc_raw(channel)
voltage = (self.__adcrefvoltage / 4096) * raw
return voltage
def read_adc_raw(self, channel):
# Read the raw value from the selected channel on the ADC
# Channel = 1 or 2
if ((channel > 2) or (channel < 1)):
print ("ADC channel needs to be 1 or 2")
r = self.spiADC.xfer2([1, (1 + channel) << 6, 0])
ret = ((r[1] & 0x0F) << 8) + (r[2])
return ret
def set_adc_refvoltage(self, voltage):
"""

set the reference voltage for the analogue to digital converter.


The ADC uses the raspberry pi 3.3V power as a voltage reference so
using this method to set the reference to match the
exact output voltage from the 3.3V regulator will increase the
accuracy of the ADC readings.
"""
if (voltage >= 0.0) and (voltage <= 7.0):
__adcrefvoltage = voltage
else:
print ("reference voltage out of range")
return
def set_dac_voltage(self, channel, voltage):
"""
set the voltage for the selected channel on the DAC
voltage can be between 0 and 2.047 volts
"""
if ((channel > 2) or (channel < 1)):
print ("DAC channel needs to be 1 or 2")
if (voltage >= 0.0) and (voltage < self.maxDacVoltage):
rawval = (voltage / 2.048) * 4096 / self.gain
self.set_dac_raw(channel, int(rawval))
else:
print ("Invalid DAC Vout value %f. Must be between 0 and %f (non-inclusive) " % (voltage, self.maxDacVoltage))
return
def set_dac_raw(self, channel, value):
"""
Set the raw value from the selected channel on the DAC
Channel = 1 or 2
Value between 0 and 4095
"""
reg = Dac_register()
#Configurable fields
reg.bits.data = value
reg.bits.channel = channel - 1
reg.bits.gain = reg.bits.gain_to_field_val(self.gain)
#Fixed fields:
reg.bits.shutdown = 1 #Active low
#Write to device
self.spiDAC.xfer2( [ reg.bytes[1], reg.bytes[0] ])
return
#!/usr/bin/python
import RPi.GPIO as GPIO
import time
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
GPIO.setup(27,GPIO.OUT)
GPIO_PIR = 7
print "PIR Module Test (CTRL-C to exit)"
# Set pin as input

GPIO.setup(GPIO_PIR,GPIO.IN)

# Echo

Current_State = 0
Previous_State = 0
try:
print "Waiting for PIR to settle ..."
# Loop until PIR output is 0
while GPIO.input(GPIO_PIR)==1:
Current_State = 0
print " Ready"
# Loop until users quits with CTRL-C
while True :
# Read PIR state
Current_State = GPIO.input(GPIO_PIR)
if Current_State==1 and Previous_State==0:
# PIR is triggered
print " Motion detected!"
# Record previous state
GPIO.output(27,GPIO.HIGH)
time.sleep(1)
GPIO.output(27,GPIO.LOW)
Previous_State=1
elif Current_State==0 and Previous_State==1:
# PIR has returned to ready state
print " Ready"
Previous_State=0
# Wait for 10 milliseconds
time.sleep(0.01)
except KeyboardInterrupt:
print " Quit"
# Reset GPIO settings
GPIO.cleanup()
#!/usr/bin/env python
import os
import datetime
import time
import RPi.GPIO as GPIO
GPIO.setwarnings(False)
DEBUG = 1
GPIO.setmode(GPIO.BCM)
def RCtime (RCpin):
reading = 0
GPIO.setup(RCpin, GPIO.OUT)
GPIO.output(RCpin, GPIO.LOW)
time.sleep(.1)
GPIO.setup(RCpin, GPIO.IN)
# This takes about 1 millisecond per loop cycle
while (GPIO.input(RCpin) == GPIO.LOW):
reading += 1
return reading

while True:

GetDateTime = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")


LDRReading = RCtime(3)
print RCtime(3)
# Open a file
fo = open("/home/pi/Desktop/gpio_python_code/foo.txt", "wb")
fo.write (GetDateTime)
LDRReading = str(LDRReading)
fo.write ("\n")
fo.write (LDRReading)
# Close opend file
fo.close()
time.sleep(1)

import os
import glob
import time
#initialize the device
os.system('modprobe w1-gpio')
os.system('modprobe w1-therm')
base_dir = '/sys/bus/w1/devices/'
device_folder = glob.glob(base_dir + '28*')[0]
device_file = device_folder + '/w1_slave'
def read_temp_raw():
f = open(device_file, 'r')
lines = f.readlines()
f.close()
return lines
def read_temp():
lines = read_temp_raw()
while lines[0].strip()[-3:] != 'YES':
time.sleep(0.2)
lines = read_temp_raw()
equals_pos = lines[1].find('t=')
if equals_pos != -1:
temp_string = lines[1][equals_pos+2:]
temp_c = float(temp_string) / 1000.0
temp_f = temp_c * 9.0 / 5.0 + 32.0
return temp_c, temp_f
while True:
print(read_temp())
time.sleep(1)
#!/usr/bin/python
import os
import time
import RPi.GPIO as GPIO
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)

GPIO.setup(22,GPIO.OUT)
loop_count = 0
def morsecode ():
#Dot Dot Dot
GPIO.output(22,GPIO.HIGH)
time.sleep(.1)
GPIO.output(22,GPIO.LOW)
time.sleep(.1)
GPIO.output(22,GPIO.HIGH)
time.sleep(.1)
GPIO.output(22,GPIO.LOW)
time.sleep(.1)
GPIO.output(22,GPIO.HIGH)
time.sleep(.1)
#Dash Dash Dah
GPIO.output(22,GPIO.LOW)
time.sleep(.2)
GPIO.output(22,GPIO.HIGH)
time.sleep(.2)
GPIO.output(22,GPIO.LOW)
time.sleep(.2)
GPIO.output(22,GPIO.HIGH)
time.sleep(.2)
GPIO.output(22,GPIO.LOW)
time.sleep(.2)
GPIO.output(22,GPIO.HIGH)
time.sleep(.2)
GPIO.output(22,GPIO.LOW)
time.sleep(.2)
#Dot Dot Dot
GPIO.output(22,GPIO.HIGH)
time.sleep(.1)
GPIO.output(22,GPIO.LOW)
time.sleep(.1)
GPIO.output(22,GPIO.HIGH)
time.sleep(.1)
GPIO.output(22,GPIO.LOW)
time.sleep(.1)
GPIO.output(22,GPIO.HIGH)
time.sleep(.1)
GPIO.output(22,GPIO.LOW)
time.sleep(.7)
os.system('clear')
print "Morse Code"
loop_count = input("How many times would you like SOS to loop?: ")
while loop_count > 0:
loop_count = loop_count - 1
morsecode ()
#!/usr/bin/python
import os

import time
import RPi.GPIO as GPIO
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
GPIO.setup(17,GPIO.OUT)
GPIO.setup(27,GPIO.OUT)
#Setup variables for user input
led_choice = 0
count = 0
os.system('clear')
print "Which LED would you like to blink"
print "1: Red?"
print "2: Blue?"
led_choice = input("Choose your option: ")
if led_choice == 1:
os.system('clear')
print "You picked the Red LED"
count = input("How many times would you like it to blink?: ")
while count > 0:
GPIO.output(27,GPIO.HIGH)
time.sleep(1)
GPIO.output(27,GPIO.LOW)
time.sleep(1)
count = count - 1
if led_choice == 2:
os.system('clear')
print "You picked the Red LED"
count = input("How many times would you like it to blink?: ")
while count > 0:
GPIO.output(17,GPIO.HIGH)
time.sleep(1)
GPIO.output(17,GPIO.LOW)
time.sleep(1)
count = count 1
#!/usr/bin/python
import os
import time
import RPi.GPIO as GPIO
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
GPIO.setup(10, GPIO.IN)
print("------------------")
print(" Button + GPIO ")
print("------------------")
print GPIO.input(10)
while True:
if ( GPIO.input(10) == False ):

print("Button Pressed")
os.system('date')
print GPIO.input(10)
time.sleep(5)
else:
os.system('clear')
print ("Waiting for you to press a button")
time.sleep(1)
#!/usr/bin/python
import time
import RPi.GPIO as GPIO
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
GPIO.setup(17,GPIO.OUT)
GPIO.setup(27,GPIO.OUT)
while 1:
GPIO.output(17,GPIO.HIGH)
GPIO.output(27,GPIO.HIGH)
time.sleep(1)
GPIO.output(17,GPIO.LOW)
GPIO.output(27,GPIO.LOW)
time.sleep(1)
#!/usr/bin/python
import time
import RPi.GPIO as GPIO
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
GPIO.setup(17,GPIO.OUT)
GPIO.setup(27,GPIO.OUT)
#Turn LEDs on
GPIO.output(17,GPIO.HIGH)
GPIO.output(27,GPIO.HIGH)
time.sleep(1)
#Turn LEDs off
GPIO.output(17,GPIO.LOW)
GPIO.output(27,GPIO.LOW)
time.sleep(1)
#Turn LEDs on
GPIO.output(17,GPIO.HIGH)
GPIO.output(27,GPIO.HIGH)
time.sleep(1)
#Turn LEDs off
GPIO.output(17,GPIO.LOW)
GPIO.output(27,GPIO.LOW)
GPIO.cleanup
#!/usr/bin/python
import RPi.GPIO as GPIO
GPIO.setmode(GPIO.BCM)
GPIO.cleanup()
GPIO.setwarnings(False)
GPIO.setup(17,GPIO.OUT)
GPIO.setup(27,GPIO.OUT)
print "Lights on"
GPIO.output(17,GPIO.HIGH)
GPIO.output(27,GPIO.HIGH)

#!/usr/bin/python
import RPi.GPIO as GPIO
GPIO.setmode(GPIO.BCM)
GPIO.cleanup()
GPIO.setwarnings(False)
GPIO.setup(17,GPIO.OUT)
GPIO.setup(27,GPIO.OUT)
print "Lights off"
GPIO.output(17,GPIO.LOW)
GPIO.output(27,GPIO.LOW)
#!/usr/bin/python
#Print Hello world
print "Hello World!"
#!/usr/bin/env python
from smbus import SMBus
import re
import time
import sys, math, struct
rtc_address1 = 0x68
# detect i2C port number and assign to i2c_bus
for line in open('/proc/cpuinfo').readlines():
m = re.match('(.*?)\s*:\s*(.*)', line)
if m:
(name, value) = (m.group(1), m.group(2))
if name == "Revision":
if value [-4:] in ('0002', '0003'):
i2c_bus = 0
else:
i2c_bus = 1
break

bus = SMBus(i2c_bus)
def GetTime():
seconds, minutes, hours, dayofweek, day, month, year = bus.read_i2c_block_data(rtc_address1, 0, 7)
print ("%02d - %02d - %02d- %02d:%02d:%02d " % (fromBCDtoDecimal(year),
fromBCDtoDecimal(month), fromBCDtoDecimal(day)
,fromBCDtoDecimal(hours), fromBCDtoDecimal(minutes),
fromBCDtoDecimal(seconds & 0x7F)))
def fromBCDtoDecimal(x):
return x - 6 * (x >> 4)
def bin2bcd(x):
return x + 6 * (x /10)
bus.write_byte_data(rtc_address1,
bus.write_byte_data(rtc_address1,
bus.write_byte_data(rtc_address1,
bus.write_byte_data(rtc_address1,

0x00,
0x01,
0x02,
0x03,

0x00)
0x0C)
0x0C)
0x0C)

bus.write_byte_data(rtc_address1,
bus.write_byte_data(rtc_address1,
bus.write_byte_data(rtc_address1,
bus.write_byte_data(rtc_address1,

0x04,
0x05,
0x06,
0x07,

0x00)
0x0C)
0x0C)
0x00)

while True:
GetTime()
#
#
#
#

Rapsberry Pi: Loopback test script for ADC DAC pi board to test DAC and ADC chip readings / outputs
Connect 10K resistor between output 1 and input 1
Connect 10K resistor between output 2 and input 2
run sudo python loopback.py

# Version 1.0 - 06/11/2013


# Version History:
# 1.0 - Initial Release
#
#
#!/usr/bin/python
import spidev
import time
DEBUG = 0
spi = spidev.SpiDev()
spi.open(0,0)
spi.max_speed_hz = (20000000)
spidac = spidev.SpiDev()
spidac.open(0,1)
spidac.max_speed_hz = (20000000)
counter = 1
# read SPI data from MCP3002 chip
def get_adc(channel):
# Only 2 channels 0 and 1 else return -1
if ((channel > 1) or (channel < 0)):
return -1
r = spi.xfer2([1,(2+channel)<<6,0])
ret = ((r[1]&0x0F) << 8) + (r[2])
return ret
def setOutput(channel, val):
lowByte = val & 0xff;
highByte = ((val >> 8) & 0xff) | channel << 7 | 0x1 << 5 | 1 << 4;
spidac.xfer2([highByte, lowByte])

counter = 1
setOutput(0,0)
setOutput(1,0)

setOutput(0,2048)
setOutput(1,4090)
while(1):
print "%02f %02f" % (get_adc(0),get_adc(1))
#print get_adc(0)
#print "Input 2: %02f" % get_adc(1)
#counter = counter + 1
#!/usr/bin/env python
# read abelectronics ADC Pi V2 board inputs with repeating reading from each channel.
# 12 bit data rate
# # Requries Python 2.7
# Requires SMBus
# I2C API depends on I2C support in the kernel
# Version 1.0 - 18/01/2014
# Version History:
# 1.0 - Initial Release
#
# Usage: changechannel(address, hexvalue) to change to new channel on adc chips
# Usage: getadcreading(address, hexvalue) to return value in volts from selected channel.
#
# address = adc_address1 or adc_address2 - Hex address of I2C chips as configured by board header pins.
from smbus import SMBus
import re
adc_address1 = 0x68
adc_address2 = 0x69
# create byte array and fill with initial values to define size
adcreading = bytearray()
adcreading.append(0x00)
adcreading.append(0x00)
adcreading.append(0x00)
adcreading.append(0x00)
varDivisior = 1 # from pdf sheet on adc addresses and config
varMultiplier = (2.4705882/varDivisior)/1000
# detect i2C port number and assign to i2c_bus
for line in open('/proc/cpuinfo').readlines():
m = re.match('(.*?)\s*:\s*(.*)', line)
if m:
(name, value) = (m.group(1), m.group(2))
if name == "Revision":
if value [-4:] in ('0002', '0003'):
i2c_bus = 0
else:
i2c_bus = 1
break

bus = SMBus(i2c_bus)
def changechannel(address, adcConfig):
tmp= bus.write_byte(address, adcConfig)
def getadcreading(address, adcConfig):
adcreading = bus.read_i2c_block_data(address,adcConfig)
h = adcreading[0]
l = adcreading[1]
s = adcreading[2]
# wait for new data
while (s & 128):
adcreading = bus.read_i2c_block_data(address,adcConfig)
h = adcreading[0]
l = adcreading[1]
s = adcreading[2]

# shift bits to product result


t = (h << 8) | l
# check if positive or negative number and invert if needed
if (h > 128):
t = ~(0x020000 - t)
return t * varMultiplier
while True:
changechannel(adc_address1, 0x90)
print ("Channel 1: %02f" % getadcreading(adc_address1,0x90))
changechannel(adc_address1, 0xB0)
print ("Channel 2: %02f" % getadcreading(adc_address1,0xB0))
changechannel(adc_address1, 0xD0)
print ("Channel 3 :%02f" % getadcreading(adc_address1,0xD0))
changechannel(adc_address1, 0xF0)
print ("Channel 4: %02f" % getadcreading(adc_address1,0xF0))
changechannel(adc_address2, 0x90)
print ("Channel 5: %02f" % getadcreading(adc_address2,0x90))
changechannel(adc_address2, 0xB0)
print ("Channel 6: %02f" % getadcreading(adc_address2,0xB0))
changechannel(adc_address2, 0xD0)
print ("Channel 7 :%02f" % getadcreading(adc_address2,0xD0))
changechannel(adc_address2, 0xF0)
print ("Channel 8: %02f" % getadcreading(adc_address2,0xF0))
mkdir /mnt/1wire
/opt/owfs/bin/owfs --i2c=ALL:ALL --allow_other /mnt/1wire/
sleep 1
cd /mnt/1wire/
sleep 1
ls -la
sleep 1
killall owfs
umount /mnt/1wire
{

"name": "ronanguilloux/temperature-pi",

"type": "application to be run as a daemon",


"description": "Raspberry Pi based temperature logger using a DS18B20 1-Wire digital temperature sensor",
"keywords": ["temperature", "logger", "DS18B20", "sensors", "raspberry", "raspberry pi"],
"homepage": "https://github.com/ronanguilloux/temperature-pi",
"license": "MIT",
"authors": [
{
"name": "Ronan Guilloux",
"email": "ronan.guilloux@gmail.com"
}
],
"require": {
"php": ">=5.3.0",
"ronanguilloux/php-gpio": "master-dev"
},
"autoload": {
"psr-0": { "TemperaturePi": "src/" }
}
}
#!/usr/bin/env php
<?php
require __DIR__.'/vendor/autoload.php';
use TemperaturePi\Logger;
/*
if('root' !== $_SERVER['USER'] || empty($_SERVER['SUDO_USER'])){
die("[ABORT] Please run this script as root\n");
}
*/
if(!file_exists('/sys/bus/w1/devices/28-000003ced8f4/w1_slave')) {
die("[ABORT] Please run: sudo modprobe w1-gpio; sudo modprobe w1-therm\n");
}
$log = new Logger;
$log->persist(true);
$log->writeJsDatas();
if(file_exists("./vendor/ronanguilloux/php-gpio/blinker")) {
$result = exec('sudo ./vendor/ronanguilloux/php-gpio/blinker 17 200000');
}
if ( ! window.console ) console = { log: function(){} };
google.load("visualization", "1", {packages:["annotatedtimeline"]});
google.setOnLoadCallback(drawChart);
function drawChart() {
$.ajax({
url: 'js/data.js',
cache: false,
dataType: 'json',
error: function(jqXHR, textStatus, errorThrown) {
console.log(jqXHR, textStatus, errorThrown);
},
success: function(data) {
$('#chart_div').empty();
var lastTemp = data.table[data.table.length -1][1] + ' celsius';

if('undefined' != data) {
for(row in data){
for(col in data[row]) {
data[row][col][0] = new Date(eval(data[row][col][0]));
}
}
var dataTable = new google.visualization.DataTable();
dataTable.addColumn('datetime', 'Date');
dataTable.addColumn('number', 'Celsius');
for(key in data.table) {
dataTable.addRow([new Date(data.table[key][0]), parseFloat(data.table[key][1])]);
}
var options = {
title: $('title').text() + ' | Last temp: ' + lastTemp
, displayAnnotations: true
};
var chart = new google.visualization.AnnotatedTimeLine(document.getElementById('chart_div'));
chart.draw(dataTable, options);
}
//setTimeout("drawChart();", 5000); // recursion (milliseconds) for dev purpose only
}
});
}
<?php
namespace TemperaturePi;
use PhpGpio\Sensors\DS18B20;
use PhpGpio\PhpGpio;
class Logger extends \SQlite3
{
private $sensor;
private $currentTemperature;
private $dbPath;
private $dataPath;
private $db;
public function __construct()
{
$this->sensor = new DS18B20();
$this->dbPath = __DIR__ . '/../../resources/sqlite/log.db';
$this->dataPath = __DIR__ . "/../../web/js/data.js";
$this->db = 'temperature';
}
public function fetchAll()
{
$return = array();
$this->open($this->dbPath);
$result = $this->query('SELECT datetime, celsius FROM temperature');
while ($row = $result->fetchArray()) {
$return[$row['datetime']] = $row['celsius'];
}
$this->close();

return $return;

/**
* Write a ./web/js/data.js json file (see ajax calls in ./web)
* @return $this
*/
public function writeJsDatas()
{
$datas = $this->fetchAll();
$jsContent = '{ "table": [';
$index = 0;
foreach ($datas as $date=>$celsius) {
$date = date_parse_from_format("Y-m-d H:i:s", $date);
$date = sprintf("%d,%d,%d,%d,%d,%d",
$date['year']
, ($date['month']-1)
, $date['day']
, $date['hour']
, $date['minute']
, $date['second']);
$date = sprintf("new Date(%s)", $date);
$jsContent .= "\n";
if(0 < $index) {
$jsContent .= ",";
}
$jsContent .= "[".'"' . $date . '"' . "," . str_replace(',','.',$celsius) . "]";
$index++;
}
$jsContent .= "]}";
if(false === file_put_contents($this->dataPath, $jsContent)){
throw new \Exception("can't write into " . $this->dataPath);
}
}

return $this;

public function persist($trace = false, $reset = false)


{
$this->currentTemperature = $this->sensor->read();
if($trace){
echo sprintf("%s : %s celsius\n", date('Y-m-d H:i:s'), $this->currentTemperature);
}
$this->open($this->dbPath);
if ($reset) {
$this->exec('DROP TABLE temperature');
}
$this->exec('CREATE TABLE IF NOT EXISTS temperature (datetime DATETIME, celsius FLOAT)');
$insert = sprintf("INSERT INTO temperature (datetime, celsius) VALUES (datetime('NOW', 'localtime'), %s)", str_replace(',', '.', $this->currentTemperature));
$result = $this->exec($insert);
$this->close();
}

return $this;

}
#!/usr/bin/env python

import
import
import
import

sqlite3
sys
cgi
cgitb

# global variables
speriod=(15*60)-1
dbname='/var/www/templog.db'

# print the HTTP header


def printHTTPheader():
print "Content-type: text/html\n\n"

# print the HTML head section


# arguments are the page title and the table for the chart
def printHTMLHead(title, table):
print "<head>"
print " <title>"
print title
print " </title>"
print_graph_script(table)
print "</head>"
# get data from the database
# if an interval is passed,
# return a list of records from the database
def get_data(interval):
conn=sqlite3.connect(dbname)
curs=conn.cursor()
if interval == None:
curs.execute("SELECT * FROM temps")
else:
#
curs.execute("SELECT * FROM temps WHERE timestamp>datetime('now','-%s hours')" % interval)
curs.execute("SELECT * FROM temps WHERE timestamp>datetime('2013-09-19 21:30:02','-%s hours') AND timestamp<=datetime('2013-09-19 21:31:02')" % interval)
rows=curs.fetchall()
conn.close()
return rows
# convert rows from database into a javascript table
def create_table(rows):
chart_table=""

for row in rows[:-1]:


rowstr="['{0}', {1}],\n".format(str(row[0]),str(row[1]))
chart_table+=rowstr
row=rows[-1]
rowstr="['{0}', {1}]\n".format(str(row[0]),str(row[1]))
chart_table+=rowstr
return chart_table
# print the javascript to generate the chart
# pass the table generated from the database info
def print_graph_script(table):
# google chart snippet
chart_code="""
<script type="text/javascript" src="https://www.google.com/jsapi"></script>
<script type="text/javascript">
google.load("visualization", "1", {packages:["corechart"]});
google.setOnLoadCallback(drawChart);
function drawChart() {
var data = google.visualization.arrayToDataTable([
['Time', 'Temperature'],
%s
]);
var options = {
title: 'Temperature'
};
var chart = new google.visualization.LineChart(document.getElementById('chart_div'));
chart.draw(data, options);

}
</script>"""

print chart_code % (table)

# print the div that contains the graph


def show_graph():
print "<h2>Temperature Chart</h2>"
print '<div id="chart_div" style="width: 900px; height: 500px;"></div>'

# connect to the db and show some stats


# argument option is the number of hours
def show_stats(option):
conn=sqlite3.connect(dbname)
curs=conn.cursor()
if option is None:
option = str(24)

curs.execute("SELECT timestamp,max(temp) FROM temps WHERE timestamp>datetime('now','-%s hour') AND timestamp<=datetime('now')" % option)
curs.execute("SELECT timestamp,max(temp) FROM temps WHERE timestamp>datetime('2013-09-19 21:30:02','-%s hour') AND timestamp<=datetime('2013-09-19 21:31:02')"
% option)
rowmax=curs.fetchone()
rowstrmax="{0}&nbsp&nbsp&nbsp{1}C".format(str(rowmax[0]),str(rowmax[1]))
#

curs.execute("SELECT timestamp,min(temp) FROM temps WHERE timestamp>datetime('now','-%s hour') AND timestamp<=datetime('now')" % option)
curs.execute("SELECT timestamp,min(temp) FROM temps WHERE timestamp>datetime('2013-09-19 21:30:02','-%s hour') AND timestamp<=datetime('2013-09-19 21:31:02')"
% option)
rowmin=curs.fetchone()
rowstrmin="{0}&nbsp&nbsp&nbsp{1}C".format(str(rowmin[0]),str(rowmin[1]))
#

curs.execute("SELECT avg(temp) FROM temps WHERE timestamp>datetime('now','-%s hour') AND timestamp<=datetime('now')" % option)
curs.execute("SELECT avg(temp) FROM temps WHERE timestamp>datetime('2013-09-19 21:30:02','-%s hour') AND timestamp<=datetime('2013-09-19 21:31:02')" % option)
rowavg=curs.fetchone()
print "<hr>"
print
print
print
print
print
print

"<h2>Minumum temperature&nbsp</h2>"
rowstrmin
"<h2>Maximum temperature</h2>"
rowstrmax
"<h2>Average temperature</h2>"
"%.3f" % rowavg+"C"

print "<hr>"
print "<h2>In the last hour:</h2>"
print "<table>"
print "<tr><td><strong>Date/Time</strong></td><td><strong>Temperature</strong></td></tr>"
#

rows=curs.execute("SELECT * FROM temps WHERE timestamp>datetime('new','-1 hour') AND timestamp<=datetime('new')")


rows=curs.execute("SELECT * FROM temps WHERE timestamp>datetime('2013-09-19 21:30:02','-1 hour') AND timestamp<=datetime('2013-09-19 21:31:02')")
for row in rows:
rowstr="<tr><td>{0}&emsp;&emsp;</td><td>{1}C</td></tr>".format(str(row[0]),str(row[1]))
print rowstr
print "</table>"
print "<hr>"
conn.close()

def print_time_selector(option):
print """<form action="/cgi-bin/webgui.py" method="POST">
Show the temperature logs for
<select name="timeinterval">"""
if option is not None:

if option == "6":
print "<option value=\"6\" selected=\"selected\">the last 6 hours</option>"
else:
print "<option value=\"6\">the last 6 hours</option>"
if option == "12":
print "<option value=\"12\" selected=\"selected\">the last 12 hours</option>"
else:
print "<option value=\"12\">the last 12 hours</option>"
if option == "24":
print "<option value=\"24\" selected=\"selected\">the last 24 hours</option>"
else:
print "<option value=\"24\">the last 24 hours</option>"
else:
print """<option value="6">the last 6 hours</option>
<option value="12">the last 12 hours</option>
<option value="24" selected="selected">the last 24 hours</option>"""
print """
</select>
<input type="submit" value="Display">
</form>"""
# check that the option is valid
# and not an SQL injection
def validate_input(option_str):
# check that the option string represents a number
if option_str.isalnum():
# check that the option is within a specific range
if int(option_str) > 0 and int(option_str) <= 24:
return option_str
else:
return None
else:
return None
#return the option passed to the script
def get_option():
form=cgi.FieldStorage()
if "timeinterval" in form:
option = form["timeinterval"].value
return validate_input (option)
else:
return None

# main function
# This is where the program starts
def main():
cgitb.enable()

# get options that may have been passed to this script


option=get_option()
if option is None:
option = str(24)
# get data from the database
records=get_data(option)
# print the HTTP header
printHTTPheader()
if len(records) != 0:
# convert the data into a table
table=create_table(records)
else:
print "No data found"
return
# start printing the page
print "<html>"
# print the head section including the table
# used by the javascript for the chart
printHTMLHead("Raspberry Pi Temperature Logger", table)
# print the page body
print "<body>"
print "<h1>Raspberry Pi Temperature Logger</h1>"
print "<hr>"
print_time_selector(option)
show_graph()
show_stats(option)
print "</body>"
print "</html>"
sys.stdout.flush()
if __name__=="__main__":
main()

#!/usr/bin/env python
import sqlite3
import os
import time
import glob
# global variables
speriod=(15*60)-1
dbname='/var/www/templog.db'

# store the temperature in the database


def log_temperature(temp):
conn=sqlite3.connect(dbname)
curs=conn.cursor()
curs.execute("INSERT INTO temps values(datetime('now'), (?))", (temp,))
# commit the changes
conn.commit()
conn.close()
# display the contents of the database
def display_data():
conn=sqlite3.connect(dbname)
curs=conn.cursor()
for row in curs.execute("SELECT * FROM temps"):
print str(row[0])+"
"+str(row[1])
conn.close()

# get temerature
# returns None on error, or the temperature as a float
def get_temp(devicefile):
try:
fileobj = open(devicefile,'r')
lines = fileobj.readlines()
fileobj.close()
except:
return None
# get the status from the end of line 1
status = lines[0][-4:-1]
# is the status is ok, get the temperature from line 2
if status=="YES":
print status
tempstr= lines[1][-6:-1]
tempvalue=float(tempstr)/1000
print tempvalue
return tempvalue
else:
print "There was an error."
return None

# main function
# This is where the program starts
def main():

# enable kernel modules


os.system('sudo modprobe w1-gpio')
os.system('sudo modprobe w1-therm')
# search for a device file that starts with 28
devicelist = glob.glob('/sys/bus/w1/devices/28*')
if devicelist=='':
return None
else:
# append /w1slave to the device file
w1devicefile = devicelist[0] + '/w1_slave'
#

while True:
# get the temperature from the device file
temperature = get_temp(w1devicefile)
if temperature != None:
print "temperature="+str(temperature)
else:
# Sometimes reads fail on the first attempt
# so we need to retry
temperature = get_temp(w1devicefile)
print "temperature="+str(temperature)
# Store the temperature in the database
log_temperature(temperature)

#
#

# display the contents of the database


display_data()
time.sleep(speriod)

if __name__=="__main__":
main()

#!/usr/bin/env python
# -*- coding: utf-8 -*from distutils.core import setup
import glob
setup(name='pybot',
version='1.0',
description='A collection of packages and modules for robotics and RasPi',
long_description="""
This collection of packages and modules are primarly intended for developping
robotics systems with Python on a Raspberry PI.
It contains interfacing modules with various kind of hardwares.
Even if developped with the RasPi in mind, some of the modules can be used in

other environments.
In any case, most of the 'leaf' features are independant, and you can tailor the
library by removing stuff you don't need, which are most of the time organized as
sub-packages.
""",
author='Eric Pascual',
author_email='eric@pobot.org',
url='http://www.pobot.org',
download_url='https://github.com/Pobot/PyBot',
packages=[
'pybot',
'pybot.abelectronics',
'pybot.irobot',
'pybot.dbus',
'pybot.dmxl',
'pybot.dspin'
],
scripts= \
glob.glob('./bin/*.py') + \
glob.glob('./demo/*.py')
)
#!/bin/bash
# Downloads and installs dependencies used by the Pybot library.
#
# Beware that some of them can only be executed on the RasPi (GPIO library for
# instance), and thus cannot be installed on your desktop machine.
function info() {
echo "[INFO] $*"
}
function warn() {
echo "[WARN] $*"
}
[[ "$(uname -m)" == arm* ]] && is_raspi=1 || is_raspi=0
info "Fetching SPI-Py..."
wget https://github.com/lthiery/SPI-Py/archive/master.zip -O /tmp/spi-py.zip
info "Installing SPI-Py..."
(cd /tmp && unzip -o spi-py.zip && cd SPI-Py-master && python setup.py install)
info "Fetching RPi.GPIO..."
wget http://raspberry-gpio-python.googlecode.com/files/RPi.GPIO-0.5.3a.tar.gz -P /tmp
info "Installing SPI-Py..."
if [ $is_raspi ] ; then
(\
cd /tmp && \
tar xf RPi.GPIO-0.5.3a.tar.gz && \
cd RPi.GPIO-0.5.3a && \
python setup install \
)
else
warn "This package can only be installed on the RasPi."

fi
info "Fetching i2c-tools from lm-sensors..."
wget http://dl.lm-sensors.org/i2c-tools/releases/i2c-tools-3.1.0.tar.bz2 -P /tmp
info "Installing i2c-tools..."
if [ $is_raspi ] ; then
(\
cd /tmp && \
tar xf i2c-tools-3.1.0.tar.gz && \
cd i2c-tools-3.1.0 && \
make EXTRA="py-smbus" && \
make install EXTRA="py-smbus"\
)
else
warn "This package can only be installed on the RasPi."
fi

#!/usr/bin/env python
# -*- coding: utf-8 -*""" A simple parser for simple configuration or parameters files.
Python distribution ConfigParser module is great, but sometimes to much complicated
if you just need to store a basic key/value pairs list.
You'll find here a very basic function dealing with this kind of data, but offering nonetheless
a content overriding mechanism, based on a list of files loaded in turn. This way you can mimic
Linux common strategy consisting in using a system wide file /etc/killerapp.cfg, then a per-user
file ~/.killerapp.cfg and even a per-invocation named whatever you like.
Defaults values are also handled. But no variable interpolation yet.
"""
import os
__author__ = "Eric Pascual"
__email__ = "eric@pobot.org"
__copyright__ = "Copyright 2013, POBOT"
__version__ = "1.0"
__date__ = "July. 2013"
__status__ = "Development"
__license__ = "LGPL"
def parse(files, defaults=None, sep=':'):
""" Parse a list of files containing key/value pairs, and returns the loaded data as
a dictionary.
Parameters:
files:
the list of file paths which are loaded in turn, each one overriding previously
read vales. '~' symbol is expanded using the usual rules
defaults:
a dictionary providing default values for missing parameters
default: none
sep:
the character used to split the key and the value part of a parameter record

default: ':'
Returns:
a dictionary containing the values read for the provided file(s), augmented by the
provided defaults if any
Raises:
ValueError if a record of the parsed files does not conform to the expected syntax
"""
cfg = defaults if defaults else {}
for path in [p for p in [os.path.expanduser(p) for p in files] if os.path.exists(p)]:
with open(path, 'r') as f:
for line in [l for l in [l.strip() for l in f.readlines()] if not l.startswith('#')]:
parts = line.split(sep, 1)
if len(parts) == 2:
key, value = parts
cfg[key.strip()] = value.strip()
else:
raise ValueError('invalid key/value record (%s)' % line)
return cfg

#!/usr/bin/env python
# -*- coding: utf-8 -*__author__ = 'Eric Pascual'
import platform
if not platform.machine().startswith('armv6'):
raise ImportError('module %s can be used on Raspberry Pi only' % __name__)
else:
import re
# detect i2C bus id depending on the RasPi version
for line in open('/proc/cpuinfo').readlines():
m = re.match('(.*?)\s*:\s*(.*)', line)
if m:
name, value = (m.group(1), m.group(2))
if name == "Revision":
i2c_bus_id = 0 if value[-4:] in ('0002', '0003') else 1
break
# Define I2C bus and initialize it
try:
import smbus
except ImportError:
raise NotImplementedError('python-smbus is not installed on this system.')
else:
i2c_bus = smbus.SMBus(i2c_bus_id)

#!/usr/bin/env python
# -*- coding: utf-8 -*""" A couple of convenience settings and functions for using the logging facility
in a homogeneous way accross applications."""
import logging
__author__ = "Eric Pascual"
__email__ = "eric@pobot.org"
__copyright__ = "Copyright 2013, POBOT"
__version__ = "1.0"
__date__ = "June. 2013"
__status__ = "Development"
__license__ = "LGPL"
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s.%(msecs).3d [%(levelname).1s] %(name)s > %(message)s',
datefmt='%H:%M:%S'
)
NAME_WIDTH = 15
def getLogger(name, name_width=None):
if not name_width:
name_width = NAME_WIDTH
logger = logging.getLogger(name.ljust(name_width)[:name_width])
logger.addHandler(logging.NullHandler())
return logger
#!/usr/bin/env python
"""
Interface for using a I2C LCD (mod. LCD03) from Robot Electronics.
See http://www.robot-electronics.co.uk/acatalog/LCD_Displays.html
Based on documentation available at :
http://www.robot-electronics.co.uk/htm/Lcd03tech.htm
"""
import string
import threading
import time
__author__ = "Eric Pascual"
__email__ = "eric@pobot.org"
__copyright__ = "Copyright 2012, POBOT"
__version__ = "1.0"
__date__ = "Dec. 2012"
__status__ = "Development"
__license__ = "LGPL"
_keymap = '123456789*0#'
class LCD03(object):

""" Provides the interface for a LCD03 device. """


# default address (7 bits format => eq. 0xC6)
DFLT_ADDR = 0x63
# registers
REG_CMD = 0
# write
REG_FIFO_FREE = 0
# read
REG_KEYPAD_LOW = 1
# read
REG_KEYPAD_HIGH = 2 # read
REG_VER = 3
# read
# commands
CMD_HOME = 1
CMD_SET_CURSOR_POS = 2
CMD_SET_CURSOR_LC = 3
CMD_CURSOR_INVISIBLE = 4
CMD_CURSOR_UL = 5
CMD_CURSOR_BLINK = 6
CMD_BACKSPACE = 7
CMD_HTAB = 9
CMD_DOWN = 10
CMD_UP = 11
CMD_CLEAR = 12
CMD_CR = 13
CMD_CLEAR_COL = 17
CMD_TAB_SET = 18
CMD_BACKLIGHT_ON = 19
CMD_BACKLIGHT_OFF = 20
CMD_STARTMSG_OFF = 21
CMD_STARTMSG_ON = 22
CMD_ADDR_CHANGE = 25
CMD_CUSTOM_CHARGEN = 27
CMD_KPSCAN_FAST = 28
CMD_KPSCAN_NORMAL = 29
# cursor types
CT_INVISIBLE = 0
CT_UNDERLINE = 1
CT_BLINK = 2
def __init__(self, bus, addr=DFLT_ADDR, height=4, width=20, debug=False):
""" Constructor.
Arguments:
bus:
an I2CBus/SMBus instance representing the bus this device is
connected to
addr:
the I2C device address (in 7 bits format)
height:
the number of lines of the LCD
width:
the number of character per line of the LCD
"""
self._debug = debug

self._bus = bus
self._addr = addr
self._height = height
self._width = width
self._scan_thread = None
# this lock is used to avoid issuing a command while the LCD is not ready
# (some display operations can be lengthy)
self._lock = threading.Lock()
def __del__(self):
""" Destructor.
Ensures the keypad scanning thread is not left hanging if any.
"""
self.keypad_autoscan_stop()
@property
def height(self):
""" The LCD height, as its number of lines. """
return self._height
@property
def width(self):
""" The LCD width, as its number of characters per line. """
return self._width
def get_version(self):
""" Returns the firmware version. """
return self._bus.read_byte_data(self._addr, self.REG_VER)
def clear(self):
""" Clears the display and move the cursor home. """
with self._lock:
self._bus.write_byte_data(self._addr, self.REG_CMD, self.CMD_CLEAR)
time.sleep(0.1)
def home(self):
""" Moves the cursor home (top left corner). """
self._bus.write_byte_data(self._addr, self.REG_CMD, self.CMD_HOME)
def goto_pos(self, pos):
""" Moves the cursor to a given position.
Arguments:
pos:
the position (between 1 and height * width)
"""
self._bus.write_block_data(self._addr, self.REG_CMD,
[self.CMD_SET_CURSOR_POS, pos])
def goto_line_col(self, line, col):
""" Moves the cursor to a given position.

Arguments:
line:
the line number (between 1 and height)
col:
the column number (between 1 and width)
"""
self._bus.write_block_data(self._addr, self.REG_CMD,
[self.CMD_SET_CURSOR_LC, line, col])
def write(self, s):
""" Writes a text at the current position.
Argument:
s:
the text (max length : 32)
"""
with self._lock:
self._bus.write_block_data(self._addr, self.REG_CMD,
[ord(c) for c in s[:32]])
time.sleep(0.1)
def backspace(self):
""" Moves the cursor one position left and erase the character there. """
self._bus.write_byte_data(self._addr, self.REG_CMD, self.CMD_BACKSPACE)
def htab(self):
""" Moves the cursor to the next tabulation. """
self._bus.write_byte_data(self._addr, self.REG_CMD, self.CMD_HTAB)
def move_down(self):
""" Moves the cursor one position downwards. """
self._bus.write_byte_data(self._addr, self.REG_CMD, self.CMD_DOWN)
def move_up(self):
""" Moves the cursor one position upwards. """
self._bus.write_byte_data(self._addr, self.REG_CMD, self.CMD_UP)
def cr(self):
""" Moves the cursor to the line start. """
self._bus.write_byte_data(self._addr, self.REG_CMD, self.CMD_CR)
def clear_column(self):
""" Clears the column at the cursor position. """
self._bus.write_byte_data(self._addr, self.REG_CMD, self.CMD_CLEAR_COL)
def tab_set(self, pos):
""" Defines a tabulation position.
Arguments:
pos:
the position (between 1 and 11)
"""
if pos in range(1, 11):
self._bus.write_block_data(self._addr, self.REG_CMD,
[self.CMD_TAB_SET, pos])

def set_backlight(self, on):


""" Controls the backlight.
Arguments:
on:
True to turn the backlight on, False to turn it off
"""
self._bus.write_byte_data(self._addr, self.REG_CMD,
self.CMD_BACKLIGHT_ON if on else self.CMD_BACKLIGHT_OFF)
def set_startup_message(self, on):
""" Controls the display of the startup message.
Arguments:
on:
True to display the message, False to hide it
"""
self._bus.write_byte_data(self._addr, self.REG_CMD,
self.CMD_STARTMSG_ON if on else self.CMD_STARTMSG_OFF)
def set_cursor_type(self, ct):
""" Controls the cursor type.
Arguments:
ct:
The cursor type (CT_INVISIBLE, CT_UNDERLINE, CT_BLINK)
"""
if ct in range(3):
self._bus.write_byte_data(self._addr, self.REG_CMD,
self.CMD_CURSOR_INVISIBLE + ct)
def get_keypad_state(self):
""" Returns the keypad state, as a bit field. """
data = self._bus.read_block_data(self._addr, self.REG_KEYPAD_LOW, 2)
try:
return (data[1] << 8) + data[0]
except:
# be fault tolerant in case of I/O problem
return 0
@staticmethod
def state_to_keys(state):
""" Converts a keypad state bit field into the corresponding
list of keys. """
keys = []
for k in _keymap:
if state & 1:
keys.append(k)
state >>= 1
return keys
def get_keys(self):
""" Returns the list of keys currently pressed on the keypad. """
return self.state_to_keys(self.get_keypad_state())
def hsep(self, line=None, pattern='-'):

"""Draw an horizontal separator across the display.


Arguments:
line:
the line on which the separator is drawn.
default: the current one.
pattern:
the pattern to be used for the line
default: '-'
"""
s = (pattern * self._width)[:self._width]
if line and line in range(1, self._height + 1):
self.goto_line_col(line, 1)
else:
self.cr()
self.write(s)
def write_at(self, s, line, col):
""" Convenience method to write a text at a given location.
Arguments:
s:
the text
line, col:
the text position
"""
self.goto_line_col(line, col)
self.write(s)
def center_text_at(self, s, line):
""" Convenience method to write a centered text on a given line.
Arguments:
s:
the text
line:
the text line
"""
self.write_at(string.center(s, self._width), line, 1)
def keypad_autoscan_start(self, callback):
""" Starts the automatic keypad scan.
Does nothing if already active.
Arguments:
callback:
a callable to be invoked when a keypad state change has
been detected. The callable will be passed the array of
pressed keys and the instance of the LCD as arguments. So
it must be defined as def ...(keys, lcd)
Returns:
True if started, False if already running

Exceptions:
ValueError:
if callback argument is not a valid callable
"""
if self._scan_thread:
return False
if not callable(callback):
raise ValueError('callback is not a valid callable')
# always work in fast scan
self._bus.write_byte_data(self._addr, self.REG_CMD, self.CMD_KPSCAN_FAST)
self._scan_thread = threading.Thread(None, self._keypad_scanner, name='scanner', args=(callback,))
self._scan_keypad = True
self._scan_thread.start()
return True
def keypad_autoscan_stop(self):
""" Stops the keypad autoscan if active.
Returns:
True if stopped, False if not already running
"""
if self._scan_thread:
self._scan_keypad = False
self._scan_thread = None
return True
else:
return False
def _keypad_scanner(self, callback):
""" Internal method used to scan the keypad. Not to be called directly by externals. """
last_keys = None
while self._scan_keypad:
with self._lock:
keys = self.get_keys()
if keys and keys != last_keys:
callback(keys, self)
last_keys = keys
time.sleep(0.1)

#!/usr/bin/env python
# -*- coding: utf-8 -*""" A specialized I2C interface implementation, for use with USB adapters such as
Robot Electronics one (http://www.robot-electronics.co.uk/htm/usb_i2c_tech.htm)
"""
__author__ = 'Eric Pascual'

import serial
import threading
import time
from .i2c import I2CBus
class USB2I2CBus(I2CBus):
""" USB interface for an I2C bus.
Gives access to an I2C bus via a virtual serial port.
It differs a bit from standard I2C commands for block reads, since the adapter
needs to know how many bytes are expected.
"""
I2C_SGL = 0x53
I2C_MUL = 0x54
I2C_AD1 = 0x55
I2C_AD2 = 0x56
I2C_USB = 0x5A
def __init__(self, dev, **kwargs):
I2CBus.__init__(self, **kwargs)
self._serial = serial.Serial(dev, baudrate=19200, timeout=0.5)
self._serial.flushInput()
self._serial.flushOutput()
# I/O serialization lock to be as much thread safe as possible
self._lock = threading.Lock()
def _send_cmd(self, data):
if isinstance(data, list):
# stringify a byte list
data = ''.join([chr(b) for b in data])
if self._debug or self._simulate:
print(':Tx> %s' % ' '.join('%02x' % ord(b) for b in data))
if self._simulate:
return
with self._lock:
self._serial.write(data)
self._serial.flush()
def _get_reply(self, nbytes):
if self._simulate:
print ('<Rx: -- no Rx data when running in simulated I/O mode --')
return []
with self._lock:
data = []
cnt = 0
maxwait = time.time() + self._serial.timeout
while cnt < nbytes and time.time() < maxwait:
data = data + [ord(c) for c in self._serial.read(nbytes - cnt)]
cnt = len(data)
self._serial.flushInput()

if self._debug:
rx = ' '.join('%02x' % b for b in data)
print('<Rx: %s' % rx)
return data
def read_byte(self, addr):
self._send_cmd([self.I2C_SGL, (addr << 1) + 1])
return self._get_reply(1)[0]
def write_byte(self, addr, data):
self._send_cmd([self.I2C_SGL, addr << 1, data & 0xff])
result = self._get_reply(1)[0]
if not result:
raise RuntimeError('write_byte failed with result=%d' % result)
def read_byte_data(self, addr, reg):
return self.read_block_data(addr, reg, 1)[0]
def write_byte_data(self, addr, reg, data):
return self.write_block_data(addr, reg, [data])
def read_word_data(self, addr, reg):
data = self.read_block_data(addr, reg, 2)
if self.msb_first:
return (data[0] << 8) + data[1]
else:
return (data[1] << 8) + data[0]
def write_word_data(self, addr, reg, data):
if self.msb_first:
return self.write_block_data(addr, reg,
[(data >> 8) & 0xff, data & 0xff])
else:
return self.write_block_data(addr, reg,
[data & 0xff, (data >> 8) & 0xff])
def read_block_data(self, addr, reg, count):
self._send_cmd([self.I2C_AD1, (addr << 1) + 1, reg, count])
return self._get_reply(count)
def write_block_data(self, addr, reg, data):
self._send_cmd([self.I2C_AD1, addr << 1, reg, len(data)] + data)
result = self._get_reply(1)[0]
if not result:
raise RuntimeError('write_block_data failed with result=%d' % result)
#!/usr/bin/env python
# -*- coding: utf-8 -*"""
A couple of classes for interacting with devices accessible on I2C buses.
"""
__author__ = "Eric Pascual (eric@pobot.org)"
import threading

class I2CBus(object):
"""
Abstract root class for I2C interface implementations
"""
def __init__(self, debug=False, simulate=False, msb_first=False):
self.msb_first = msb_first
self._debug = debug
self._simulate = simulate
def read_byte(self, addr):
""" Read a single byte from a device.
:param int addr: the device address
:return: the byte value
:rtype: int
"""
raise NotImplementedError()
def write_byte(self, addr, data):
""" Write a single byte to a device.
:param int addr: the device address
:param int data: the byte value
"""
raise NotImplementedError()
def read_byte_data(self, addr, reg):
""" Read the content of a byte size register of a device
:param int addr: device address
:param int reg: register address on the device
:return: the register content
:rtype: int
"""
raise NotImplementedError()
def write_byte_data(self, addr, reg, data):
""" Set the content of a byte size register of a device
:param int addr: device address
:param int reg: register address on the device
:param int data: the register content
"""
raise NotImplementedError()
def read_word_data(self, addr, reg):
""" Read the content of a word size register of a device
:param int addr: device address
:param int reg: register address on the device
:return: the register content
:rtype: int
"""
raise NotImplementedError()
def write_word_data(self, addr, reg, data):
""" Set the content of a word size register of a device
:param int addr: device address
:param int reg: register address on the device

:param int data: the register content


"""
raise NotImplementedError()
def read_block_data(self, addr, reg):
""" Read a block of bytes, starting at a given register of the device.
The count of bytes of received data is defined by the device.
:param int addr: device address
:param int reg: register address on the device
:return: the data read
:rtype: array of [int]
"""
raise NotImplementedError()
def write_block_data(self, addr, reg, data):
""" Write a block of bytes, starting at a given register of the device
:param int addr: device address
:param int reg: register address on the device
:param data: the bytes to write
:type data: array of [int]
"""
raise NotImplementedError()
try:
from smbus import SMBus
except ImportError:
# in case of smbus support not available, we define a simulation class, usable for unit tests for instance.
class SimulatedSMBus(object):
""" A fake SMBus, allowing to simulate reads by providing the expected reply and tracing data
exchanges.
For non documented methods, just have a look at :py:class:`smbus.SMBus`, since they are identical.
"""
@staticmethod
def _trace(msg):
print('[SMBus.simul] ' + msg)
def __init__(self, bus_id=1):
self._trace('simulated SMBus created for id=%d' % bus_id)
self._bus_id = bus_id
self._data = [0] * 4
def simulate_reply(self, data):
""" Prepare the reply which will be returned by subsequent read(s)
:param data: data to be returned by next read operation
"""
self._data = data[:]
def read_byte(self, addr):
self._trace("reading byte 0x%x from device %d:0x%x" % (self._data[0], self._bus_id, addr))
return self._data[0]
def write_byte(self, addr, value):
self._trace("writing byte 0x%x to device %d:0x%x" % (value, self._bus_id, addr))

def read_byte_data(self, addr, reg):


self._trace("reading byte 0x%x from reg 0x%x of device %d:0x%x" % (self._data[0], reg, self._bus_id, addr))
return self._data[0]
def write_byte_data(self, addr, reg, value):
self._trace("writing byte 0x%x to reg 0x%x of device %d:0x%x" % (value, reg, self._bus_id, addr))
def read_word_data(self, addr, reg):
self._trace("reading word 0x%x from reg 0x%x of device %d:0x%x" % (self._data[0], reg, self._bus_id, addr))
return self._data[0]
def write_word_data(self, addr, reg, value):
self._trace("writing word 0x%x to reg 0x%x of device %d:0x%x" % (value, reg, self._bus_id, addr))
def read_block_data(self, addr, reg):
self._trace("block reading from reg 0x%x of device %d:0x%x -> %s" % (reg, self._bus_id, addr, self._data))
return self._data
def write_block_data(self, addr, reg, data):
self._trace("block writing %s to reg 0x%x of device %d:0x%x" % (data, reg, self._bus_id, addr))
def read_i2c_block_data(self, addr, reg, count):
self._trace("I2C block reading from reg 0x%x of device %d:0x%x -> %s" %
(reg, self._bus_id, addr, self._data)
)
return self._data
def write_i2c_block_data(self, addr, reg, data):
self._trace("I2C block writing %s to reg 0x%x of device %d:0x%x" % (data, reg, self._bus_id, addr))
SMBus = SimulatedSMBus
class MTSMBus(I2CBus):
""" Multi-thread compatible SMBus bus.
This is just a wrapper of SMBus, serializing I/O on the bus for use
in multi-threaded context and adding _i2c_ variants of block transfers.
"""
def __init__(self, bus_id=1, **kwargs):
"""
:param int bus_id: the SMBus id (see Raspberry Pi documentation)
:param kwargs: parameters transmitted to :py:class:`smbus.SMBus` initializer
"""
I2CBus.__init__(self, **kwargs)
self._bus = SMBus(bus_id)
# I/O serialization lock
self._lock = threading.Lock()
def read_byte(self, addr):
with self._lock:
return self._bus.read_byte(addr)
def write_byte(self, addr, data):
with self._lock:

self._bus.write_byte(addr, data)
def read_byte_data(self, addr, reg):
with self._lock:
return self._bus.read_byte_data(addr, reg)
def write_byte_data(self, addr, reg, data):
with self._lock:
self._bus.write_byte_data(addr, reg, data)
def read_word_data(self, addr, reg):
with self._lock:
return self._bus.read_word_data(addr, reg)
def write_word_data(self, addr, reg, data):
with self._lock:
self._bus.write_word_data(addr, reg, data)
def read_block_data(self, addr, reg):
with self._lock:
return self._bus.read_block_data(addr, reg)
def write_block_data(self, addr, reg, data):
with self._lock:
self._bus.write_block_data(addr, reg, data)
def read_i2c_block_data(self, addr, reg, count):
with self._lock:
return self._bus.read_i2c_block_data(addr, reg, count)
def write_i2c_block_data(self, addr, reg, data):
with self._lock:
self._bus.write_i2c_block_data(addr, reg, data)

#!/usr/bin/env python
# -*- coding: utf-8 -*""" This module allows code using GPIOs on the RaspberryPi to be executed on
a system which is NOT a RaspberryPi (your development station for instance).
This does not mean that we are going to play with the GPIOs of the current system
(supposing it has some) but that we will just mimic the original calls, replacing
actions by trace messages, so that you'll be able to unit test your code on the
development station.
On a real hardware, it will transparently switch to RPi.GPIO module to do the real job.
In order to use it, you just have to replace
>>> from RPi import GPIO
by
>>> from pybot.gpio import GPIO
Leave all the other statements involving GPIO untouched.
By default, reading an input will always return a low state. It is possible to define the

simulated state by using something like the following statement before reading the GPIO :
>>> GPIO.input_states[3] = GPIO.HIGH
Subsequent calls to GPIO.input(3) will return GPIO.HIGH instead of the default GPIO.LOW state.
"""
__author__ = 'Eric Pascual'
# test if we are running on a real RasPi or not and define GPIO symbol accordingly
import platform
if platform.machine().startswith('armv6'):
from RPi import GPIO
else:
print('[WARNING] ****** GPIO module is simulated since we are not running on a RaspberryPi ******')
class GPIO(object):
""" This class mimics the original RPi.GPIO module.
(http://sourceforge.net/projects/raspberry-gpio-python)
WARNINGS:
1/ Constant values are NOT the real ones. Anyway, it's a very bad idea to write code depending
on constants real value, since this can change.
2/ the current version does not implement all the methods available in the native GPIO module
for the moment. Maybe this will change as new fake ones will be required.
"""
OUT, IN = 0, 1
LOW, HIGH = 0, 1
BOARD, BCM = 0, 1
PUD_DOWN, PUD_UP = 0, 1
_modes = {
BOARD: 'BOARD',
BCM: 'CHIP'
}
_directions = {
OUT: 'OUTPUT',
IN: 'INPUT'
}
_states = {
LOW: 'LOW',
HIGH: 'HIGH'
}
_pullups = {
PUD_DOWN: 'DOWN',
PUD_UP: 'UP'
}
input_states = {}
@staticmethod
def _trace(msg):

print("[GPIO] %s" % msg)


@staticmethod
def setwarnings(enabled):
GPIO._trace("warnings sets to %s" % enabled)
@staticmethod
def setmode(addressing_mode):
GPIO._trace("addressing mode set to %s" % GPIO._modes[addressing_mode])
@staticmethod
def setup(io_num, direction, pull_up_down=PUD_UP):
GPIO._trace(
"GPIO %d configured as %s with pull up set to %s" % (
io_num, GPIO._directions[direction], GPIO._pullups[pull_up_down]
))
@staticmethod
def output(io_num, state):
GPIO._trace("Output %d set to %s" % (io_num, GPIO._states[state]))
@staticmethod
def input(io_num):
# get the simulated state if defined, otherwise set it to the default value
try:
state = GPIO.input_states[io_num]
except KeyError:
GPIO.input_states[io_num] = state = GPIO.LOW
GPIO._trace("Reading input %d (simulated state=%s)" % (io_num, state))
return state
@staticmethod
def cleanup():
GPIO._trace("GPIOs returned to their default settings")
del platform
#!/usr/bin/env python
# -*- coding: utf-8 -*from __future__ import print_function
import sys
import argparse
def print_err(msg, *args):
'''Prints a message on stderr, prefixing it by [ERROR].
Arguments:
msg:
the message to be printed, which can be a format string
using the string.format specification
args:
optional values passed to the format method
'''
print(("[ERROR] " + str(msg)).format(*args), file=sys.stderr)

def die(*args):
''' Prints an error message on stderr and abors execution with return code
set to 2.
'''
print_err(*args)
sys.exit(2)
def add_argparse_general_options(parser):
''' Adds common general options.
Added options are:
- verbose
- debug mode
Arguments:
parser:
the parser being defined
'''
parser.add_argument('-v', '--verbose', dest='verbose', action='store_true',
help='verbose output')
parser.add_argument('-D', '--debug', dest='debug', action='store_true',
help='activates debug mode')
def get_argument_parser(**kwargs):
''' Returns a command line parser initialized with common settings.
Settings used :
- general options as defined by add_argparse_general_options
The parser is also set for displaying default values in help
'''
parser = argparse.ArgumentParser(
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
**kwargs
)
add_argparse_general_options(parser)
return parser
#!/usr/bin/env python
# -*- coding: utf-8 -*""" A couple of helper functions and classes for using Avahi tools
(instead of going trhough D-Bus path, which is somewhat less simple).
Note: this will run on Linux only, Sorry guys :/
"""
import sys
if not sys.platform.startswith('linux'):
raise Exception('this code runs on Linux only')
import subprocess
def whereis(bin_name):
""" Internal helper for locating the path of a binary.
Just a wrapper of the system command "whereis".

Parameters:
bin_name:
the name of the binary
Returns:
the first reply returned by whereis if any, None otherwise.
"""
output = subprocess.check_output(['whereis', bin_name])\
.splitlines()[0]\
.split(':')[1]\
.strip()
if output:
# returns the first place we found
return output.split()[0]
else:
return None
def find_service(svc_name, svc_type):
""" Finds a given Avahi service, using the avahi-browse command.
Parameters:
svc_name:
the name of the service, without the leading '_'
scv_type:
the type of the service, without the leading '_' and the
trailing "._tcp' suffix
Returns:
a list of matching locations, each entry being a tuple
composed of:
- the name of the host on which the service is published
- its IP address
- the port on which the service is accessible
In case no match is found, an empty list is returned
Raises:
AvahiNotFound if the avahi-browse command is not available
"""
_me = find_service
try:
cmdpath = _me.avahi_browse
except AttributeError:
cmdpath = _me.avahi_browse = whereis('avahi-browse')
if not cmdpath:
raise AvahiNotFound('cannot find avahi-browse')
locations = []
output = subprocess.check_output([cmdpath, '_%s._tcp' % svc_type, '-trp']).splitlines()
for line in [l for l in output if l.startswith('=;')]:
_netif, _ipver, _name, _type, _domain, hostname, hostaddr, port, _desc = \
line[2:].split(';')
if not svc_name or _name == svc_name:
locations.append((hostname, hostaddr, int(port)))
return locations

class AvahiService(object):
""" A simple class wrapping service publishing and unpublishing. """
_cmdpath = whereis('avahi-publish-service')
def __init__(self, svc_name, svc_type, svc_port):
""" Constructor.
Parameters:
svc_name:
the name of the service
svc_type:
the type of the service. The leading '_' and trailing
'._tcp' suffix can be omitted for readability's sake.
They will be added automatically if needed.
"""
if not self._cmdpath:
raise AvahiNotFound('avahi-publish-service not available')
self._process = None
self._svc_name = svc_name
if not (svc_type.startswith('_') and svc_type.endswith('_tcp')):
self._svc_type = '_%s._tcp' % svc_type
else:
self._svc_type = svc_type
self._svc_port = svc_port
def publish(self):
""" Publishes the service.
Starts the avahi-publish-service in a separate process. Do nothing
if the service is already published.
"""
if self._process:
return
self._process = subprocess.Popen([
self._cmdpath,
self._svc_name, self._svc_type, str(self._svc_port)
], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
if not self._process:
raise AvahiError('unable to publish service')
def unpublish(self):
""" Unpublishes the service, if previously published.
Do nothing if the service is not yet publisehd.
"""
if self._process:
self._process.terminate()
self._process = None
class AvahiError(Exception):
""" Root of module specific errors. """
pass

class AvahiNotFound(AvahiError):
""" Dedicated error for command not found situations. """
pass
#!/usr/bin/env python
# -*- coding: utf-8 -*""" iRobote Create robot interface module.
This module provides a class modeling the robot and handling the communication
with the Create via its serial link. It also includes various additional
definitions and helpers.
Refer to the iRobot Create Open Interface reference document available on
iRobot Web site (http://www.irobot.com/filelibrary/pdfs/hrd/create/Create%20Open%20Interface_v2.pdf)
"""
__author__ = "Eric Pascual"
__email__ = "eric@pobot.org"
__copyright__ = "Copyright 2013, POBOT"
__version__ = "1.0"
__date__ = "June. 2013"
__status__ = "Development"
__license__ = "LGPL"
import serial
import threading
import time
import struct
import math
from collections import namedtuple
#
# Create physical characteristics
#
WHEEL_TO_WHEEL_DIST = 260 # mm
MAX_ABSOLUTE_SPEED = 500 # mm/sec
#
# Open Interface command opcodes
#
OI_SOFT_RESET
OI_START
OI_MODE_PASSIVE
OI_MODE_SAFE
OI_MODE_FULL
OI_DRIVE
OI_DRIVE_DIRECT
OI_LEDS
OI_DIGITAL_OUTPUTS
OI_LOW_SIDE_DRIVERS
OI_SEND_IR
OI_READ_SENSORS
OI_READ_SENSOR_LIST
OI_STREAM_SENSOR_LIST

=7
= 128
= OI_START
= 131
= 132
= 145
= 147
= 138
= 149
= 148

= 137
= 139
= 151
= 142

OI_STREAM_PAUSE_RESUME
OI_SCRIPT_DEFINE
OI_SCRIPT_PLAY
OI_SCRIPT_SHOW
OI_WAIT_TIME
OI_WAIT_DISTANCE
OI_WAIT_ANGLE
OI_WAIT_EVENT

=
=
=
=
= 155

150
152
153
154

= 156
= 157
= 158

#
# LEDs related defines
#
# power LED color
OI_LED_GREEN = 0
OI_LED_YELLOW = 63
OI_LED_ORANGE = 127
OI_LED_RED
# power LED pre-defined
OI_LED_OFF
OI_LED_FULL
OI_LED_ON

= 255
intensities
=0
= 255
= OI_LED_FULL

# "play" LED bit mask


OI_LED_PLAY
=2
# "advance" LED bit mask
OI_LED_ADVANCE = 8
OI_ALL_LEDS

= OI_LED_PLAY | OI_LED_ADVANCE

#
# Buttons masks and names
#
OI_BUTTONS_PLAY
=1
OI_BUTTONS_ADVANCE
=4
OI_ALL_BUTTONS
= OI_BUTTONS_PLAY | OI_BUTTONS_ADVANCE
OI_BUTTON_NAMES = {
1: "play",
4: "advance"
}
#
# Digital outputs
#
OI_DOUT_0
OI_DOUT_1
OI_DOUT_2
OI_ALL_DOUTS

=1
=2
=4
= OI_DOUT_0 | OI_DOUT_1 | OI_DOUT_2

#
# Low side drivers
#
OI_DRIVER_0

=1

OI_DRIVER_1
=2
OI_DRIVER_2
=4
OI_ALL_DRIVERS = OI_DRIVER_0 | OI_DRIVER_1 | OI_DRIVER_2
#
# Special values for the radius parameter of the "drive" command
#
OI_DRIVE_STRAIGHT
OI_SPIN_CW
OI_SPIN_CCW

= 0x8000
= 0xFFFF
= 0x0001

#
# Logic sensors masks
#
OI_BUMPER_RIGHT
OI_BUMPER_LEFT
OI_WHEEL_DROP_RIGHT
OI_WHEEL_DROP_LEFT
OI_CASTER_DROP
OI_ALL_SENSORS
= 0x1f

=
=
=
=

0x02
0x04
0x08
0x10

= 0x01

#
# Drivers and wheel overcurrent flags masks
#
OI_OVERCURRENT_DRV_0 = 0x02
OI_OVERCURRENT_DRV_1 = 0x01
OI_OVERCURRENT_DRV_2 = 0x04
OI_OVERCURRENT_WHEEL_R = 0x08
OI_OVERCURRENT_WHEEL_L = 0x10
#
# Sensor packets
#
OI_PACKET_GROUP_0

=0

OI_PACKET_GROUP_1
OI_PACKET_GROUP_SENSOR_STATES

=1
= OI_PACKET_GROUP_1

OI_PACKET_BUMPS_AND_WHEEL_DROPS
OI_PACKET_WALL
OI_PACKET_CLIFF_LEFT
OI_PACKET_CLIFF_FRONT_LEFT
OI_PACKET_CLIFF_FRONT_RIGHT
OI_PACKET_CLIFF_RIGHT
OI_PACKET_VIRTUAL_WALL
OI_PACKET_OVERCURRENT
= 14
OI_PACKET_UNUSED_1
OI_PACKET_UNUSED_2

=7

OI_PACKET_GROUP_2
OI_PACKET_GROUP_UI_AND_ODOMETRY
OI_PACKET_RECEIVED_IR_BYTE

=
=
=
=
=

=8

9
10
11
12
13
= 15
= 16

=2
= OI_PACKET_GROUP_2
= 17

OI_PACKET_BUTTONS
OI_PACKET_DISTANCE
OI_PACKET_ANGLE

= 18
= 19

OI_PACKET_GROUP_3
OI_PACKET_GROUP_BATTERY

=2
= OI_PACKET_GROUP_3

= 20

OI_PACKET_CHARGING_STATE
OI_PACKET_BATTERY_VOLTAGE
OI_PACKET_BATTERY_CURRENT
OI_PACKET_BATTERY_TEMPERATURE
OI_PACKET_BATTERY_CHARGE
OI_PACKET_BATTERY_CAPACITY

=
=
=
=
=
=

21
22
23
24
25
26

OI_PACKET_GROUP_4
OI_PACKET_GROUP_SIGNALS

=4
= OI_PACKET_GROUP_4

OI_PACKET_WALL_SIGNAL
OI_PACKET_CLIFF_LEFT_SIGNAL
OI_PACKET_CLIFF_FRONT_LEFT_SIGNAL
OI_PACKET_CLIFF_FRONT_RIGHT_SIGNAL
OI_PACKET_CLIFF_RIGHT_SIGNAL
OI_PACKET_CARGO_BAY_DIGITAL_IN
OI_PACKET_CARGO_BAY_ANALOG_IN
OI_PACKET_CHARGING_SOURCES

=
=
=
=

29
30
31
32

OI_PACKET_GROUP_5
OI_PACKET_GROUP_MISC

OI_PACKET_GROUP_6
OI_PACKET_GROUP_ALL

= 35
= 36
= 37
= 39
= 41
= 42

= 38
= 40

=6
= OI_PACKET_GROUP_6
= OI_PACKET_GROUP_0
= OI_PACKET_REQUESTED_VELOCITY_LEFT

#
# Packet sizes array, indexed by packets ids
#
OI_PACKET_SIZES = [
26, 10, 6, 10, 14, 12, 52,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 2, 2,
1, 2, 2, 1, 2, 2,
2, 2, 2, 2, 2, 1, 2, 1,
1, 1, 1, 1, 2, 2, 2, 2
]

= 33
= 34
=5
= OI_PACKET_GROUP_5

OI_PACKET_CURRENT_MODE
OI_PACKET_SONG_NUMBER
OI_PACKET_SONG_PLAYING
OI_PACKET_STREAM_PACKETS_COUNT
OI_PACKET_REQUESTED_VELOCITY
OI_PACKET_REQUESTED_RADIUS
OI_PACKET_REQUESTED_VELOCITY_RIGHT
OI_PACKET_REQUESTED_VELOCITY_LEFT

OI_PACKET_MIN
OI_PACKET_MAX

= 27
= 28

# Packet header byte when straming is used


OI_PACKET_HEADER

= 19

#
# Array of the npack formats for packet groups,
# indexed by the group id
PACKET_STRUCT_FORMATS = [
None,
'!BBBBBBBBBB',
'!BBhh',
'!BHhbHH',
'!HHHHHBHB',
'!BBBBhhhh',
None
]
# compute the formats of compound packets
PACKET_STRUCT_FORMATS[0] = \
PACKET_STRUCT_FORMATS[1] + \
PACKET_STRUCT_FORMATS[2][1:] + \
PACKET_STRUCT_FORMATS[3][1:]
PACKET_STRUCT_FORMATS[6] = \
PACKET_STRUCT_FORMATS[0] + \
PACKET_STRUCT_FORMATS[4][1:] + \
PACKET_STRUCT_FORMATS[5][1:]
#
# Named tuples used for packet groups friendly unpacking
#
GroupPacket1 = namedtuple('GroupPacket1', [
'bumps_and_wheels',
'wall',
'cliff_left',
'cliff_front_left',
'cliff_front_right',
'cliff_right',
'virtual_wall',
'overcurrents',
'unused1',
'unused2'
])
GroupPacket2 = namedtuple('GroupPacket2', [
'ir_byte',
'buttons',
'distance',
'angle'
])
GroupPacket3 = namedtuple('GroupPacket3', [
'charging_state',
'voltage',
'current',
'battery_temp',
'battery_charge',

])

'battery_capacity'

GroupPacket4 = namedtuple('GroupPacket4', [
'wall_signal',
'cliff_left_signal',
'cliff_front_left_signal',
'cliff_front_right_signal',
'cliff_right_signal',
'user_dins',
'user_ain',
'charging_sources'
])
GroupPacket5 = namedtuple('GroupPacket5', [
'oi_mode',
'song_number',
'song_playing',
'nb_of_stream_packets',
'velocity',
'radius',
'right_velocity',
'left_velocity'
])
# Index of namedtuples, keyed by the corresponding group packet id
GROUP_PACKET_CLASSES = [
None,
GroupPacket1,
GroupPacket2,
GroupPacket3,
GroupPacket4,
GroupPacket5
]
#
# Stream listener FSM states (internal use)
#
_STATE_IDLE = 0
_STATE_GOT_HEADER = 1
_STATE_GOT_LENGTH = 2
_STATE_IN_PACKET = 3
_STATE_IN_CHECKSUM = 4
#
# Wait events
#
OI_WAIT_WHEEL_DROP
=1
OI_WAIT_FRONT_WHEEL_DROP
=2
OI_WAIT_LEFT_WHEEL_DROP
=3
OI_WAIT_RIGHT_WHEEL_DROP
=4
OI_WAIT_BUMP
=5
OI_WAIT_LEFT_BUMP
=6
OI_WAIT_RIGHT_BUMP
=7

OI_WAIT_VIRTUAL_WALL
=8
OI_WAIT_WALL
=9
OI_WAIT_CLIFF
= 10
OI_WAIT_LEFT_CLIFF
= 11
OI_WAIT_FRONT_LEFT_CLIFF
= 12
OI_WAIT_FRONT_RIGHT_CLIFF
= 13
OI_WAIT_RIGHT_CLIFF
= 14
OI_WAIT_HOME_BASE
= 15
OI_WAIT_BTN_ADVANCE
= 16
OI_WAIT_BTN_PLAY
= 17
OI_WAIT_DIN0
= 18
OI_WAIT_DIN1
= 19
OI_WAIT_DIN2
= 20
OI_WAIT_DIN3
= 21
OI_WAIT_PASSIVE_MODE
= 22
OI_WAIT_EVENT_MIN
OI_WAIT_EVENT_MAX

= OI_WAIT_WHEEL_DROP
= OI_WAIT_PASSIVE_MODE

def inverse(b):
""" Two-complement inverse value computation, use to wait
for the negation of a given event in the above list."""
return (~b & 0xff) + 1
#
# Command builder
#
class Command(object):
""" The Command class is a collection of static methods returning the
bytes sequence corresponding to a given command.
It allows a more friendly and readable elaboration of commands, especially
when defining scripts.
Commands naming is based on the content of the "Open Interface Command
Reference" chapter of the documentation. Please refer to this document for
a full explanation of the commands parameters.
"""
@staticmethod
def start():
return [OI_START]
@staticmethod
def reset():
return [OI_SOFT_RESET]
@staticmethod
def mode(mode):
if mode in [OI_MODE_FULL, OI_MODE_PASSIVE, OI_MODE_SAFE]:
return [mode]
else:
raise ValueError('invalid mode')
@staticmethod
def drive(velocity, radius=OI_DRIVE_STRAIGHT):

return [OI_DRIVE] + _int16_to_bytes(velocity) + _int16_to_bytes(radius)


@staticmethod
def drive_direct(velocity_right, velocity_left):
return [OI_DRIVE_DIRECT] + _int16_to_bytes(velocity_right) + _int16_to_bytes(velocity_left)
@staticmethod
def stop():
return Command.drive_direct(0, 0)
@staticmethod
def leds(led_bits, power_color=OI_LED_GREEN, power_level=OI_LED_FULL):
return [OI_LEDS, led_bits & OI_ALL_LEDS, power_color & 0xff, power_level & 0xff]
@staticmethod
def digital_outputs(states):
return [OI_DIGITAL_OUTPUTS, states & OI_ALL_DOUTS]
@staticmethod
def low_side_drivers(states):
return [OI_LOW_SIDE_DRIVERS, states & OI_ALL_DRIVERS]
@staticmethod
def send_ir(data):
return [OI_SEND_IR, data & 0xff]
@staticmethod
def sensors(packet_id):
if OI_PACKET_MIN <= packet_id <= OI_PACKET_MAX:
return [OI_READ_SENSORS, packet_id]
else:
raise ValueError('invalid packet id (%d)' % packet_id)
@staticmethod
def query_list(packet_ids):
return [OI_READ_SENSOR_LIST, len(packet_ids)] + packet_ids
@staticmethod
def stream(packet_ids):
return [OI_STREAM_SENSOR_LIST, len(packet_ids)] + packet_ids
@staticmethod
def stream_pause_resume(state):
return [OI_STREAM_PAUSE_RESUME, state & 0x01]
@staticmethod
def define_script(script):
return [OI_SCRIPT_DEFINE, len(script)] + script
@staticmethod
def play_script():
return [OI_SCRIPT_PLAY]
@staticmethod
def wait_time(delay):
return [OI_WAIT_TIME, delay & 0xff]

@staticmethod
def wait_distance(distance):
return [OI_WAIT_DISTANCE] + _int16_to_bytes(distance)
@staticmethod
def wait_angle(angle):
return [OI_WAIT_ANGLE] + _int16_to_bytes(angle)
@staticmethod
def wait_event(event):
return [OI_WAIT_EVENT, event & 0xff]
def _hex_format(b):
return '%02x' % ord(b)
def _dec_format(b):
return str(ord(b))
class IRobotCreate(object):
""" Models the Create robot and provides convenient methods to interact with it.
Note that this class does not make use of the Command one to avoid stacking
too many method calls. It could have be done to avoid duplicating in some
way the command definitions, but the benefit didn't compensate the drawbacks.
"""
def __init__(self, port, baudrate=57600, debug=False, simulate=False, hexdebug=False):
""" Constructor:
Arguments:
port:
the serial port used for the serial link with it
baudrate:
serial link speed (default: 57600)
debug:
if True, activates the trace of serial data exchanges and various
other debugging help
simulate:
if True, simulate data exchanges
hexdebug:
displays bytes in hex format in the trace (default format is
decimal, since used by all the examples included in the documentation)
"""
self._serial = serial.Serial(port, baudrate=baudrate, timeout=0.5)
self._serial.flushInput()
self._serial.flushOutput()
# I/O serialization lock to be as much thread safe as possible
self._lock = threading.Lock()
self._debug = debug
self._debug_fmt = _hex_format if hexdebug else _dec_format
self._hexdebug = hexdebug
self._simulate = simulate
self._stream_listener = None
self._timer = None

@property
def serial(self):
""" Access to the serial link instance."""
return self._serial
@property
def debug_settings(self):
""" Provides the current debug settings as a tuple containing :
- the debug status
- the debug format for serial data trace
- the simulate option state
"""
return (self._debug, self._debug_fmt, self._simulate)
def _send_block(self, data):
if not isinstance(data, str):
# stringify a byte list
data = ''.join([chr(b) for b in data])
if self._debug or self._simulate:
print(':Tx> %s' % ' '.join(self._debug_fmt(b) for b in data))
if self._simulate:
return
with self._lock:
self._serial.write(data)
self._serial.flush()
def _send_byte(self, byte):
self._send_block(chr(byte))
def _get_reply(self, nbytes):
if self._simulate:
print ('<Rx: -- no Rx data when running in simulated I/O mode --')
return []
with self._lock:
data = ''
cnt = 0
maxwait = time.time() + self._serial.timeout
while cnt < nbytes and time.time() < maxwait:
data = data + self._serial.read(nbytes - cnt)
cnt = len(data)
self._serial.flushInput()
if self._debug:
rx = ' '.join(self._debug_fmt(b) for b in data)
print('<Rx: %s' % rx)
return data
def start(self, mode=OI_MODE_PASSIVE):
""" Initializes the Create.
Includes a short pause for letting enough time to the beast for
getting ready.

Parameters:
mode:
the mode in which to place the Create (default: passive)
Raises:
ValueError if mode parameter is not of the expected ones
"""
self._send_byte(OI_START)
if mode != OI_MODE_PASSIVE:
self.set_mode(mode)
time.sleep(1)
def reset(self):
""" Soft reset of the Create."""
self._send_byte(OI_SOFT_RESET)
def set_mode(self, mode):
""" Sets the operating mode of the Create.
Parameters:
mode:
the mode in which to place the Create (default: passive)
Raises:
ValueError if mode parameter is not of the expected ones
"""
if mode in [OI_MODE_PASSIVE, OI_MODE_SAFE, OI_MODE_FULL]:
self._send_byte(mode)
# force the power LED to stay on in full mode (otherwise one may
# think the beast has been turned off
if mode == OI_MODE_FULL:
self.set_leds(0, OI_LED_GREEN, OI_LED_FULL)
else:
raise ValueError('invalid mode (%s)' % mode)
def passive_mode(self):
""" Shorthand form for passive mode setting."""
self.set_mode(OI_MODE_PASSIVE)
def safe_mode(self):
""" Shorthand form for safe mode setting."""
self.set_mode(OI_MODE_SAFE)
def full_mode(self):
""" Shorthand form for full mode setting."""
self.set_mode(OI_MODE_FULL)
def _delayed_stop_move(self):
self.stop_move()
self._timer = None
def _cancel_delayed_stop_move(self):
if self._timer:
self._timer.cancel()
self._timer = None
def drive(self, velocity, radius=OI_DRIVE_STRAIGHT, distance=None):

""" Makes the robot drive, based on speed and path curvature.
If a distance is provided, a delayed stop is initiated (using a Timer),
based on the time for this distance to be traveled using the requested
speed.
If no distance is provided, it is up to the caller to manage when to stop
or change the move.
In both cases, the methods exits immediatly.
Note:
The distance checking is implemented using a poor man's strategy
based on the ETA (estimated time of arrival).
Another strategy would be to create a short script using a wait_distance
command and run it. Although it has chances to be more accurate, this
has the nasty drawback to "mute the line" while the script is running,
and thus preventing any event (such as bumper hit) or commands to be
exchanged.
Parameters:
velocity:
robot's center velocity (in mm/sec)
radius:
radius (in mm) of the path to be folowed
(defaut: special value corresponding to a straight path
distance:
distance to be traveled (in mm). If provided, the method
will exists only after this distance is supposed to be traveled,
based on the requested speed. If no distance is provided, the
method exists at once, leaving to robot moving.
Raises:
ValueError if velocity is outside valid range
"""
# be sure we will not get a pending stop during the move
self._cancel_delayed_stop_move()
if distance is not None and distance == 0:
return
if not -MAX_ABSOLUTE_SPEED <= velocity <= MAX_ABSOLUTE_SPEED:
raise ValueError('invalid velocity (%f)' % velocity)
self._send_block([OI_DRIVE] + _int16_to_bytes(velocity) + _int16_to_bytes(radius))
if distance:
self._timer = threading.Timer(
time_for_distance(distance, velocity),
self._delayed_stop_move
)
self._timer.start()
def drive_direct(self, left_vel, right_vel):
""" Makes the robot drive by directly setting the wheel speeds.
No distance control is proposed here.

Parameters:
left_vel, right_vel:
wheel velocities (in mm/sec)
"""
# be sure we will not get a pending stop during the move
self._cancel_delayed_stop_move()
self._send_block([OI_DRIVE_DIRECT] + _int16_to_bytes(left_vel) + _int16_to_bytes(right_vel))
def stop_move(self):
""" Stops any current move."""
self._cancel_delayed_stop_move()
self.drive_direct(0, 0)
def spin(self, velocity, spin_dir=OI_SPIN_CW, angle=None):
""" Makes the robot spin on itself at a given speed.
If a spin angle is provided, the method will wait for the time for this angle
to be reached (based on the requested speed) and then stops the robot and exit.
In addition, the provided spin direction (if any) is ignored, and replaced by
the one infered from the angle sign (positive = CCW)
If no angle is provided, the move is initiated and the method exits immdiatly,
leaving the robot moving. It is the responsability of the caller to handle
what should happen then.
See drive() method documentation for implementation note about the strategy
used to track the angle.
Parameters:
velocity:
robot's wheels velocity (in mm/sec)
spin_dir:
the spin direction (CW or CCW) (default: CW)
angle:
optional spin angle (in degrees)
Raises:
ValueError if spin direction is invalid
"""
# be sure we will not get a pending stop during the move
self._cancel_delayed_stop_move()
if angle is not None:
if angle != 0:
spin_dir = OI_SPIN_CCW if angle > 0 else OI_SPIN_CW
else:
return
elif spin_dir not in [OI_SPIN_CCW, OI_SPIN_CW]:
raise ValueError('invalid spin direction (%d)' % spin_dir)
self.drive(velocity, spin_dir)
if angle:
self._timer = threading.Timer(
time_for_angle(angle, velocity),
self._delayed_stop_move
)

self._timer.start()
def set_leds(self, led_bits, pwr_color, pwr_lvl):
""" Changes the state of the Create LEDs.
Parameters:
led_bits:
a bits mask providing the ON status of the PLAY and ADVANCE LEDs
pwr_color:
the color of the POWER LED
pwr_level:
the intensity of the POWER LED
"""
self._send_block([OI_LEDS,
led_bits & OI_ALL_LEDS,
pwr_color & 0xff,
pwr_lvl & 0xff
])
def set_digital_outs(self, states):
""" Changes the state of the Create digital outputs.
Parameters:
states:
a bit mask containing the state of the DOUTs
"""
self._send_block([OI_DIGITAL_OUTPUTS,
states & OI_ALL_DOUTS
])
def set_low_side_drivers(self, states):
""" Changes the state of the Create low-side drivers.
Parameters:
states:
a bit mask containing the state of the LSDs
"""
self._send_block([OI_LOW_SIDE_DRIVERS,
states & OI_ALL_DRIVERS
])
def send_ir(self, data):
""" Emits an IR byte.
Parameters:
data:
the byte to be sent
"""
self._send_block([OI_SEND_IR,
data & 0xff
])
def get_sensor_packet(self, packet_id):
""" Gets a sensor packet or packets group.
Parameters:
packet_id:

the id of the requested packet or group


Returns:
the reply returned by the Create
Raises:
ValueError if invalid packet id
"""
if not OI_PACKET_MIN <= packet_id <= OI_PACKET_MAX:
raise ValueError('invalid packet id (%d)' % packet_id)
self._send_block([OI_READ_SENSORS,
packet_id & 0xff
])
return self._get_reply(OI_PACKET_SIZES[packet_id])
def get_unpacked_sensor_packet(self, packet_id):
""" Convenience method wich returns an unpacked form for some
of the packet groups.
It only works for groups 1 to 5.
Parameters:
packet_id:
the requested packet group id
Returns:
the reply unpacked as the corresponding namedtuple
Raises:
ValueError in packet_id is out of range
"""
if not 1 <= packet_id <= 5:
raise ValueError('out of range packet id (%d)' % packet_id)
data = self.get_sensor_packet(packet_id)
return GROUP_PACKET_CLASSES[packet_id]._make( #pylint: disable=W0212
struct.unpack_from(PACKET_STRUCT_FORMATS[packet_id], data)
)
def get_sensor_packet_list(self, packet_ids):
""" Gets a list of sensor packets.
Parameters:
packed_ids:
a list containing the ids of the requested packets
Returns:
a list containing the corresponding packets returned by the Create
"""
self._send_block([OI_READ_SENSOR_LIST, len(packet_ids)] + packet_ids)
nbytes = reduce(
lambda x, y: x + y,
[OI_PACKET_SIZES[id_] for id_ in packet_ids if 0 <= id_ <= 42]
)
return self._get_reply(nbytes)

def get_unpacked_sensor_packet_list(self, packet_ids):


""" Same as get_unpacked_sensor_packet, but for a list of packets.
Same restrictions apply for the packet ids.
Parameters:
packet_ids:
a list containing the ids of the requested packets
"""
data = self.get_sensor_packet_list(packet_ids)
res = []
pos = 0
for id_ in packet_ids:
if not 1 <= id_ <= 5:
raise ValueError('out of range packet id (%d)' % id_)
lg = OI_PACKET_SIZES[id_]
res.append(
GROUP_PACKET_CLASSES[id_]._make(
struct.unpack_from(
PACKET_STRUCT_FORMATS[id_],
data[pos : pos + lg]
)
)
)
pos += lg
return res

#pylint: disable=W0212

def get_buttons(self):
""" Gets the current state of the buttons, as the list of pressed ones.
The returned list contains the button ids (OI_BUTTONS_xxx) and can be
empty if no button is currently pressed.
"""
reply = self.get_sensor_packet(OI_PACKET_BUTTONS)
return byte_to_buttons(reply[0])
def get_bumpers(self):
""" Gets the current state of the bumper, as the list of pressed parts.
The returned list contains the bumper part ids (OI_BUMPER_xxx) and can be
empty if the bumper is currently not pressed.
"""
reply = self.get_sensor_packet(OI_PACKET_BUMPS_AND_WHEEL_DROPS)
return byte_to_bumpers(reply[0])
def get_wheel_drops(self):
""" Gets the current state of the wheel drop sensors, as the list of
dropped ones.
The returned list contains the drop sensor (OI_WHEEL_DROP_xxx, OI_CASTER_DROP)
and can be empty if all wheels are on the ground.
"""
reply = self.get_sensor_packet(OI_PACKET_BUMPS_AND_WHEEL_DROPS)
return byte_to_wheel_drops(reply[0])

def define_script(self, script):


""" Records a script to be played later.
The Command class can be used to make easier the building of the
script bytes sequence.
Parameters:
script:
the script, as its bytes sequence
"""
self._send_block([OI_SCRIPT_DEFINE, len(script) & 0xff] + script)
def play_script(self):
""" Plays the previously recorded script."""
self._send_byte(OI_SCRIPT_PLAY)
def _start_stream_listener(self, packet_event):
self._stream_listener = StreamListener(self, packet_event)
self._stream_listener.start()
def _kill_stream_listener(self):
self._stream_listener.stop()
self._stream_listener = None
def stream_packets(self, packet_ids, packet_event):
""" Starts the continuous stream of sensor packets.
Parameters:
packet_ids:
the list of the ids of the packet to be streamed
packet_event:
an instance of threading.Event used to signal the availability
of packets and communicate them to the caller
"""
if not self._stream_listener:
self._start_stream_listener(packet_event)
self._send_block([OI_STREAM_SENSOR_LIST, len(packet_ids) & 0xff] + packet_ids)
def stream_pause(self, paused):
""" Pauses or resumes the packets streaming."""
self._send_block([OI_STREAM_PAUSE_RESUME, 1 if paused else 0])
def stream_shutdown(self):
""" Shutdowns an ongoing packet streaming.
It is a good habit to call this method before leaving the application,
to avoid letting orphan threads alive. Calling it if not streaming is
active does not harm, since not done.
"""
if self._stream_listener:
self.stream_pause(True)
self._kill_stream_listener()
class StreamListener(threading.Thread):
""" The packet stream listener.

This class is internal use only and is not supposed to be used


by the application.
"""
def __init__(self, robot, packet_event):
threading.Thread.__init__(self)
self.name = 'stream_listener'
self._stopevent = threading.Event()
self._robot = robot
self._packet_event = packet_event
def run(self):
_serial = self._robot.serial
debug, _dbgfmt, _siumlate = self._robot.debug_settings
_serial.flushInput()
state = _STATE_IDLE
packets = []
total_expected = expected_bytes = 0
packet_id = 0
packet_data = ''
while not self._stopevent.isSet():
while _serial.inWaiting():
b = ord(_serial.read()[0])
if debug:
print('<Rx: %d - state=%d - total_expected=%d - expected_bytes=%d' %
(b, state, total_expected, expected_bytes)
)
if state == _STATE_IDLE:
if b == OI_PACKET_HEADER:
packets = []
state = _STATE_GOT_HEADER
elif state == _STATE_GOT_HEADER:
total_expected = b
state = _STATE_GOT_LENGTH
elif state == _STATE_GOT_LENGTH:
packet_id = b
packet_data = ''
expected_bytes = OI_PACKET_SIZES[packet_id]
total_expected -= 1
state = _STATE_IN_PACKET
elif state == _STATE_IN_PACKET:
packet_data += chr(b)
total_expected -= 1
expected_bytes -= 1
if expected_bytes == 0:
packets.append((packet_id, packet_data))
if total_expected == 0:
state = _STATE_IN_CHECKSUM
elif state == _STATE_IN_CHECKSUM:
# don't care checking for the moment
# notify a set of packets is available
self._packet_event.packets = packets
self._packet_event.set()

state = _STATE_IDLE
# check if someone requested us to stop
self._stopevent.wait(0.01)
def stop(self):
self._stopevent.set()
def _int16_to_bytes(v):
""" Convenience function to convert a 16 bits int into the corresponding
bytes sequence."""
i16 = int(v)
return [(i16 & 0xff00) >> 8, i16 & 0xff]
def byte_to_buttons(byte):
""" Convenience function to convert a bit mask byte into a list of button ids."""
return [b for b in [OI_BUTTONS_PLAY, OI_BUTTONS_ADVANCE] if b & int(byte)]
def byte_to_bumpers(byte):
""" Convenience function to convert a bit mask byte into a list of bumper parts."""
return [b for b in [OI_BUMPER_LEFT, OI_BUMPER_RIGHT] if b & int(byte)]
def byte_to_wheel_drops(byte):
""" Convenience function to convert a bit mask byte into a list of wheel drop sensors."""
return [b for b in [OI_WHEEL_DROP_LEFT, OI_WHEEL_DROP_RIGHT, OI_CASTER_DROP] if b & int(byte)]
def leds_to_byte(leds):
""" Convenience function to convert a list of LEDs to the corresponding bit mask."""
return reduce(lambda x, y : x | y, int(leds))
def dump_group_packets(gpackets):
""" Helper function printing a list of packets in a friendly way.
Works only for packets havind a namedtuple associated representation.
"""
if type(gpackets) is not list:
gpackets = [gpackets]
for pkt in gpackets:
print("%s : " % type(pkt).__name__)
for k, v in pkt._asdict().iteritems():
#pylint: disable=W0212
print(" - %s : %s" % (k, v))
def time_for_distance(dist_mm, velocity):
""" Returns the theoritical time required to travel a given distance
at a given speed."""
return abs(float(dist_mm) / velocity)
def time_for_angle(angle_deg, velocity):
""" Returns the theoritical time required to spin a given angle
with a given wheel speed."""
return time_for_distance(
math.radians(angle_deg) * WHEEL_TO_WHEEL_DIST / 2,
velocity
)
def make_script(*commands):

""" Returns the bytes sequence of a script composed of the given commands."""
return reduce(lambda x, y: x + y, commands)
#!/usr/bin/env python
# -*- coding: utf-8 -*#pylint: disable=W0401,W0614
""" iRobote Create robot interface module.
This module provides a class modeling the robot and handling the communication
with the Create via its serial link. It also includes various additional
definitions and helpers.
Refer to the iRobot Create Open Interface reference document available on
iRobot Web site (http://www.irobot.com/filelibrary/pdfs/hrd/create/Create%20Open%20Interface_v2.pdf)
"""
__author__ = "Eric Pascual"
__email__ = "eric@pobot.org"
__copyright__ = "Copyright 2013, POBOT"
__version__ = "1.0"
__date__ = "June. 2013"
__status__ = "Development"
__license__ = "LGPL"
import math
from .defs import *
def inverse(b):
""" Two-complement inverse value computation, use to wait
for the negation of a given event in the above list."""
return (~b & 0xff) + 1
def int16_to_bytes(v):
""" Convenience function to convert a 16 bits int into the corresponding
bytes sequence."""
i16 = int(v)
return [(i16 & 0xff00) >> 8, i16 & 0xff]
def byte_to_buttons(byte):
""" Convenience function to convert a bit mask byte into a list of button ids."""
return [b for b in [OI_BUTTONS_PLAY, OI_BUTTONS_ADVANCE] if b & byte]
def byte_to_bumpers(byte):
""" Convenience function to convert a bit mask byte into a list of bumper parts."""
return [b for b in [OI_BUMPER_LEFT, OI_BUMPER_RIGHT] if b & byte]
def byte_to_wheel_drops(byte):
""" Convenience function to convert a bit mask byte into a list of wheel drop sensors."""
return [b for b in [OI_WHEEL_DROP_LEFT, OI_WHEEL_DROP_RIGHT, OI_CASTER_DROP] if b & byte]

def byte_to_drivers(byte):
""" Convenience function to convert a bit mask byte into a list of driver ids."""
return [b for b in [OI_DRIVER_0, OI_DRIVER_1, OI_DRIVER_2] if b & byte]
def leds_to_byte(leds):
""" Convenience function to convert a list of LEDs to the corresponding bit mask."""
return reduce(lambda x, y : x | y, int(leds))
def dump_group_packets(gpackets):
""" Helper function printing a list of packets in a friendly way.
Works only for packets having a namedtuple associated representation.
"""
if type(gpackets) is not list:
gpackets = [gpackets]
for pkt in gpackets:
print("%s : " % type(pkt).__name__)
for k, v in pkt._asdict().iteritems():
#pylint: disable=W0212
print(" - %s : %s" % (k, v))
def time_for_distance(dist_mm, velocity):
""" Returns the theoretical time required to travel a given distance
at a given speed."""
return abs(float(dist_mm) / float(velocity))
def time_for_angle(angle_deg, velocity):
""" Returns the theoretical time required to spin a given angle
with a given wheel speed."""
return time_for_distance(
math.radians(angle_deg) * WHEEL_TO_WHEEL_DIST / 2,
velocity
)
def make_script(*commands):
""" Returns the bytes sequence of a script composed of the given commands."""
return reduce(lambda x, y: x + y, commands)

class Song(object):
""" Song helper
"""
_MUSIC_SCALE = ('C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B')
_REST = 0
def __init__(self):
self._score = []
@staticmethod
def note_number(note, octave=0):
note = note.upper()

if note not in Song._MUSIC_SCALE:


raise ValueError('invalid note')
if not 0 <= octave <= 8:
raise ValueError('invalid octave')
res = 24 + octave * len(Song._MUSIC_SCALE) + Song._MUSIC_SCALE.index(note)
if 31 <= res <= 127:
return res
else:
raise ValueError('out of range note/octave')
@property
def score(self):
return self._score
def add_note(self, note, octave, duration):
self._score.append(Song.note_number(note, octave) if note else Song._REST)
self._score.append(duration)
return self
def clear(self):
self._score = []
def encode(self, notes):
self._score = []
for note, octave, duration in notes:
self.add_note(note, octave, duration)
def split(self):
lg = 2 * OI_SONG_MAX_LEN
return [
self._score[i:i+lg] for i in xrange(0, len(self._score), lg)
]
def change_tempo(self, ratio):
if not self._score:
return
for i in xrange(0, len(self._score), 2):
self._score[i] = int(self._score[i] * ratio) & 0xff
#!/usr/bin/env python
# -*- coding: utf-8 -*""" iRobote Create robot interface package.
Refer to the iRobot Create Open Interface reference document available on
iRobot Web site (http://www.irobot.com/filelibrary/pdfs/hrd/create/Create%20Open%20Interface_v2.pdf)
Constants definitions.
"""
__author__ = "Eric Pascual"
__email__ = "eric@pobot.org"
__copyright__ = "Copyright 2013, POBOT"
__version__ = "1.0"
__date__ = "June. 2013"

__status__ = "Development"
__license__ = "LGPL"
from collections import namedtuple
#
# Create physical characteristics
#
WHEEL_TO_WHEEL_DIST = 260 # mm
MAX_ABSOLUTE_SPEED = 500 # mm/sec
#
# Open Interface command opcodes
#
OI_SOFT_RESET
OI_START
OI_MODE_PASSIVE
OI_MODE_SAFE
OI_MODE_FULL
OI_DRIVE
OI_DRIVE_DIRECT
OI_LEDS
OI_DIGITAL_OUTPUTS
OI_LOW_SIDE_DRIVERS
OI_PWM_LOW_SIDE_DRIVERS
OI_SEND_IR
OI_READ_SENSORS
OI_READ_SENSOR_LIST
OI_STREAM_SENSOR_LIST
OI_STREAM_PAUSE_RESUME
OI_SCRIPT_DEFINE
OI_SCRIPT_PLAY
OI_SCRIPT_SHOW
OI_WAIT_TIME
OI_WAIT_DISTANCE
OI_WAIT_ANGLE
OI_WAIT_EVENT
OI_SONG_RECORD
OI_SONG_PLAY

=7
= 131
= 132
= 145
= 147
= 138
= 144
= 149
= 148

= 155

#
# LEDs related defines
#
# power LED color
OI_LED_GREEN = 0
OI_LED_YELLOW = 63
OI_LED_ORANGE = 127
OI_LED_RED
# power LED pre-defined
OI_LED_OFF
OI_LED_FULL
OI_LED_ON
# "play" LED bit mask

= 255
intensities
=0
= 255
= OI_LED_FULL

=
=
=
=

150
152
153
154

= 156
= 157
= 158
= 140
= 141

= 128
= OI_START
= 137
= 139

= 151
= 142

OI_LED_PLAY
=2
# "advance" LED bit mask
OI_LED_ADVANCE = 8
OI_ALL_LEDS

= OI_LED_PLAY | OI_LED_ADVANCE

#
# Buttons masks and names
#
OI_BUTTONS_PLAY
=1
OI_BUTTONS_ADVANCE
=4
OI_ALL_BUTTONS
= OI_BUTTONS_PLAY | OI_BUTTONS_ADVANCE
OI_BUTTON_NAMES = {
OI_BUTTONS_PLAY
: "play",
OI_BUTTONS_ADVANCE : "advance"
}
#
# Digital outputs
#
OI_DOUT_0
OI_DOUT_1
OI_DOUT_2
OI_ALL_DOUTS

=1
=2
=4
= OI_DOUT_0 | OI_DOUT_1 | OI_DOUT_2

#
# Low side drivers
#
OI_DRIVER_0
=1
OI_DRIVER_1
=2
OI_DRIVER_2
=4
OI_ALL_DRIVERS = OI_DRIVER_0 | OI_DRIVER_1 | OI_DRIVER_2
#
# Special values for the radius parameter of the "drive" command
#
OI_DRIVE_STRAIGHT
OI_SPIN_CW
OI_SPIN_CCW

= 0x8000
= 0xFFFF
= 0x0001

#
# Logic sensors masks
#
OI_BUMPER_RIGHT
OI_BUMPER_LEFT
OI_WHEEL_DROP_RIGHT
OI_WHEEL_DROP_LEFT
OI_CASTER_DROP
OI_ALL_SENSORS
= 0x1f
OI_BUMPER_NAMES = {

= 0x01
=
=
=
=

0x02
0x04
0x08
0x10

OI_BUMPER_RIGHT
OI_BUMPER_LEFT

: 'right',
: 'left'

}
OI_WHEEL_DROP_NAMES = {
OI_WHEEL_DROP_RIGHT
: 'right wheel',
OI_WHEEL_DROP_LEFT
: 'left wheel',
OI_CASTER_DROP
: 'caster'
}
#
# Drivers and wheel overcurrent flags masks
#
OI_OVERCURRENT_DRV_0 = 0x02
OI_OVERCURRENT_DRV_1 = 0x01
OI_OVERCURRENT_DRV_2 = 0x04
OI_OVERCURRENT_WHEEL_R = 0x08
OI_OVERCURRENT_WHEEL_L = 0x10
#
# Sensor packets
#
OI_PACKET_GROUP_0

=0

OI_PACKET_GROUP_1
OI_PACKET_GROUP_SENSOR_STATES

=1
= OI_PACKET_GROUP_1

OI_PACKET_BUMPS_AND_WHEEL_DROPS
OI_PACKET_WALL
OI_PACKET_CLIFF_LEFT
OI_PACKET_CLIFF_FRONT_LEFT
OI_PACKET_CLIFF_FRONT_RIGHT
OI_PACKET_CLIFF_RIGHT
OI_PACKET_VIRTUAL_WALL
OI_PACKET_OVERCURRENT
= 14
OI_PACKET_UNUSED_1
OI_PACKET_UNUSED_2

=7

OI_PACKET_GROUP_2
OI_PACKET_GROUP_UI_AND_ODOMETRY
OI_PACKET_RECEIVED_IR_BYTE
OI_PACKET_BUTTONS
OI_PACKET_DISTANCE
OI_PACKET_ANGLE

=
=
=
=
=

= 15
= 16
=2
= OI_PACKET_GROUP_2
= 17

OI_PACKET_GROUP_3
OI_PACKET_GROUP_BATTERY
OI_PACKET_CHARGING_STATE
OI_PACKET_BATTERY_VOLTAGE
OI_PACKET_BATTERY_CURRENT
OI_PACKET_BATTERY_TEMPERATURE
OI_PACKET_BATTERY_CHARGE

=8

9
10
11
12
13

= 18
= 19

= 20

=2
= OI_PACKET_GROUP_3
=
=
=
=
=

21
22
23
24
25

OI_PACKET_BATTERY_CAPACITY

= 26

OI_PACKET_GROUP_4
OI_PACKET_GROUP_SIGNALS

=4
= OI_PACKET_GROUP_4

OI_PACKET_WALL_SIGNAL
OI_PACKET_CLIFF_LEFT_SIGNAL
OI_PACKET_CLIFF_FRONT_LEFT_SIGNAL
OI_PACKET_CLIFF_FRONT_RIGHT_SIGNAL
OI_PACKET_CLIFF_RIGHT_SIGNAL
OI_PACKET_CARGO_BAY_DIGITAL_IN
OI_PACKET_CARGO_BAY_ANALOG_IN
OI_PACKET_CHARGING_SOURCES

=
=
=
=

29
30
31
32

OI_PACKET_GROUP_5
OI_PACKET_GROUP_MISC

= 33
= 34
=5
= OI_PACKET_GROUP_5

OI_PACKET_CURRENT_MODE
OI_PACKET_SONG_NUMBER
OI_PACKET_SONG_PLAYING
OI_PACKET_STREAM_PACKETS_COUNT
OI_PACKET_REQUESTED_VELOCITY
OI_PACKET_REQUESTED_RADIUS
OI_PACKET_REQUESTED_VELOCITY_RIGHT
OI_PACKET_REQUESTED_VELOCITY_LEFT

= 35
= 36
= 37
= 39
= 41
= 42

OI_PACKET_GROUP_6
OI_PACKET_GROUP_ALL
OI_PACKET_MIN
OI_PACKET_MAX

= 27
= 28

= 38
= 40

=6
= OI_PACKET_GROUP_6
= OI_PACKET_GROUP_0
= OI_PACKET_REQUESTED_VELOCITY_LEFT

#
# Packet sizes array, indexed by packets ids
#
OI_PACKET_SIZES = [
26, 10, 6, 10, 14, 12, 52,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 2, 2,
1, 2, 2, 1, 2, 2,
2, 2, 2, 2, 2, 1, 2, 1,
1, 1, 1, 1, 2, 2, 2, 2
]
# Packet header byte when streaming is used
OI_PACKET_HEADER

= 19

#
# Array of the npack formats for packet groups,
# indexed by the group id
PACKET_STRUCT_FORMATS = [
None,
'!BBBBBBBBBB',
'!BBhh',

'!BHhbHH',
'!HHHHHBHB',
'!BBBBhhhh',
None
]
# compute the formats of compound packets
PACKET_STRUCT_FORMATS[0] = \
PACKET_STRUCT_FORMATS[1] + \
PACKET_STRUCT_FORMATS[2][1:] + \
PACKET_STRUCT_FORMATS[3][1:]
PACKET_STRUCT_FORMATS[6] = \
PACKET_STRUCT_FORMATS[0] + \
PACKET_STRUCT_FORMATS[4][1:] + \
PACKET_STRUCT_FORMATS[5][1:]
#
# Named tuples used for packet groups friendly unpacking
#
GroupPacket1 = namedtuple('GroupPacket1', [
'bumps_and_wheels',
'wall',
'cliff_left',
'cliff_front_left',
'cliff_front_right',
'cliff_right',
'virtual_wall',
'overcurrents',
'unused1',
'unused2'
])
GroupPacket2 = namedtuple('GroupPacket2', [
'ir_byte',
'buttons',
'distance',
'angle'
])
GroupPacket3 = namedtuple('GroupPacket3', [
'charging_state',
'voltage',
'current',
'battery_temp',
'battery_charge',
'battery_capacity'
])
GroupPacket4 = namedtuple('GroupPacket4', [
'wall_signal',
'cliff_left_signal',
'cliff_front_left_signal',
'cliff_front_right_signal',
'cliff_right_signal',
'user_dins',
'user_ain',
'charging_sources'

])
GroupPacket5 = namedtuple('GroupPacket5', [
'oi_mode',
'song_number',
'song_playing',
'nb_of_stream_packets',
'velocity',
'radius',
'right_velocity',
'left_velocity'
])
# Index of named tuples, keyed by the corresponding group packet id
GROUP_PACKET_CLASSES = [
None,
GroupPacket1,
GroupPacket2,
GroupPacket3,
GroupPacket4,
GroupPacket5
]
#
# Wait events
#
OI_WAIT_WHEEL_DROP
=1
OI_WAIT_FRONT_WHEEL_DROP
=2
OI_WAIT_LEFT_WHEEL_DROP
=3
OI_WAIT_RIGHT_WHEEL_DROP
=4
OI_WAIT_BUMP
=5
OI_WAIT_LEFT_BUMP
=6
OI_WAIT_RIGHT_BUMP
=7
OI_WAIT_VIRTUAL_WALL
=8
OI_WAIT_WALL
=9
OI_WAIT_CLIFF
= 10
OI_WAIT_LEFT_CLIFF
= 11
OI_WAIT_FRONT_LEFT_CLIFF
= 12
OI_WAIT_FRONT_RIGHT_CLIFF
= 13
OI_WAIT_RIGHT_CLIFF
= 14
OI_WAIT_HOME_BASE
= 15
OI_WAIT_BTN_ADVANCE
= 16
OI_WAIT_BTN_PLAY
= 17
OI_WAIT_DIN0
= 18
OI_WAIT_DIN1
= 19
OI_WAIT_DIN2
= 20
OI_WAIT_DIN3
= 21
OI_WAIT_PASSIVE_MODE
= 22
OI_WAIT_EVENT_MIN
OI_WAIT_EVENT_MAX
OI_SONG_MAX_LEN = 16
#!/usr/bin/env python
# -*- coding: utf-8 -*-

= OI_WAIT_WHEEL_DROP
= OI_WAIT_PASSIVE_MODE

#pylint: disable=W0401,W0614
""" iRobote Create robot interface module.
This module provides the class modeling the robot and handling the communication
with the Create via its serial link.
Refer to the iRobot Create Open Interface reference document available on
iRobot Web site (http://www.irobot.com/filelibrary/pdfs/hrd/create/Create%20Open%20Interface_v2.pdf)
"""
import serial
import threading
import time
import struct
from collections import namedtuple
import logging
from .defs import *
from .utils import *
from pybot import log
__author__ = "Eric Pascual"
__email__ = "eric@pobot.org"
__copyright__ = "Copyright 2013, POBOT"
__version__ = "1.0"
__date__ = "June. 2013"
__status__ = "Development"
__license__ = "LGPL"
LedsState = namedtuple('LedsState', 'play advance pwr_color pwr_level')
class TraceOptions(object):
TRACE_RXTX, TRACE_STREAM_FSA = range(2)
_TRACE_FLAGS_ATTRS = ('rxtx', 'stream_fsa')
def __init__(self, trace_format='h', flags=None):
if trace_format == 'h':
self.trace_fmt = lambda b : '%02x' % ord(b)
elif trace_format == 'd':
self.trace_fmt = lambda b : str(ord(b))
else:
raise ValueError('invalid trace format')
for i, attr in enumerate(self._TRACE_FLAGS_ATTRS):
setattr(self, attr, i in flags)
class CreateError(Exception):
pass
class IRobotCreate(object):
""" Models the Create robot and provides convenient methods to interact with it.
Note that this class does not make use of the Command one to avoid stacking

too many method calls. It could have be done to avoid duplicating in some
way the command definitions, but the benefit didn't compensate the drawbacks.
"""
def __init__(self, port, baudrate=57600, debug=False, simulate=False, trace=None):
""" Constructor:
Arguments:
port:
the serial port used for the serial link with it
baudrate:
serial link speed (default: 57600)
debug:
if True, activates the trace of serial data exchanges and various
other debugging help
simulate:
if True, simulate data exchanges
trace:
display format of the communication trace. None = no trace, 'd' = decimal, 'h' = hex
default : no trace
"""
self._serial = serial.Serial(port, baudrate=baudrate, timeout=0.5)
self._serial.flushInput()
self._serial.flushOutput()
# I/O serialization lock to be as much thread safe as possible
self._lock = threading.Lock()
self._debug = debug
self._trace = trace
if trace:
if not isinstance(trace, TraceOptions):
raise ValueError('trace options type mismatch')
self._trace_func = trace.trace_fmt
else:
self._trace_func = None
self._simulate = simulate
self._log = log.getLogger(type(self).__name__)
if self._debug:
self._log.setLevel(logging.DEBUG)
self._stream_listener = None
self._timer = None
# persistent status of the LEDs, so that it is possible to simplify the
# state change requests by specifying only the modifications
self._leds = None
# Persistent settings of low side drivers PWM duty cycle
# Initial state is supposed off (this will be set in constructor)
self._pwm = [0]*3
# events used to notify asynchronous operations
self._evt_packets = threading.Event()
self._evt_move = threading.Event()

@property
def serial(self):
""" Access to the serial link instance."""
return self._serial
@property
def debug_settings(self):
""" Provides the current debug settings as a tuple containing :
- the debug status
- the trace settings
- the simulate option state
"""
return (self._debug, self._trace, self._simulate)
@property
def leds(self):
""" The current state of the LEDs, as a LedState named tuple."""
return self._leds
@property
def drivers_pwm(self):
""" The current settings of the low side drivers PWM."""
return self._pwm
@property
def evt_packets(self):
""" The event object used to notify the availability of packets while in steam mode."""
return self._evt_packets
@property
def evt_move(self):
""" The event object used to notify that an asynchronous move is complete."""
return self._evt_move
@property
def log(self):
return self._log
def _send_block(self, data):
if type(data) is list:
# stringify a byte list
data = ''.join([chr(b) for b in data])
if (self._trace_func and self._trace.rxtx) or self._simulate:
self._log.debug(':Tx> %s', ' '.join(self._trace_func(b) for b in data))
if self._simulate:
return
with self._lock:
self._serial.flushInput()
self._serial.write(data)
self._serial.flushOutput()
def _send_byte(self, byte):
self._send_block(chr(byte))
def _get_reply(self, nbytes):

if self._simulate:
print ('<Rx: -- no Rx data when running in simulated I/O mode --')
return []
with self._lock:
data = ''
cnt = 0
maxwait = time.time() + self._serial.timeout
while cnt < nbytes and time.time() < maxwait:
data = data + self._serial.read(nbytes - cnt)
cnt = len(data)
if not data:
raise CreateError('no reply received')
if self._trace_func and self._trace.rxtx:
rx = ' '.join(self._trace_func(b) for b in data)
self._log.debug('<Rx: %s', rx)
return data
def start(self, mode=OI_MODE_PASSIVE):
""" Initializes the Create.
Includes a short pause for letting enough time to the beast for
getting ready.
Parameters:
mode:
the mode in which to place the Create (default: passive)
Raises:
ValueError if mode parameter is not of the expected ones
"""
self._send_byte(OI_START)
time.sleep(0.2)
# check all is ok be getting the battery level
mV, = struct.unpack('!H', self.get_sensor_packet(OI_PACKET_BATTERY_VOLTAGE))
self._log.info('battery voltage : %.2f', mV / 1000.0)
if mode != OI_MODE_PASSIVE:
self.set_mode(mode)
time.sleep(0.2)
self.set_low_side_drivers(0)
self.set_digital_outs(0)
def reset(self):
""" Soft reset of the Create."""
self.stream_shutdown()
self._send_byte(OI_SOFT_RESET)
time.sleep(4)
def shutdown(self):
""" Shutdowns the robot actuators and streaming."""
self.stream_shutdown()
self.stop_move()

self.set_low_side_drivers(0)
self.set_digital_outs(0)
def set_mode(self, mode):
""" Sets the operating mode of the Create.
Parameters:
mode:
the mode in which to place the Create (default: passive)
Raises:
ValueError if mode parameter is not of the expected ones
"""
if mode in [OI_MODE_PASSIVE, OI_MODE_SAFE, OI_MODE_FULL]:
self._send_byte(mode)
# force the power LED to stay on in full mode (otherwise one may
# think the beast has been turned off
if mode == OI_MODE_FULL:
self.set_leds(pwr_color=OI_LED_GREEN, pwr_level=OI_LED_FULL)
else:
raise ValueError('invalid mode (%s)' % mode)
def passive_mode(self):
""" Shorthand form for passive mode setting."""
self.set_mode(OI_MODE_PASSIVE)
def safe_mode(self):
""" Shorthand form for safe mode setting."""
self.set_mode(OI_MODE_SAFE)
def full_mode(self):
""" Shorthand form for full mode setting."""
self.set_mode(OI_MODE_FULL)
def _delayed_stop_move(self):
if self._debug:
self._log.debug('==> move complete')
self._timer = None
self.stop_move()
# signal that the move is ended
self._evt_move.set()
def _cancel_delayed_stop_move(self):
if self._timer:
self._timer.cancel()
self._timer = None
if self._debug:
self._log.debug('==> signaling move complete (in cancel)')
self._evt_move.set()
def _schedule_stop_move(self, delay):
self._timer = threading.Timer(delay, self._delayed_stop_move)
self._evt_move.clear()
self._timer.start()
if self._debug:
self._log.debug('delayed stop scheduled in %s secs', self._timer.interval)

def drive(self, velocity, radius=OI_DRIVE_STRAIGHT, distance=None):


""" Makes the robot drive, based on speed and path curvature.
If a distance is provided, a delayed stop is initiated (using a Timer),
based on the time for this distance to be traveled using the requested
speed.
If no distance is provided, it is up to the caller to manage when to stop
or change the move.
In both cases, the methods exits immediately.
Note:
The distance checking is implemented using a poor man's strategy
based on the ETA (estimated time of arrival).
Another strategy would be to create a short script using a wait_distance
command and run it. Although it has chances to be more accurate, this
has the nasty drawback to "mute the line" while the script is running,
and thus preventing any event (such as bumper hit) or commands to be
exchanged.
Parameters:
velocity:
robot's center velocity (in mm/sec)
radius:
radius (in mm) of the path to be followed
(default: special value corresponding to a straight path
distance:
distance to be traveled (in mm). If provided, the method
will exists only after this distance is supposed to be traveled,
based on the requested speed. If no distance is provided, the
method exists at once, leaving to robot moving.
Raises:
ValueError if velocity is outside valid range
"""
if self._debug:
self._log.debug("drive(velocity=%s, radius=%s, distance=%s)",
velocity, radius, distance)
# be sure we will not get a pending stop during the move
self._cancel_delayed_stop_move()
if distance is not None and distance == 0:
return
if not -MAX_ABSOLUTE_SPEED <= velocity <= MAX_ABSOLUTE_SPEED:
raise ValueError('invalid velocity (%f)' % velocity)
self._send_block([OI_DRIVE] + int16_to_bytes(velocity) + int16_to_bytes(radius))
if distance:
self._schedule_stop_move(time_for_distance(distance, velocity))
def drive_direct(self, left_vel, right_vel):
""" Makes the robot drive by directly setting the wheel speeds.
No distance control is proposed here.

Parameters:
left_vel, right_vel:
wheel velocities (in mm/sec)
"""
if self._debug:
self._log.debug("drive_direct(left_vel=%s, right_vel=%s)", left_vel, right_vel)
# be sure we will not get a pending stop during the move
self._cancel_delayed_stop_move()
self._send_block([OI_DRIVE_DIRECT] + int16_to_bytes(left_vel) + int16_to_bytes(right_vel))
def stop_move(self):
""" Stops any current move."""
self._cancel_delayed_stop_move()
self.drive_direct(0, 0)
def spin(self, velocity, spin_dir=OI_SPIN_CW, angle=None):
""" Makes the robot spin on itself at a given speed.
If a spin angle is provided, the method will wait for the time for this angle
to be reached (based on the requested speed) and then stops the robot and exit.
In addition, the provided spin direction (if any) is ignored, and replaced by
the one inferred from the angle sign (positive = CCW)
If no angle is provided, the move is initiated and the method exits immediately,
leaving the robot moving. It is the responsibility of the caller to handle
what should happen then.
See drive() method documentation for implementation note about the strategy
used to track the angle.
Parameters:
velocity:
robot's wheels velocity (in mm/sec)
spin_dir:
the spin direction (CW or CCW) (default: CW)
angle:
optional spin angle (in degrees)
Raises:
ValueError if spin direction is invalid
"""
if self._debug:
self._log.debug("spin(velocity=%s, spin_dir=%s, angle=%s)",
velocity, spin_dir, angle
)
# be sure we will not get a pending stop during the move
self._cancel_delayed_stop_move()
if angle is not None:
if angle != 0:
spin_dir = OI_SPIN_CCW if angle > 0 else OI_SPIN_CW
else:
return

elif spin_dir not in [OI_SPIN_CCW, OI_SPIN_CW]:


raise ValueError('invalid spin direction (%d)' % spin_dir)
self.drive(velocity, spin_dir)
if angle:
self._schedule_stop_move(time_for_angle(angle, velocity))
def set_leds(self, play=None, advance=None, pwr_color=None, pwr_level=None):
""" Changes the state of the Create LEDs.
Parameters:
play:
PLAY led state (default: unchanged)
advance:
ADVANCE led state (default: unchanged)
pwr_color:
the color of the POWER LED (default: unchanged)
pwr_level:
the intensity of the POWER LED (default: unchanged)
"""
if play is None:
play = self._leds.play if self._leds else False
if advance is None:
advance = self._leds.advance if self._leds else False
if pwr_color is None:
pwr_color = self._leds.pwr_color if self._leds else OI_LED_GREEN
else:
pwr_color &= 0xff
if pwr_level == None:
pwr_level = self._leds.pwr_level if self._leds else 0
else:
pwr_level &= 0xff
led_bits = 0
if play:
led_bits |= OI_LED_PLAY
if advance:
led_bits |= OI_LED_ADVANCE
self._send_block([OI_LEDS, led_bits, pwr_color, pwr_level])
self._leds = LedsState(play, advance, pwr_color, pwr_level)
def set_digital_outs(self, states):
""" Changes the state of the Create digital outputs.
Parameters:
states:
a bit mask containing the state of the DOUTs
"""
self._send_block([OI_DIGITAL_OUTPUTS,
states & OI_ALL_DOUTS
])
def set_low_side_drivers(self, states):
""" Changes the ON/OFF state of the Create low-side drivers.
Parameters:

states:
a bit mask containing the state of the LSDs
"""
self._send_block([OI_LOW_SIDE_DRIVERS,
states & OI_ALL_DRIVERS
])
for num, mask in enumerate([OI_DRIVER_0, OI_DRIVER_1, OI_DRIVER_2]):
if states & mask == 0:
self._pwm[num] = 0
def low_side_driver_pwm(self, drv0=None, drv1=None, drv2=None): #pylint: disable=W0613
""" Sets the PWM duty cycle of low side drivers.
Only specified settings are changed, the other ones being kept
as is. Duty cycles are provided as a number in range [0, 100]
Parameters:
drv0, drv1, drv2:
duty cycle of the corresponding driver
(default: None => unchanged)
Raises:
ValueError if out of range value provided
"""
# work on cached values to be able to rollback in case of trouble
wrk = self._pwm
# a bit of introspection to avoid writing repetitive code
for drvnum in xrange(3):
pwm = locals()['drv%d' % drvnum] # BEWARE: update this if arg names are modified
if pwm is None:
# if parameter is unset, use the current setting
pwm = self._pwm[drvnum]
else:
if 0 <= pwm <= 100:
# don't update the saved sttings now, in case one of the
# parameters is invalid
wrk[drvnum] = pwm
else:
raise ValueError('invalid PWM setting (%s) for driver %d' % (pwm, drvnum))
self._send_block(
[OI_PWM_LOW_SIDE_DRIVERS] +
# PWMs are sent in reverse sequence
[int(pwm * 128. / 100.) & 0xff for pwm in wrk[::-1]]
)
# we can commit the new settings now
self._pwm = wrk
def send_ir(self, data):
""" Emits an IR byte.
Parameters:
data:
the byte to be sent
"""
self._send_block([OI_SEND_IR,

data & 0xff


])
def get_sensor_packet(self, packet_id):
""" Gets a sensor packet or packets group.
Parameters:
packet_id:
the id of the requested packet or group
Returns:
the reply returned by the Create
Raises:
ValueError if invalid packet id
"""
if not OI_PACKET_MIN <= packet_id <= OI_PACKET_MAX:
raise ValueError('invalid packet id (%d)' % packet_id)
self._send_block([OI_READ_SENSORS,
packet_id & 0xff
])
return self._get_reply(OI_PACKET_SIZES[packet_id])
def get_unpacked_sensor_packet(self, packet_id):
""" Convenience method which returns an unpacked form for some
of the packet groups.
It only works for groups 1 to 5.
Parameters:
packet_id:
the requested packet group id
Returns:
the reply unpacked as the corresponding namedtuple
Raises:
ValueError in packet_id is out of range
"""
if not 1 <= packet_id <= 5:
raise ValueError('out of range packet id (%d)' % packet_id)
data = self.get_sensor_packet(packet_id)
return GROUP_PACKET_CLASSES[packet_id]._make( #pylint: disable=W0212
struct.unpack_from(PACKET_STRUCT_FORMATS[packet_id], data)
)
def get_sensor_packet_list(self, packet_ids):
""" Gets a list of sensor packets.
Parameters:
packed_ids:
a list containing the ids of the requested packets
Returns:

a list containing the corresponding packets returned by the Create


"""
self._send_block([OI_READ_SENSOR_LIST, len(packet_ids)] + packet_ids)
nbytes = sum([OI_PACKET_SIZES[id_] for id_ in packet_ids if 0 <= id_ <= 42])
return self._get_reply(nbytes)
def get_unpacked_sensor_packet_list(self, packet_ids):
""" Same as get_unpacked_sensor_packet, but for a list of packets.
Same restrictions apply for the packet ids.
Parameters:
packet_ids:
a list containing the ids of the requested packets
"""
data = self.get_sensor_packet_list(packet_ids)
res = []
pos = 0
for id_ in packet_ids:
if not 1 <= id_ <= 5:
raise ValueError('out of range packet id (%d)' % id_)
lg = OI_PACKET_SIZES[id_]
res.append(
GROUP_PACKET_CLASSES[id_]._make(
struct.unpack_from(
PACKET_STRUCT_FORMATS[id_],
data[pos : pos + lg]
)
)
)
pos += lg
return res

#pylint: disable=W0212

def get_buttons(self):
""" Gets the current state of the buttons, as the list of pressed ones.
The returned list contains the button ids (OI_BUTTONS_xxx) and can be
empty if no button is currently pressed.
"""
reply = self.get_sensor_packet(OI_PACKET_BUTTONS)
return byte_to_buttons(ord(reply[0]))
def get_bumpers(self):
""" Gets the current state of the bumper, as the list of pressed parts.
The returned list contains the bumper part ids (OI_BUMPER_xxx) and can be
empty if the bumper is currently not pressed.
"""
reply = self.get_sensor_packet(OI_PACKET_BUMPS_AND_WHEEL_DROPS)
return byte_to_bumpers(ord(reply[0]))
def get_wheel_drops(self):
""" Gets the current state of the wheel drop sensors, as the list of
dropped ones.

The returned list contains the drop sensor (OI_WHEEL_DROP_xxx, OI_CASTER_DROP)


and can be empty if all wheels are on the ground.
"""
reply = self.get_sensor_packet(OI_PACKET_BUMPS_AND_WHEEL_DROPS)
return byte_to_wheel_drops(ord(reply[0]))
def define_script(self, script):
""" Records a script to be played later.
The Command class can be used to make easier the building of the
script bytes sequence.
Parameters:
script:
the script, as its bytes sequence
"""
self._send_block([OI_SCRIPT_DEFINE, len(script) & 0xff] + script)
def play_script(self):
""" Plays the previously recorded script."""
self._send_byte(OI_SCRIPT_PLAY)
def _start_stream_listener(self, packet_event):
if self._debug:
self._log.debug('start stream listener')
self._stream_listener = _StreamListener(self, packet_event)
self._stream_listener.start()
def _kill_stream_listener(self):
if self._debug:
self._log.debug('kill stream listener')
self._stream_listener.stop()
self._stream_listener.join(1)
self._stream_listener = None
def _is_streaming(self):
return self._stream_listener is not None
def stream_packets(self, packet_ids):
""" Starts the continuous stream of sensor packets.
The evt_packets event instance provided by this class is used to signal and
communicate the received events. Caller can wait on it to synchronize their
process.
Parameters:
packet_ids:
the list of the ids of the packet to be streamed
"""
if not self._stream_listener:
self._start_stream_listener(self._evt_packets)
self._send_block([OI_STREAM_SENSOR_LIST, len(packet_ids) & 0xff] + packet_ids)
def stream_pause(self):
""" Pauses the packets streaming."""
self._send_block([OI_STREAM_PAUSE_RESUME, 0])

def stream_resume(self):
""" Resumes the packets streaming."""
self._send_block([OI_STREAM_PAUSE_RESUME, 1])
def stream_shutdown(self):
""" Shutdowns an ongoing packet streaming.
It is a good habit to call this method before leaving the application,
to avoid letting orphan threads alive. Calling it if not streaming is
active does not harm, since not done.
"""
if self._stream_listener:
if self._debug:
self._log.debug('shutdown packet streaming')
self.stream_pause()
self._kill_stream_listener()
def song_record(self, song_num, data):
""" Records a song.
Refer to iRobot Create OI documentation (p.11) for songs data encoding.
Parameters:
song_num:
the song number (0 <= n <= 15)
data:
the song data
Raises:
ValueError if song number is invalid
"""
if not 0 <= song_num <= 15:
raise ValueError('invalid song _number (%s)' % song_num)
self._send_block([OI_SONG_RECORD, song_num, len(data) / 2] + data)
def song_sequence_record(self, start_num, data):
""" Records a long song by spliting it in consecutive songs.
Parameters:
start_num:
number of the first song to be recorded
data:
the full song data
Returns:
the range of the recorded songs number
"""
sequence = [ data[i:i+32] for i in xrange(0, len(data), 32) ]
for i, song_data in enumerate(sequence):
self.song_record(start_num + i, song_data)
if self._debug:
self._log.debug('sequence %d recorded with data=%s', i, song_data)
return [start_num + i for i in xrange(len(sequence))]
def song_play(self, song_num):
""" Plays a recorded song.

Parameters:
song_num:
the song number (0 <= n <= 15)
Raises:
ValueError if song number is invalid
"""
if not 0 <= song_num <= 15:
raise ValueError('invalid song _number (%s)' % song_num)
self._send_block([OI_SONG_PLAY, song_num])
def song_sequence_play(self, song_numbers):
""" Plays a sequence of songs.
Parameters:
song_numbers:
a list of song numbers (0 <= n <= 15)
Raises:
ValueError if song number is invalid
IndexError if sequence is empty
"""
# pause an ongoing streaming, since will conflict with song end test
# Note: we do it unconditionally, in case a streaming was started by a
# previous run, and not stopped when exiting the process.
self.stream_pause()
for num in song_numbers:
self.song_play(num)
if self._debug:
self._log.debug("playing song %d", num)
# wait for the song is over before starting next one if any
while ord(self.get_sensor_packet(OI_PACKET_SONG_PLAYING)[0]):
time.sleep(0.01)
if self._debug:
self._log.debug(" --> finished")
if self._is_streaming():
if self._debug:
self._log.debug("resume streaming")
self.stream_resume()
#
# Stream listener FSM states (internal use)
#
_STATE_IDLE = 0
_STATE_GOT_HEADER = 1
_STATE_AT_PACKET_START = 2
_STATE_IN_PACKET = 3
_STATE_IN_CHECKSUM = 4
class _StreamListener(threading.Thread):
""" The packet stream listener.

This class is internal use only and is not supposed to be used


by the application.
"""
def __init__(self, robot, packet_event):
threading.Thread.__init__(self)
self.name = 'stream_listener'
self._stopevent = threading.Event()
self._robot = robot
self._packet_event = packet_event
def run(self):
_serial = self._robot.serial
_debug, trace, _simulate = self._robot.debug_settings
_serial.flushInput()
state = _STATE_IDLE
packets = []
total_expected = expected_bytes = 0
packet_id = 0
packet_data = ''
while not self._stopevent.isSet():
while _serial.inWaiting():
b = ord(_serial.read()[0])
if trace and trace.stream_fsa:
self._robot.log.debug(
'<Rx: %d - state=%d - total_expected=%d - expected_bytes=%d',
b, state, total_expected, expected_bytes
)
if state == _STATE_IDLE:
if b == OI_PACKET_HEADER:
packets = []
state = _STATE_GOT_HEADER
elif state == _STATE_GOT_HEADER:
total_expected = b
# if trace:
#
self._robot.log.debug('at stream frame start (expecting %d bytes total)', total_expected)
state = _STATE_AT_PACKET_START
elif state == _STATE_AT_PACKET_START:
packet_id = b
packet_data = ''
expected_bytes = OI_PACKET_SIZES[packet_id]
# if trace:
#
self._robot.log.debug('at stream packet %d start. (expecting %d bytes)', packet_id, expected_bytes)
total_expected -= 1
state = _STATE_IN_PACKET
elif state == _STATE_IN_PACKET:
packet_data += chr(b)
total_expected -= 1
expected_bytes -= 1
if expected_bytes == 0:
if trace and trace.stream_fsa:
self._robot.log.debug('packet %d available', packet_id)
packets.append((packet_id, packet_data))
if total_expected == 0:

state = _STATE_IN_CHECKSUM
else:
state = _STATE_AT_PACKET_START
elif state == _STATE_IN_CHECKSUM:
# don't care checking for the moment
# notify a set of packets is available
self._packet_event.packets = packets
self._packet_event.set()
if trace and trace.stream_fsa:
self._robot.log.debug('packets availability signaled')
state = _STATE_IDLE
# check if someone requested us to stop
self._stopevent.wait(0.01)
# avoid some listener keep waiting forever
self._packet_event.packets = None
self._packet_event.set()
def stop(self):
self._stopevent.set()
#!/usr/bin/env python
# -*- coding: utf-8 -*#pylint: disable=W0401,W0614
""" iRobote Create robot interface package.
Refer to the iRobot Create Open Interface reference document available on
iRobot Web site (http://www.irobot.com/filelibrary/pdfs/hrd/create/Create%20Open%20Interface_v2.pdf)
Command builder utility class
"""
from .defs import *
from .utils import *
__author__ = "Eric Pascual"
__email__ = "eric@pobot.org"
__copyright__ = "Copyright 2013, POBOT"
__version__ = "1.0"
__date__ = "June. 2013"
__status__ = "Development"
__license__ = "LGPL"
#
# Command builder
#
class Command(object):
""" The Command class is a collection of static methods returning the
bytes sequence corresponding to a given command.
It allows a more friendly and readable elaboration of commands, especially
when defining scripts.

Commands naming is based on the content of the "Open Interface Command


Reference" chapter of the documentation. Please refer to this document for
a full explanation of the commands parameters.
"""
@staticmethod
def start():
return [OI_START]
@staticmethod
def reset():
return [OI_SOFT_RESET]
@staticmethod
def mode(mode):
if mode in [OI_MODE_FULL, OI_MODE_PASSIVE, OI_MODE_SAFE]:
return [mode]
else:
raise ValueError('invalid mode')
@staticmethod
def drive(velocity, radius=OI_DRIVE_STRAIGHT):
return [OI_DRIVE] + int16_to_bytes(velocity) + int16_to_bytes(radius)
@staticmethod
def drive_direct(velocity_right, velocity_left):
return [OI_DRIVE_DIRECT] + int16_to_bytes(velocity_right) + int16_to_bytes(velocity_left)
@staticmethod
def stop():
return Command.drive_direct(0, 0)
@staticmethod
def leds(led_bits, power_color=OI_LED_GREEN, power_level=OI_LED_FULL):
return [OI_LEDS, led_bits & OI_ALL_LEDS, power_color & 0xff, power_level & 0xff]
@staticmethod
def digital_outputs(states):
return [OI_DIGITAL_OUTPUTS, states & OI_ALL_DOUTS]
@staticmethod
def low_side_drivers(states):
return [OI_LOW_SIDE_DRIVERS, states & OI_ALL_DRIVERS]
@staticmethod
def send_ir(data):
return [OI_SEND_IR, data & 0xff]
@staticmethod
def sensors(packet_id):
if OI_PACKET_MIN <= packet_id <= OI_PACKET_MAX:
return [OI_READ_SENSORS, packet_id]
else:
raise ValueError('invalid packet id (%d)' % packet_id)
@staticmethod
def query_list(packet_ids):

return [OI_READ_SENSOR_LIST, len(packet_ids)] + packet_ids


@staticmethod
def stream(packet_ids):
return [OI_STREAM_SENSOR_LIST, len(packet_ids)] + packet_ids
@staticmethod
def stream_pause_resume(state):
return [OI_STREAM_PAUSE_RESUME, state & 0x01]
@staticmethod
def define_script(script):
return [OI_SCRIPT_DEFINE, len(script)] + script
@staticmethod
def play_script():
return [OI_SCRIPT_PLAY]
@staticmethod
def wait_time(delay):
return [OI_WAIT_TIME, delay & 0xff]
@staticmethod
def wait_distance(distance):
return [OI_WAIT_DISTANCE] + int16_to_bytes(distance)
@staticmethod
def wait_angle(angle):
return [OI_WAIT_ANGLE] + int16_to_bytes(angle)
@staticmethod
def wait_event(event):
return [OI_WAIT_EVENT, event & 0xff]
#!/usr/bin/env python
# -*- coding: utf-8 -*#pylint: disable=W0401,W0614
""" iRobote Create robot interface package. """
# make all package modules directly visible when importing the package
from
from
from
from

.defs import *
.utils import *
.command import *
.create import *

#!/usr/bin/env python
# -*- coding: utf-8 -*#pylint: disable=W0401,W0614
""" dSpin (aka STMicroElectronics L6470) interface.
Utility functions.
Reference documentation available at:
http://www.st.com/internet/analog/product/248592.jsp

Have also a llok at this article:


http://www.pobot.org/Driver-evolue-pour-moteur-pas-a.html
"""
__author__ = "Eric Pascual"
__email__ = "eric@pobot.org"
__copyright__ = "Copyright 2013, POBOT"
__version__ = "1.0"
__date__ = "July. 2013"
__status__ = "Development"
__license__ = "LGPL"
from .defs import *
def speed_steps_to_par(stepsPerSec):
""" Returns the SPEED register value corresponding to a value given
as a step count per second."""
return (int) (stepsPerSec * 67.108864 + 0.5)
def accdec_steps_to_par(stepsPerSec):
""" Returns the ACC and DEC registers value corresponding to a value
given as a step count per second^2."""
return (int) (stepsPerSec * 0.068719476736 + 0.5)
def maxspeed_steps_to_par(stepsPerSec):
""" Returns the MAX_SPEED register value corresponding to a value given
as a step count per second."""
return (int) (stepsPerSec * 0.065536 + 0.5)
def minspeed_steps_to_par(stepsPerSec):
""" Returns the MIN_SPPED register value corresponding to a value given
as a step count per second.
This value is used to switch the low speed optimization mechanism.
"""
return (int) (stepsPerSec * 4.194304 + 0.5)
def fsspeed_steps_to_par(stepsPerSec):
""" Returns the FS_SPD register value corresponding to a value given
as a step count per second.
"""
return (int) (stepsPerSec * 0.065536)
def intspeed_steps_to_par(stepsPerSec):
""" Returns the INT_SPEED register value corresponding to a value given
as a step count per second.
"""
return (int) (stepsPerSec * 4.194304 + 0.5)
def kval_pct_to_par(pct):
return (int) (pct / 0.390525 + 0.5)
def bemf_slope_pct_to_par(pct):
return (int) (pct / 0.00156862745098 + 0.5)
def ktherm_to_par(ktherm):
return (int) ((ktherm - 1) / 0.03125 + 0.5)

def stallth_to_par(mA):
return (int) ((mA - 31.25) / 31.25 + 0.5)
#
# This dictionary contains the offsets of the fields composing the CONFIG
# register. It is dynamically initialized at module import time by
# introspecting the constants defining the respective bit masks of these
# fields. Shift value is obtained by finding the position of the first 1,
# counting from the right of the mask.
#
_REG_CFG_SHIFTS = dict(
[(n, bin(eval(n))[::-1].find('1')) for n in globals().keys() if n.startswith('dSPIN_CONFIG_')]
)
def unpack_config_reg(value):
""" Returns the different bit fields of a CONFIG register value as a dictionary.
Keys are the names of the bit fields, as defined in the reference documentation
(and used to defined the dSPIN_CONFIG_xxx constants)
"""
offs = len('dSPIN_CONFIG_')
return dict(
[(k[offs:], (value & eval(k)) >> v) for k, v in _REG_CFG_SHIFTS.iteritems()]
)
#
# Same for STATUS register values
#
_REG_STATUS_SHIFTS = dict(
[(n, bin(eval(n))[::-1].find('1')) for n in globals().keys() if n.startswith('dSPIN_STATUS_')]
)
def unpack_status_reg(value):
offs = len('dSPIN_STATUS_')
return dict(
[(k[offs:], (value & eval(k)) >> v) for k, v in _REG_STATUS_SHIFTS.iteritems()]
)
#
# Same for STEP_MODE register values
#
_REG_STEP_MODE_SHIFTS = dict(
[(n, bin(eval(n))[::-1].find('1')) for n in globals().keys() if n.startswith('dSPIN_STEP_MODE_')]
)
def unpack_step_mode_reg(value):
offs = len('dSPIN_STEP_MODE_')
return dict(
[(k[offs:], (value & eval(k)) >> v) for k, v in _REG_STEP_MODE_SHIFTS.iteritems()]
)
#!/usr/bin/env python
# -*- coding: utf-8 -*""" dSpin (aka STMicroElectronics L6470) package.

Symbolic constants definitions.


Reference documentation available at:
http://www.st.com/internet/analog/product/248592.jsp
Have also a look at this article:
http://www.pobot.org/Driver-evolue-pour-moteur-pas-a.html
"""
__author__ = "Eric Pascual"
__email__ = "eric@pobot.org"
__copyright__ = "Copyright 2013, POBOT"
__version__ = "1.0"
__date__ = "July. 2013"
__status__ = "Development"
__license__ = "LGPL"
#
# dSpin registers
#
dSPIN_REG_ABS_POS
= 0x01
dSPIN_REG_EL_POS
= 0x02
dSPIN_REG_MARK
= 0x03
dSPIN_REG_SPEED
= 0x04
dSPIN_REG_ACC
= 0x05
dSPIN_REG_DEC
= 0x06
dSPIN_REG_MAX_SPEED
= 0x07
dSPIN_REG_MIN_SPEED
= 0x08
dSPIN_REG_KVAL_HOLD
= 0x09
dSPIN_REG_KVAL_RUN
= 0x0A
dSPIN_REG_KVAL_ACC
= 0x0B
dSPIN_REG_KVAL_DEC
= 0x0C
dSPIN_REG_INT_SPD
= 0x0D
dSPIN_REG_ST_SLP
= 0x0E
dSPIN_REG_FN_SLP_ACC = 0x0F
dSPIN_REG_FN_SLP_DEC = 0x10
dSPIN_REG_K_THERM
= 0x11
dSPIN_REG_ADC_OUT
= 0x12
dSPIN_REG_OCD_TH
= 0x13
dSPIN_REG_STALL_TH
= 0x14
dSPIN_REG_FS_SPD
= 0x15
dSPIN_REG_STEP_MODE
= 0x16
dSPIN_REG_ALARM_EN
= 0x17
dSPIN_REG_CONFIG
= 0x18
dSPIN_REG_STATUS
= 0x19
#
# dSpin commands
#
# Identifiers names are built by prepending "dSPIN_CMD_" to the command name,
# as it appears in the datasheet.
#
dSPIN_CMD_NOP
= 0x00
dSPIN_CMD_SET_PARAM
= 0x00
dSPIN_CMD_GET_PARAM
= 0x20
dSPIN_CMD_RUN
= 0x50

dSPIN_CMD_STEP_CLOCK = 0x58
dSPIN_CMD_MOVE
= 0x40
dSPIN_CMD_GOTO
= 0x60
dSPIN_CMD_GOTO_DIR
= 0x68
dSPIN_CMD_GO_UNTIL
= 0x82
dSPIN_CMD_RELEASE_SW = 0x92
dSPIN_CMD_GO_HOME
= 0x70
dSPIN_CMD_GO_MARK
= 0x78
dSPIN_CMD_RESET_POS
= 0xD8
dSPIN_CMD_RESET_DEVICE = 0xC0
dSPIN_CMD_SOFT_STOP
= 0xB0
dSPIN_CMD_HARD_STOP
= 0xB8
dSPIN_CMD_SOFT_HIZ
= 0xA0
dSPIN_CMD_HARD_HIZ
= 0xA8
dSPIN_CMD_GET_STATUS = 0xD0
#
# STEP_MODE register
#
dSPIN_STEP_MODE_SYNC_EN
= 0x80 # SYNC_EN field mask
dSPIN_STEP_MODE_SYNC_SEL = 0x70 # SYNC_SEL field mask
dSPIN_STEP_MODE_WRITE
= 0x08 # WRITE field mask
dSPIN_STEP_MODE_STEP_SEL = 0x07 # STEP_SEL field mask
#
# Step modes.
#
dSPIN_STEP_SEL_1
= 0x00
dSPIN_STEP_SEL_1_2
= 0x01
dSPIN_STEP_SEL_1_4
= 0x02
dSPIN_STEP_SEL_1_8
= 0x03
dSPIN_STEP_SEL_1_16
= 0x04
dSPIN_STEP_SEL_1_32
= 0x05
dSPIN_STEP_SEL_1_64
= 0x06
dSPIN_STEP_SEL_1_128 = 0x07
#
# Sync modes.
#
dSPIN_SYNC_SEL_1_2
dSPIN_SYNC_SEL_1
dSPIN_SYNC_SEL_2
dSPIN_SYNC_SEL_4
dSPIN_SYNC_SEL_8
dSPIN_SYNC_SEL_16
dSPIN_SYNC_SEL_32
dSPIN_SYNC_SEL_64

= 0x00
= 0x10
= 0x20
= 0x30
= 0x40
= 0x50
= 0x60
= 0x70

#
# Sync enabling.
#
dSPIN_SYNC_EN
dSPIN_SYNC_DIS

= 0x80
= 0x00

#
# ALARM_EN register flags.
#
dSPIN_ALARM_EN_OVERCURRENT
= 0x0
dSPIN_ALARM_EN_THERMAL_SHUTDOWN
= 0x0
dSPIN_ALARM_EN_THERMAL_WARNING
= 0x0
dSPIN_ALARM_EN_UNDER_VOLTAGE
= 0x0
dSPIN_ALARM_EN_STALL_DET_A
= 0x1
dSPIN_ALARM_EN_STALL_DET_B
= 0x2
dSPIN_ALARM_EN_SW_TURN_ON
= 0x4
dSPIN_ALARM_EN_WRONG_NPERF_CMD
= 0x80
#
# CONFIG register
#
dSPIN_CONFIG_OSC_SEL
dSPIN_CONFIG_SW_MODE
dSPIN_CONFIG_EN_VSCOMP
dSPIN_CONFIG_OC_SD
dSPIN_CONFIG_POW_SR
dSPIN_CONFIG_F_PWM_DEC
dSPIN_CONFIG_F_PWM_INT

= 0x000F # OSC_SEL field mask


= 0x0010 # SW_MODE field mask
= 0x0020 # EN_VSCOMP field mask
= 0x0080 # OC_SD field mask
= 0x0300 # POW_SR field mask
= 0x1C00 # F_PWM_DEC field mask
= 0xE000 # F_PWM_INT field mask

#
# Bit masks used by the register descriptors
#
MASK_4
MASK_5
MASK_7
MASK_8
MASK_9
MASK_10
MASK_12
MASK_13
MASK_14
MASK_16
MASK_20
MASK_22
#
#
#
#
#
#
#
#

= 0x00000F
= 0x00001F
= 0x00007F
= 0x0000FF
= 0x0001FF
= 0x0003FF
= 0x000FFF
= 0x001FFF
= 0x003FFF
= 0x00FFFF
= 0x0FFFFF
= 0x3FFFFF

dSPIN register descritors table


The table is indexed by the register number, and contains tuples
providing :
- the register size (in bytes)
- the masks for the bits used by the value

dSPIN_REG_DESCR = [
( 0, 0 ),
# reg 0 does not exist
( 3, MASK_22 ),
# dSPIN_REG_ABS_POS
( 2, MASK_9 ),
# dSPIN_REG_EL_POS
( 3, MASK_22 ),
# dSPIN_REG_MARK

(
(
(
(
(
(
(
(
(
(
(
(
(
(
(
(
(
(
(
(
(
(

3,
2,
2,
2,
2,
1,
1,
1,
1,
2,
1,
1,
1,
1,
1,
1,
1,
2,
1,
1,
2,
2,

MASK_20 ),
MASK_12 ),
MASK_12 ),
MASK_10 ),
MASK_13 ),
MASK_8 ),
MASK_8 ),
MASK_8 ),
MASK_8 ),
MASK_14 ),
MASK_8 ),
MASK_8 ),
MASK_8 ),
MASK_4 ),
MASK_5 ),
MASK_4 ),
MASK_7 ),
MASK_10 ),
MASK_8 ),
MASK_8 ),
MASK_16 ),
MASK_16 )

# dSPIN_REG_SPEED
# dSPIN_REG_ACC
# dSPIN_REG_DEC
# dSPIN_REG_MAX_SPEED
# dSPIN_REG_MIN_SPEED
# dSPIN_REG_KVAL_HOLD
# dSPIN_REG_KVAL_RUN
# dSPIN_REG_KVAL_ACC
# dSPIN_REG_KVAL_DEC
# dSPIN_REG_INT_SPD
# dSPIN_REG_ST_SLP
# dSPIN_REG_FN_SLP_ACC
# dSPIN_REG_FN_SLP_DEC
# dSPIN_REG_K_THERM
# dSPIN_REG_ADC_OUT
# dSPIN_REG_OCD_TH
# dSPIN_REG_STALL_TH
# dSPIN_REG_FS_SPD
# dSPIN_REG_STEP_MODE
# dSPIN_REG_ALARM_EN
# dSPIN_REG_CONFIG
# dSPIN_REG_STATUS

]
#
# Oscillator selectors
#
dSPIN_OSC_SEL_INT_16MHZ = 0x0000
# Internal 16MHz, no output
dSPIN_OSC_SEL_INT_16MHZ_OSCOUT_2MHZ = 0x0008 # Default; internal 16MHz 2MHz output
dSPIN_OSC_SEL_INT_16MHZ_OSCOUT_4MHZ = 0x0009 # Internal 16MHz 4MHz output
dSPIN_OSC_SEL_INT_16MHZ_OSCOUT_8MHZ = 0x000A # Internal 16MHz 8MHz output
dSPIN_OSC_SEL_INT_16MHZ_OSCOUT_16MHZ = 0x000B # Internal 16MHz 16MHz output
dSPIN_OSC_SEL_EXT_8MHZ_XTAL_DRIVE = 0x0004
# External 8MHz crystal
dSPIN_OSC_SEL_EXT_16MHZ_XTAL_DRIVE = 0x0005
# External 16MHz crystal
dSPIN_OSC_SEL_EXT_24MHZ_XTAL_DRIVE = 0x0006
# External 24MHz crystal
dSPIN_OSC_SEL_EXT_32MHZ_XTAL_DRIVE = 0x0007
# External 32MHz crystal
dSPIN_OSC_SEL_EXT_8MHZ_OSCOUT_INVERT = 0x000C # External 8MHz crystal output inverted
dSPIN_OSC_SEL_EXT_16MHZ_OSCOUT_INVERT = 0x000D # External 16MHz crystal output inverted
dSPIN_OSC_SEL_EXT_24MHZ_OSCOUT_INVERT = 0x000E # External 24MHz crystal output inverted
dSPIN_OSC_SEL_EXT_32MHZ_OSCOUT_INVERT = 0x000F # External 32MHz crystal output inverted
#
# Switch signal usage
#
dSPIN_SW_MODE_HARD_STOP = 0x0000 # Default; hard stop motor on switch.
dSPIN_SW_MODE_USER = 0x0010
# Tie to the GoUntil and ReleaseSW
#
# Motor voltage compensation mode
#
dSPIN_VS_COMP_DISABLE = 0x0000
dSPIN_VS_COMP_ENABLE = 0x0020
#

# Disable motor voltage compensation.


# Enable motor voltage compensation.

# Over-current shutdown settings


#
dSPIN_OC_SD_DISABLE = 0x0000
dSPIN_OC_SD_ENABLE = 0x0080

# Bridges do NOT shutdown on OC detect


# Bridges shutdown on OC detect

#
# Power slew-rate settings
#
dSPIN_POW_SR_180V_us = 0x0000
dSPIN_POW_SR_290V_us = 0x0200
dSPIN_POW_SR_530V_us = 0x0300

# 180V/us
# 290V/us
# 530V/us

#
# PWM clock divider
#
dSPIN_PWM_DIV_1
dSPIN_PWM_DIV_2
dSPIN_PWM_DIV_3
dSPIN_PWM_DIV_4
dSPIN_PWM_DIV_5
dSPIN_PWM_DIV_6
dSPIN_PWM_DIV_7

=
=
=
=
=
=
=

(0x00)
(0x01)
(0x02)
(0x03)
(0x04)
(0x05)
(0x06)

<<
<<
<<
<<
<<
<<
<<

13
13
13
13
13
13
13

#
# PWM clock multiplier
#
dSPIN_PWM_MUL_0_625 = (0x00) << 10
dSPIN_PWM_MUL_0_75 = (0x01) << 10
dSPIN_PWM_MUL_0_875 = (0x02) << 10
dSPIN_PWM_MUL_1 = (0x03) << 10
dSPIN_PWM_MUL_1_25 = (0x04) << 10
dSPIN_PWM_MUL_1_5 = (0x05) << 10
dSPIN_PWM_MUL_1_75 = (0x06) << 10
dSPIN_PWM_MUL_2 = (0x07) << 10
#
# STATUS register masks
#
dSPIN_STATUS_HIZ
= 0x0001 # high when bridges are in HiZ mode
dSPIN_STATUS_BUSY
= 0x0002 # mirrors BUSY pin
dSPIN_STATUS_SW_F
= 0x0004 # low when switch open, high when closed
dSPIN_STATUS_SW_EVN
= 0x0008 # active high, set on switch falling edge,
dSPIN_STATUS_DIR
= 0x0010 # Indicates current motor direction.
dSPIN_STATUS_MOT_STATUS
= 0x0060 # Motor status
dSPIN_STATUS_NOTPERF_CMD
= 0x0080 # Last command not performed.
dSPIN_STATUS_WRONG_CMD
= 0x0100 # Last command not valid.
dSPIN_STATUS_UVLO
= 0x0200 # Undervoltage lockout is active
dSPIN_STATUS_TH_WRN
= 0x0400 # Thermal warning
dSPIN_STATUS_TH_SD
= 0x0800 # Thermal shutdown
dSPIN_STATUS_OCD
= 0x1000 # Overcurrent detected
dSPIN_STATUS_STEP_LOSS_A
= 0x2000 # Stall detected on A bridge
dSPIN_STATUS_STEP_LOSS_B
= 0x4000 # Stall detected on B bridge

dSPIN_STATUS_SCK_MOD

= 0x8000 # Step clock mode is active

#
# Motor status values
#
dSPIN_MOT_STATUS_STOPPED = 0
# Motor stopped
dSPIN_MOT_STATUS_ACCELERATION = 0x01 << 5 # Motor accelerating
dSPIN_MOT_STATUS_DECELERATION = 0x02 << 5 # Motor decelerating
dSPIN_MOT_STATUS_CONST_SPD = 0x03 << 5
# Motor at constant speed
#
# Motor directions
#
dSPIN_DIR_REV = 0x00
dSPIN_DIR_FWD = 0x01
#
# Action options
#
dSPIN_ACTION_RESET = 0x00
dSPIN_ACTION_COPY = 0x01
#
# Factory reset configuration register value
#
dSPIN_FACT_RESET_CONFIG = 0x2E88

#!/usr/bin/env python
# -*- coding: utf-8 -*#pylint: disable=W0401,W0614
""" dSpin (aka STMicroElectronics L6470) interface.
API classes.
This module is written for the Raspberry Pi but can be adapted easily.
Reference documentation available at:
http://www.st.com/internet/analog/product/248592.jsp
Have also a look at this article:
http://www.pobot.org/Driver-evolue-pour-moteur-pas-a.html
"""
__author__ = "Eric Pascual"
__email__ = "eric@pobot.org"
__copyright__ = "Copyright 2013, POBOT"
__version__ = "1.0"
__date__ = "July. 2013"
__status__ = "Development"
__license__ = "LGPL"
import spi

import time
import RPi.GPIO as GPIO
import logging
import pybot.log as log
from .defs import *
from .utils import *
RASPI_SPI_DEVICE = '/dev/spidev0.0'
class DSPin(object):
""" dSPIN control and interface class.
Internally relies on spi and RPi.GPIO modules.
"""
def __init__(self, cs, stdby, not_busy=None,
debug=False, trace=False):
""" Constructor.
IMPORTANT: Signal pin numbers above use the P1 header numbering (and not the
processor pins one).
Parameters:
cs:
pin number of the /CHIP SELECT signal
stdby:
pin number of the /STANDBY signal
not_busy:
(optional) pin number of the /BUSY signal if used to monitor the moves
debug:
debug messages activation flag
default: False
trace:
SPI data exchanges trace activation flag (requires debug=True)
Warning: can slow down things
default: False
"""
self._debug = debug
self._log = log.getLogger(type(self).__name__)
if self._debug:
self._log.setLevel(logging.DEBUG)
self._trace = debug and trace
self._port = RASPI_SPI_DEVICE
GPIO.setmode(GPIO.BOARD)
GPIO.setwarnings(False)
self._cs = cs
GPIO.setup(cs, GPIO.OUT)
self._stdby = stdby
GPIO.setup(stdby, GPIO.OUT)

self._not_busy = not_busy
if not_busy:
GPIO.setup(not_busy, GPIO.IN)
GPIO.output(self._cs, 1)
def init(self):
""" Context initialization.
Must be called before any attempt to use the dSPIN.
"""
self.reset_chip()
spi.openSPI(device=self._port, bits=8, mode=3)
if self._debug:
self._log.debug('dSPIN interface initialized')
def shutdown(self):
""" Final cleanup.
Not really mandatory, but strongly suggested when closing the application,
at least for avoiding letting some GPIOs as outputs and risking shorting them and
frying th RasPi.
"""
if self._debug:
self._log.debug('shutdown dSPIN interface')
self.soft_hiZ()
spi.closeSPI()
for ch in [ ch for ch in (self._cs, self._stdby, self._not_busy) if ch]:
GPIO.setup(ch, GPIO.IN)
def reset_chip(self):
""" dSPIN chip initialization sequence."""
if self._debug:
self._log.debug('resetting dSPIN chip')
GPIO.output(self._stdby, 1)
time.sleep(0.001)
GPIO.output(self._stdby, 0)
time.sleep(0.001)
GPIO.output(self._stdby, 1)
time.sleep(0.001)
def _spi_write(self, b):
""" Writes a single byte of data too SPI.
Don't forget that the dSPIN version of the SPI protocol is
a bit special, since it requires to toggle the CS signal
*for each* byte of data to be written, and not around the whole
message.
Parameters:
b:
the byte to be sent (value is coerced as a byte)
"""
if self._trace:
self._log.debug(':SPI Wr> %0.2x', b)

GPIO.output(self._cs, 0)
spi.transfer((b & 0xff,))
GPIO.output(self._cs, 1)
def _spi_write_int24(self, v, max_value):
""" 'Packaged' SPI write of a 3 bytes long integer value.
Parameters:
v:
the value to be written
max_value:
the upper bound the provided value will be clamped to
"""
if (v > max_value):
v = max_value
self._spi_write(v >> 16)
self._spi_write(v >> 8)
self._spi_write(v)
def _spi_read(self):
""" Reads a single byte from SPI.
Returns:
the byte
"""
GPIO.output(self._cs, 0)
res = spi.transfer((0,))[0] & 0xff
GPIO.output(self._cs, 1)
if self._trace:
self._log.debug(':SPI Rd> %0.2x', res)
return res
def set_register(self, reg, value):
""" Sets the value of a dSPIN register.
The SPI operations are driven by the register descriptors stored in
the dSPIN_REG_DESCR table of the dpsin.defs module."
Parameters:
reg:
the register number
value:
the value to be set
"""
lg, mask = dSPIN_REG_DESCR[reg]
if value > mask:
value = mask
self._spi_write(dSPIN_CMD_SET_PARAM | reg)
# we could have factored statements by using successive
# length tests, but this implementation is more efficient
if lg == 3:
self._spi_write(value >> 16)
self._spi_write(value >> 8)

self._spi_write(value)
elif lg == 2:
self._spi_write(value >> 8)
self._spi_write(value)
elif lg == 1:
self._spi_write(value)
def get_register(self, reg):
""" Returns the current value of a register.
Parameters:
reg:
the register number
Returns:
the register value
"""
lg, mask = dSPIN_REG_DESCR[reg]
self._spi_write(dSPIN_CMD_GET_PARAM | reg)
value = 0
for _i in xrange(lg):
value = (value << 8) | self._spi_read()
return value & mask
def get_status(self):
""" Returns the dSPIN status as a 16 bits integer value."""
self._spi_write(dSPIN_CMD_GET_STATUS)
res = self._spi_read() << 8
res |= self._spi_read()
return res
def get_config(self):
""" Returns the dSPIN current configuration."""
return self.get_register(dSPIN_REG_CONFIG)
def get_current_speed(self):
""" Returns the current motor speed, in steps per second."""
steps_per_tick = self.get_register(dSPIN_REG_SPEED)
return (int) (steps_per_tick * 0.0149)
def enable_low_speed_optimization(self, enabled):
""" Controls the low speed optimization mechanism."""
self.set_register(dSPIN_REG_MIN_SPEED, (0x1000 if enabled else 0))
def step_clock(self, direction):
""" Moves the motor one step in the provided direction."""
self._spi_write(dSPIN_CMD_STEP_CLOCK | direction)
def move(self, n_step, direction):
""" Moves the motor from the current position.
Parameters:
n_step:
the step count
direction:

the move direction


"""
self._spi_write(dSPIN_CMD_MOVE | (direction & 0x01))
self._spi_write_int24(n_step, MASK_22)
def run(self, stepsPerSec, direction):
""" Runs the motor at a given speed in a given direction.
Parameters:
stepsPerSec:
the target speed
direction:
the move direction
"""
self._spi_write(dSPIN_CMD_RUN | (direction & 0x01))
self._spi_write_int24(speed_steps_to_par(stepsPerSec), MASK_20)
def goto_pos(self, pos, direction=None):
""" Moves the motor to an absolute position, using the currently configured max speed.
Parameters:
pos:
the target position (as a step count)
direction:
if provided, forces the move direction . If unset, the minimal physical path
is used (see documentation paragraph 6.7.2)
"""
if direction:
self._spi_write(dSPIN_CMD_GOTO_DIR | (direction & 0x01))
else:
self._spi_write( dSPIN_CMD_GOTO)
self._spi_write_int24(pos, MASK_22)
def go_until_switch(self, action, direction, stepsPerSec):
""" Runs the motor until the switch input state changes to low.
Parameters:
action:
action on completion (dSPIN_ACTION_xxx)
direction:
move direction
stepsPerSec:
move speed
"""
self._spi_write(dSPIN_CMD_GO_UNTIL | action | direction)
self._spi_write_int24(speed_steps_to_par(stepsPerSec), MASK_22)
def release_switch(self, action, direction):
""" Runs the motor at minimum speed until the switch is released.
Parameters:
action:
action on completion (dSPIN_ACTION_xxx)
direction:
move direction
"""
self._spi_write(dSPIN_CMD_RELEASE_SW | action | direction)

def go_home(self):
""" Returns to stored home position, using the currently configured maximum speed."""
self._spi_write( dSPIN_CMD_GO_HOME)
def reset_pos(self):
""" Resets the position register to 0."""
self._spi_write( dSPIN_CMD_RESET_POS)
def go_mark(self):
""" Moves to the previously marked position."""
self._spi_write( dSPIN_CMD_GO_MARK)
def reset_device(self):
""" Resets the device."""
self._spi_write( dSPIN_CMD_RESET_DEVICE)
def soft_stop(self):
""" Decelerates and stops the motor using the currently configured deceleration rate.
Motors remains energized after stop.
"""
self._spi_write( dSPIN_CMD_SOFT_STOP)
def hard_stop(self):
""" Immediately stops the motor.
Motors remains energized after stop.
"""
self._spi_write( dSPIN_CMD_HARD_STOP)
def soft_hiZ(self):
""" Decelerates and stops the motor using the currently configured deceleration rate.
Motor is no more energized after stop (outputs in HiZ state).
"""
self._spi_write( dSPIN_CMD_SOFT_HIZ)
def hard_hiZ(self):
""" Immediately stops the motor.
Motor is no more energized after stop (outputs in HiZ state).
"""
self._spi_write( dSPIN_CMD_SOFT_HIZ)
self._spi_write( dSPIN_CMD_HARD_HIZ)
def wait_untill_not_busy(self):
""" Blocks until the busy signal is set."""
if self._not_busy:
while not GPIO.input(self._not_busy):
time.sleep(0.001)
else:
if self._log:
self._log.warn("busy pin not set")
def is_busy(self):
""" Tells if we are busy for the moment."""

return not GPIO.input(self._not_busy) if self._not_busy else False


def get_absolute_position(self):
""" Returns the current absolute position."""
return self.get_register(dSPIN_REG_ABS_POS)
def get_step_mode(self):
""" Returns the current settings of the step mode.
Returned value is one of dSPIN_STEP_SEL_xxx
"""
return self.get_register(dSPIN_REG_STEP_MODE) & dSPIN_STEP_MODE_STEP_SEL
def set_step_mode(self, step_sel):
""" Sets the step mode.
Parameters:
step_sel:
the step mode selector (one of dSPIN_STEP_SEL_xxx)
"""
val = \
(self.get_register(dSPIN_REG_STEP_MODE) & ~dSPIN_STEP_MODE_STEP_SEL) | \
(step_sel & dSPIN_STEP_MODE_STEP_SEL)
self.set_register(dSPIN_REG_STEP_MODE, val)
def set_max_speed(self, stepsPerSec):
""" Sets the maximum speed.
Parameters:
stepsPerSec:
the speed, in steps per second
"""
self.set_register(dSPIN_REG_MAX_SPEED, maxspeed_steps_to_par(stepsPerSec))
def set_acceleration(self, stepsPerSec2):
""" Sets the maximum acceleration rate.
Parameters:
stepsPerSec2:
the acceleration, in steps per second^2
"""
self.set_register(dSPIN_REG_ACC, accdec_steps_to_par(stepsPerSec2))
def set_deceleration(self, stepsPerSec2):
""" Sets the maximum deceleration rate.
Parameters:
stepsPerSec2:
the deceleration, in steps per second^2
"""
self.set_register(dSPIN_REG_DEC, accdec_steps_to_par(stepsPerSec2))
#!/usr/bin/env python
# -*- coding: utf-8 -*#
# dmxl_commons.py
#

#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#

Copyright 2013 Eric PASCUAL <eric <at> pobot <dot> org>


This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
MA 02110-1301, USA.

''' Common definitions for command line interface dmxl tools '''
from __future__ import print_function
import os
import sys
import argparse
from . import classes as dmxl
__author__ = 'Eric PASCUAL (POBOT)'
if os.name == 'posix':
DFLT_PORT = '/dev/ttyUSB0'
else:
DFLT_PORT = 'COM1'
def dmxl_id(s):
'''Servo id argparse option value checker.
The normal usage of this function is as an option type specifier when
definition an option argument, but it can also be used as a value converter.
Arguments:
s:
the option value as provided in the command line
Returns:
the id as an integer
Raises:
argparse.ArgumentTypeError if invalid value passed
'''
try:
dmxlid = int(s)
if dmxlid not in range(1, 255):
raise argparse.ArgumentTypeError('value not in range [1..254]')
return dmxlid

except ValueError:
raise argparse.ArgumentTypeError('not a valid integer (%s)' % s)
def dmxl_regnum(s):
'''Servo register number argparse option value checker.
See dmxl_id function for detailed documentation.
'''
try:
intval = int(s)
except ValueError:
raise argparse.ArgumentTypeError('not a valid integer (%s)' % s)
dmxl.Register.check_id(intval)
return intval
def add_bus_argparse_argument_group(parser):
'''Adds common options to an argparse parser being defined.
Added options are:
- port on which the bus interface is connected
- baud rate
- time out
They are gathered in a group named 'Bus options'.
Arguments:
parser:
the parser being defined
'''
group = parser.add_argument_group('Bus options')
group.add_argument('-p', '--port', dest='port',
help='the serial port of the bus interface',
default=DFLT_PORT)
group.add_argument('-b', '--baudrate', dest='baudrate', type=int,
help='the bus speed',
default=1000000)
group.add_argument('-T', '--timeout', dest='timeout', type=float,
help='bus timeout (in secs.)',
default=0.05)
def add_argparse_general_options(parser):
''' Adds common general options.
Added options are:
- verbose
Arguments:
parser:
the parser being defined
'''
parser.add_argument('-v', '--verbose', dest='verbose', action='store_true',
help='verbose output')
parser.add_argument('-D', '--debug', dest='debug', action='store_true',
help='debug mode (will trace communications)')

def get_argument_parser(**kwargs):
''' Returns a command line parser initialized with common settings.
Settings used :
- general options as defined by add_argparse_general_options
- Dynamixel bus options as defined by add_bus_argparse_argument_group
The parser is also set for displaying default values in help
'''
parser = argparse.ArgumentParser(
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
**kwargs
)
add_argparse_general_options(parser)
add_bus_argparse_argument_group(parser)
return parser
#!/usr/bin/env python
# -*- coding: utf-8 -*""" A simple model for a serial robotic arm build with Dynamixel servos. """
import time
import math
from . import classes as dmxl
from .classes import Register
logger = None
def pose_distance(p1, p2):
""" Returns the distance between two poses.
The distance is defined as the classical euclidian distance, working in a
space in which a coordinate represent the angle of a joint.
Parameters:
p1, p2:
the poses which distance is computed. They can be either a
dictionary which keys are the joint ids, and values are the joint
angles, or the equivalent tuples list.
Both poses must be related to the same poses space of course
Returns:
the euclidian distance between the poses
Raises:
ValueError if poses components don't refer to the same poses space
"""
if type(p1) is not dict:
p1 = dict(p1)
if type(p2) is not dict:
p2 = dict(p2)
if p1.viewkeys() != p2.viewkeys():
raise ValueError('poses components mismatch')

return math.sqrt(sum(d*d for d in [p1[jid] - p2[jid] for jid in p1.iterkeys()]))


class Joint(object):
""" A joint of the arm.
Joints are moved by servos of course, and this class acts as the interface
between the user's vision based in terms of the physical aspects of the joint
and the corresponding commands and settings of the servo. By instance, when
dealing with position we talk about angles and not servo steps.
"""
ORIENTATION_CCW = 1
ORIENTATION_CW = -1
_setup_is_valid = False
def __init__(self, dmxlid, intf,
angles_origin=0,
angles_orientation=ORIENTATION_CCW,
angles_range=(0,300),
angles_resolution=300. / 1023,
servo_setup=None):
""" Constructor.
Has lots of parameters, but common defaults for most of them.
Parameters:
dmxlid:
the id of the joint servo
intf:
the Dynamixel interface used to talked to the servos
angles_origin:
the servo position corresponding to 0 degrees
(default : 0)
angles_orientation:
the direction in which angles are counted positive
(default: counter-clockwise, ie trigonometric)
angles_range:
a tuple containing the valid range for angles
(default: (0, 300) which is the max travel for AX12 and alike)
angles_resolution:
the conversion ratio between degrees and steps (deg = pos * angles_resolution)
(default: AX12 value, is 1023 steps for 300 degrees.
servo_setup:
a dictionary containing the values to be set to the servo registers.
Keys are the register names (see dxml_lib.Register class)
(default: None)
Raises:
ValueError if parameters out of range, of if inconsistency in angles related
settings (origin, orientation and range).
"""
if not 1 <= dmxlid <= 253:
raise ValueError('invalid servo id')
if not intf:
raise ValueError('intf cannot be None')
if not isinstance(intf, dmxl.DynamixelBusInterface):
raise TypeError('intf must be DynamixelBusInterface instance')

self._dmxlid = dmxlid
self._intf = intf
self.angles_origin = angles_origin
self.angles_orientation = angles_orientation
self.angles_range = angles_range
self.angles_resolution = angles_resolution
self.servo_setup = {
Register.ReturnDelay : 0,
Register.TorqueLimit : 800,
Register.MovingSpeed : 0x3ff
}
if servo_setup:
self.servo_setup.update(servo_setup)
if self.angles_orientation == Joint.ORIENTATION_CCW:
self.servo_setup[Register.CWAngleLimit] = self.angle_to_pos(angles_range[0])
self.servo_setup[Register.CCWAngleLimit] = self.angle_to_pos(angles_range[1])
else:
self.servo_setup[Register.CCWAngleLimit] = self.angle_to_pos(angles_range[0])
self.servo_setup[Register.CWAngleLimit] = self.angle_to_pos(angles_range[1])
if self.servo_setup[Register.CWAngleLimit] >= self.servo_setup[Register.CCWAngleLimit]:
msg = 'inconsistency in setup of angles orientation, origin and range'
if logger:
logger.error(msg)
raise ValueError(msg)
self._setup_is_valid = True
self.apply_servo_setup()
#
#
#
#
#

print('*** servo id=%d' % self._dmxlid)


print('- setup:')
self._dump_setup(self.servo_setup)
print(' - registers:')
self.dump_regs()
@property
def dmxlid(self):
return self._dmxlid
@staticmethod
def _dump_setup(setup):
for k, v in [(k, setup[k]) for k in sorted(setup.keys())]:
Register.dump(k, v)
def dump_regs(self):
self._intf.dump_regs(self._dmxlid)
def apply_servo_setup(self):
if self._setup_is_valid:
for reg, value in self.servo_setup.iteritems():
self._intf.write_register(self._dmxlid, reg, value)

else:
raise RuntimeError('cannot apply invalid settings')
def angle_to_pos(self, angle):
msg = None
if angle < self.angles_range[0]:
angle = self.angles_range[0]
msg = 'angle clamped to low bound'
elif angle > self.angles_range[1]:
angle = self.angles_range[1]
msg = 'angle clamped to high bound'
if msg and logger:
logger.warning('[%d] %s (%f)' % (self._dmxlid, msg, angle))
return max(0,
min(1023,
int(round(angle / self.angles_resolution * self.angles_orientation)
+ self.angles_origin)
)
)
def pos_to_angle(self, pos):
return (pos - self.angles_origin) * self.angles_resolution * self.angles_orientation
def set_goal_angle(self, angle, speed=None, immed=True):
pos = self.angle_to_pos(angle)
if speed:
self._intf.write_register(self._dmxlid, Register.MovingSpeed, value=speed)
self._intf.write_register(self._dmxlid, Register.GoalPosition, pos, immed)
return pos
def get_current_angle(self):
pos = self._intf.read_register(self._dmxlid, Register.CurrentPosition)
return self.pos_to_angle(pos)
def is_moving(self):
return self._intf.read_register(self._dmxlid, Register.Moving)
def set_enable(self, state):
self._intf.write_register(self._dmxlid, Register.TorqueEnable, state)
class DynamixelArm(object):
""" A serial arm, made of a chain of several joints. """
def __init__(self, intf, config):
""" Constructor.
Parameters:
intf (dmxl_lib.DynamixelBusInterface):
a DynamixelBusInterface instance
config (dict):
a dictionary containing the configuration of
the arm
.. note::

The keys of the configuration dictionary are the identifiers


by which the joints are identified by the various methods.
The associated value are dictionaries containing
the Joint constructor parameters as kwargs
Raises:
ValueError:
if intf or config is None
TypeError:
if config is not a dict
"""
if not intf:
raise ValueError("intf parameter cannot be None")
if not config:
raise ValueError("config parameter cannot be None")
if type(config) is not dict:
raise TypeError('config parameter must be a dict')
self._intf = intf
self._joints = {}
self._max_dist2 = 0
for joint_id, cfg in config.iteritems():
if type(cfg) is not dict:
raise TypeError('joint configuration must be a dict')
self._joints[joint_id] = Joint(intf=intf, **cfg)
self.config_changed()
def config_changed(self):
""" Updates internal states which depends on the arm configuration.
Must be called if any change is done at joint level after the arm
initial creation, such as angle range modification for instance.
"""
# compute the reference value used when computing the normalized pose
# distance (see dist_from_pose() method)
self._max_dist2 = sum(d*d for d in [
r[0]-r[1] for r in [
j.angles_range for j in self._joints.itervalues()
]
]
)
def __getitem__(self, joint_id):
""" Returns a joint given its identifier.
Parameters:
joint_id (str):
the id of the joint
Raises:
KeyError if joint does not exist
"""
return self._joints[joint_id]

def __delitem__(self, joint_id):


raise RuntimeError('unsupported operation')
def __setitem__(self, joint_id, joint):
raise RuntimeError('unsupported operation')
def __len__(self):
return len(self._joints)
def __iter__(self):
""" Returns an iterator over the collection of joints."""
return self._joints.itervalues()
def __repr__(self):
return '<%s %s>' % (
self.__class__.__name__,
' '.join(['%s:%.1f' % (joint_id, joint.get_current_angle()) for
joint_id, joint in self._joints.iteritems()])
)
def set_enable(self, state):
"""Activates or deactivates the joint servos."""
for joint in self._joints.itervalues():
joint.set_enable(state)
def set_joint_angle(self, joint_id, angle, speed=None, wait=False, immed=True):
""" Sets the target angle of a given joint.
Parameters:
joint_id (str):
the id of the joint
angle (int):
the target angle in degrees. Allowed range : [-150, +150]
speed (int):
an optional speed used to override the default one used to
reach the target position. Allowed range : [0, 1023]
wait (bool):
an optional boolean (default: False) telling if the function
must return at once or wait until the position is reached.
immed (bool):
if True the movement is executed at once. Otherwise we just
set the target position and it is up to the caller to trigger
the execution (synchronized moves use case). Note that the wait
parameter has no meaning (and is ignored) if the move is
not immediate.
Returns:
the servo position corresponding to the requested angle (see Joint.set_goal_angle())
Raises:
KeyError:
if joint does not exist

ValueError:
if parameter(s) out of valid range
"""
joint = self._joints[joint_id]
pos = joint.set_goal_angle(angle, speed, immed)
if immed and wait:
while joint.is_moving():
time.sleep(0.05)
return pos
def is_joint_moving(self, joint_id):
""" Tells if a given joint is still moving or not.
Parameters:
joint (str):
the key of the joint
Raises:
KeyError if joint does not exist
"""
return self._joints[joint_id].is_moving()
def execute(self, gesture):
""" Executes a sequence of move statements.
The sequence is a list of statements, which are executed in sequence,
the complete execution of the current one being waited for before
processing the next one.
Each statement is either a move statement, or a set of move statements
to be executed in parallel.
A move statement is a tuple composed of :
- a joint id
- a target angle
- an optional move speed
A given move (or set of simultaneous move) statement(s) in the sequence
must have been completed before executing the next one.
Parameters:
gesture:
an instance of Gesture describing the move
"""
use_sync_read = isinstance(self._intf, dmxl.USB2AX)
all_ids = []
for stmt in gesture.sequence:
# will contain the goal positions keyed by the corresponding servo id
move_data = {}
if type(stmt) is tuple:
# single move statement

dmxlid, goal_pos = self._prepare_move(stmt)


move_data[dmxlid] = goal_pos
elif type(stmt) is set:
# set of simultaneous moves statement
for inner_stmt in stmt:
dmxlid, goal_pos = self._prepare_move(inner_stmt)
move_data[dmxlid] = goal_pos
else:
raise ValueError('invalid sequence (%s)' % stmt)
# accumulate ids of servos involved in th whole sequence
# (we'll use this list for the final torque hold state)
for _id in [_id for _id in move_data.keys() if _id not in all_ids]:
all_ids.append(_id)
# make all pending moves to occur
self._intf.action()
# Wait for all the involved joints to complete their move.
# Note that we don't use the moving status here since it seems to
# be updated at a pace slower than real time, which leads to delays
# between successive moves of the sequence
if use_sync_read:
dmxlids = move_data.keys()
goal_positions = [move_data[dmxlid] for dmxlid in dmxlids]
while True:
current_positions = self._intf.sync_read(dmxlids, Register.CurrentPosition)
reached = [abs(a-b) <= gesture.tolerance
for a,b in zip(goal_positions, current_positions)]
if all(reached):
break
# we could optimize by removing ids of servo having reached
# their goal positions, but there are good chances that the
# added computational overhead will not be balanced by the
# time saved by not querying these servos (apart perhaps
# for very crowded servo assemblies, but this has to be
# assessed)
else:
while move_data:
for dmxlid, goal_position in move_data.items():
error = abs(goal_position
- self._intf.read_register(dmxlid, Register.CurrentPosition))
if error <= gesture.tolerance:
del move_data[dmxlid]
if not gesture.hold_torque:
#self.set_enable(False)
for dmxlid in all_ids:
self._intf.write_register(dmxlid, Register.TorqueEnable, 0)
def _prepare_move(self, stmt):
""" Internal method processing the individual moves of a statement.

Parameters:
stmt (tuple) :
the statement to be prepared.
Items : (joint_id, angle [, move_speed])
Returns:
a tuple (id of the joint servo, goal position equivalent to the angle)
"""
if len(stmt) == 3:
joint_id, angle, speed = stmt
else:
joint_id, angle = stmt
speed = None
joint = self._joints[joint_id]
try:
pos = joint.set_goal_angle(angle, speed, immed=False)
except RuntimeError:
if logger:
logger.error('set_goal_angle error : joint=%s goal_angle=%.1f' % (joint_id, angle))
raise
return joint.dmxlid, pos
def get_current_pose(self):
""" Returns the current pose of the arm.
A pose is list of tuples, containing each a joint id and its position.
"""
return [(jid, joint.get_current_angle())
for jid, joint in self._joints.iteritems()]
def set_pose(self, pose, speed=None, sequential=False):
""" Set the pose of the arm.
Joints moves are done in the sequence given by the pose list.
Parameters:
pose:
the arm pose, as a list of tuples, each one composed of a servo id and
the corresponding target angle
speed:
see set_joint_angle
sequential:
if True, each move waits for the previous one to complete
before start (default: False)
"""
for jid, angle in pose:
self.set_joint_angle(jid, angle, speed, wait=sequential)
def dist_from_pose(self, pose):
""" Returns the normalized distance between the current arm pose and a given one.
The distance is defined as the Euclidian distance in a space defined a
coordinate system which components are the joints angle. It is
normalized by reference to the span of each joint.

Parameters:
pose:
the reference pose
Returns:
the normalized distance.
"""
p1 = dict(self.get_current_pose())
p2 = dict(pose)
if p1.viewkeys() != p2.viewkeys():
raise ValueError('pose definition mismatch')
d2 = sum(d*d for d in [p1[jid] - p2[jid] for jid in p1.iterkeys()])
return d2 / self._max_dist2
def closest_pose(self, poses):
""" Returns the index of the pose in the provided list which is the
closest from the current one.
Parameters:
poses:
a list of pose
Returns:
the index of the closest pose
"""
dists = [self.dist_from_pose(pose) for pose in poses]
return dists.index(min(dists))
def move_to_closest_pose(self, poses, **kwargs):
""" Moves the arm to the closest pose in the list.
Parameters:
poses:
a list of poses
**kwargs:
set_pose() arguments
Returns:
the index of the pose the arms moved to
"""
ndx = self.closest_pose(poses)
self.set_pose(poses[ndx], **kwargs)
return ndx
class InvalidMove(Exception):
def __init__(self, from_pose, to_pose):
super(InvalidMove, self).__init__()
self.from_pose, self.to_pose = from_pose, to_pose
def __str__(self):
return "cannot move from '%s' to '%s'" % (self.from_pose, self.to_pose)
class InvalidPose(Exception):
def __init__(self, pose):
super(InvalidPose, self).__init__()

self.pose = pose
def __str__(self):
return 'invalid pose : %s' % (self.pose,)
class Gesture(object):
def __init__(self, seq, hold_torque=False, tolerance=10):
""" Constructor
Parameters:
seq :
the sequence of moves
hold_torque (bool):
if True, actuators' torque will be maintained when the sequence is
completed (default: False)
tolerance (int):
the absolute value of the error between the target position and
the current one under which a movement in progress will be considered
as complete. Don't use 0 here since depending on the compliance
settings of the servo, there are room for the target position
not being exactly reached, and thus the move never being considered as
complete. Side effect : using 1024 (or more) is equivalent to executing
all the moves at the same time, since they will always be considered as
complete.
"""
self.sequence = seq
self.hold_torque = hold_torque
self.tolerance = tolerance
class GestureEngine(object):
""" A kindof state machine managing the possible arm gestures between poses.
"""
def __init__(self, arm):
self._gestures = {}
self._current_pose = None
self._arm = arm
def __repr__(self):
return '<%s pose:%s>' % (self.__class__.__name__, self._current_pose)
def add_gesture(self, from_pose, to_pose, sequence,
hold_torque=False,
tolerance=10):
""" Defines an arm gesture from a pose to another one.
Parameters:
from_pose:
starting pose
to_pose:
ending pose
sequence:
sequence of join moves for executing the gesture
hold_torque:
tells if joint torque must be held after the gesture completion

tolerance:
the clearance to the target joint position
"""
if from_pose not in self._gestures:
self._gestures[from_pose] = {}
self._gestures[from_pose][to_pose] = Gesture(sequence, hold_torque, tolerance)
def set_gestures(self, gestures):
self._gestures = gestures
self._current_pose = None
def get_current_pose(self):
""" Returns the current arm pose. """
return self._current_pose
def set_current_pose(self, pose_id):
""" Sets the current pose.
The pose must be one of those defined by the add_gesture method,
otherwise an InvalidPose exception is triggered.
"""
if pose_id in self._gestures:
self._current_pose = pose_id
else:
raise InvalidPose(pose_id)
def initialize(self):
""" Defines the initial pose and moves the arm to it.
Must be overriden by sub-classes
"""
raise NotImplementedError()
def move_to(self, pose_id):
""" Executes a gesture by moving from the current pose to the requested
one.
Acceptability of the gesture with respect to the current pose
of the arm is checked, based on the gestures table derived
from all the add_gesture calls.
Parameters:
pose_id:
the target pose, which must have been defined previously
with the add_gesture method.
Raises:
InvalidMove if gesture cannot be done
"""
if not self._current_pose:
raise RuntimeError('current pose is not defined')
if pose_id == self._current_pose:
return
try:
gesture = self._gestures[self._current_pose][pose_id]

except KeyError:
raise InvalidMove(self._current_pose, pose_id)
else:
if logger:
logger.info("moving from '%s' to '%s'" % (self._current_pose, pose_id))
self._arm.execute(gesture)
self._current_pose = pose_id
if logger:
logger.info('current pose is now : %s' % self._current_pose)
import dbus.service
from pybot.lcd03 import LCD03
import pybot.log
import time
import threading
# I2C_BUS_ID = 1
BUSNAME = 'org.pobot.rob.Console'
IF_DISPLAY = 'org.pobot.rob.Console.display'
IF_INPUT = 'org.pobot.rob.Console.input'
IF_CONTROL = 'org.pobot.rob.Console.control'
OBJPATH = '/org/pobot/rob/Console/object'
_logger = pybot.log.getLogger('console')
class ConsoleService(dbus.service.Object):
_loop = None
def __init__(self, i2c_bus, dbus_bus, dbus_loop):
self._loop = dbus_loop
self._lcd = LCD03(i2c_bus)
self._lcd.set_cursor_type(LCD03.CT_INVISIBLE)
self._scan_thread = None
self._menu = None
connection = dbus.service.BusName(BUSNAME, bus=dbus_bus)
dbus.service.Object.__init__(self, connection, OBJPATH)
_logger.info('started')
@dbus.service.method(IF_DISPLAY)
def clear(self):
self._lcd.clear()
self._menu = None
@dbus.service.method(IF_DISPLAY)
def home(self):
self._lcd.home()
@dbus.service.method(IF_DISPLAY, in_signature='b')
def set_backlight(self, on):

self._lcd.set_backlight(on)
@dbus.service.method(IF_DISPLAY, in_signature='suu')
def write_at(self, s, line, col):
self._lcd.write_at(s, line, col)
@dbus.service.method(IF_DISPLAY, in_signature='su')
def center_text_at(self, s, line):
self._lcd.center_text_at(s, line)
@dbus.service.method(IF_DISPLAY)
def show_splash(self):
self._lcd.clear()
self._lcd.center_text_at('DroidBuster v1.0', 1)
self._lcd.center_text_at('-**-', 2)
@dbus.service.method(IF_DISPLAY, in_signature='ssas')
def display_menu(self, menu_id, title, options):
optcnt = len(options)
if not 0 < optcnt < 7:
raise ValueError('invalid option count (%d)' % optcnt)
self._lcd.clear()
self._lcd.center_text_at(title, 1)
col = 1
offs = 2
colw = 18 if optcnt < 4 else 7
for i, s in enumerate(options):
if i == 3:
offs, col = -1, 12
self._lcd.write_at('%d.%s' % (i + 1, s[:colw]), i + offs, col)
self._menu = (menu_id, optcnt)
_logger.info('displaying menu: %s' % str(self._menu))
self.listen_to_keypad(True)
@dbus.service.method(IF_INPUT, out_signature='as')
def get_keys(self):
return self._lcd.get_keys()
@dbus.service.signal(IF_INPUT, signature='as')
def key_pressed(self, keys):
pass
@dbus.service.signal(IF_INPUT, signature='si')
def option_selected(self, menu_id, option):
_logger.info("option '%d' selected in menu '%s'" % (option, menu_id))
if option < 4:
col, line = 2, option + 1
else:
col, line = 13, option - 2
self._lcd.write_at('>', line, col)
self._menu = None
@dbus.service.method(IF_CONTROL, in_signature='b')
def listen_to_keypad(self, state):

if state:
if self._lcd.keypad_autoscan_start(self._autoscan_callback):
_logger.info('listening to keypad')
else:
if self._lcd.keypad_autoscan_stop():
_logger.info('no more listening to keypad')
def _autoscan_callback(self, keys, _lcd):
if self._menu:
menu_id, optcnt = self._menu
try:
opt = int(keys[0])
if opt in range(1, optcnt + 1):
self.option_selected(menu_id, opt)
self.listen_to_keypad(False)
self._selected_option = opt
except:
pass
else:
self.key_pressed(keys)
def run(self):
if self._loop:
_logger.info('running')
self._loop.run()
_logger.info('terminating')
self.listen_to_keypad(False)
self._lcd.set_backlight(False)
@dbus.service.method(IF_CONTROL)
def shutdown(self):
if self._loop:
self._loop.quit()
#!/usr/bin/env python
# -*- coding: utf-8 -*""" A set of simple classes for interacting with the ServoPi board from AB Electronics
(https://www.abelectronics.co.uk/products/3/Raspberry-Pi/44/Servo-Pi).
At the end of the chain is the individual servo, provided by the class :py:class:`Servo`.
Instances are attached to a :py:class:`Port`, modeling a real MCP23017 port. Ports are themselves attached to a
:py:class:`MCP23017`, which is itself attached to a :py:class:`IOPiBoard`.
All intricacies related to registers configuration and alike are hidden from the user,
and a lot of things are optimized by caching instances and data for avoiding paying a too high price
for a high level design. Even if the added overhead could seem penalizing in term of performances,
this is not that obvious, since all the processing done here must be done somewhere anyway. There
are thus chances that the effective penalty (if ever any) will be negligible for most of the
applications.
To allow several classes instances being used for modeling a configuration with multiple boards,
possibly of different types, the underlying bus must be created outside and provided as a dependency
(see documentation about de "Dependency Injection" design pattern). Being able to provide whatever

implementation of the basic read and write operations allows the use of a fake class simulating
real operations, which made possible to test the rest of the application on a system not having
a SMBus resource available, such as the development station PC for instance, or providing access to
an I2C bus using an USB adapter for instance.
Here is an example of the code used for configuring and reading an input.
>>>
>>>
>>>
>>>
>>>
>>>
>>>
>>>
>>>
>>>

from pybot.raspi import i2c_bus


from pybot.abelectronics.adcpi import ADCPiBoard
...
# create an instance of the board (using default I2C addresses)
board = ServoPiBoard(i2c_bus)
# define a servo, connected to channel 1 on the board header
servo = board.get_servo(1)
...
# move it to median position
servo.set_position(0.5)

Pretty simple, no ?
Beyond the basic usage illustrated above, the servo instance can be configured in a lot
of different ways :
- pulse durations can be set to adapt to the model, or to limit the position extent
- custom values can be assigned to these limits, so that instead of manipulating the
angles in range 0 to 180, it is possible to use a percent of the total span, or
shift the angles domain to range -90.0 to 90.0, or any convention representing
something meaningful at the application level
Refer to :py:class:`Servo` and :py:class:`StopSpecification` classes for details.
"""
__author__ = 'Eric PASCUAL for POBOT'
__version__ = '1.0.0'
__email__ = 'eric@pobot.org'
import math
import time
from collections import namedtuple
from functools import total_ordering
@total_ordering
class StopSpecification(namedtuple("StopSpecification", "logical_position msecs")):
""" A named tuple defining a stop position (i.e. extent limit) of the servo.
The position is defined by the corresponding pulse duration and the associated
application domain value. This value can be an angle in degrees, a 0-100 range percentage,...
Said differently, it defines the scale, the unit and the possible direction reversal used
by the application to provide a position (absolute or relative) for the servo.
For instance, setting a servo with min and max stops set to (90, 0.5) and (-90, 2.5)
respectively will allow the application to deal with degrees positions, with 90 corresponding
to 3 o'clock and -90 to 9 o'clock (which reverses the angle sign from the default settings,
counting them in CCW mode).
Using pulse durations inside the physical limits of the servo provides a software extent
limitation, since the effective orders sent to the servo will never be outside this limited

range.
An order relation is defined, based on the pulse duration (since the logical position can
be in reverse direction). The equality relation is based on both instance attributes.
"""
__slots__ = ()
def __new__(cls, logical_position, msecs):
"""
:param float logical_position: the application level value associated to this stop
:param float msecs: the associated pulse duration
:raise: ValueError if the duration is outside a reasonable range for servos
"""
if not 0.0 <= msecs <= 3.0:
raise ValueError('msecs must be in [0.0-3.0]')
return super(StopSpecification, cls).__new__(cls, float(logical_position), float(msecs))
def __lt__(self, other):
return self.msecs < other.msecs
def __le__(self, other):
return self.msecs <= other.msecs
def __eq__(self, other):
return self.msecs == other.msecs and self.logical_position == other.logical_position
def __sub__(self, other):
return StopSpecification(self.logical_position - other.logical_position, self.msecs - other.msecs)
def __add__(self, other):
return StopSpecification(self.logical_position + other.logical_position, self.msecs + other.msecs)
def scale(self, factor):
""" Returns a scaled instance by multiplying both attributes by a given factor.
:param float factor: scaling factor
:return: a new instance, with attributes set to the original ones scaled
"""
return StopSpecification(self.logical_position * factor, self.msecs * factor)
def __str__(self):
return '(p:%.1f t:%.1f)' % (self.logical_position, self.msecs)
class Servo(object):
""" Logical model of a servo controlled by the board.
"""
DEFAULT_POS_MIN, DEFAULT_POS_MAX = (0.0, 180.0)
DEFAULT_MS_MIN, DEFAULT_MS_MAX = (0.6, 2.4)
DEFAULT_STOP_MIN = StopSpecification(DEFAULT_POS_MIN, DEFAULT_MS_MIN)
DEFAULT_STOP_MAX = StopSpecification(DEFAULT_POS_MAX, DEFAULT_MS_MAX)
def __init__(self, board, channel, stop_min=DEFAULT_STOP_MIN, stop_max=DEFAULT_STOP_MAX):
""" If default settings are used, the servo model is supposed to honor a standard
0.5 to 2.5 ms pulse for a 180 degrees total horn course counted clock wise. The servo position
will thus be given in subsequent calls as a floating point value representing the horn angle in degrees.

This can be customized by specifying specific stop position definition, both in terms of pulse width
and associated application level position value.
:param ServoPiBoard board: the ServoPi board controlling the servo
:param int channel: the channel (1 to 16) to which the servo is connected
:param StopSpecification stop_min: specifications of the min stop position (position with the shorter pulse),
if not the default one
:param StopSpecification stop_max: specifications of the max stop position, if not the default one
:raise: ValueError if a parameter value is not valid
:raise: TypeError if the stop definitions are not of the expected type
"""
if not board:
raise ValueError('board parameter is mandatory')
if not 1 <= channel <= 16:
raise ValueError('channel must be in [1-16]')
if not (isinstance(stop_min, StopSpecification) and isinstance(stop_max, StopSpecification)):
raise TypeError('invalid stop definition(s) type')
if stop_max <= stop_min:
raise ValueError('stop definitions are reversed')
self._board = board
channel -= 1
self._regs = [r + 4 * channel for r in ServoPiBoard.LED0_x]
self._stop_min = stop_min
self._stop_max = stop_max
self._median = (stop_min + stop_max).scale(0.5)
self._span = stop_max - stop_min
self._pos_to_ms = float(self._span.msecs) / float(self._span.logical_position)
self._position_to_msecs = self._position_to_msec_direct if self._pos_to_ms > 0 else self._position_to_msec_inverted
self._current_position = None
def __str__(self):
return "{min=%s max=%s}" % (self._stop_min, self._stop_max)
__repr__ = __str__
def _position_to_msec_direct(self, position):
position = min(max(position, self._stop_min.logical_position), self._stop_max.logical_position)
return self._stop_min.msecs + self._pos_to_ms * (position - self._stop_min.logical_position), position
def _position_to_msec_inverted(self, position):
position = min(max(position, self._stop_max.logical_position), self._stop_min.logical_position)
return self._stop_max.msecs + self._pos_to_ms * (position - self._stop_max.logical_position), position
def set_position(self, position, force=False):
""" Moves the servo to the given position.
The position is expressed in application logical unit. No move is done if the
requested position is the same as the current one, except if the `force` parameter
is set to True.
:param float position: the servo position (see :py:meth:`Servo.__init__` documentation for
possible values
:param bool force: if True, the move process will be engaged, whatever is the current position
"""
if not force and position == self._current_position:
return

ms, real_position = self._position_to_msecs(position)


raw = self._board.ms_to_reg(ms)
values = (0, 0, raw & 0xff, raw >> 8)
for r, v in zip(self._regs, values):
self._board.write_reg(r, v)
self._current_position = real_position
@property
def current_position(self):
""" The current position set point. Be aware that it can be different from the real
position, either because it didn't reach it already, or cannot do it.
"""
return self._current_position
def goto_minimum_position(self, force=False):
""" Moves the servo to its minimum position as defined by `stop_min` constructor parameter.
:param bool force: see :pyh:meth:`Servo.set_position`
"""
self.set_position(self._stop_min.logical_position, force)
def goto_maximum_position(self, force=False):
""" Moves the servo to its maximum position as defined by `stop_max` constructor parameter.
:param bool force: see :pyh:meth:`Servo.set_position`
"""
self.set_position(self._stop_max.logical_position, force)
def goto_median_position(self, force=False):
""" Moves the servo half way between its minimum and maximum positions.
:param bool force: see :pyh:meth:`Servo.set_position`
"""
self.set_position(self._median.logical_position, force)
def relative_move(self, d_position, force=False):
""" Moves the servo by a relative amount from its current set point.
This method requires an absolute position being previously set.
:param float d_position: the relative move, provided in application logical unit
:param bool force: see :pyh:meth:`Servo.set_position`
"""
if not d_position:
return
if self._current_position is None:
raise Exception("current position not yet defined")
self.set_position(self._current_position + d_position, force)
def set_floating(self):
""" Puts the servo in floating mode by deactivating pulses generation.
"""
for r in self._regs:
self._board.write_reg(r, 0)

class ServoPiBoard(object):
""" Models the ServoPi board.
In addition to global configuration and chip dialogs, it provides a factory method to
obtain Servo instances attached to it. Returned instances are cached for optimizing
identical requests.
"""
MODE1 = 0x00
MODE2 = 0x01
SUBADR1 = 0x02
SUBADR2 = 0x03
SUBADR3 = 0x04
ALLCALLADR = 0x05
LED0_ON_L = 0x06
LED0_ON_H = 0x07
LED0_OFF_L = 0x08
LED0_OFF_H = 0x09
ALL_LED_ON_L = 0xFA
ALL_LED_ON_H = 0xFB
ALL_LED_OFF_L = 0xFC
ALL_LED_OFF_H = 0xFD
PRE_SCALE = 0xFE
LED0_x = (LED0_ON_L, LED0_ON_H, LED0_OFF_L, LED0_OFF_H)
_ENABLE_GPIO = 7
DEFAULT_ADDRESS = 0x40
DEFAULT_PWM_FREQ = 60
_ms_to_reg = None
def __init__(self, bus, i2c_addr=DEFAULT_ADDRESS, pwm_freq=DEFAULT_PWM_FREQ, use_output_enable=False):
"""
:param bus: the I2C/SMBus the board is connected to
:param int i2c_addr: the board I2C address
:param int pwm_freq: the PWM frequency
:param bool use_output_enable: set to True if we want to use the output enable signal of the
PCA9685 chip. Remember to short the OE pads on the board in this case.
"""
self._bus = bus
self._i2c_addr = i2c_addr
self._servos = {}
self.write_reg(self.MODE1, 0)
# optimize to GPIO stuff loading depending on the fact we really need it or not
if use_output_enable:
from pybot.gpio import GPIO
self._GPIO = GPIO
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BOARD)
GPIO.setup(self._ENABLE_GPIO, GPIO.OUT)
self.enable_all()
else:
self._GPIO = None

self.set_pwm_freq(pwm_freq)
def shutdown(self):
""" Puts all servos in floating mode and disables PCA9685 outputs.
"""
for servo in self._servos.values():
servo.set_floating()
self.disable_all()
def set_pwm_freq(self, hz):
""" Configures the PWM frequency
:param int hz: frequency
"""
scale_value = 25000000.0 # 25MHz
scale_value /= 4096.0
# 12-bit
scale_value /= float(hz)
scale_value -= 1.0
self._ms_to_reg = 4.096 * hz
pre_scale = math.floor(scale_value + 0.5)
old_mode = self.read_reg(self.MODE1)
new_mode = (old_mode & 0x7F) | 0x10
self.write_reg(self.MODE1, new_mode)
self.write_reg(self.PRE_SCALE, int(math.floor(pre_scale)))
self.write_reg(self.MODE1, old_mode)
time.sleep(0.005)
self.write_reg(self.MODE1, old_mode | 0x80)
def enable_all(self):
""" Enables all the chip outputs
"""
if self._GPIO:
self._GPIO.output(self._ENABLE_GPIO, 0)
def disable_all(self):
""" Disables all the chip outputs
"""
if self._GPIO:
self._GPIO.output(self._ENABLE_GPIO, 1)
def write_reg(self, reg, value):
""" Chip register setter
"""
self._bus.write_byte_data(self._i2c_addr, reg, value)
def read_reg(self, reg):
""" Chip register getter
"""
return self._bus.read_byte_data(self._i2c_addr, reg) & 0xff
def ms_to_reg(self, ms):
""" Convenience method for converting a duration to the corresponding register encoding value.
:param float ms: duration to convert
:return: corresponding register encoded value

:rtype: int
"""
return int(ms * self._ms_to_reg)
def get_servo(self, channel, stop_min=Servo.DEFAULT_STOP_MIN, stop_max=Servo.DEFAULT_STOP_MAX):
""" Factory method returning an instance of the class :py:meth:`Servo` with the given
configuration.
Instances are cached for optimization.
:param int channel: channel number (1-16) to which the servo is connected
:param StopSpecification stop_min: see :py:class:`Servo` constructor
:param StopSpecification stop_max: see :py:class:`Servo` constructor
:return: a Servo instance
:raise: ValueError is invalid channel number
"""
if not 1 <= channel <= 16:
raise ValueError("invalid channel num (%d)" % channel)
try:
return self._servos[channel]
except KeyError:
servo = Servo(self, channel, stop_min=stop_min, stop_max=stop_max)
self._servos[channel] = servo
return servo
#!/usr/bin/env python
# -*- coding: utf-8 -*""" A set of simple classes for interacting with the IOPi board from AB Electronics
(https://www.abelectronics.co.uk/products/3/Raspberry-Pi/18/IO-Pi).
At the end of the chain is the individual IO, provided by the classes :py:class:`DigitalInput`
and :py:class:`DigitalOutput` (they will ease the transition for Arduino fans ;). IO instances
are attached to a :py:class:`Port`, modeling a real MCP23017 port. Ports are themselves attached to a
:py:class:`MCP23017`, which is itself attached to a :py:class:`IOPiBoard`.
All these intricacies such as bit masking on register values and alike are hidden from the user,
and a lot of things are optimized by caching instances and data for avoiding paying too high a price
for a high level design. Even if the added overhead could seem penalizing in term of performances,
this is not that obvious, since all the processing done here must be done somewhere anyway. There
are thus chances that the effective penalty (if ever any) will be negligible for most of the
applications.
To allow several classes instances being used for modeling a configuration with multiple boards,
possibly of different types, the underlying bus must be created outside and provided as a dependency
(see documentation about de "Dependency Injection" design pattern). Being able to provide whatever
implementation of the basic read and write operations allows the use of a fake class simulating
real operations, which made possible to test the rest of the application on a system not having
a SMBus resource available, such as the development station PC for instance, or providing access to
an I2C bus using an USB adapter for instance. For examples of this, have a look at our i2c module
(https://github.com/Pobot/PyBot/blob/master/pybot/i2c.py). We have included there an enhanced smbus
class (:py:class:`pybot.i2c.SMBusI2CBus`), taking care of serializing I/O operations, in case of
multi-threaded usage.
Here is an example of the code used for controlling a LED connected to pin 3 of IC_1 expander
header. We suppose that the LED is wired in positive logic, i.e. lightened when the IO is high.
>>> from pybot.raspi import i2c_bus
>>> from pybot.abelectronics.iopi import IOPiBoard

>>>
>>>
>>>
>>>
>>>
>>>
>>>
>>>
>>>
>>>
>>>

...
# create an instance of the board
board = IOPiBoard(i2c_bus)
# define an output IO corresponding to the one connected to the LED
led = board.get_digital_output(board.EXPANDER_1, 3)
...
# switch the LED on by setting the IO in high state
led.set()
...
# switch the LED off by setting the IO in low state
led.clear()

Here is another example for reading an input, f.i. a switch connected on pin 11.
>>>
>>>
>>>
>>>
>>>
>>>

board = IOPiBoard(i2c_bus)
switch = board.get_digital_input(board.EXPANDER_1, 11, pullup_enabled=True
...
# read the state of the switch
if switch.is_set():
...

Pretty simple, no ?
"""
__author__ = 'Eric PASCUAL for POBOT'
__version__ = '2.0.0'
__email__ = 'eric@pobot.org'
class IOPiBoard(object):
""" This class represents a whole IOPi expansion board.
Its main role is to act as a factory for individual IOs, so that the application
will not deal with bit masking and other boring chores.
It can be used directly to manipulate the registers of the 4 embedded I/O ports,
but usually it's simpler to use instances of the Port class for this. This is
fully equivalent, but user code is more readable this way.
This costs a small overhead since the Port methods delegates to Board ones, taking
care of passing them the additional parameters, but unless you have a critical
performances problem, it should do the trick most of the time.
"""
EXPANDER_1 = 0
EXPANDER_2 = 1
EXP1_DEFAULT_ADDRESS = 0x20
EXP2_DEFAULT_ADDRESS = 0x21
def __init__(self, bus, exp1_addr=EXP1_DEFAULT_ADDRESS, exp2_addr=EXP2_DEFAULT_ADDRESS):
""" An instance of the I2C/SMBus class must be provided here. The calls used in the
classes of this module are based on the smbus.SMBus interface. Any implementation providing
the same API can thus be used, including fake ones for testing.
:param bus: the I2C/SMBus instance

:param int exp1_addr: I2C address of expander 1


:param int exp2_addr: I2C address of expander 1
"""
self._bus = bus
self.expanders = (
Expander(bus, exp1_addr),
Expander(bus, exp2_addr),
)
self._ios = {}
def _get_io(self, expander_num, board_io_num, direction, pullup_enabled=False):
if expander_num not in (self.EXPANDER_1, self.EXPANDER_2):
raise ValueError("invalid expander num (%d)" % expander_num)
if not 1 <= board_io_num <= 16:
raise ValueError("invalid IO num (%d)" % board_io_num)
key = (expander_num << 8) + board_io_num
try:
# try first to get the object from the cache
io = self._ios[key]
except KeyError:
# not yet created => do it
port_num, io_num = self._board_io_num_to_port_io(board_io_num)
port = self.expanders[expander_num].ports[port_num]
# create the instance of the appropriate class, depending on the IO type
if direction == IO.DIR_INPUT:
io = DigitalInput(port, io_num, pullup_enabled=pullup_enabled)
else:
io = DigitalOutput(port, io_num)
# cache the result
self._ios[key] = io
return io
def get_digital_input(self, expander_num, board_io_num, pullup_enabled=False):
""" Factory method returning a DigitalInput instance for a given IO, and configures
it as requested.
:param int expander_num: IOPiBoard.EXPANDER_1 or IOPiBoard.EXPANDER_2
:param int board_io_num: the pin number of the IO on the expander header
:param pullup_enabled: should the internal pull-up be enabled or not
:return: the IO object
:rtype: DigitalInput
"""
return self._get_io(expander_num, board_io_num, direction=IO.DIR_INPUT, pullup_enabled=pullup_enabled)
def get_digital_output(self, expander_num, board_io_num):
""" Factory method returning a DigitalOutput instance for a given IO.
:param int expander_num: IOPiBoard.EXPANDER_1 or IOPiBoard.EXPANDER_2
:param int board_io_num: the pin number of the IO on the expander header
:return: the IO object
:rtype: DigitalOutput
"""
return self._get_io(expander_num, board_io_num, direction=IO.DIR_OUTPUT)
def read(self):
""" Reads all ports of all expanders and returns their values as a single 32 bits integer.

The returned integer is built as follows:


MSB1 = expander_2.port_B
LSB1 = expander_2.port_A
MSB0 = expander_1.port_B
LSB0 = expander_1.port_A
:return: all board ports content
"""
return ((self.expanders[self.EXPANDER_2].read() << 16) | self.expanders[self.EXPANDER_1].read()) & 0xffffffff
def reset(self):
""" Resets both expanders of the board
"""
for expander in self.expanders:
expander.reset()
@staticmethod
def _board_io_num_to_port_io(board_io_num):
board_io_num -= 1
return board_io_num / 8, board_io_num % 8
class Expander(object):
""" Models the MCP23017 expander chip, and handles its low level operations.
This class aggregates the two ports included in the chip.
"""
PORT_A = 0
PORT_B = 1
# the register addressing scheme used here supposes that IOCON.BANK is set to 0 (default value)
# and thus that port registers are sequential, so that address(xxxB) == address(xxxA + 1)
IODIR = 0x00
IPOL = 0x02
GPINTEN = 0x04
DEFVAL = 0x06
INTCON = 0x08
IOCON = 0x0A
GPPU = 0x0C
INTF = 0x0E
INTCAP = 0x10
GPIO = 0x12
OLAT = 0x14
# IOCON register flag masks
IOCON_INTPOL = 0x02
IOCON_ODR = 0x04
IOCON_HAEN = 0x08
IOCON_DISSLW = 0x10
IOCON_SEQOP = 0x20
IOCON_MIRROR = 0x40
IOCON_BANK = 0x80
def __init__(self, bus, i2c_addr):
"""
:param bus: the I2C/SMBus instance
:param int i2c_addr: expander I2C address

"""
self._bus = bus
self._addr = i2c_addr
self.ports = (
Port(self, self.PORT_A),
Port(self, self.PORT_B)
)
def read_register(self, addr):
""" Reads a chip register.
:param int addr: register address
:return: register content
:rtype: int
"""
return self._bus.read_byte_data(self._addr, addr) & 0xff
def write_register(self, reg, data):
""" Writes a chip register.
Since the method takes care of clamping the passed value to a byte
extent, it also returns the clamped value for convenience.
:param int reg: register address
:param int data: register value
:return: the written data
:rtype: int
"""
data &= 0xff
self._bus.write_byte_data(self._addr, reg, data)
return data
def read(self):
""" Reads both expander ports and return their values as a 16 bits integer.
:return: 2 bytes integer with PORTB and PORTA values as respectively MSB and LSB
:rtype: int
"""
return ((self.ports[Expander.PORT_B].read() << 8) | self.ports[Expander.PORT_A].read()) & 0xffff
def reset(self):
""" Resets both ports
"""
for port in self.ports:
port.reset()
class Port(object):
""" Model of an IO port of an expander.
Implements a caching mechanism for non volatile registers, so that modification
operations are optimized by removing the need for physically reading the registers
content.
"""
# sets the cache in unset state
_IODIR_cache = None
_GPPU_cache = None

_IPOL_cache = None
_IOCON_cache = None
_GPINTEN_cache = None
_INTCON_cache = None
_DEFVAL_cache = None
def __init__(self, expander, port_num):
"""
:param Expander expander: Expander instance this port belongs to
:param int port_num: the port num (Expander.PORT_A or Expander.PORT_B).
"""
if port_num not in (Expander.PORT_A, Expander.PORT_B):
raise ValueError("invalid port num (%d)" % port_num)
self._expander = expander
self._port_num = port_num
# initializes the registers cache
self.update_cache()
def update_cache(self):
""" Updates the registers cache. """
self._IODIR_cache = self._expander.read_register(Expander.IODIR + self._port_num)
self._GPPU_cache = self._expander.read_register(Expander.GPPU + self._port_num)
self._IPOL_cache = self._expander.read_register(Expander.IPOL + self._port_num)
self._IOCON_cache = self._expander.read_register(Expander.IOCON + self._port_num)
self._GPINTEN_cache = self._expander.read_register(Expander.GPINTEN + self._port_num)
self._DEFVAL_cache = self._expander.read_register(Expander.DEFVAL + self._port_num)
self._INTCON_cache = self._expander.read_register(Expander.INTCON + self._port_num)
@staticmethod
def _change_bit(bit_num, value, byte):
return byte | (1 << bit_num) if value else byte & ~ (1 << bit_num)
@staticmethod
def _change_bit_with_mask(bit_mask, value, byte):
return byte | bit_mask if value else byte & ~ bit_mask
@staticmethod
def _test_bit(bit_num, byte):
return (byte & (1 << bit_num)) != 0
@staticmethod
def _test_bit_with_mask(bit_mask, byte):
return (byte & bit_mask) != 0
@staticmethod
def _check_io_num(io_num):
if not 0 <= io_num < 8:
raise ValueError('invalid IO num (%d)' % io_num)
@property
def io_directions(self):
""" Returns the current settings of the port IO directions. """
return self._IODIR_cache
@io_directions.setter
def io_directions(self, dirs):
""" Sets the port IO directions configuration.

:param int dirs: a byte containing the IO direction flags for all the IO of the port
"""
self._IODIR_cache = self._expander.write_register(Expander.IODIR + self._port_num, dirs)
def set_io_direction(self, io_num, direction):
""" Sets the direction of a single IO
:param int io_num: the IO num ([0-7])
:param int direction: IO.DIR_INPUT or IO.DIR_INPUT
:raise: ValueError if out of range io_num
"""
self._check_io_num(io_num)
self.io_directions = self._change_bit(io_num, direction == IO.DIR_INPUT, self._GPPU_cache)
@property
def pullups_enabled(self):
""" Returns the current settings of the port inputs pullups. """
return self._GPPU_cache
@pullups_enabled.setter
def pullups_enabled(self, settings):
""" Configures the port inputs pullups. """
self._GPPU_cache = self._expander.write_register(Expander.GPPU + self._port_num, settings)
def enable_pullup(self, io_num, enabled):
""" Configures a single input pullup.
:param int io_num: the IO num ([0-7])
:param bool enabled: is the pullup enabled ?
:raise: ValueError if out of range io_num
"""
self._check_io_num(io_num)
self.pullups_enabled = self._change_bit(io_num, enabled, self._GPPU_cache)
@property
def inputs_inverted(self):
""" Returns the current settings of the port inputs polarity inversion. """
return self._IPOL_cache
@inputs_inverted.setter
def inputs_inverted(self, settings):
""" Configures the port inputs polarity inversion. """
self._IPOL_cache = self._expander.write_register(Expander.IPOL + self._port_num, settings)
def invert_input(self, io_num, inverted):
""" Configures the inversion of a given input.
:param int io_num: the IO num ([0-7])
:param bool inverted: is the input inverted ?
:raise: ValueError if out of range io_num
"""
self._check_io_num(io_num)
self.interrupts_enabled = self._change_bit(io_num, inverted, self._IPOL_cache)
@property
def interrupts_enabled(self):
""" Returns the current settings of the port inputs interrupts enabling. """
return self._GPINTEN_cache
@interrupts_enabled.setter

def interrupts_enabled(self, settings):


""" Configures the port inputs interrupts enabling. """
self._GPINTEN_cache = self._expander.write_register(Expander.GPINTEN + self._port_num, settings)
def enable_interrupt(self, io_num, enabled):
""" Enables interrupts for a given input.
:param int io_num: the IO num ([0-7])
:param bool enabled: is interrupt enabled ?
:raise: ValueError if out of range io_num
"""
self._check_io_num(io_num)
self.interrupts_enabled = self._change_bit(io_num, enabled, self._GPINTEN_cache)
@property
def interrupt_sources(self):
""" Returns the current settings of the port interrupt compare sources. """
return self._INTCON_cache
@interrupt_sources.setter
def interrupt_sources(self, settings):
""" Configures the port interrupt compare sources. """
self._INTCON_cache = self._expander.write_register(Expander.INTCON + self._port_num, settings)
def set_interrupt_source(self, io_num, source):
""" Sets the compare source for input interrupts for a given input.
:param int io_num: the IO num ([0-7])
:param int source: IO.INT_COMPARE or IO.INT_CHANGE
:raise: ValueError if out of range io_num
"""
self._check_io_num(io_num)
self.interrupt_sources = self._change_bit(io_num, source, self._INTCON_cache)
@property
def default_values(self):
""" Returns the current settings of the port interrupt default values. """
return self._DEFVAL_cache
@default_values.setter
def default_values(self, settings):
""" Configures the port interrupt default values. """
self._DEFVAL_cache = self._expander.write_register(Expander.DEFVAL + self._port_num, settings)
def set_default_value(self, io_num, value):
""" Sets the input change default value for a given input.
:param int io_num: the IO num ([0-7])
:param int value: default value (0 or 1)
:raise: ValueError if out of range io_num
"""
self._check_io_num(io_num)
self.default_values = self._change_bit(io_num, value, self._DEFVAL_cache)
@property
def configuration(self):
return self._IOCON_cache
@configuration.setter
def configuration(self, value):

self._IOCON_cache = self._expander.write_register(self._IOCON_cache + self._port_num, value)


def write(self, value):
""" Write a value to the port
:param int value: the value to be written
:return: the clamped value (see :py:meth:`Expander.write_register`)
:rtype: int
"""
return self._expander.write_register(Expander.GPIO + self._port_num, value)
def read(self):
""" Reads the port.
:return: the port content
"""
return self._expander.read_register(Expander.GPIO + self._port_num) & 0xff
def reset(self):
""" Puts the port in default POR state
"""
self.interrupts_enabled = 0
self.pullups_enabled = 0
self.io_directions = 0xff
def dump(self):
""" Internal method for debugging."""
for k, v in self.__dict__.iteritems():
print("%20s : %s" % (k, v))
class IO(object):
""" Root class for modeling a single IO or a port.
"""
DIR_OUTPUT = 0
DIR_INPUT = 1
INT_CHANGE = 0
INT_COMPARE = 1
def __init__(self, port, num, is_input):
"""
:param Port port: the port the IO is attached to
:param int num: IO num ([0-7])
:param bool is_input: is an input or not ?
"""
if not 0 <= num < 8:
raise ValueError('invalid IO num (%d)' % num)
self._port = port
port.set_io_direction(num, IO.DIR_INPUT if is_input else IO.DIR_OUTPUT)
self._mask = 1 << num
@property
def port(self):
""" The port this IO belongs to.
"""
return self._port
@property

def mask(self):
""" The bit mask of this IO.
It can be useful for manipulating IOs with port single read/write
for optimizing access.
"""
return self._mask
class _ReadableIOMixin(object):
""" A mixin gathering read operations applicable to IOs.
It can be used equally for inputs and outputs, and will read the latches
for the later.
"""
_port = None
_mask = None
def read(self):
""" Returns the bit value (0 or 1) of the IO. """
return 1 if self._port.read() & self._mask else 0
def is_set(self):
""" Returns True if the IO is high. """
return self.read()
def is_clear(self):
""" Returns True if the IO is low. """
return not self.read()
class DigitalInput(IO, _ReadableIOMixin):
""" A specialized IO modeling an input."""
def __init__(self, port, num, pullup_enabled=False):
"""
:param Port port: the port this IO belongs to
:param int num: the IO number ([0-7])
:param bool pullup_enabled: should the pullup be enabled ?
"""
super(DigitalInput, self).__init__(port, num, is_input=True)
self._port.enable_pullup(num, pullup_enabled)
class DigitalOutput(IO, _ReadableIOMixin):
""" A specialized IO modeling an output."""
def __init__(self, port, num):
"""
:param Port port: the port this IO belongs to
:param int num: the IO number ([0-7])
"""
super(DigitalOutput, self).__init__(port, num, is_input=False)
def set(self):
""" Turns the output high."""
self._port.write(self._port.read() | self._mask)
def clear(self):

""" Turns the output low."""


self._port.write(self._port.read() & (~ self._mask))
#!/usr/bin/env python
# -*- coding: utf-8 -*""" A set of simple classes for interacting with the ADCPi board from AB Electronics
(https://www.abelectronics.co.uk/products/3/Raspberry-Pi/17/ADC-Pi-V2---Raspberry-Pi-Analogue-to-Digital-converter).
At the end of the chain is the individual input, provided by the class :py:class:`AnalogInput`
(this will ease the transition for Arduino fans ;). ADC input instances
are attached to a :py:class:`ADCPiBoard`, modeling the whole board and managing the common tasks.
Details about chip configuration are hidden from the user, and a lot of things are optimized by caching
what can be cached for avoiding paying too high a price for a high level design. Even if the added overhead
could seem penalizing in term of performances, this is not that obvious, since all the processing done here
must be done somewhere anyway. There are thus chances that the effective penalty (if ever any) will be
negligible for most of the applications.
To allow several classes instances being used for modeling a configuration with multiple boards,
possibly of different types, the underlying bus must be created outside and provided as a dependency
(see documentation about de "Dependency Injection" design pattern). Being able to provide whatever
implementation of the basic read and write operations allows the use of a fake class simulating
real operations, which made possible to test the rest of the application on a system not having
a SMBus resource available, such as the development station PC for instance, or providing access to
an I2C bus using an USB adapter for instance.
Here is an example of the code used for configuring and reading an input.
>>>
>>>
>>>
>>>
>>>
>>>
>>>
>>>
>>>
>>>

from pybot.raspi import i2c_bus


from pybot.abelectronics.adcpi import ADCPiBoard
...
# create an instance of the board (using default I2C addresses)
board = ADCPiBoard(i2c_bus)
# define an input, connected to pin 3 on the board header
ldr_voltage = board.get_analog_input(3, rate=..., gain=...)
...
# read it
v = ldr_voltage.read()

Pretty simple, no ?
"""
__author__ = 'Eric PASCUAL for POBOT'
__version__ = '2.0.0'
__email__ = 'eric@pobot.org'
class ADCPiBoard(object):
""" This class models an ADCPi board.
It also acts as a factory to provide instances of individual ADC inputs.
"""
# sample rates
RATE_12, RATE_14, RATE_16, RATE_18 = range(4)
# gains

GAIN_x1, GAIN_x2, GAIN_x4, GAIN_x8 = range(4)


# resolutions (bits)
RESOLUTIONS = (12, 14, 16, 18)
CONV1_DEFAULT_ADDRESS = 0x68
CONV2_DEFAULT_ADDRESS = 0x68
def __init__(self, bus, conv1_addr=CONV1_DEFAULT_ADDRESS , conv2_addr=CONV2_DEFAULT_ADDRESS):
""" An instance of the I2C/SMBus class must be provided here. The calls used in the
classes of this module are based on the smbus.SMBus interface. Any implementation providing
the same API can thus be used, including fake ones for testing.
:param bus: the I2C/SMBus instance
:param int conv1_addr: I2C address of converter 1
:param int conv2_addr: I2C address of converter 1
"""
self._bus = bus
self._converters = (
Converter(bus, conv1_addr),
Converter(bus, conv2_addr),
)
self._adcs = {}
def get_analog_input(self, board_input_num, rate=RATE_12, gain=GAIN_x1):
""" Convenience factory method returning an instance of :py:class:`AnalogInput`
representing an individual input.
Returned instances are cached, so that requesting an already requested input
returns the existing one and does not created a new one.
:param int board_input_num: the input number (in [1-8])
:param int rate: the sampling rate selector (ADCPiBoard.RATE_xx)
:param int gain: the input amplifier gain selector (ADCPiBoard.GAIN_xn)
:return: an instance of AnalogInput
:rtype: AnalogInput
"""
if not 1 <= board_input_num <= 8:
raise ValueError("invalid input num (%d)" % board_input_num)
try:
# try first to get the object from the cache
adc = self._adcs[board_input_num]
except KeyError:
# not yet created => do it
conv_num, channel_num = self._board_input_num_to_conv_channel(board_input_num)
converter = self._converters[conv_num]
# create the instance of the ADC class
adc = AnalogInput(converter, channel_num, rate, gain)
# cache the result
self._adcs[board_input_num] = adc
return adc
@staticmethod
def _board_input_num_to_conv_channel(board_io_num):
board_io_num -= 1

return board_io_num / 4, board_io_num % 4


class Converter(object):
""" Models the MCP3424 converter chip, and handles its low level operations.
"""
def __init__(self, bus, i2c_addr):
"""
:param bus: the I2C/SMBus instance
:param int i2c_addr: expander I2C address
"""
self._bus = bus
self._addr = i2c_addr
def read_raw(self, config, count=32):
""" Performs a raw read
We can request just the number of needed bytes, to avoid transferring
the SMBus default 32 bytes chunk.
:param int config: the configuration byte
:param int count: number of requested bytes
:return: the data bytes as returned by the chip
:rtype: list
"""
return self._bus.read_i2c_block_data(self._addr, config, count)
class AnalogInput(object):
""" Models an ADC input.
Once instantiated, signal values are obtained using methods :py:meth:`read_voltage` and
:py:meth:`read_raw`.
The input is configured (sampling rate and resolution, PGA gain) at instantiation time,
and cannot be changed after.
"""
# configuration register masks
NOT_READY = 0x80
CONTINUOUS_CONVERSION = 0x10
_gain_factors = (0.5, 1.0, 2.0, 4.0)
_lsb_factors = (0.0005, 0.000125, 0.00003125, 0.0000078125)
def _decoder_12(self, raw):
h, m = raw[:2]
return 0 if h & 0x08 else ((h & 0x07) << 8) | m
def _decoder_14(self, raw):
h, m = raw[:2]
return 0 if h & 0x20 else ((h & 0x1f) << 8) | m
def _decoder_16(self, raw):
h, m = raw[:2]
return 0 if h & 0x80 else ((h & 0x7f) << 8) | m

def _decoder_18(self, raw):


h, m, l = raw[:3]
return 0 if h & 0x02 else ((h & 0x01) << 16) | (m << 8) | l
# for each sampling rate : (decoding_method, reply length)
_decoding_specs = {
ADCPiBoard.RATE_12: (_decoder_12, 3),
ADCPiBoard.RATE_14: (_decoder_14, 3),
ADCPiBoard.RATE_16: (_decoder_16, 3),
ADCPiBoard.RATE_18: (_decoder_18, 4)
}
def __init__(self, converter, channel_num, rate=ADCPiBoard.RATE_12, gain=ADCPiBoard.GAIN_x1, single=False):
"""
:param Converter converter: the MCP chip this input belongs to
:param int channel_num: ADC channel num ([0-3]
:param bool single: True for single sample mode
:param int rate: sample rate and resolution selector (RATE_nn)
:param int gain: PGA gain (GAIN_xn)
"""
if not converter:
raise ValueError('converter parameter is mandatory')
if not 0 <= channel_num <= 3:
raise ValueError('invalid channel number (%s)' % channel_num)
if not ADCPiBoard.RATE_12 <= rate <= ADCPiBoard.RATE_18:
raise ValueError('invalid resolution (%s)' % rate)
if not ADCPiBoard.GAIN_x1 <= gain <= ADCPiBoard.GAIN_x8:
raise ValueError('invalid gain (%s)' % gain)
self._converter = converter
self._channel_num = channel_num
self._config = (
(channel_num << 5) |
(self.CONTINUOUS_CONVERSION if not single else 0) |
rate | gain
) & 0xff
self._decoder, self._reply_len = self._decoding_specs[rate]
# Note: the last factor of the formula hereafter has been found by experimental measurement
self._scale_factor = self._lsb_factors[rate] / self._gain_factors[gain] * 2.478439425
def read_voltage(self):
""" Samples the input and converts the raw reading to corresponding voltage.
:return: the input voltage
:rtype: float
"""
return self.read_raw() * self._scale_factor
def convert_raw(self, raw):
return raw * self._scale_factor
def read_raw(self):
""" Samples the input and returns its raw value.
Proper decoding is applied, based on configured gain and sample rate.
:return: the input raw value
:rtype: int

"""
while True:
raw = self._converter.read_raw(self._config, self._reply_len)
cfg = raw[-1]
if not(cfg & self.NOT_READY):
return self._decoder(self, raw)
#!/usr/bin/env python
# -*- coding: utf-8 -*"""
This example makes servos connected to ports 1 to 3 sweep back and forth in various ways.
"""
__author__ = 'Eric Pascual'
import time
import math
import sys
from pybot.abelectronics.servopi import ServoPiBoard, StopSpecification, Servo
try:
from pybot.raspi import i2c_bus
except ImportError:
from pybot.i2c import SimulatedSMBus
i2c_bus = SimulatedSMBus()
print(sys.modules[__name__].__doc__.strip())
print("\nHit Ctrl-C to terminate.")
board = ServoPiBoard(i2c_bus)
servos = {
# servo 1 set to normal move extent, the horn angle being specified in the [-90, 90] range
board.get_servo(
1, stop_min=StopSpecification(-90, Servo.DEFAULT_MS_MIN), stop_max=StopSpecification(90, Servo.DEFAULT_MS_MAX)
),
# servo 2 set to a reduced move extent, the horn angle being restricted to the [-45, 45] range. The servo will
# not move when the requested position is outside these bounds
board.get_servo(
2, stop_min=StopSpecification(-45, 1), stop_max=StopSpecification(45, 2)
),
# servo 3 set as servo 1, but with direction reversed (note the logical position signs)
board.get_servo(
3, stop_min=StopSpecification(90, Servo.DEFAULT_MS_MIN), stop_max=StopSpecification(-90, Servo.DEFAULT_MS_MAX)
)
}
try:
a, d_a = 0, math.radians(10)
while True:
# make the position span the [-90, 90] range, using a sinusoidal control law to get smooth direction changes
# by decelerating and accelerating at inversion points
position = math.cos(a) * 90.0
for servo in servos:
servo.set_position(position)

# don't care incrementing the motion control variable infinitely : integers are not bound in Python.
# BTW chances are that we will have stopped the demo long before the 16 bits threshold is reached ;)
a += d_a
time.sleep(0.05)
except KeyboardInterrupt:
print(" caught. Terminating program\n")
print('Bringing back servos to their median position')
for servo in servos:
servo.goto_median_position()
# let them complete their moves before shutting down the control pulses generation
time.sleep(1)
print('Shutdown ServoPi board')
board.shutdown()
#!/usr/bin/env python
# -*- coding: utf-8 -*"""
This example blinks a LED connected between pin 8 of IC_1 header and the ground.
It also reads the state of buttons connected between pin 1, 2 and 3 of IC_1 header and the ground.
"""
__author__ = 'Eric Pascual'
from pybot.abelectronics.iopi import IOPiBoard
from pybot import raspi
import time
import sys
print(sys.modules[__name__].__doc__.strip())
print("\nHit Ctrl-C to terminate.")
board = IOPiBoard(raspi.i2c_bus)
led = board.get_digital_output(IOPiBoard.EXPANDER_1, 8)
buttons = [board.get_digital_input(IOPiBoard.EXPANDER_1, i, pullup_enabled=True) for i in range(1, 4)]
last_states = [1] * 3
def display_buttons_state():
states = [buttons[i].is_set() for i in range(0, 3)]
if states != last_states:
print("\033[30Dbuttons: %s" % states),
sys.stdout.flush()
return states
try:
on = True
next_change = 0
while True:

now = time.time()
if now >= next_change:
if on:
led.set()
else:
led.clear()
next_change = now + 0.5
on = not on
last_states = display_buttons_state()
time.sleep(0.1)
except KeyboardInterrupt:
print(" caught. Terminating program")
led.clear()
#!/usr/bin/env python
# -*- coding: utf-8 -*"""
This example reads an analog input connected to pin 1 of the header.
"""
__author__ = 'Eric Pascual'
import sys
import time
from pybot.abelectronics.adcpi import ADCPiBoard
try:
from pybot.raspi import i2c_bus
except ImportError:
from pybot.i2c import SimulatedSMBus
i2c_bus = SimulatedSMBus()
print(sys.modules[__name__].__doc__.strip())
print("\nHit Ctrl-C to terminate.")
board = ADCPiBoard(i2c_bus)
ain_1 = board.get_analog_input(1, rate=ADCPiBoard.RATE_12)
def display_voltage_and_raw(v, r):
print("\033[80Dinput voltage = %f (%d)\033[K" % (v, r)),
sys.stdout.flush()
def display_voltage(v):
print("\033[80Dinput voltage = %f\033[K" % v),
sys.stdout.flush()
try:
on = True
while True:
# raw = ain_1.read_raw()
# voltage = ain_1.convert_raw(raw)

# display_voltage_and_raw(voltage, raw)
display_voltage(ain_1.read_voltage())
time.sleep(0.5)
except KeyboardInterrupt:
print(" caught. Terminating program")
#!/usr/bin/env python
# -*- coding: utf-8 -*import os, sys
if not os.getuid() == 0:
sys.exit('Needs to be root for running this script.')
import RPi.GPIO as GPIO
import time
import subprocess
BTN_IO = 4
GPIO.setmode(GPIO.BCM)
GPIO.setup(BTN_IO, GPIO.IN, GPIO.PUD_UP)
print('monitoring started')
while True:
pressed = (GPIO.input(BTN_IO) == 0)
if pressed:
time.sleep(4)
pressed = (GPIO.input(BTN_IO) == 0)
if pressed:
break
else:
time.sleep(0.1)
print('Shutdown button pressed. System is going to halt now')
subprocess.Popen('/sbin/halt')
#!/usr/bin/env python
# -*- coding: utf-8 -*__author__ = 'Eric Pascual'
import json
import time
import logging
from tornado.web import RequestHandler
from controller import DemonstratorController
BARRIER_LDR_INPUT_ID = 1
BW_DETECTOR_LDR_INPUT_ID = 2
class Logged(object):
def __init__(self):

self.logger = logging.getLogger(self.__class__.__name__)
class WSBarrierSample(RequestHandler, Logged):
def get(self):
try:
current_mA = self.application.controller.sample_barrier_input()
except IOError as e:
self.set_status(status_code=404, reason="IOError (barrier sensor)")
self.finish()
else:
self.finish(json.dumps({
"current": current_mA,
}))
class WSBarrierSampleAndAnalyze(RequestHandler, Logged):
def get(self):
try:
current_mA = self.application.controller.sample_barrier_input()
except IOError as e:
self.set_status(status_code=404, reason="IOError (barrier sensor)")
self.finish()
else:
detection = self.application.controller.analyze_barrier_input(current_mA)
self.finish(json.dumps({
"current": current_mA,
"detection": detection
}))
class WSBarrierLight(RequestHandler, Logged):
def post(self):
status = self.get_argument("status") == '1'
self.application.controller.set_barrier_light(status);
class WSBarrierCalibrationSample(WSBarrierSample):
def get(self):
self.application.controller.set_barrier_light(True);
time.sleep(2)
super(WSBarrierCalibrationSample, self).get()
self.application.controller.set_barrier_light(False);
class WSBarrierCalibrationStatus(RequestHandler, Logged):
def get(self):
self.finish(json.dumps({
"calibrated": 1 if self.application.controller.barrier_is_calibrated() else 0
}))
class WSBarrierCalibrationStore(RequestHandler, Logged):
def post(self):
free, occupied = (float(self.get_argument(a)) for a in ('free', 'occupied'))
self.logger.info("storing references : free=%f occupied=%f", free, occupied)
self.application.controller.set_barrier_reference_levels(free, occupied)

self.application.controller.save_calibration()
class WSBWDetectorSample(RequestHandler, Logged):
def get(self):
try:
current_mA = self.application.controller.sample_bw_detector_input()
except IOError as e:
self.set_status(status_code=404, reason="IOError (B/W detector sensor)")
self.finish()
else:
self.finish(json.dumps({
"current": current_mA
}))
class WSBWDetectorSampleAndAnalyze(RequestHandler, Logged):
def get(self):
try:
current_mA = self.application.controller.sample_bw_detector_input()
except IOError as e:
self.set_status(status_code=404, reason="IOError (B/W detector sensor)")
self.finish()
else:
color = self.application.controller.analyze_bw_detector_input(current_mA)
self.finish(json.dumps({
"current": current_mA,
"color": "white" if color == self.application.controller.BW_WHITE else "black"
}))
class WSBWDetectorLight(RequestHandler, Logged):
def post(self):
status = self.get_argument("status") == '1'
self.application.controller.set_bw_detector_light(status);
class WSBWDetectorCalibrationSample(WSBWDetectorSample):
def get(self):
self.application.controller.set_bw_detector_light(True);
time.sleep(2)
super(WSBWDetectorCalibrationSample, self).get()
self.application.controller.set_bw_detector_light(False);
class WSBWDetectorCalibrationStatus(RequestHandler, Logged):
def get(self):
self.finish(json.dumps({
"calibrated": 1 if self.application.controller.bw_detector_is_calibrated() else 0
}))
class WSBWDetectorCalibrationStore(RequestHandler, Logged):
def post(self):
black, white = (float(self.get_argument(a)) for a in ('b', 'w'))
self.logger.info("storing references : black=%f white=%f", black, white)
self.application.controller.set_bw_detector_reference_levels(black, white)

self.application.controller.save_calibration()
class WSColorDetectorSample(RequestHandler, Logged):
def get(self):
color = self.get_argument('color', None)
if color and color in '0rgb':
self.application.controller.set_color_detector_light('0rgb'.index(color))
time.sleep(1)
try:
current_mA = self.application.controller.sample_color_detector_input()
except IOError as e:
self.set_status(status_code=404, reason="IOError (color_detector)")
self.finish()
else:
self.finish(json.dumps({
"current": current_mA
}))
finally:
if color:
self.application.controller.set_color_detector_light(0)
class WSColorDetectorAnalyze(RequestHandler):
def get(self):
sample = [self.get_argument(comp) for comp in ('r', 'g', 'b')]
color, decomp = self.application.controller.analyze_color_input(sample)
self.finish(json.dumps({
"color": DemonstratorController.COLOR_NAMES[color],
"decomp": [d * 100 for d in decomp]
}))
class WSColorDetectorLight(RequestHandler, Logged):
def post(self, color):
self.application.controller.set_color_detector_light('0rgb'.index(color));
class WSColorDetectorCalibrationStore(RequestHandler, Logged):
def post(self, color):
if color not in ('w', 'b'):
raise ValueError("invalid color parameter : %s" % color)
r, g, b = (float(self.get_argument(a)) for a in ('r', 'g', 'b'))
self.logger.info("storing references : R=%f G=%f B=%f", r, g, b)
self.application.controller.set_color_detector_reference_levels(color, (r, g, b))
self.application.controller.save_calibration()
class WSColorDetectorCalibrationStatus(RequestHandler, Logged):
def get(self):
self.finish(json.dumps({
"calibrated": 1 if self.application.controller.color_detector_is_calibrated() else 0
}))

class WSCalibrationData(RequestHandler, Logged):


def get(self):
self.finish(self.application.controller.get_calibration_cfg_as_dict())
#!/usr/bin/env python
# -*- coding: utf-8 -*__author__ = 'Eric Pascual'
from tornado.web import RequestHandler
import os
class UIHandler(RequestHandler):
def get_template_args(self):
return {
'app_title':"Capteurs de lumire et de couleur"
}
def get(self, *args, **kwargs):
""" By default, the get method displays the "Not yet implemented message".
"""
self.render(
os.path.join(self.application.template_home, "nyi.html"),
**self.get_template_args()
)
class UIHome(UIHandler):
def get(self, *args, **kwargs):
self.render(
os.path.join(self.application.template_home, "home.html"),
**self.get_template_args()
)
class UIHBarrier(UIHandler):
def get(self, *args, **kwargs):
template_args = self.get_template_args()
template_args['demo_title'] = "Barrire optique"
self.render(
os.path.join(self.application.template_home, "barrier.html"),
**template_args
)
class UIWBDetector(UIHandler):
def get(self, *args, **kwargs):
template_args = self.get_template_args()
template_args['demo_title'] = "Dtecteur noir/blanc"
self.render(
os.path.join(self.application.template_home, "bwdetector.html"),
**template_args
)
class UIColorDetector(UIHandler):

def get(self, *args, **kwargs):


template_args = self.get_template_args()
template_args['demo_title'] = "Dtecteur couleur"
self.render(
os.path.join(self.application.template_home, "colordetector.html"),
**template_args
)
class UICalibration(UIHandler):
def get(self, *args, **kwargs):
template_args = self.get_template_args()
template_args["calibration_cfg"] = self.application.controller.get_calibration_cfg_as_dict()
self.render(
os.path.join(self.application.template_home, "calibration.html"),
**template_args
)

#!/usr/bin/env python
# -*- coding: utf-8 -*import json
__author__ = 'Eric Pascual (for POBOT)'
import tornado.ioloop
import tornado.web
import tornado.log
from tornado.web import HTTPError
import os
import logging
import uimodules
import wsapi
import webui
_here = os.path.dirname(__file__)
class DemoColorApp(tornado.web.Application):
""" The Web application
"""
_res_home = os.path.join(_here, "static")
_templates_home = os.path.join(_here, "templates")
settings = {
'template_path': _templates_home,
'ui_modules': uimodules,
'port': 8080
}
handlers = [
(r"/css/(.*)", tornado.web.StaticFileHandler, {"path": os.path.join(_res_home, 'css')}),
(r"/js/(.*)", tornado.web.StaticFileHandler, {"path": os.path.join(_res_home, 'js')}),

(r"/img/(.*)", tornado.web.StaticFileHandler, {"path": os.path.join(_res_home, 'img')}),


# user interface
(r"/", webui.UIHome),
(r"/barrier", webui.UIHBarrier),
(r"/bw_detector", webui.UIWBDetector),
(r"/color_detector", webui.UIColorDetector),
(r"/calibration", webui.UICalibration),
# API wWeb services
(r"/calibration/data", wsapi.WSCalibrationData),
(r"/barrier/sample", wsapi.WSBarrierSample),
(r"/barrier/analyze", wsapi.WSBarrierSampleAndAnalyze),
(r"/barrier/light", wsapi.WSBarrierLight),
(r"/barrier/status", wsapi.WSBarrierCalibrationStatus),
(r"/calibration/barrier/sample", wsapi.WSBarrierCalibrationSample),
(r"/calibration/barrier/store", wsapi.WSBarrierCalibrationStore),
(r"/bw_detector/sample", wsapi.WSBWDetectorSample),
(r"/bw_detector/analyze", wsapi.WSBWDetectorSampleAndAnalyze),
(r"/bw_detector/light", wsapi.WSBWDetectorLight),
(r"/bw_detector/status", wsapi.WSBWDetectorCalibrationStatus),
(r"/calibration/bw_detector/sample", wsapi.WSBWDetectorCalibrationSample),
(r"/calibration/bw_detector/store", wsapi.WSBWDetectorCalibrationStore),
(r"/color_detector/sample", wsapi.WSColorDetectorSample),
(r"/color_detector/analyze", wsapi.WSColorDetectorAnalyze),
(r"/color_detector/light/(?P<color>[0rgb])", wsapi.WSColorDetectorLight),
(r"/color_detector/status", wsapi.WSColorDetectorCalibrationStatus),
(r"/calibration/color_detector/sample", wsapi.WSColorDetectorSample),
(r"/calibration/color_detector/store/(?P<color>[wb])", wsapi.WSColorDetectorCalibrationStore),
]
def __init__(self, controller, debug=False):
self.log = logging.getLogger(self.__class__.__name__)
self.log.setLevel(logging.INFO)
self.log.info('starting')
self._controller = controller
self.debug = debug
if self.debug:
self.log.setLevel(logging.DEBUG)
else:
# avoid Tornado logging all access requests if not in debug mode
logging.getLogger("tornado.access").setLevel(logging.WARN)
self.settings['debug'] = debug
super(DemoColorApp, self).__init__(self.handlers, **self.settings)
@property
def template_home(self):
return self._templates_home

@property
def controller(self):
return self._controller
def start(self, listen_port=8080, ):
""" Starts the application
"""
self._controller.start()
self.listen(listen_port)
try:
self.log.info('listening on port %d', listen_port)
tornado.ioloop.IOLoop.instance().start()
except KeyboardInterrupt:
print # cosmetic to keep log messages nicely aligned
self.log.info('SIGTERM caught')
finally:
self._controller.shutdown()

#!/usr/bin/env python
# -*- coding: utf-8 -*__author__ = 'Eric Pascual'
import logging
from random import gauss
class ADCPi(object):
def __init__(self, address=0x68, address2=0x69, rate=18):
self._log = logging.getLogger('ADCPi')
self._log.info('creating with address=0x%.2x, address2=0x%.2x, rate=%d', address, address2, rate)
def readVoltage(self, input_id):
return gauss(4.2, 0.1)
class BlinkM(object):
def __init__(self, bus=1, addr=0x09):
self._log = logging.getLogger('BlinkM')
self._log.info('created with bus=%d addr=0x%.2x', bus, addr)
def go_to(self, r, g, b):
self._log.info('color changed to R=%d G=%d B=%d', r, g, b)
def reset(self):
self._log.info('reset')
class GPIO(object):
BOARD = 'board'
OUT = 'out'
HIGH = 1
LOW = 0

def __init__(self):
self._log = logging.getLogger('GPIO')
def setmode(self, mode):
self._log.info('setting mode to "%s"' % mode)
def setup(self, pin, mode):
self._log.info('setup pin %d to mode "%s"', pin, mode)
def output(self, pin, state):
self._log.info('setting pin %d to %d', pin, state)
def cleanup(self, ):
self._log.info('cleanup')
#!/usr/bin/env python
# -*- coding: utf-8 -*""" Color sensing demonstrator web application.
"""
__author__ = 'Eric Pascual (for POBOT)'
import logging
import sys
from webapp import DemoColorApp
from controller import DemonstratorController
_CONFIG_FILE_NAME = "demo-color.cfg"
if __name__ == '__main__':
import argparse
logging.basicConfig(
format="%(asctime)s.%(msecs).3d [%(levelname).1s] %(name)s > %(message)s",
datefmt='%H:%M:%S'
)
log = logging.getLogger()
log.setLevel(logging.INFO)
try:
parser = argparse.ArgumentParser(
description=__doc__,
formatter_class=argparse.ArgumentDefaultsHelpFormatter
)
parser.add_argument(
'-p', '--port',
help='HTTP server listening port',
dest='listen_port',
default=8080)
parser.add_argument(
'-D', '--debug',
help='activates debug mode',
dest='debug',
action='store_true')

parser.add_argument(
'-c', '--cfgdir',
help='configuration files directory path',
dest='cfg_dir',
default=None)
parser.add_argument(
'-S', '--simul',
help='simulates hardware',
dest='simulation',
action='store_true')
cli_args = parser.parse_args()
if cli_args.debug:
log.warn('debug mode activated')
log.info("command line arguments : %s", cli_args)
ctrl = DemonstratorController(debug=cli_args.debug, simulation=cli_args.simulation, cfg_dir=cli_args.cfg_dir)
app = DemoColorApp(ctrl, debug=cli_args.debug)
app.start(listen_port=cli_args.listen_port)
except Exception as e:
log.exception('unexpected error - aborting')
sys.exit(1)
else:
log.info('terminated')
#!/usr/bin/env python
# -*- coding: utf-8 -*__author__ = 'Eric Pascual'
import configuration
import logging
ADCPi = None
BlinkM = None
GPIO = None
def set_simulation_mode(simulated_hw):
global ADCPi
global BlinkM
global GPIO
if not simulated_hw:
from extlibs.ABElectronics_ADCPi import ADCPi
from extlibs.pyblinkm import BlinkM
import RPi.GPIO as GPIO
else:
from simulation import ADCPi, BlinkM
import simulation
GPIO = simulation.GPIO()

class DemonstratorController(object):
LDR_BARRIER = 0
LDR_BW = 1
LDR_COLOR = 2
AMBIENT = 0
LIGHTENED = 1
BW_BLACK = 0
BW_WHITE = 1
COLOR_UNDEF = 0
COLOR_RED = 1
COLOR_GREEN = 2
COLOR_BLUE = 3
COLOR_BLACK = 4
COLOR_WHITE = 5
COLOR_NAMES = (
'undef',
'red',
'green',
'blue',
'black',
'white'
)
COLOR_COMPONENTS = (
# (R, G, B)
(0, 0, 0),
(255, 0, 0),
(0, 128, 0),
(0, 0, 255),
(0, 0, 0),
(255, 255, 255)
)
def __init__(self, debug=False, simulation=False, cfg_dir=None):
self._log = logging.getLogger(self.__class__.__name__)
self._system_cfg = configuration.SystemConfiguration(
cfg_dir=cfg_dir,
autoload=True
)
set_simulation_mode(simulation)
self._blinkm = BlinkM(addr=self._system_cfg.blinkm_addr)
try:
self._blinkm.reset()
except IOError:
self._log.error("BlinkM reset error. Maybe not here")
self._blinkm = None
self._adc = ADCPi(
self._system_cfg.adc1_addr,
self._system_cfg.adc2_addr,

self._system_cfg.adc_bits

GPIO.setmode(GPIO.BOARD)
self._barrier_adc = self._system_cfg.barrier_adc
self._barrier_led_gpio = self._system_cfg.barrier_led_gpio
GPIO.setup(self._barrier_led_gpio, GPIO.OUT)
self._bw_detector_adc = self._system_cfg.bw_detector_adc
self._bw_detector_led_gpio = self._system_cfg.bw_detector_led_gpio
GPIO.setup(self._bw_detector_led_gpio, GPIO.OUT)
self._color_detector_adc = self._system_cfg.color_detector_adc
self._listen_port = self._system_cfg.listen_port
self._shunts = self._system_cfg.shunts
# process stored calibration data
self._barrier_threshold = \
self._bw_detector_threshold = \
self._white_rgb_levels = \
self._black_rgb_levels = None
self._calibration_cfg = configuration.CalibrationConfiguration(
cfg_dir=cfg_dir,
autoload=True
)
if self._calibration_cfg.barrier_is_set():
self.set_barrier_reference_levels(*self._calibration_cfg.barrier)
if self._calibration_cfg.bw_detector_is_set():
self.set_bw_detector_reference_levels(*self._calibration_cfg.bw_detector)
if self._calibration_cfg.color_detector_is_set():
self.set_color_detector_reference_levels('w', self._calibration_cfg.color_detector_white)
self.set_color_detector_reference_levels('b', self._calibration_cfg.color_detector_black)
@property
def blinkm(self):
return self._blinkm
@property
def adc(self):
return self._adc
@property
def gpio(self):
return self._gpio
def start(self):
pass

def shutdown(self):
GPIO.cleanup()
def shunt(self, input_id):
return self._shunts[input_id]
def threshold(self, input_id):
if input_id == self.LDR_BARRIER:
return self._barrier_threshold
elif input_id == self.LDR_BW:
return self._bw_detector_threshold
else:
raise ValueError('no threshold defined for input (%d)' % input_id)
def sample_barrier_input(self):
v = self.adc.readVoltage(self._barrier_adc)
i_mA = v / self._shunts[self.LDR_BARRIER] * 1000.
return i_mA
def set_barrier_reference_levels(self, level_free, level_occupied):
self._calibration_cfg.barrier = [level_free, level_occupied]
self._barrier_threshold = (level_free + level_occupied) / 2.
def set_barrier_light(self, on):
GPIO.output(self._barrier_led_gpio, 1 if on else 0)
def barrier_is_calibrated(self):
return self._barrier_threshold is not None
def analyze_barrier_input(self, i_mA):
if not self.barrier_is_calibrated():
raise NotCalibrated('barrier')
detection = i_mA < self._barrier_threshold
return detection
def sample_bw_detector_input(self):
v = self.adc.readVoltage(self._bw_detector_adc)
i_mA = v / self._shunts[self.LDR_BW] * 1000.
return i_mA
def set_bw_detector_reference_levels(self, level_black, level_white):
self._calibration_cfg.bw_detector = [level_black, level_white]
self._bw_detector_threshold = (level_black + level_white) / 2.
def set_bw_detector_light(self, on):
GPIO.output(self._bw_detector_led_gpio, 1 if on else 0)
def bw_detector_is_calibrated(self):
return self._bw_detector_threshold is not None
def analyze_bw_detector_input(self, i_mA):
if not self.bw_detector_is_calibrated():
raise NotCalibrated('bw_detector')
color = self.BW_BLACK if i_mA < self._bw_detector_threshold else self.BW_WHITE

return color
def sample_color_detector_input(self):
v = self.adc.readVoltage(self._color_detector_adc)
i_mA = v / self._shunts[self.LDR_COLOR] * 1000.
return i_mA
def set_color_detector_reference_levels(self, white_or_black, levels):
if white_or_black == 'b':
self._calibration_cfg.color_detector_black = levels[:]
elif white_or_black == 'w':
self._calibration_cfg.color_detector_white = levels[:]
else:
raise ValueError("invalid white/black option (%s)" % white_or_black)
def set_color_detector_light(self, color):
if self._blinkm:
self._blinkm.go_to(*(self.COLOR_COMPONENTS[color]))
else:
self._log.error("BlinkM not available")
def color_detector_is_calibrated(self):
return self._calibration_cfg.color_detector_is_set()
def analyze_color_input(self, rgb_sample):
if not self.color_detector_is_calibrated():
raise NotCalibrated('color_detector')
# normalize color components in [0, 1] and in the white-black range
self._log.debug("analyze %s", rgb_sample)
rgb_sample = [float(s) for s in rgb_sample]
comps = [max((s - b) / (w - b), 0)
for w, b, s in zip(
self._calibration_cfg.color_detector_white,
self._calibration_cfg.color_detector_black,
rgb_sample
)]
self._log.debug("--> comps=%s", comps)
sum_comps = sum(comps)
if sum_comps > 0:
relative_levels = [c / sum_comps for c in comps]
else:
relative_levels = [0] * 3
self._log.debug("--> relative_levels=%s", relative_levels)
min_comps, max_comps = min(comps), max(comps)
if min_comps > 0.9:
color = self.COLOR_WHITE
elif max_comps < 0.2:
color = self.COLOR_BLACK
else:
over_50 = [c > 0.5 for c in relative_levels]
if any(over_50):
color = over_50.index(True) + 1
else:

color = self.COLOR_UNDEF
self._log.debug("--> color=%s", self.COLOR_NAMES[color])
return color, relative_levels
def save_calibration(self):
self._calibration_cfg.save()
def get_calibration_cfg_as_dict(self):
return self._calibration_cfg.as_dict()
class ControllerException(Exception):
pass
class NotCalibrated(ControllerException):
pass
# !/usr/bin/env python
# -*- coding: utf-8 -*__author__ = 'Eric Pascual'
import os
import json
APP_NAME = 'pobot-demo-color'
class Configuration(object):
CONFIG_FILE_NAME = None
_data = None
_path = None
def __init__(self, autoload=False, cfg_dir=None):
self._cfg_dir = cfg_dir
self._path = self.get_default_path()
if autoload:
self.load()
def get_default_path(self):
if self._cfg_dir:
return os.path.join(self._cfg_dir, self.CONFIG_FILE_NAME)
elif os.getuid() == 0:
return os.path.join('/etc', APP_NAME, self.CONFIG_FILE_NAME)
else:
return os.path.expanduser(os.path.join('~', '.' + APP_NAME, self.CONFIG_FILE_NAME))
def load(self, path=None):
if not path:
path = self._path
self._data.update(json.load(file(path, 'rt')))
def save(self, path=None):
if not path:
path = self._path

json.dump(self._data, file(path, 'wt'), indent=4)


class SystemConfiguration(Configuration):
CONFIG_FILE_NAME = "system.cfg"
def __init__(self, *args, **kwargs):
self._data = {
'listen_port': 8080,
'blinkm_addr': 0x09,
'adc1_addr': 0x68,
'adc2_addr': 0x69,
'adc_bits': 12,
'shunts': [10000] * 3,
'barrier_adc': 1,
'bw_detector_adc': 2,
'color_detector_adc': 3,
'barrier_led_gpio': 12,
'bw_detector_led_gpio': 13,
}
super(SystemConfiguration, self).__init__(*args, **kwargs)
@property
def listen_port(self):
return self._data['listen_port']
@listen_port.setter
def listen_port(self, value):
self._data['listen_port'] = value
@property
def blinkm_addr(self):
return self._data['blinkm_addr']
@blinkm_addr.setter
def blinkm_addr(self, value):
self._data['blinkm_addr'] = value
@property
def adc1_addr(self):
return self._data['adc1_addr']
@adc1_addr.setter
def adc1_addr(self, value):
self._data['adc1_addr'] = value
@property
def adc2_addr(self):
return self._data['adc2_addr']
@adc2_addr.setter
def adc2_addr(self, value):
self._data['adc2_addr'] = value
@property
def adc_bits(self):
return self._data['adc_bits']

@adc_bits.setter
def adc_bits(self, value):
self._data['adc_bits'] = value
@property
def shunts(self):
return self._data['shunts'][:]
@shunts.setter
def shunts(self, value):
self._data['shunts'] = value[:]
@property
def barrier_adc(self):
return self._data['barrier_adc']
@barrier_adc.setter
def barrier_adc(self, value):
self._data['barrier_adc'] = value
@property
def bw_detector_adc(self):
return self._data['bw_detector_adc']
@bw_detector_adc.setter
def bw_detector_adc(self, value):
self._data['bw_detector_adc'] = value
@property
def color_detector_adc(self):
return self._data['color_detector_adc']
@color_detector_adc.setter
def color_detector_adc(self, value):
self._data['color_detector_adc'] = value
@property
def barrier_led_gpio(self):
return self._data['barrier_led_gpio']
@barrier_led_gpio.setter
def barrier_led_gpio(self, value):
self._data['barrier_led_gpio'] = value
@property
def bw_detector_led_gpio(self):
return self._data['bw_detector_led_gpio']
@bw_detector_led_gpio.setter
def bw_detector_led_gpio(self, value):
self._data['bw_detector_led_gpio'] = value
class CalibrationConfiguration(Configuration):
CONFIG_FILE_NAME = "calibration.cfg"
_V2_0 = [0] * 2

_V3_0 = [0] * 3
def __init__(self, *args, **kwargs):
self._data = {
'barrier': [0, 0],
# (free, occupied)
'bw_detector': [0, 0], # (black, white)
'color_detector': {
'b': [0] * 3,
# (R, G, B)
'w': [0] * 3
}
}
super(CalibrationConfiguration, self).__init__(*args, **kwargs)
@property
def barrier(self):
return self._data['barrier'][:]
@barrier.setter
def barrier(self, value):
self._data['barrier'] = value[:]
def barrier_is_set(self):
return self._data['barrier'] != self._V2_0
@property
def bw_detector(self):
return self._data['bw_detector'][:]
@bw_detector.setter
def bw_detector(self, value):
self._data['bw_detector'] = value[:]
def bw_detector_is_set(self):
return self._data['bw_detector'] != self._V2_0
@property
def color_detector_black(self):
return self._data['color_detector']['b'][:]
@color_detector_black.setter
def color_detector_black(self, value):
self._data['color_detector']['b'] = value[:]
@property
def color_detector_white(self):
return self._data['color_detector']['w'][:]
@color_detector_white.setter
def color_detector_white(self, value):
self._data['color_detector']['w'] = value[:]
def color_detector_is_set(self):
return self.color_detector_white != self._V3_0 \
and self.color_detector_black != self._V3_0
def is_complete(self):
return self.barrier_is_set() and self.bw_detector_is_set() and self.color_detector_is_set()

def is_new(self):
return not self.barrier_is_set() and not self.bw_detector_is_set() and not self.color_detector_is_set()
def as_dict(self):
return self._data
#!/usr/bin/python
import smbus
import re
#
#
#
#
#
#
#

================================================
ABElectronics ADC Pi V2 8-Channel ADC
Version 1.0 Created 09/05/2014
Requires python smbus to be installed
================================================

class ADCPi :
# internal variables
__address = 0x68 # default address for adc 1 on adc pi and delta-sigma pi
__address2 = 0x69 # default address for adc 2 on adc pi and delta-sigma pi
__config1 = 0x1C # PGAx1, 18 bit, one-shot conversion, channel 1
__currentchannel1 = 1 # channel variable for adc 1
__config2 = 0x1C # PGAx1, 18 bit, one-shot conversion, channel 1
__currentchannel2 = 1 # channel variable for adc2
__bitrate = 18 # current bitrate
__pga = 1 # current pga setting
__signbit = 0 # signed bit checker

# create byte array and fill with initial values to define size
__adcreading = bytearray()
__adcreading.append(0x00)
__adcreading.append(0x00)
__adcreading.append(0x00)
__adcreading.append(0x00)
# detect i2C port number and assign to i2c_bus
for line in open('/proc/cpuinfo').readlines():
m = re.match('(.*?)\s*:\s*(.*)', line)
if m:
(name, value) = (m.group(1), m.group(2))
if name == "Revision":
if value [-4:] in ('0002', '0003'):
i2c_bus = 0
else:
i2c_bus = 1
break
# Define I2C bus and init
global bus
bus = smbus.SMBus(i2c_bus);

#local methods
def __updatebyte(self, byte, bit, value):
# internal method for setting the value of a single bit within a byte
if value == 0:
return byte & ~(1 << bit)
elif value == 1:
return byte | (1 << bit)
def __checkbit(self, byte, bit):
# internal method for reading the value of a single bit within a byte
if byte & (1 << bit):
return 1
else:
return 0
def __twos_comp(self, val, bits):
if( (val&(1<<(bits-1))) != 0 ):
val = val - (1<<bits)
return val
def __setchannel(self, channel):
# internal method for updating the config to the selected channel
if channel < 5:
if channel != self.__currentchannel1:
if channel == 1:
self.__config1 = self.__updatebyte(self.__config1, 5, 0)
self.__config1 = self.__updatebyte(self.__config1, 6, 0)
self.__currentchannel1 = 1
if channel == 2:
self.__config1 = self.__updatebyte(self.__config1, 5, 1)
self.__config1 = self.__updatebyte(self.__config1, 6, 0)
self.__currentchannel1 = 2
if channel == 3:
self.__config1 = self.__updatebyte(self.__config1, 5, 0)
self.__config1 = self.__updatebyte(self.__config1, 6, 1)
self.__currentchannel1 = 3
if channel == 4:
self.__config1 = self.__updatebyte(self.__config1, 5, 1)
self.__config1 = self.__updatebyte(self.__config1, 6, 1)
self.__currentchannel1 = 4
else:
if channel != self.__currentchannel2:
if channel == 5:
self.__config2 = self.__updatebyte(self.__config2, 5, 0)
self.__config2 = self.__updatebyte(self.__config2, 6, 0)
self.__currentchannel2 = 5
if channel == 6:
self.__config2 = self.__updatebyte(self.__config2, 5, 1)
self.__config2 = self.__updatebyte(self.__config2, 6, 0)
self.__currentchannel2 = 6
if channel == 7:
self.__config2 = self.__updatebyte(self.__config2, 5, 0)
self.__config2 = self.__updatebyte(self.__config2, 6, 1)
self.__currentchannel2 = 7

if channel == 8:
self.__config2 = self.__updatebyte(self.__config2, 5, 1)
self.__config2 = self.__updatebyte(self.__config2, 6, 1)
self.__currentchannel2 = 8
return
#init object with i2caddress, default is 0x68, 0x69 for ADCoPi board
def __init__(self, address=0x68, address2=0x69, rate=18):
self.__address = address
self.__address2 = address2
self.setBitRate(rate)

def readVoltage(self, channel):


# returns the voltage from the selected adc channel - channels 1 to 8
raw = self.readRaw(channel)
if self.__signbit == 1: return 0 # returned a negative voltage so return 0
pga = self.__pga / 2.048
if self.__bitrate == 12: lsb
if self.__bitrate == 14: lsb
if self.__bitrate == 16: lsb
if self.__bitrate == 18: lsb

=
=
=
=

2.048
2.048
2.048
2.048

/
/
/
/

4096
16384
65536
262144

voltage = (raw * (lsb/pga)) * 2.448579823702253


return voltage
def readRaw(self, channel):
# reads the raw value from the selected adc channel - channels 1 to 8
self.__setchannel(channel) # get the config and i2c address for the selected channel
if (channel < 5):
config = self.__config1
address = self.__address
else:
config = self.__config2
address = self.__address2
while 1: # keep reading the adc data until the conversion result is ready
__adcreading = bus.read_i2c_block_data(address,config)
if self.__bitrate == 18:
h = __adcreading[0]
m = __adcreading[1]
l = __adcreading[2]
s = __adcreading[3]
else:
h = __adcreading[0]
m = __adcreading[1]
s = __adcreading[2]
if self.__checkbit(s, 7) == 0:
break;
self.__signbit = 0

t = 0.0
# extract the returned bytes and combine in the correct order
if self.__bitrate == 18:
t = ((h & 0b00000001) << 16) | (m << 8) | l
if self.__checkbit(h, 1) == 1:
self.__signbit = 1
if self.__bitrate == 16:
t = (h << 8) | m
if self.__checkbit(h, 7) == 1:
self.__signbit = 1
if self.__bitrate == 14:
t = ((h & 0b00011111) << 8) | m
if self.__checkbit(h, 5) == 1:
self.__signbit = 1
if self.__bitrate == 12:
t = ((h & 0b00000111) << 8) | m
if self.__checkbit(h, 3) == 1:
self.__signbit = 1
return t
def setPGA(self, gain):
# PGA gain selection
#1 = 1x
#2 = 2x
#4 = 4x
#8 = 8x
if gain == 1:
self.__config1 = self.__updatebyte(self.__config1,
self.__config1 = self.__updatebyte(self.__config1,
self.__config2 = self.__updatebyte(self.__config2,
self.__config2 = self.__updatebyte(self.__config2,
self.__pga = 1
if gain == 2:
self.__config1 = self.__updatebyte(self.__config1,
self.__config1 = self.__updatebyte(self.__config1,
self.__config2 = self.__updatebyte(self.__config2,
self.__config2 = self.__updatebyte(self.__config2,
self.__pga = 2
if gain == 4:
self.__config1 = self.__updatebyte(self.__config1,
self.__config1 = self.__updatebyte(self.__config1,
self.__config2 = self.__updatebyte(self.__config2,
self.__config2 = self.__updatebyte(self.__config2,
self.__pga = 4
if gain == 8:
self.__config1 = self.__updatebyte(self.__config1,
self.__config1 = self.__updatebyte(self.__config1,
self.__config2 = self.__updatebyte(self.__config2,
self.__config2 = self.__updatebyte(self.__config2,
self.__pga = 8

0,
1,
0,
1,

0)
0)
0)
0)

0,
1,
0,
1,

1)
0)
1)
0)

0,
1,
0,
1,

0)
1)
0)
1)

0,
1,
0,
1,

1)
1)
1)
1)

bus.write_byte(self.__address, self.__config1)
bus.write_byte(self.__address2, self.__config2)
return
def setBitRate(self, rate):
# sample rate and resolution
#12 = 12 bit (240SPS max)
#14 = 14 bit (60SPS max)
#16 = 16 bit (15SPS max)
#18 = 18 bit (3.75SPS max)
if rate == 12:
self.__config1 = self.__updatebyte(self.__config1,
self.__config1 = self.__updatebyte(self.__config1,
self.__config2 = self.__updatebyte(self.__config2,
self.__config2 = self.__updatebyte(self.__config2,
self.__bitrate = 12
if rate == 14:
self.__config1 = self.__updatebyte(self.__config1,
self.__config1 = self.__updatebyte(self.__config1,
self.__config2 = self.__updatebyte(self.__config2,
self.__config2 = self.__updatebyte(self.__config2,
self.__bitrate = 14
if rate == 16:
self.__config1 = self.__updatebyte(self.__config1,
self.__config1 = self.__updatebyte(self.__config1,
self.__config2 = self.__updatebyte(self.__config2,
self.__config2 = self.__updatebyte(self.__config2,
self.__bitrate = 16
if rate == 18:
self.__config1 = self.__updatebyte(self.__config1,
self.__config1 = self.__updatebyte(self.__config1,
self.__config2 = self.__updatebyte(self.__config2,
self.__config2 = self.__updatebyte(self.__config2,
self.__bitrate = 18

2,
3,
2,
3,

0)
0)
0)
0)

2,
3,
2,
3,

1)
0)
1)
0)

2,
3,
2,
3,

0)
1)
0)
1)

2,
3,
2,
3,

1)
1)
1)
1)

bus.write_byte(self.__address, self.__config1)
bus.write_byte(self.__address2, self.__config2)
return
#!/usr/bin/env python
# -*- coding: utf-8 -*""" Shared logging settings."""
import logging
def setup_logging():
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s.%(msecs).3d [%(levelname).1s] %(name)-15.15s > %(message)s',
datefmt='%H:%M:%S'
)
#!/usr/bin/env python3
# read abelectronics ADC Pi V2 board inputs with repeating reading from each channel on a BeagleBone Black.
# Requires SMBus

# Version 1.0 - 24/07/2013


# Version History:
# 1.0 - Initial Release
#
# Usage: changechannel(address, hexvalue) to change to new channel on adc chips
# Usage: getadcreading(address, hexvalue) to return value in volts from selected channel.
#
# address = adc_address1 or adc_address2 - Hex address of I2C chips as configured by board header pins.
from smbus import SMBus
adc_address1 = 0x68
adc_address2 = 0x69
# create byte array and fill with initial values to define size
adcreading = bytearray()
adcreading.append(0x00)
adcreading.append(0x00)
adcreading.append(0x00)
adcreading.append(0x00)
varDivisior = 64 # from pdf sheet on adc addresses and config
varMultiplier = (2.4705882/varDivisior)/1000
i2c_bus = 1
bus = SMBus(i2c_bus)
def changechannel(address, adcConfig):
tmp= bus.write_byte(address, adcConfig)
def getadcreading(address, adcConfig):
adcreading = bus.read_i2c_block_data(address,adcConfig)
h = adcreading[0]
m = adcreading[1]
l = adcreading[2]
s = adcreading[3]
# wait for new data
while (s & 128):
adcreading = bus.read_i2c_block_data(address,adcConfig)
h = adcreading[0]
m = adcreading[1]
l = adcreading[2]
s = adcreading[3]
# shift bits to product result
t = ((h & 0b00000001) << 16) | (m << 8) | l
# check if positive or negative number and invert if needed
if (h > 128):
t = ~(0x020000 - t)
return t * varMultiplier
while True:

changechannel(adc_address1, 0x9C)
print ("Channel 1: %02f" % getadcreading(adc_address1,0x9C))
changechannel(adc_address1, 0xBC)
print ("Channel 2: %02f" % getadcreading(adc_address1,0xBC))
changechannel(adc_address1, 0xDC)
print ("Channel 3 :%02f" % getadcreading(adc_address1, 0xDC))
changechannel(adc_address1, 0xFC)
print ("Channel 4: %02f" % getadcreading(adc_address1, 0xFC))
changechannel(adc_address2, 0x9C)
print ("Channel 5: %02f" % getadcreading(adc_address2, 0x9C))
changechannel(adc_address2, 0xBC)
print ("Channel 6: %02f" % getadcreading(adc_address2, 0xBC))
changechannel(adc_address2, 0xDC)
print ("Channel 7: %02f" % getadcreading(adc_address2, 0xDC))
changechannel(adc_address2, 0xFC)
print ("Channel 8: %02f" % getadcreading(adc_address2, 0xFC))

S-ar putea să vă placă și