initial commit

This commit is contained in:
baldeau 2025-02-23 20:40:38 +01:00
commit a429c81bcb
7 changed files with 293 additions and 0 deletions

9
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,9 @@
{
"go.toolsEnvVars": {
"GOOS": "linux",
"GOARCH": "arm",
"GOROOT": "/Users/fabian/Library/Caches/tinygo/goroot-a02c847ffd7efbf1e1c759b3cf7d01927ed38f0106b48dbfb04faff6caf9ce40",
"GOFLAGS": "-tags=cortexm,baremetal,linux,arm,nrf52840,nrf,softdevice,s140v7,nrf52840_reset_uf2,xiao_ble,tinygo,purego,osusergo,math_big_pure_go,gc.conservative,scheduler.tasks,serial.usb"
},
"go.buildTags": "softdevice",
}

13
Makefile Normal file
View File

@ -0,0 +1,13 @@
xiao: flash
prepare:
./flashing_time.py
flash:
tinygo flash -target=xiao-ble -size=short
production:
tinygo flash -target=xiao-ble -serial=none -size=short
testing:
tinygo flash -target=xiao-ble -serial=none -size=short -opt=2 -gc=leaking -scheduler=tasks

25
README.md Normal file
View File

@ -0,0 +1,25 @@
# Pet Activity Recorder
This project contains the code for the microcontroller for recording gyro data and sending it to a connected device via Bluetooth.
## Usage
Only connect one microcontroller at the same time!
- Flash the code to the connected microcontroller.
```bash
make flash
```
- Flash the code in production mode (deactivated serial).
```bash
make production
```
- Flash the code in testing mode (enhanced optimisations that need more testing)
```bash
make testing
```

15
flashing_time.py Executable file
View File

@ -0,0 +1,15 @@
#!/usr/bin/env python
from subprocess import run
# requires pySerial:
# python -m pip install pyserial
import serial
# grab the port for the seeed nrf52 (only connect one at a time)
data = run("ls /dev/ | grep 'cu.usbmodem'", capture_output=True, shell=True, text=True)
ser = serial.Serial("/dev/" + data.stdout.strip(), 1200)
# send 16 null characters as reset signal to enter the bootloader
ser.write(b"\x00" * 16)
ser.close()

19
go.mod Normal file
View File

@ -0,0 +1,19 @@
module xiao-pet-tracker
go 1.23.1
require (
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/godbus/dbus/v5 v5.1.0 // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
github.com/saltosystems/winrt-go v0.0.0-20240509164145-4f7860a3bd2b // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/soypat/cyw43439 v0.0.0-20240609122733-da9153086796 // indirect
github.com/soypat/seqs v0.0.0-20240527012110-1201bab640ef // indirect
github.com/tinygo-org/cbgo v0.0.4 // indirect
github.com/tinygo-org/pio v0.0.0-20231216154340-cd888eb58899 // indirect
golang.org/x/exp v0.0.0-20230728194245-b0cb94b80691 // indirect
golang.org/x/sys v0.11.0 // indirect
tinygo.org/x/bluetooth v0.10.0 // indirect
tinygo.org/x/drivers v0.28.0 // indirect
)

39
go.sum Normal file
View File

@ -0,0 +1,39 @@
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/saltosystems/winrt-go v0.0.0-20240509164145-4f7860a3bd2b h1:du3zG5fd8snsFN6RBoLA7fpaYV9ZQIsyH9snlk2Zvik=
github.com/saltosystems/winrt-go v0.0.0-20240509164145-4f7860a3bd2b/go.mod h1:CIltaIm7qaANUIvzr0Vmz71lmQMAIbGJ7cvgzX7FMfA=
github.com/sirupsen/logrus v1.5.0/go.mod h1:+F7Ogzej0PZc/94MaYx/nvG9jOFMD2osvC3s+Squfpo=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/soypat/cyw43439 v0.0.0-20240609122733-da9153086796 h1:1/r2URInjjFtWqT61gU7YGVCq3BRyXt/C7z4oLRF9Lo=
github.com/soypat/cyw43439 v0.0.0-20240609122733-da9153086796/go.mod h1:1Otjk6PRhfzfcVHeWMEeku/VntFqWghUwuSQyivb2vE=
github.com/soypat/seqs v0.0.0-20240527012110-1201bab640ef h1:phH95I9wANjTYw6bSYLZDQfNvao+HqYDom8owbNa0P4=
github.com/soypat/seqs v0.0.0-20240527012110-1201bab640ef/go.mod h1:oCVCNGCHMKoBj97Zp9znLbQ1nHxpkmOY9X+UAGzOxc8=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/tinygo-org/cbgo v0.0.4 h1:3D76CRYbH03Rudi8sEgs/YO0x3JIMdyq8jlQtk/44fU=
github.com/tinygo-org/cbgo v0.0.4/go.mod h1:7+HgWIHd4nbAz0ESjGlJ1/v9LDU1Ox8MGzP9mah/fLk=
github.com/tinygo-org/pio v0.0.0-20231216154340-cd888eb58899 h1:/DyaXDEWMqoVUVEJVJIlNk1bXTbFs8s3Q4GdPInSKTQ=
github.com/tinygo-org/pio v0.0.0-20231216154340-cd888eb58899/go.mod h1:LU7Dw00NJ+N86QkeTGjMLNkYcEYMor6wTDpTCu0EaH8=
golang.org/x/exp v0.0.0-20230728194245-b0cb94b80691 h1:/yRP+0AN7mf5DkD3BAI6TOFnd51gEoDEb8o35jIFtgw=
golang.org/x/exp v0.0.0-20230728194245-b0cb94b80691/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
tinygo.org/x/bluetooth v0.10.0 h1:42n8qj2tuF5AfdbAUR2Nv45EhtVmbDFH6UoWnt6lzZQ=
tinygo.org/x/bluetooth v0.10.0/go.mod h1:t/Vm2a/rslsBoqFQKCBsWQw/cmRicQq+8Tl3tj5RCRI=
tinygo.org/x/drivers v0.28.0 h1:ROVrGGXddmpn2+oV/Bu3LceYbtPCJckmgIqvPcN/L0k=
tinygo.org/x/drivers v0.28.0/go.mod h1:T6snsUqS0RAxOANxiV81fQwLxDDNmprxTAYzmxoA7J0=

173
main.go Normal file
View File

