Created
December 27, 2021 03:50
-
-
Save standarddeviant/f0879b15a7f5ff8bd3795dba802d4ab3 to your computer and use it in GitHub Desktop.
BLE IMU Tx/Rx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<html> | |
<head> | |
<title>BLE IMU Receiver</title> | |
</head> | |
<body style="font-family:'Inconsolata', sans-serif;"> | |
<div style="text-align: center;"> | |
<h1>BLE IMU Receiver</h1> | |
Web-Bluetooth requires user interaction to scan + connect. Click the button below.<br> | |
<button id="scan-button" style="width:200; height:50;"> | |
<h3>Scan + Connect</h3> | |
</button> | |
</div> | |
<div id="chart_time" style="min-height: 500px; width:100%;"></div> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/echarts/5.2.2/echarts.min.js" integrity="sha512-ivdGNkeO+FTZH5ZoVC4gS4ovGSiWc+6v60/hvHkccaMN2BXchfKdvEZtviy5L4xSpF8NPsfS0EVNSGf+EsUdxA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/msgpack5/5.3.2/msgpack5.min.js"></script> | |
<script> | |
var data_vecs_time = [ [], [], [], [], [], [] ]; | |
let tmpnow = new Date(); | |
var data_vecs_time_last_update = tmpnow.getTime()/1000.0; | |
const DATA_TIME_MAX_SECONDS = 30; | |
const CHART_UPDATE_EVERY_SECONDS = 0.5; | |
const xyz_colors = ["#1b9e77", "#d95f02", "#7570b3", "#1b9e77", "#d95f02", "#7570b3"] | |
sensor_chart_time = echarts.init(document.getElementById('chart_time')) | |
// const tixrange = [0, 1, 2, 3, 4, 5]; | |
// const tylabels = ['X', 'Y', 'Z', 'X', 'Y', 'Z']; | |
const tixrange = [0, 1, 2]; | |
const tylabels = ['X', 'Y', 'Z']; | |
let echarts_time_opts = { | |
grid: [ | |
{left: '15%', top: '7%', width: '70%', height: '22%'}, | |
{left: '15%', top: '40%', width: '70%', height: '22%'}, | |
{left: '15%', bottom: '7%', width: '70%', height: '22%'}, | |
// {left: '7%', top: '7%', width: '38%', height: '22%'}, | |
// {left: '7%', top: '40%', width: '38%', height: '22%'}, | |
// {left: '7%', bottom: '7%', width: '38%', height: '22%'}, | |
// {right: '7%', top: '7%', width: '38%', height: '22%'}, | |
// {right: '7%', top: '40%', width: '38%', height: '22%'}, | |
// {right: '7%', bottom: '7%', width: '38%', height: '22%'}, | |
], | |
title: [ | |
{left: '20%', top: '0%', text: 'Accel [m/s^2]'}, | |
// {right: '20%', top: '0%', text: 'Gyro [deg/s]' }, | |
], | |
xAxis: tixrange.map((ix) => { | |
return { | |
gridIndex: ix, | |
animation: false, | |
boundaryGap: false, | |
type: 'value', | |
min: 'dataMin', | |
max: 'dataMax', | |
scale: true, | |
axisLabel: { | |
show: true, | |
minInterval: 5, | |
maxInterval: 10, | |
rotate: 45, | |
formatter: ((value) => { | |
dt = new Date(1000 * value); | |
var minutes = dt.getMinutes(); | |
var seconds = dt.getSeconds(); | |
var label = String(minutes) + ':'; | |
// console.log(dt); | |
if(seconds < 10) { | |
label += '0'; | |
} | |
label += String(seconds); | |
// console.log(label); | |
return label; | |
}) | |
} | |
} | |
}), | |
yAxis: tixrange.map((ix) => { | |
return { | |
gridIndex: ix, | |
type: 'value', | |
scale: true, | |
minInterval: 0.01, | |
name: tylabels[ix], | |
nameLocation: 'middle', | |
nameGap: 30, | |
} | |
}), | |
series: tixrange.map((ix) => { | |
return { | |
data: data_vecs_time[ix], | |
xAxisIndex: ix, | |
yAxisIndex: ix, | |
type: 'line', | |
lineStyle: { | |
color: xyz_colors[ix] | |
}, | |
symbol: 'none', | |
animation: false, | |
// animationDuration: 100, | |
hoverAnimation: false | |
} | |
}) | |
}; | |
console.log(echarts_time_opts); | |
sensor_chart_time.setOption(echarts_time_opts) | |
window.addEventListener('resize', function(){ | |
if(sensor_chart_time != null && sensor_chart_time != undefined){ | |
sensor_chart_time.resize(); | |
} | |
}); | |
let SVC_UUID = '05C53569-5E1A-4624-9623-2457010A2413'.toLowerCase(); | |
let RX_UUID = '05C53569-5E1B-4624-9623-2457010A2413'.toLowerCase(); | |
let TX_UUID = '05C53569-5E1C-4624-9623-2457010A2413'.toLowerCase(); | |
var msgpack = msgpack5(); | |
var bluetoothDevice; | |
var notifyCharac; | |
async function onButtonClick() { | |
console.log(SVC_UUID) | |
console.log(RX_UUID) | |
console.log(TX_UUID) | |
try { | |
if (!bluetoothDevice) { | |
await requestDevice(); | |
} | |
await connectDeviceAndCacheCharacteristics(); | |
console.log('Waiting for notifications...'); | |
//await batteryLevelCharacteristic.readValue(); | |
} catch(error) { | |
console.log('Argh! ' + error); | |
} | |
} | |
document.querySelector('#scan-button').addEventListener('click', function() { | |
//if (isWebBluetoothEnabled()) { | |
onButtonClick(); | |
//} | |
}); | |
async function requestDevice() { | |
console.log('Requesting any Bluetooth Device...'); | |
bluetoothDevice = await navigator.bluetooth.requestDevice({ | |
// filters: [...] <- Prefer filters to save energy & show relevant devices. | |
acceptAllDevices: true, | |
optionalServices: [SVC_UUID] | |
//filters: [ { services: [SVC_UUID] } ] | |
}); | |
bluetoothDevice.addEventListener('gattserverdisconnected', onDisconnected); | |
} | |
async function connectDeviceAndCacheCharacteristics() { | |
if (bluetoothDevice.gatt.connected && notifyCharac) { | |
return; | |
} | |
console.log('Connecting to GATT Server...'); | |
const server = await bluetoothDevice.gatt.connect(); | |
console.log('Getting Custom Service...'); | |
const service = await server.getPrimaryService(SVC_UUID); | |
console.log('Getting Battery Level Characteristic...'); | |
notifyCharac = await service.getCharacteristic(TX_UUID); | |
console.log('Starting Custom Notifications...'); | |
notifyCharac.addEventListener('characteristicvaluechanged', handleNotification); | |
await notifyCharac.startNotifications(); | |
//document.querySelector('#startNotifications').disabled = false; | |
//document.querySelector('#stopNotifications').disabled = true; | |
} | |
/* This function will be called when `readValue` resolves and | |
* characteristic value changes since `characteristicvaluechanged` event | |
* listener has been added. */ | |
function handleNotification(event) { | |
let now = new Date(); | |
let data = event.target.value; | |
// console.log(data) | |
let acc_xyz = msgpack.decode(data); | |
// let dvecs = data_vecs_time; | |
// update echarts chart, we manually remove points from view, | |
// but we have total control | |
for(ix=0; ix<3; ix++) { | |
let dvec = data_vecs_time[ix]; | |
dvec.push([now.getTime()/1000.0, acc_xyz[ix]]); | |
// TODO - find a more efficient way to trim old values from data | |
while(dvec.length >= 2) { | |
let time_span = Math.abs(dvec[dvec.length - 1][0] - dvec[0][0]) | |
// console.log(time_span); | |
if(time_span <= DATA_TIME_MAX_SECONDS) { | |
break; | |
} | |
dvec.shift(); | |
} | |
//console.log(dvec); | |
//console.log({time: Math.abs(now - last_update), thresh: ECHARTS_UPDATE_EVERY}); | |
} | |
now = data_vecs_time[0][data_vecs_time[0].length - 1][0]; | |
// console.log(`now is ${now}`); | |
// console.log(`data_vecs_time_last_update is ${data_vecs_time_last_update}`); | |
// console.log(`tdiff = ${Math.abs(now - data_vecs_time_last_update)}`) | |
if(Math.abs(now - data_vecs_time_last_update) >= CHART_UPDATE_EVERY_SECONDS) { | |
// console.log('ya?') | |
let fake_tixrange = [0, 1, 2] | |
data_vecs_time_last_update = now; | |
sensor_chart_time.setOption( | |
{ | |
series : fake_tixrange.map((ix) => { | |
return { | |
data: data_vecs_time[ix] | |
} | |
}) | |
} | |
); | |
} | |
} | |
async function onStartNotificationsButtonClick() { | |
try { | |
log('Starting Custom Notifications...'); | |
await notifyCharac.startNotifications(); | |
console.log('> Notifications started'); | |
//document.querySelector('#startNotifications').disabled = true; | |
//document.querySelector('#stopNotifications').disabled = false; | |
} catch(error) { | |
console.log('Argh! ' + error); | |
} | |
} | |
async function onStopNotificationsButtonClick() { | |
try { | |
log('Stopping Battery Level Notifications...'); | |
await batteryLevelCharacteristic.stopNotifications(); | |
log('> Notifications stopped'); | |
document.querySelector('#startNotifications').disabled = false; | |
document.querySelector('#stopNotifications').disabled = true; | |
} catch(error) { | |
log('Argh! ' + error); | |
} | |
} | |
function onResetButtonClick() { | |
if (batteryLevelCharacteristic) { | |
batteryLevelCharacteristic.removeEventListener('characteristicvaluechanged', | |
handleBatteryLevelChanged); | |
batteryLevelCharacteristic = null; | |
} | |
// Note that it doesn't disconnect device. | |
bluetoothDevice = null; | |
log('> Bluetooth Device reset'); | |
} | |
async function onDisconnected() { | |
log('> Bluetooth Device disconnected'); | |
try { | |
await connectDeviceAndCacheCharacteristics() | |
} catch(error) { | |
log('Argh! ' + error); | |
} | |
} | |
</script> | |
</body> | |
</html> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# NOTE: in main.py , put the following | |
# from ble_imu_transmitter import BLE_IMU_Transmitter | |
# ble_imu = BLE_IMU_Transmitter('m5atm') | |
from machine import Pin, Timer, SoftI2C | |
from mpu6886 import MPU6886 | |
from neopixel import NeoPixel | |
import ubluetooth | |
import umsgpack | |
def f2u8(x, shift=9.8, scale=9.8*2): | |
_scale = 255 / scale | |
o = int( (x+shift) * _scale ) | |
if o > 30: | |
return 30 | |
elif o < 0: | |
return 0 | |
return o | |
def a2u8(xyz): | |
return f2u8(xyz, shift=9.8, scale=9.8*64) | |
def a2rgb(a): | |
x,y,z = a | |
r,g,b = a2u8(x), a2u8(y), a2u8(z) | |
return r,g,b | |
def update_leds_a(a): | |
x,y,z = a | |
r,g,b = a2rgb(a) | |
for ix in range(NUM_LEDS): | |
np[ix] = (r, g, b) | |
np.write() | |
NUM_LEDS = 25 | |
class BLE_IMU_Transmitter(): | |
def __init__(self, name): | |
self.i2c = SoftI2C(scl=Pin(21), sda=Pin(25)) | |
print(self.i2c) | |
self.sensor = MPU6886(self.i2c) | |
print("MPU6886 id: " + hex(self.sensor.whoami)) | |
self.init_neopixel() | |
self.led_on = False | |
self.ui_timer = Timer(3) | |
self.sensor_timer = Timer(2) | |
self.name = name | |
self.ble = ubluetooth.BLE() | |
self.ble.active(True) | |
self.disconnected() | |
self.ble.irq(self.ble_irq) | |
self.register() | |
self.advertiser() | |
def init_neopixel(self): | |
self.np_pin = Pin(27, Pin.OUT) | |
self.np = NeoPixel(self.np_pin, NUM_LEDS) | |
for ix in range(NUM_LEDS): | |
self.np[ix] = (0,0,0) | |
self.np.write() | |
def connected(self): | |
# TODO - dry out connected + disconnected code | |
self.ui_timer.deinit() | |
def cn_cb(t): | |
bval = 10 * int(self.led_on) | |
for ix in range(NUM_LEDS): | |
self.np[ix] = (0, 0, bval) | |
self.np.write() | |
self.led_on = not self.led_on | |
self.ui_timer.init(period=1000, mode=Timer.PERIODIC, callback=cn_cb) | |
self.sensor_timer.deinit() | |
def sensor_cb(t): | |
acc_xyz = self.sensor.acceleration | |
mp_buf_acc_xyz = umsgpack.dumps(acc_xyz) | |
self.send(mp_buf_acc_xyz) | |
self.sensor_timer.init(period=200, mode=Timer.PERIODIC, callback=sensor_cb) | |
def disconnected(self): | |
# TODO - dry out connected + disconnected code | |
self.sensor_timer.deinit() | |
self.ui_timer.deinit() | |
def dc_cb(t): | |
rval = 10 * int(self.led_on) | |
for ix in range(NUM_LEDS): | |
self.np[ix] = (rval, 0, 0) | |
self.np.write() | |
self.led_on = not self.led_on | |
self.ui_timer.init(period=1000, mode=Timer.PERIODIC, callback=dc_cb) | |
def ble_irq(self, event, data): | |
global message | |
if event == 1: #_IRQ_CENTRAL_CONNECT: | |
# A central has connected to this peripheral | |
self.connected() | |
elif event == 2: #_IRQ_CENTRAL_DISCONNECT: | |
# A central has disconnected from this peripheral. | |
self.advertiser() | |
self.disconnected() | |
elif event == 3: #_IRQ_GATTS_WRITE: | |
# A client has written to this characteristic or descriptor. | |
buffer = self.ble.gatts_read(self.rx) | |
message = buffer.decode('UTF-8').strip() | |
print(message) | |
def register(self): | |
# Custom Service | |
SVC_UUID = '05C53569-5E1A-4624-9623-2457010A2413' | |
RX_UUID = '05C53569-5E1B-4624-9623-2457010A2413' | |
TX_UUID = '05C53569-5E1C-4624-9623-2457010A2413' | |
BLE_SVC = ubluetooth.UUID(SVC_UUID) | |
BLE_RX = (ubluetooth.UUID(RX_UUID), ubluetooth.FLAG_WRITE) | |
BLE_TX = (ubluetooth.UUID(TX_UUID), ubluetooth.FLAG_NOTIFY) | |
BLE_PRO = (BLE_SVC, (BLE_TX, BLE_RX,)) | |
SERVICES = (BLE_PRO, ) | |
((self.tx, self.rx,), ) = self.ble.gatts_register_services(SERVICES) | |
def send(self, data): | |
self.ble.gatts_notify(0, self.tx, data + '\n') | |
def advertiser(self): | |
name = bytes(self.name, 'UTF-8') | |
adv_data = bytearray('\x02\x01\x02') + bytearray((len(name) + 1, 0x09)) + name | |
adv_data += bytearray('\x11\x07') | |
adv_data += bytearray('\x13\x24\x0A\x01\x57\x24\x23\x96\x24\x46\x1A\x5E\x69\x35\xC5\x05') | |
self.ble.gap_advertise(100, adv_data) | |
print(adv_data) | |
print("\r\n") | |
# adv_data | |
# raw: 0x02010209094553503332424C45 | |
# b'\x02\x01\x02\t\tESP32BLE' | |
# | |
# 0x02 - General discoverable mode | |
# 0x01 - AD Type = 0x01 | |
# 0x02 - value = 0x02 | |
# https://jimmywongiot.com/2019/08/13/advertising-payload-format-on-ble/ | |
# https://docs.silabs.com/bluetooth/latest/general/adv-and-scanning/bluetooth-adv-data-basics | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment