Skip to content

Commit a15f1cd

Browse files
committed
Add PyTrack example.
1 parent d3e7891 commit a15f1cd

File tree

6 files changed

+470
-0
lines changed

6 files changed

+470
-0
lines changed

pytrack-example/boot.py

Whitespace-only changes.

pytrack-example/lib/CayenneLPP.py

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
"""
2+
Source: https://github.com/simonqbs/cayennelpp-python
3+
"""
4+
5+
import struct
6+
import math
7+
8+
LPP_DIGITAL_INPUT = 0 # 1 byte
9+
LPP_DIGITAL_OUTPUT = 1 # 1 byte
10+
LPP_ANALOG_INPUT = 2 # 2 bytes, 0.01 signed
11+
LPP_ANALOG_OUTPUT = 3 # 2 bytes, 0.01 signed
12+
LPP_LUMINOSITY = 101 # 2 bytes, 1 lux unsigned
13+
LPP_PRESENCE = 102 # 1 byte, 1
14+
LPP_TEMPERATURE = 103 # 2 bytes, 0.1°C signed
15+
LPP_RELATIVE_HUMIDITY = 104 # 1 byte, 0.5% unsigned
16+
LPP_ACCELEROMETER = 113 # 2 bytes per axis, 0.001G
17+
LPP_BAROMETRIC_PRESSURE = 115 # 2 bytes 0.1 hPa Unsigned
18+
LPP_GYROMETER = 134 # 2 bytes per axis, 0.01 °/s
19+
LPP_GPS = 136 # 3 byte lon/lat 0.0001 °, 3 bytes alt 0.01 meter
20+
21+
# Data ID + Data Type + Data Size
22+
LPP_DIGITAL_INPUT_SIZE = 3 # 1 byte
23+
LPP_DIGITAL_OUTPUT_SIZE = 3 # 1 byte
24+
LPP_ANALOG_INPUT_SIZE = 4 # 2 bytes, 0.01 signed
25+
LPP_ANALOG_OUTPUT_SIZE = 4 # 2 bytes, 0.01 signed
26+
LPP_LUMINOSITY_SIZE = 4 # 2 bytes, 1 lux unsigned
27+
LPP_PRESENCE_SIZE = 3 # 1 byte, 1
28+
LPP_TEMPERATURE_SIZE = 4 # 2 bytes, 0.1°C signed
29+
LPP_RELATIVE_HUMIDITY_SIZE = 3 # 1 byte, 0.5% unsigned
30+
LPP_ACCELEROMETER_SIZE = 8 # 2 bytes per axis, 0.001G
31+
LPP_BAROMETRIC_PRESSURE_SIZE = 4 # 2 bytes 0.1 hPa Unsigned
32+
LPP_GYROMETER_SIZE = 8 # 2 bytes per axis, 0.01 °/s
33+
LPP_GPS_SIZE = 11 # 3 byte lon/lat 0.0001 °, 3 bytes alt 0.01 meter
34+
35+
class CayenneLPP:
36+
def __init__(self):
37+
self.buffer = bytearray()
38+
39+
def get_buffer(self):
40+
return self.buffer
41+
42+
def reset(self):
43+
self.buffer = bytearray()
44+
45+
def get_size(self):
46+
return len(self.buffer)
47+
48+
def add_temperature(self, channel, value):
49+
val = math.floor(value * 10);
50+
51+
self.buffer.extend(struct.pack('b', channel))
52+
self.buffer.extend(struct.pack('b', LPP_TEMPERATURE))
53+
self.buffer.extend(struct.pack('b', val >> 8))
54+
self.buffer.extend(struct.pack('b', val))
55+
56+
def add_relative_humidity(self, channel, value):
57+
val = math.floor(value * 2)
58+
59+
self.buffer.extend(struct.pack('b', channel))
60+
self.buffer.extend(struct.pack('b', LPP_RELATIVE_HUMIDITY))
61+
self.buffer.extend(struct.pack('b', val))
62+
63+
def add_digital_input(self, channel, value):
64+
self.buffer.extend(struct.pack('b', channel))
65+
self.buffer.extend(struct.pack('b', LPP_DIGITAL_INPUT))
66+
self.buffer.extend(struct.pack('b', value))
67+
68+
def add_digital_output(self, channel, value):
69+
self.buffer.extend(struct.pack('b', channel))
70+
self.buffer.extend(struct.pack('b', LPP_DIGITAL_OUTPUT))
71+
self.buffer.extend(struct.pack('b', value))
72+
73+
def add_analog_input(self, channel, value):
74+
val = math.floor(value * 100)
75+
76+
self.buffer.extend(struct.pack('b', channel))
77+
self.buffer.extend(struct.pack('b', LPP_ANALOG_INPUT))
78+
self.buffer.extend(struct.pack('b', val >> 8))
79+
self.buffer.extend(struct.pack('b', val))
80+
81+
def add_analog_output(self, channel, value):
82+
val = math.floor(value * 100)
83+
84+
self.buffer.extend(struct.pack('b', channel))
85+
self.buffer.extend(struct.pack('b', LPP_ANALOG_OUTPUT))
86+
self.buffer.extend(struct.pack('b', val >> 8))
87+
self.buffer.extend(struct.pack('b', val))
88+
89+
def add_luminosity(self, channel, value):
90+
self.buffer.extend(struct.pack('b', channel))
91+
self.buffer.extend(struct.pack('b', LPP_LUMINOSITY))
92+
self.buffer.extend(struct.pack('b', value >> 8))
93+
self.buffer.extend(struct.pack('b', value))
94+
95+
def add_presence(self, channel, value):
96+
self.buffer.extend(struct.pack('b', channel))
97+
self.buffer.extend(struct.pack('b', LPP_PRESENCE))
98+
self.buffer.extend(struct.pack('b', value))
99+
100+
def add_accelerometer(self, channel, x, y, z):
101+
vx = math.floor(x * 1000)
102+
vy = math.floor(y * 1000)
103+
vz = math.floor(z * 1000)
104+
105+
self.buffer.extend(struct.pack('b', channel))
106+
self.buffer.extend(struct.pack('b', LPP_ACCELEROMETER))
107+
self.buffer.extend(struct.pack('b', vx >> 8))
108+
self.buffer.extend(struct.pack('b', vx))
109+
self.buffer.extend(struct.pack('b', vy >> 8))
110+
self.buffer.extend(struct.pack('b', vy))
111+
self.buffer.extend(struct.pack('b', vz >> 8))
112+
self.buffer.extend(struct.pack('b', vz))
113+
114+
def add_barometric_pressure(self, channel, value):
115+
val = math.floor(value * 10)
116+
117+
self.buffer.extend(struct.pack('b', channel))
118+
self.buffer.extend(struct.pack('b', LPP_BAROMETRIC_PRESSURE))
119+
self.buffer.extend(struct.pack('b', val >> 8))
120+
self.buffer.extend(struct.pack('b', val))
121+
122+
def add_gryrometer(self, channel, x, y, z):
123+
vx = math.floor(x * 100)
124+
vy = math.floor(y * 100)
125+
vz = math.floor(z * 100)
126+
127+
self.buffer.extend(struct.pack('b', channel))
128+
self.buffer.extend(struct.pack('b', LPP_GYROMETER))
129+
self.buffer.extend(struct.pack('b', vx >> 8))
130+
self.buffer.extend(struct.pack('b', vx))
131+
self.buffer.extend(struct.pack('b', vy >> 8))
132+
self.buffer.extend(struct.pack('b', vy))
133+
self.buffer.extend(struct.pack('b', vz >> 8))
134+
self.buffer.extend(struct.pack('b', vz))
135+
136+
def add_gps(self, channel, latitude, longitude, meters):
137+
lat = math.floor(latitude * 10000)
138+
lon = math.floor(longitude * 10000)
139+
alt = math.floor(meters * 100)
140+
141+
self.buffer.extend(struct.pack('b', channel))
142+
self.buffer.extend(struct.pack('b', LPP_GPS))
143+
self.buffer.extend(struct.pack('b', lat >> 16))
144+
self.buffer.extend(struct.pack('b', lat >> 8))
145+
self.buffer.extend(struct.pack('b', lat))
146+
self.buffer.extend(struct.pack('b', lon >> 16))
147+
self.buffer.extend(struct.pack('b', lon >> 8))
148+
self.buffer.extend(struct.pack('b', lon))
149+
self.buffer.extend(struct.pack('b', alt >> 16))
150+
self.buffer.extend(struct.pack('b', alt >> 8))
151+
self.buffer.extend(struct.pack('b', alt))

pytrack-example/lib/L76GNSS.py

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
from machine import Timer
2+
import time
3+
import gc
4+
import binascii
5+
6+
7+
class L76GNSS:
8+
9+
GPS_I2CADDR = const(0x10)
10+
11+
def __init__(self, pytrack=None, sda='P22', scl='P21', timeout=None):
12+
if pytrack is not None:
13+
self.i2c = pytrack.i2c
14+
else:
15+
from machine import I2C
16+
self.i2c = I2C(0, mode=I2C.MASTER, pins=(sda, scl))
17+
18+
self.chrono = Timer.Chrono()
19+
20+
self.timeout = timeout
21+
self.timeout_status = True
22+
23+
self.reg = bytearray(1)
24+
self.i2c.writeto(GPS_I2CADDR, self.reg)
25+
26+
def _read(self):
27+
self.reg = self.i2c.readfrom(GPS_I2CADDR, 64)
28+
return self.reg
29+
30+
def _convert_coords(self, gngll_s):
31+
lat = gngll_s[1]
32+
lat_d = (float(lat) // 100) + ((float(lat) % 100) / 60)
33+
lon = gngll_s[3]
34+
lon_d = (float(lon) // 100) + ((float(lon) % 100) / 60)
35+
if gngll_s[2] == 'S':
36+
lat_d *= -1
37+
if gngll_s[4] == 'W':
38+
lon_d *= -1
39+
return(lat_d, lon_d)
40+
41+
def coordinates(self, debug=False):
42+
lat_d, lon_d, debug_timeout = None, None, False
43+
if self.timeout is not None:
44+
self.chrono.reset()
45+
self.chrono.start()
46+
nmea = b''
47+
while True:
48+
if self.timeout is not None and self.chrono.read() >= self.timeout:
49+
self.chrono.stop()
50+
chrono_timeout = self.chrono.read()
51+
self.chrono.reset()
52+
self.timeout_status = False
53+
debug_timeout = True
54+
if not self.timeout_status:
55+
gc.collect()
56+
break
57+
nmea += self._read().lstrip(b'\n\n').rstrip(b'\n\n')
58+
gngll_idx = nmea.find(b'GNGLL')
59+
if gngll_idx >= 0:
60+
gngll = nmea[gngll_idx:]
61+
e_idx = gngll.find(b'\r\n')
62+
if e_idx >= 0:
63+
try:
64+
gngll = gngll[:e_idx].decode('ascii')
65+
gngll_s = gngll.split(',')
66+
lat_d, lon_d = self._convert_coords(gngll_s)
67+
except Exception:
68+
pass
69+
finally:
70+
nmea = nmea[(gngll_idx + e_idx):]
71+
gc.collect()
72+
break
73+
else:
74+
gc.collect()
75+
if len(nmea) > 410: # i suppose it can be safely changed to 82, which is longest NMEA frame
76+
nmea = nmea[-5:] # $GNGL without last L
77+
time.sleep(0.1)
78+
self.timeout_status = True
79+
if debug and debug_timeout:
80+
print('GPS timed out after %f seconds' % (chrono_timeout))
81+
return(None, None)
82+
else:
83+
return(lat_d, lon_d)

pytrack-example/lib/LIS2HH12.py

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
import math
2+
import time
3+
import struct
4+
from machine import Pin
5+
6+
7+
FULL_SCALE_2G = const(0)
8+
FULL_SCALE_4G = const(2)
9+
FULL_SCALE_8G = const(3)
10+
11+
ODR_POWER_DOWN = const(0)
12+
ODR_10_HZ = const(1)
13+
ODR_50_HZ = const(2)
14+
ODR_100_HZ = const(3)
15+
ODR_200_HZ = const(4)
16+
ODR_400_HZ = const(5)
17+
ODR_800_HZ = const(6)
18+
19+
ACC_G_DIV = 1000 * 65536
20+
21+
class LIS2HH12:
22+
23+
ACC_I2CADDR = const(30)
24+
25+
PRODUCTID_REG = const(0x0F)
26+
CTRL1_REG = const(0x20)
27+
CTRL2_REG = const(0x21)
28+
CTRL3_REG = const(0x22)
29+
CTRL4_REG = const(0x23)
30+
CTRL5_REG = const(0x24)
31+
ACC_X_L_REG = const(0x28)
32+
ACC_X_H_REG = const(0x29)
33+
ACC_Y_L_REG = const(0x2A)
34+
ACC_Y_H_REG = const(0x2B)
35+
ACC_Z_L_REG = const(0x2C)
36+
ACC_Z_H_REG = const(0x2D)
37+
ACT_THS = const(0x1E)
38+
ACT_DUR = const(0x1F)
39+
40+
def __init__(self, pysense = None, sda = 'P22', scl = 'P21'):
41+
if pysense is not None:
42+
self.i2c = pysense.i2c
43+
else:
44+
from machine import I2C
45+
self.i2c = I2C(0, mode=I2C.MASTER, pins=(sda, scl))
46+
47+
self.reg = bytearray(1)
48+
self.odr = 0
49+
self.full_scale = 0
50+
self.x = 0
51+
self.y = 0
52+
self.z = 0
53+
self.int_pin = None
54+
self.act_dur = 0
55+
self.debounced = False
56+
57+
self.scales = {FULL_SCALE_2G: 4000, FULL_SCALE_4G: 8000, FULL_SCALE_8G: 16000}
58+
self.odrs = [0, 10, 50, 100, 200, 400, 800]
59+
60+
whoami = self.i2c.readfrom_mem(ACC_I2CADDR , PRODUCTID_REG, 1)
61+
if (whoami[0] != 0x41):
62+
raise ValueError("LIS2HH12 not found")
63+
64+
# enable acceleration readings at 50Hz
65+
self.set_odr(ODR_50_HZ)
66+
67+
# change the full-scale to 4g
68+
self.set_full_scale(FULL_SCALE_4G)
69+
70+
# set the interrupt pin as active low and open drain
71+
self.i2c.readfrom_mem_into(ACC_I2CADDR , CTRL5_REG, self.reg)
72+
self.reg[0] |= 0b00000011
73+
self.i2c.writeto_mem(ACC_I2CADDR , CTRL5_REG, self.reg)
74+
75+
# make a first read
76+
self.acceleration()
77+
78+
def acceleration(self):
79+
x = self.i2c.readfrom_mem(ACC_I2CADDR , ACC_X_L_REG, 2)
80+
self.x = struct.unpack('<h', x)
81+
y = self.i2c.readfrom_mem(ACC_I2CADDR , ACC_Y_L_REG, 2)
82+
self.y = struct.unpack('<h', y)
83+
z = self.i2c.readfrom_mem(ACC_I2CADDR , ACC_Z_L_REG, 2)
84+
self.z = struct.unpack('<h', z)
85+
_mult = self.scales[self.full_scale] / ACC_G_DIV
86+
return (self.x[0] * _mult, self.y[0] * _mult, self.z[0] * _mult)
87+
88+
def roll(self):
89+
x,y,z = self.acceleration()
90+
rad = math.atan2(-x, z)
91+
return (180 / math.pi) * rad
92+
93+
def pitch(self):
94+
x,y,z = self.acceleration()
95+
rad = -math.atan2(y, (math.sqrt(x*x + z*z)))
96+
return (180 / math.pi) * rad
97+
98+
def set_full_scale(self, scale):
99+
self.i2c.readfrom_mem_into(ACC_I2CADDR , CTRL4_REG, self.reg)
100+
self.reg[0] &= ~0b00110000
101+
self.reg[0] |= (scale & 3) << 4
102+
self.i2c.writeto_mem(ACC_I2CADDR , CTRL4_REG, self.reg)
103+
self.full_scale = scale
104+
105+
def set_odr(self, odr):
106+
self.i2c.readfrom_mem_into(ACC_I2CADDR , CTRL1_REG, self.reg)
107+
self.reg[0] &= ~0b01110000
108+
self.reg[0] |= (odr & 7) << 4
109+
self.i2c.writeto_mem(ACC_I2CADDR , CTRL1_REG, self.reg)
110+
self.odr = odr
111+
112+
def enable_activity_interrupt(self, threshold, duration, handler=None):
113+
# Threshold is in mg, duration is ms
114+
self.act_dur = duration
115+
116+
_ths = int((threshold * self.scales[self.full_scale]) / 2000 / 128) & 0x7F
117+
_dur = int((duration * self.odrs[self.odr]) / 1000 / 8)
118+
119+
self.i2c.writeto_mem(ACC_I2CADDR , ACT_THS, _ths)
120+
self.i2c.writeto_mem(ACC_I2CADDR , ACT_DUR, _dur)
121+
122+
# enable the activity/inactivity interrupt
123+
self.i2c.readfrom_mem_into(ACC_I2CADDR , CTRL3_REG, self.reg)
124+
self.reg[0] |= 0b00100000
125+
self.i2c.writeto_mem(ACC_I2CADDR , CTRL3_REG, self.reg)
126+
127+
self._user_handler = handler
128+
self.int_pin = Pin('P13', mode=Pin.IN)
129+
self.int_pin.callback(trigger=Pin.IRQ_FALLING | Pin.IRQ_RISING, handler=self._int_handler)
130+
131+
def activity(self):
132+
if not self.debounced:
133+
time.sleep_ms(self.act_dur)
134+
self.debounced = True
135+
if self.int_pin():
136+
return True
137+
return False
138+
139+
def _int_handler(self, pin_o):
140+
if self._user_handler is not None:
141+
self._user_handler(pin_o)
142+
else:
143+
if pin_o():
144+
print('Activity interrupt')
145+
else:
146+
print('Inactivity interrupt')

pytrack-example/lib/pytrack.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
from pycoproc import Pycoproc
2+
3+
__version__ = '1.4.0'
4+
5+
class Pytrack(Pycoproc):
6+
7+
def __init__(self, i2c=None, sda='P22', scl='P21'):
8+
Pycoproc.__init__(self, i2c, sda, scl)

0 commit comments

Comments
 (0)