Skip to content

Commit 77e0f03

Browse files
authored
Merge pull request #60 from manchoz/main
Add the DataHarvester sketch and companion py app
2 parents bb0133b + a9d8405 commit 77e0f03

File tree

3 files changed

+269
-0
lines changed

3 files changed

+269
-0
lines changed
+117
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
/*
2+
This sketch shows how the use the Nicla Sense ME to collect
3+
acceleration data at maximum speed, and how to send them to
4+
a companion app via UART.
5+
6+
It uses a COBS-based, packed-oriented serial protocol to establish
7+
a reliable communication to the companion app, to receive operation
8+
commands and to send collected data.
9+
10+
Requirements:
11+
- Nicla Sense ME
12+
- PacketSerial library https://github.com/bakercp/PacketSerial
13+
- Commander.py companion app
14+
*/
15+
16+
#include <Nicla_System.h>
17+
#include <Arduino_BHY2.h>
18+
19+
// Manage the communication with the companion app
20+
#include <PacketSerial.h>
21+
PacketSerial myPacketSerial;
22+
23+
auto capture { false };
24+
25+
// Send data as packed struct, ie. as the raw bytes
26+
struct __attribute__((__packed__)) Data {
27+
float ts;
28+
float x;
29+
float y;
30+
float z;
31+
};
32+
33+
SensorXYZ accel(SENSOR_ID_ACC_PASS);
34+
35+
void setup()
36+
{
37+
nicla::begin();
38+
nicla::leds.begin();
39+
BHY2.begin();
40+
accel.begin();
41+
42+
// Init the PacketSerial communication
43+
myPacketSerial.begin(115200);
44+
// Set the function for handling commands from the companion app
45+
myPacketSerial.setPacketHandler(&onPacketReceived);
46+
47+
pinMode(LED_BUILTIN, OUTPUT);
48+
for (auto i = 0u; i < 10; i++) {
49+
nicla::leds.setColor(green);
50+
delay(25);
51+
nicla::leds.setColor(off);
52+
delay(25);
53+
}
54+
}
55+
56+
void loop()
57+
{
58+
// Update communication-channel and sensors
59+
myPacketSerial.update();
60+
BHY2.update();
61+
62+
// Check for a receive buffer overflow (optional).
63+
if (myPacketSerial.overflow()) {
64+
for (auto i = 0u; i < 5; i++) {
65+
nicla::leds.setColor(green);
66+
delay(25);
67+
nicla::leds.setColor(off);
68+
delay(25);
69+
}
70+
}
71+
72+
// Capture and send data as soon as we read it
73+
if (capture == true) {
74+
auto now = micros() / 1000.0;
75+
76+
// Collect data from accel sensor
77+
Data data { now, accel.x(), accel.y(), accel.z() };
78+
constexpr size_t dataBufLen { sizeof(Data) };
79+
uint8_t dataBuf[dataBufLen] {};
80+
81+
// Convert the Data struct to an array of bytes
82+
memcpy(dataBuf, reinterpret_cast<void*>(&data), dataBufLen);
83+
84+
// Send data
85+
myPacketSerial.send(dataBuf, dataBufLen);
86+
}
87+
}
88+
89+
90+
// Parse commands from the companion app
91+
void onPacketReceived(const uint8_t* buffer, size_t size)
92+
{
93+
uint8_t tempBuffer[size];
94+
95+
for (auto i = 0u; i < 2; i++) {
96+
nicla::leds.setColor(green);
97+
delay(25);
98+
nicla::leds.setColor(off);
99+
delay(25);
100+
}
101+
102+
memcpy(tempBuffer, buffer, size);
103+
104+
switch (tempBuffer[0]) {
105+
case 'R':
106+
nicla::leds.setColor(green);
107+
capture = true;
108+
break;
109+
case 'S':
110+
nicla::leds.setColor(off);
111+
capture = false;
112+
break;
113+
114+
default:
115+
break;
116+
}
117+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
#!/usr/bin/env python3
2+
#
3+
# This scripts is the companion application for the DataHarvester sketch.
4+
# It is a very basic application and can be improved and extended.
5+
#
6+
# It waits for user input to control the data acquisition operations and the
7+
# connection to the Arduino board and to save the received data to a CSV file.
8+
#
9+
# It establish a serial communication to the board and uses COBS as enconding and
10+
# reliable transport protocol.
11+
#
12+
# The main thread will always wait for user input while a second one is created
13+
# to exchange both control and data with the Arduino board.
14+
# The two threads coordinate via a simple Queue.
15+
16+
# Thread and Queue
17+
from threading import Thread
18+
import queue
19+
20+
# Helpers
21+
from datetime import date, datetime
22+
import struct
23+
24+
# User input
25+
import readline
26+
import argparse
27+
28+
# Communication with the Arduino board
29+
import serial
30+
from cobs import cobs
31+
32+
33+
# The main serial object
34+
# Initialise later
35+
ser = serial.Serial()
36+
37+
# The function to be run in the Arduino-facing thread
38+
def receiver(cmd_q, uart, name, tag):
39+
f = None
40+
running = False
41+
cmd = 'N'
42+
43+
while True:
44+
# Check for commands from the main thread
45+
try:
46+
cmd = cmd_q.get(block=False)
47+
except queue.Empty:
48+
pass
49+
50+
# Start acquisition, prepare output file, etc.
51+
if cmd == 'R':
52+
running = True
53+
today = datetime.today()
54+
iso = datetime.isoformat(today)
55+
filename = f'{name}_{tag}_{iso}.csv'
56+
f = open(filename, 'wt')
57+
print(f'timestamp,accX,accY,accZ', file=f)
58+
cmd = 'N'
59+
# Stop acquisition
60+
elif cmd == 'S':
61+
running = False
62+
if f != None:
63+
f.close()
64+
cmd = 'N'
65+
# Close connection
66+
elif cmd == 'C':
67+
running = False
68+
# Quit program
69+
elif cmd == 'Q':
70+
running = False
71+
if f != None:
72+
if not f.closed:
73+
f.close()
74+
break
75+
76+
# Receive data packet-by-packet and save to file
77+
if running:
78+
if uart.is_open:
79+
# Read full COBS packet
80+
data = uart.read_until(b'\x00')
81+
n = len(data)
82+
if n > 0:
83+
# Skip last byte
84+
decoded = cobs.decode(data[0:(n - 1)])
85+
# Unpack the binary data
86+
ts, x, y, z = struct.unpack('ffff', decoded)
87+
# Create CSV line and print
88+
print(f'{ts},{x},{y},{z}', file=f)
89+
f.flush()
90+
91+
92+
# The main thread
93+
if __name__ == '__main__':
94+
95+
# Parse arugments
96+
# - port == Arduino Serial Port
97+
# - name == Filename
98+
# - tag == Custom tag to append to filename
99+
parser = argparse.ArgumentParser()
100+
parser.add_argument('port')
101+
parser.add_argument('name')
102+
parser.add_argument('tag')
103+
args = parser.parse_args()
104+
105+
ser.port = args.port
106+
ser.baudrate = 115200
107+
108+
ser.close()
109+
ser.open()
110+
111+
# Inter-thread communication queue
112+
q = queue.Queue()
113+
114+
# Arduino-facing thread
115+
recv_thread = Thread(target=receiver, args=(q, ser, args.name, args.tag))
116+
recv_thread.start()
117+
118+
# Wait for user input
119+
while True:
120+
s = input("> ").upper()
121+
122+
# Send commands to receiver thread
123+
if s == 'S':
124+
print('Stopping...')
125+
q.put('S')
126+
elif s == 'R':
127+
print('Running...')
128+
q.put('R')
129+
elif s == 'C':
130+
print('Closing...')
131+
q.put('C')
132+
if ser.is_open:
133+
ser.close()
134+
elif s == 'O':
135+
print('Opening...')
136+
if not ser.is_open:
137+
ser.open()
138+
elif s == 'Q':
139+
print('Quitting...')
140+
q.put('Q')
141+
recv_thread.join()
142+
if ser.is_open:
143+
ser.close()
144+
break
145+
146+
# Send commands to the Arduino board
147+
if ser.is_open:
148+
enc = cobs.encode(s.encode())
149+
ser.write(enc)
150+
ser.write(b'\x00')
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
pyserial
2+
cobs

0 commit comments

Comments
 (0)