@ -0,0 +1,173 @@
package main
import (
"encoding/binary"
"fmt"
"machine"
"time"
"tinygo.org/x/bluetooth"
"tinygo.org/x/drivers/lsm6ds3tr"
)
var adapter = bluetooth.DefaultAdapter
var isBleConnected bool = false
var isCapturing bool = false
var (
LSM6DS3TRService = [16]byte{0x4C, 0x53, 0x4D, 0x36, 0x44, 0x53, 0x33, 0x54, 0x52, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65}
accelerationData = [16]byte{0x61, 0x63, 0x63, 0x65, 0x6C, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6F, 0x6E, 0x44, 0x61, 0x74, 0x61}
unixTimeStampRst = [16]byte{0x75, 0x6E, 0x69, 0x78, 0x54, 0x69, 0x6D, 0x65, 0x53, 0x74, 0x61, 0x6D, 0x70, 0x52, 0x73, 0x74}
capturingService = [16]byte{0x63, 0x61, 0x70, 0x74, 0x75, 0x72, 0x69, 0x6E, 0x67, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65}
//tempSenseService = [16]byte{0x74, 0x65, 0x6D, 0x70, 0x53, 0x65, 0x6E, 0x73, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65}
//temperatureSense = [16]byte{0x74, 0x65, 0x6D, 0x70, 0x65, 0x72, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x65, 0x6E, 0x73, 0x65}
)
var currentTimeStamp time.Time = time.Now()
var lastTimeStamp time.Time = time.Now()
const sleepDuration time.Duration = time.Millisecond * 100
func main() {
// Configure LSM6DS3TR
machine.I2C0.Configure(machine.I2CConfig{})
accel := lsm6ds3tr.New(machine.I2C0)
err := accel.Configure(lsm6ds3tr.Configuration{})
if err != nil {
for {
println("Failed to configure", err.Error())
time.Sleep(time.Second)
}
}
// Configure Bluetooth
must("enable BLE stack", adapter.Enable())
adv := adapter.DefaultAdvertisement()
must("config adv", adv.Configure(bluetooth.AdvertisementOptions{
LocalName: "Go Bluetooth",
ManufacturerData: []bluetooth.ManufacturerDataElement{
// 0xFFFF: Special Use/Default ID
// Bluetooth Company Identifiers:
// https://gist.github.com/angorb/f92f76108b98bb0d81c74f60671e9c67
{CompanyID: 0xffff, Data: []byte{0x01, 0x02}},
},
}))
adapter.SetConnectHandler(func(device bluetooth.Device, connected bool) {
if connected {
isBleConnected = true
adv.Stop()
} else {
isBleConnected = false
isCapturing = false
adv.Start()
}
})
//
// Start Bluetooth advertisment
must("start adv", adv.Start())
var senseCharacteristic bluetooth.Characteristic
must("add sense service", adapter.AddService(&bluetooth.Service{
UUID: bluetooth.NewUUID(LSM6DS3TRService),
Characteristics: []bluetooth.CharacteristicConfig{
{
Handle: &senseCharacteristic,
UUID: bluetooth.NewUUID(accelerationData),
// can only send a max amount of 20 bytes in one packet
//Value: []byte{},
Flags: bluetooth.CharacteristicNotifyPermission | bluetooth.CharacteristicReadPermission | bluetooth.CharacteristicWritePermission | bluetooth.CharacteristicWriteWithoutResponsePermission,
},
{
UUID: bluetooth.NewUUID(unixTimeStampRst),
Flags: bluetooth.CharacteristicNotifyPermission | bluetooth.CharacteristicReadPermission | bluetooth.CharacteristicWritePermission | bluetooth.CharacteristicWriteWithoutResponsePermission,
WriteEvent: func(client bluetooth.Connection, offset int, value []byte) {
if len(value) != 8 {
return
}
millisFromEpoch := binary.BigEndian.Uint64(value)
currentTimeStamp = time.Unix(0, int64(millisFromEpoch)*int64(time.Millisecond))
},
},
{
UUID: bluetooth.NewUUID(capturingService),
Flags: bluetooth.CharacteristicNotifyPermission | bluetooth.CharacteristicReadPermission | bluetooth.CharacteristicWritePermission | bluetooth.CharacteristicWriteWithoutResponsePermission,
WriteEvent: func(client bluetooth.Connection, offset int, value []byte) {
if len(value) != 1 {
return
}
if value[0] == 1 {
isCapturing = true
} else {
isCapturing = false
}
},
},
},
}))
// var tempCharacteristic bluetooth.Characteristic
// must("add temperature service", adapter.AddService(&bluetooth.Service{
// UUID: bluetooth.NewUUID(tempSenseService),
// Characteristics: []bluetooth.CharacteristicConfig{
// {
// Handle: &tempCharacteristic,
// UUID: bluetooth.NewUUID(temperatureSense),
// Value: []byte(tempData),
// Flags: bluetooth.CharacteristicNotifyPermission | bluetooth.CharacteristicReadPermission,
// },
// },
// }))
// Main Loop
for {
// Only read and update sensor data
// with an active bluetooth connection
// and when `isCapturing` is set to true
if isBleConnected && isCapturing {
X, Y, Z, _ := accel.ReadRotation()
x, y, z, _ := accel.ReadAcceleration()
arrRot := valuesToByteArray(X, Y, Z, int8(1))
arrAcc := valuesToByteArray(x, y, z, int8(2))
currentTimeStamp = currentTimeStamp.Add(time.Now().Sub(lastTimeStamp))
fmt.Println("TIME: ", time.Now().Sub(lastTimeStamp))
arrTime := timeStampToByteArray(currentTimeStamp.UnixMilli(), int8(3))
senseCharacteristic.Write(arrRot)
senseCharacteristic.Write(arrAcc)
senseCharacteristic.Write(arrTime)
}
if isCapturing {
lastTimeStamp = time.Now()
}
time.Sleep(sleepDuration)
}
}
func valuesToByteArray(x int32, y int32, z int32, p int8) []byte {
byteSlice := make([]byte, 13)
binary.LittleEndian.PutUint32(byteSlice, uint32(x))
binary.LittleEndian.PutUint32(byteSlice[4:], uint32(y))
binary.LittleEndian.PutUint32(byteSlice[8:], uint32(z))
byteSlice[12] = byte(p)
return byteSlice
}
func timeStampToByteArray(value int64, p int8) []byte {
byteSlice := make([]byte, 9)
binary.LittleEndian.PutUint64(byteSlice, uint64(value))
byteSlice[8] = byte(p)
return byteSlice
}
func must(action string, err error) {
if err != nil {
panic("failed to " + action + ": " + err.Error())
}
}