diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3bc48f9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,43 @@ +*.py[cod] + +# C extensions +*.so + +# Packages +*.egg +*.egg-info +dist +build +eggs +parts +bin +var +sdist +develop-eggs +.installed.cfg +lib +lib64 +__pycache__ + +# Installer logs +pip-log.txt + +# Unit test / coverage reports +.coverage +.tox +nosetests.xml + +# Translations +*.mo + +# Mr Developer +.mr.developer.cfg +.project +.pydevproject +.db + +# Added tng +client_secrets.json +sample.dat +google_calendar.py.bak +*.bak diff --git a/bluez_configuration.md b/bluez_configuration.md new file mode 100644 index 0000000..d7c861d --- /dev/null +++ b/bluez_configuration.md @@ -0,0 +1,21 @@ +Instructions +============ + +Taken from Klaus Seiler's comment on http://mike.saunby.net/2013/04/raspberry-pi-and-ti-cc2541-sensortag.html + + +get a recent bluez version from http://www.bluez.org/ +# wget https://www.kernel.org/pub/linux/bluetooth/bluez-5.4.tar.xz +extract +# tar xvf bluez-5.4.tar.xz + +get the necessary libs +# apt-get install libusb-dev libdbus-1-dev libglib2.0-dev automake libudev-dev libical-dev libreadline-dev + +systemd is not needed, see later + +configure and build SW: +# cd bluez-5.4 +# ./configure --disable-systemd +# make +# make install diff --git a/redis_demo/sensor_calcs.py b/redis_demo/sensor_calcs.py new file mode 100755 index 0000000..1cf2870 --- /dev/null +++ b/redis_demo/sensor_calcs.py @@ -0,0 +1,170 @@ +# +# Michael Saunby. April 2013 +# +# Read temperature from the TMP006 sensor in the TI SensorTag. + +# This algorithm borrowed from +# http://processors.wiki.ti.com/index.php/SensorTag_User_Guide#Gatt_Server +# which most likely took it from the datasheet. I've not checked it, other +# than noted that the temperature values I got seemed reasonable. +# +# Copyright 2013 Michael Saunby +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +tosigned = lambda n: float(n-0x10000) if n>0x7fff else float(n) +tosignedbyte = lambda n: float(n-0x100) if n>0x7f else float(n) + +def calcTmpTarget(objT, ambT): + + objT = tosigned(objT) + ambT = tosigned(ambT) + + m_tmpAmb = ambT/128.0 + Vobj2 = objT * 0.00000015625 + Tdie2 = m_tmpAmb + 273.15 + S0 = 6.4E-14 # Calibration factor + a1 = 1.75E-3 + a2 = -1.678E-5 + b0 = -2.94E-5 + b1 = -5.7E-7 + b2 = 4.63E-9 + c2 = 13.4 + Tref = 298.15 + S = S0*(1+a1*(Tdie2 - Tref)+a2*pow((Tdie2 - Tref),2)) + Vos = b0 + b1*(Tdie2 - Tref) + b2*pow((Tdie2 - Tref),2) + fObj = (Vobj2 - Vos) + c2*pow((Vobj2 - Vos),2) + tObj = pow(pow(Tdie2,4) + (fObj/S),.25) + tObj = (tObj - 273.15) + return tObj + +# +# Again from http://processors.wiki.ti.com/index.php/SensorTag_User_Guide#Gatt_Server +# +def calcHum(rawT, rawH): + # -- calculate temperature [deg C] -- + t = -46.85 + 175.72/65536.0 * rawT + + rawH = float(int(rawH) & ~0x0003); # clear bits [1..0] (status bits) + # -- calculate relative humidity [%RH] -- + rh = -6.0 + 125.0/65536.0 * rawH # RH= -6 + 125 * SRH/2^16 + return (t, rh) + + +# +# Again from http://processors.wiki.ti.com/index.php/SensorTag_User_Guide#Gatt_Server +# but combining all three values and giving magnitude. +# Magnitude tells us if we are at rest, falling, etc. + +def calcAccel(rawX, rawY, rawZ): + accel = lambda v: tosignedbyte(v) / 64.0 # Range -2G, +2G + xyz = [accel(rawX), accel(rawY), accel(rawZ)] + mag = (xyz[0]**2 + xyz[1]**2 + xyz[2]**2)**0.5 + return (xyz, mag) + + +# +# Again from http://processors.wiki.ti.com/index.php/SensorTag_User_Guide#Gatt_Server +# but combining all three values. +# + +def calcMagn(rawX, rawY, rawZ): + magforce = lambda v: (tosigned(v) * 1.0) / (65536.0/2000.0) + return [magforce(rawX),magforce(rawY),magforce(rawZ)] + + +class Barometer: + +# Ditto. +# Conversion algorithm for barometer temperature +# +# Formula from application note, rev_X: +# Ta = ((c1 * Tr) / 2^24) + (c2 / 2^10) +# +# c1 - c8: calibration coefficients the can be read from the sensor +# c1 - c4: unsigned 16-bit integers +# c5 - c8: signed 16-bit integers +# + + def calcBarTmp(self, raw_temp): + c1 = self.m_barCalib.c1 + c2 = self.m_barCalib.c2 + val = long((c1 * raw_temp) * 100) + temp = val >> 24 + val = long(c2 * 100) + temp += (val >> 10) + return float(temp) / 100.0 + + +# Conversion algorithm for barometer pressure (hPa) +# +# Formula from application note, rev_X: +# Sensitivity = (c3 + ((c4 * Tr) / 2^17) + ((c5 * Tr^2) / 2^34)) +# Offset = (c6 * 2^14) + ((c7 * Tr) / 2^3) + ((c8 * Tr^2) / 2^19) +# Pa = (Sensitivity * Pr + Offset) / 2^14 +# + def calcBarPress(self,Tr,Pr): + c3 = self.m_barCalib.c3 + c4 = self.m_barCalib.c4 + c5 = self.m_barCalib.c5 + c6 = self.m_barCalib.c6 + c7 = self.m_barCalib.c7 + c8 = self.m_barCalib.c8 + # Sensitivity + s = long(c3) + val = long(c4 * Tr) + s += (val >> 17) + val = long(c5 * Tr * Tr) + s += (val >> 34) + # Offset + o = long(c6) << 14 + val = long(c7 * Tr) + o += (val >> 3) + val = long(c8 * Tr * Tr) + o += (val >> 19) + # Pressure (Pa) + pres = ((s * Pr) + o) >> 14 + return float(pres)/100.0 + + + class Calib: + + # This works too + # i = (hi<<8)+lo + def bld_int(self, lobyte, hibyte): + return (lobyte & 0x0FF) + ((hibyte & 0x0FF) << 8) + + def __init__( self, pData ): + self.c1 = self.bld_int(pData[0],pData[1]) + self.c2 = self.bld_int(pData[2],pData[3]) + self.c3 = self.bld_int(pData[4],pData[5]) + self.c4 = self.bld_int(pData[6],pData[7]) + self.c5 = tosigned(self.bld_int(pData[8],pData[9])) + self.c6 = tosigned(self.bld_int(pData[10],pData[11])) + self.c7 = tosigned(self.bld_int(pData[12],pData[13])) + self.c8 = tosigned(self.bld_int(pData[14],pData[15])) + + + def __init__(self, rawCalibration): + self.m_barCalib = self.Calib( rawCalibration ) + return + + def calc(self, rawT, rawP): + self.m_raw_temp = tosigned(rawT) + self.m_raw_pres = rawP # N.B. Unsigned value + bar_temp = self.calcBarTmp( self.m_raw_temp ) + bar_pres = self.calcBarPress( self.m_raw_temp, self.m_raw_pres ) + return( bar_temp, bar_pres) + + diff --git a/redis_demo/sensortag.sh b/redis_demo/sensortag.sh new file mode 100755 index 0000000..24b9c81 --- /dev/null +++ b/redis_demo/sensortag.sh @@ -0,0 +1,59 @@ +#!/bin/sh + +### BEGIN INIT INFO +# Provides: sensortag +# Required-Start: $all +# Required-Stop: $all +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: Read sensortag and send output to redis +# Description: Read sensortag and send output to redis +### END INIT INFO + +# Change the next 3 lines to suit where you install your script and what you want to call it +DIR=/usr/local/bin +DAEMON=${DIR}/sensortag_redis.py +DAEMON_NAME=sensortag_redis.py + +# This next line determines what user the script runs as. +# Root generally not recommended but necessary if you are using the Raspberry Pi GPIO from Python. +DAEMON_USER=root + +# The process ID of the script when it runs is stored here: +PIDFILE=/var/run/$DAEMON_NAME.pid + +. /lib/lsb/init-functions + +do_start () { + log_daemon_msg "Starting system $DAEMON_NAME daemon" + start-stop-daemon --start --background --pidfile $PIDFILE --make-pidfile --user $DAEMON_USER --startas $DAEMON +# start-stop-daemon --start --pidfile $PIDFILE --make-pidfile --user $DAEMON_USER --startas ${DAEMON} + log_end_msg $? +} +do_stop () { + log_daemon_msg "Stopping system $DAEMON_NAME daemon" + start-stop-daemon --stop --pidfile $PIDFILE --retry 10 + log_end_msg $? +} + +case "$1" in + + start|stop) + do_${1} + ;; + + restart|reload|force-reload) + do_stop + do_start + ;; + + status) + status_of_proc "$DAEMON_NAME" "$DAEMON" && exit 0 || exit $? + ;; + *) + echo "Usage: /etc/init.d/$DEAMON_NAME {start|stop|restart|status}" + exit 1 + ;; + +esac +exit 0 diff --git a/redis_demo/sensortag_redis.py b/redis_demo/sensortag_redis.py new file mode 100755 index 0000000..a1f28ab --- /dev/null +++ b/redis_demo/sensortag_redis.py @@ -0,0 +1,213 @@ +#!/usr/bin/env python +# Michael Saunby. April 2013 +# +# Notes. +# pexpect uses regular expression so characters that have special meaning +# in regular expressions, e.g. [ and ] must be escaped with a backslash. +# +# Copyright 2013 Michael Saunby +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pexpect +import sys +import time +from sensor_calcs import * +import select +import redis + +redthis = redis.StrictRedis(host='433board',port=6379, db=0, socket_timeout=3) +redis_queue="/dastardly/sensor" + + +def floatfromhex(h): + t = float.fromhex(h) + if t > float.fromhex('7FFF'): + t = -(float.fromhex('FFFF') - t) + pass + return t + +class SensorTag: + + def __init__( self, bluetooth_adr ): + self.con = pexpect.spawn('gatttool -b ' + bluetooth_adr + ' --interactive') + self.con.expect('\[LE\]>', timeout=600) + print "Preparing to connect. You might need to press the side button..." + self.con.sendline('connect') + # test for success of connect + self.con.expect('Connection successful.*\[LE\]>') + # Earlier versions of gatttool returned a different message. Use this pattern - + #self.con.expect('\[CON\].*>') + self.cb = {} + return + + self.con.expect('\[CON\].*>') + self.cb = {} + return + + def char_write_cmd( self, handle, value ): + # The 0%x for value is VERY naughty! Fix this! + cmd = 'char-write-cmd 0x%02x 0%x' % (handle, value) + print cmd + self.con.sendline( cmd ) + return + + def char_read_hnd( self, handle ): + self.con.sendline('char-read-hnd 0x%02x' % handle) + self.con.expect('descriptor: .*? \r') + after = self.con.after + rval = after.split()[1:] + return [long(float.fromhex(n)) for n in rval] + + # Notification handle = 0x0025 value: 9b ff 54 07 + def notification_loop( self ): + while True: +# print ("Sleeping 2") +# time.sleep(2) +# print ("Finished") + try: + pnum = self.con.expect('Notification handle = .*? \r', timeout=4) + except pexpect.TIMEOUT: + print "TIMEOUT exception!" + break + if pnum==0: + after = self.con.after + hxstr = after.split()[3:] + handle = long(float.fromhex(hxstr[0])) + #try: + if True: + self.cb[handle]([long(float.fromhex(n)) for n in hxstr[2:]]) + #except: + # print "Error in callback for %x" % handle + # print sys.argv[1] + pass + else: + print "TIMEOUT!!" + pass + + def register_cb( self, handle, fn ): + self.cb[handle]=fn; + return + + +class SensorCallbacks: + + data = {} + + def __init__(self,addr): + self.data['addr'] = addr + + def tmp006(self,v): + objT = (v[1]<<8)+v[0] + ambT = (v[3]<<8)+v[2] + targetT = calcTmpTarget(objT, ambT) + self.data['t006'] = targetT + print "T006 %.1f" % targetT + + def accel(self,v): + (xyz,mag) = calcAccel(v[0],v[1],v[2]) + self.data['accl'] = xyz + print "ACCL", xyz + + def humidity(self, v): + rawT = (v[1]<<8)+v[0] + rawH = (v[3]<<8)+v[2] + (t, rh) = calcHum(rawT, rawH) + self.data['humd'] = [t, rh] +# print "HUMD %.1f" % rh + try: + redthis.set("humidity%s" % redis_queue, rh) + except: + print ("Unable to update redis") + + def baro(self,v): + global barometer + global datalog + rawT = (v[1]<<8)+v[0] + rawP = (v[3]<<8)+v[2] + (temp, pres) = self.data['baro'] = barometer.calc(rawT, rawP) + self.data['time'] = long(time.time() * 1000); +# print "BARO", temp, pres + try: + redthis.set("temperature%s" % redis_queue, temp) + redthis.expire("temperature%s" % redis_queue, 240) + redthis.set("pressure%s" % redis_queue , pres) + except: + print ("Unable to update redis") + time.sleep(120) + + def magnet(self,v): + x = (v[1]<<8)+v[0] + y = (v[3]<<8)+v[2] + z = (v[5]<<8)+v[4] + xyz = calcMagn(x, y, z) + self.data['magn'] = xyz + print "MAGN", xyz + + def gyro(self,v): + print "GYRO", v + +def main(): + global datalog + global barometer + + bluetooth_adr = "1C:BA:8C:20:CC:96" +# bluetooth_adr = sys.argv[1] + if len(sys.argv) > 2: + datalog = open(sys.argv[2], 'w+') + + + while True: + + tag = SensorTag(bluetooth_adr) + cbs = SensorCallbacks(bluetooth_adr) + + # enable TMP006 sensor +# tag.register_cb(0x25,cbs.tmp006) +# tag.char_write_cmd(0x29,0x01) +# tag.char_write_cmd(0x26,0x0100) + + # enable accelerometer +# tag.register_cb(0x2d,cbs.accel) +# tag.char_write_cmd(0x31,0x01) +# tag.char_write_cmd(0x2e,0x0100) + + # enable humidity + tag.register_cb(0x38, cbs.humidity) + tag.char_write_cmd(0x3c,0x01) + tag.char_write_cmd(0x39,0x0100) + + # enable magnetometer +# tag.register_cb(0x40,cbs.magnet) +# tag.char_write_cmd(0x44,0x01) +# tag.char_write_cmd(0x41,0x0100) + + # enable gyroscope +# tag.register_cb(0x57,cbs.gyro) +# tag.char_write_cmd(0x5b,0x07) +# tag.char_write_cmd(0x58,0x0100) + + # fetch barometer calibration + tag.char_write_cmd(0x4f,0x02) + rawcal = tag.char_read_hnd(0x52) + barometer = Barometer( rawcal ) + # enable barometer + tag.register_cb(0x4b,cbs.baro) + tag.char_write_cmd(0x4f,0x01) + tag.char_write_cmd(0x4c,0x0100) + + tag.notification_loop() + +if __name__ == "__main__": + main() + diff --git a/sensortag/sensortag.sh b/sensortag/sensortag.sh index 65a56be..323289c 100755 --- a/sensortag/sensortag.sh +++ b/sensortag/sensortag.sh @@ -4,4 +4,4 @@ export XIVELY_API_KEY="QwertyUiopAsdfgHjklZxcvBnm1234567890" export XIVELY_FEED_ID="1234567890" ./sensortag_xively.py DE:AD:DE:AD:DE:AD - +#1C:BA:8C:20:CC:96 diff --git a/sensortag/sensortag_test.py b/sensortag/sensortag_test.py index de74469..f9eea77 100755 --- a/sensortag/sensortag_test.py +++ b/sensortag/sensortag_test.py @@ -55,15 +55,17 @@ def calcTmpTarget(objT, ambT): bluetooth_adr = sys.argv[1] tool = pexpect.spawn('gatttool -b ' + bluetooth_adr + ' --interactive') -tool.expect('\[LE\]>') +tool.expect('.*\[LE\]>', timeout=600) print "Preparing to connect. You might need to press the side button..." tool.sendline('connect') # test for success of connect -tool.expect('\[CON\].*>') +# tool.expect('\[CON\].*>') +# Alternative test for success of connect +tool.expect('Connection successful.*\[LE\]>') tool.sendline('char-write-cmd 0x29 01') tool.expect('\[LE\]>') +time.sleep(1) while True: - time.sleep(1) tool.sendline('char-read-hnd 0x25') tool.expect('descriptor: .*') rval = tool.after.split() @@ -71,5 +73,6 @@ def calcTmpTarget(objT, ambT): ambT = floatfromhex(rval[4] + rval[3]) #print rval calcTmpTarget(objT, ambT) + time.sleep(100) diff --git a/sensortag/sensortag_xively.py b/sensortag/sensortag_xively.py index 50a71ae..3275dd7 100755 --- a/sensortag/sensortag_xively.py +++ b/sensortag/sensortag_xively.py @@ -25,7 +25,7 @@ from sensor_calcs import * import json import select -from xively_fns import xively_init, xively_write +#from xively_fns import xively_init, xively_write def floatfromhex(h): @@ -96,7 +96,7 @@ def register_cb( self, handle, fn ): barometer = None datalog = sys.stdout -xively_feed = None +#xively_feed = None def datalog_out(data): # The socket or output file might not be writeable @@ -152,7 +152,7 @@ def baro(self,v): self.data['time'] = long(time.time() * 1000); print "BARO", temp, pres #datalog_out(self.data) - xively_write(xively_feed, self.data) +# xively_write(xively_feed, self.data) def magnet(self,v): x = (v[1]<<8)+v[0] @@ -168,13 +168,13 @@ def gyro(self,v): def main(): global datalog global barometer - global xively_feed +# global xively_feed bluetooth_adr = sys.argv[1] if len(sys.argv) > 2: datalog = open(sys.argv[2], 'w+') - xively_feed = xively_init() +# xively_feed = xively_init() while True: