commit a429c81bcb11b1e7d31601cd573d1f22648aa386 Author: baldeau Date: Sun Feb 23 20:40:38 2025 +0100 initial commit diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..44a8686 --- /dev/null +++ b/.vscode/settings.json @@ -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", +} \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..3dc4f9b --- /dev/null +++ b/Makefile @@ -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 \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..e75a03e --- /dev/null +++ b/README.md @@ -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 +``` diff --git a/flashing_time.py b/flashing_time.py new file mode 100755 index 0000000..7784ed7 --- /dev/null +++ b/flashing_time.py @@ -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() diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..e596203 --- /dev/null +++ b/go.mod @@ -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 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..8d5d409 --- /dev/null +++ b/go.sum @@ -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= diff --git a/main.go b/main.go new file mode 100644 index 0000000..0ebae45 --- /dev/null +++ b/main.go @@ -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()) + } +}