Examples Python
Examples Python
/*
* 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;
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};
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;
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 *
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;
}
}
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;
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;
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},
};
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 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
====
**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
================================================
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,
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
"""
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
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
"""
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)
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:
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)
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:
import time
import os
#
#
#
#
#
#
================================================
ABElectronics Expander Pi | Tester
Version 1.0 Created 29/03/2015
run with: python3 tester.py
================================================
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)
```
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
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.
#!/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,
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
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
# 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
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:
"""
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)
```
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":
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)
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
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')
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
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)
```
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)
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
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
0)
0)
1)
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,
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.
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:
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
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]
"name": "ronanguilloux/temperature-pi",
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;
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'
}
</script>"""
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}   {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}   {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 </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>"
#
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()
#!/usr/bin/env python
import sqlite3
import os
import time
import glob
# global variables
speriod=(15*60)-1
dbname='/var/www/templog.db'
# 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():
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)
#
#
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):
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])
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
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):
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
= 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
= 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):
@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:
#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])
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()
__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)
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
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,
#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.
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.
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.
.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
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.
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
#
# 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_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
#
#
# 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
#
# 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:
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."""
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
''' 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')
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()
#
#
#
#
#
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::
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
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.
>>>
>>>
>>>
>>>
>>>
>>>
>>>
>>>
>>>
>>>
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
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
"""
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 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):
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
"""
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
}))
#!/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')}),
@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
@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)
=
=
=
=
2.048
2.048
2.048
2.048
/
/
/
/
4096
16384
65536
262144
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
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))