Compare commits

...

36 Commits

Author SHA1 Message Date
unlogisch04
f46d20f5cb add Serial Buffer to limit the data written in 1 cycle 2026-03-23 19:57:46 +01:00
dependabot[bot]
939ad705ce Bump actions/upload-artifact from 6 to 7 (#517)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 6 to 7.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-02 15:01:06 +03:00
Sapphire
7d800efdbf Don't set m_LastPacketTimestamp on handshake when we are connected (#513)
If another tracker is trying to find the server, they will send the
handshake packet to the whole network, which prevents us from realising
if the connection to server has timed out.
2026-01-25 07:07:30 +02:00
Butterscotch!
12f4b22dac Revert "Unfuck accelerometer" (#480)
* Revert "Unfuck accelerometer"

This reverts commit 0425f66561.

* Bump protocol version
2026-01-07 08:56:52 +03:00
dependabot[bot]
6cd29c7cea Bump actions/checkout from 5 to 6 (#497)
Bumps [actions/checkout](https://github.com/actions/checkout) from 5 to 6.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-15 11:09:31 +03:00
dependabot[bot]
33dc84c00b Bump actions/cache from 4 to 5 (#504)
Bumps [actions/cache](https://github.com/actions/cache) from 4 to 5.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-15 11:08:07 +03:00
dependabot[bot]
37658155c7 Bump actions/upload-artifact from 5 to 6 (#503)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 5 to 6.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-15 11:07:54 +03:00
unlogisch04
c12c475fe0 Fix applicationoffset for the firmware flasher (#502)
fix flashing offset for esp32, esp32-c3 and esp32-c6
2025-12-08 14:18:46 +02:00
unlogisch04
a6aefdfe60 check if sensortoggle is populated (#501)
Not sure if it will find any edge case.  The use of .getValues() should be programmed in a different way.
2025-12-04 18:36:01 -05:00
unlogisch04
8469b6fac6 fix icm42688 (#493)
* fix_icm42688 test fullscale
Still issues with snapping

* fix temp scale, adjust buffer

* change to lowres

* fix semikolon

* Apply suggestions

Co-Authored-By: gorbit99 <5338164+gorbit99@users.noreply.github.com>

* formating

---------

Co-authored-by: gorbit99 <5338164+gorbit99@users.noreply.github.com>
2025-12-02 21:11:35 +02:00
unlogisch04
fcd49515e1 Remove 100ms blocking blink (#500)
I don't see a need for it during runtime calibration.
2025-12-02 21:10:35 +02:00
Meia
e6af00b161 Fix LSM* Accelerometer Sensitivity (#499) 2025-11-28 02:53:20 +03:00
unlogisch04
cc42ad0aa9 fix no broadcast after disconnect (#491)
fix no broadcast after disconnect After a disconnect the trackers did not send a broadcast to discover the server but did directly try to send it to the server. So if for some reason the server did change its ip address the tracker had to be rebooted.
2025-11-27 20:29:01 +02:00
gorbit99
afd376c427 Fix sampling rate miscalibration problem (#494) 2025-11-27 20:28:26 +02:00
unlogisch04
72ce713079 fix bmi270 missing in flasher (#496) 2025-11-27 20:25:54 +02:00
unlogisch04
060df4baec fix_uploadspeed for platformio (#492)
fix_uploadspeed
2025-11-27 20:25:40 +02:00
unlogisch04
2a66d12031 Move heavy string operation for static into compiler time (#495)
move heavy string operation for static into compiler time
2025-11-22 10:26:05 +02:00
gorbit99
79d2796039 Fix sensor toggles causing a crash on 0.7.0 (#487)
* Fix sensor toggles causing a crash on 0.7.0

* Formatting
2025-11-07 10:30:13 +03:00
unlogisch04
c84e898d1e fix build on python <v3.12 (#484)
shiped by platformio by default on windows
2025-11-05 22:01:43 +03:00
lucas lelievre
b6cec9cc10 Preprocessor: Fix imu adress (#486)
* Fix imu adress being wrong on extensions + fix optional tracker value to be flipped

* Set max imus back to two
2025-11-05 22:01:22 +03:00
lucas lelievre
2970f4e38d Preprocessor: prevent shell injections (#485) 2025-11-04 05:13:58 +03:00
lucas lelievre
003128c3b6 Add full support for sensor list in preprocessor (#483)
* Add support for sensor list in preprocessor

* Fix quotes
2025-11-04 03:26:52 +03:00
lucas lelievre
91b6318a8a Make git_commit script use firmware version env for fw tool (#482)
Make git_commit script use firmware verion env for fw tool when built from source
2025-11-04 03:25:46 +03:00
lucas lelievre
a17c1c2d3f Refactor board defaults - Use a common system to handle user configurable defines (#474)
* Tests

* more changes

* Remove the need for nodejs

* Better preprocessor and ci

* Fix ci maybe

* Fix ci maybe

* Fix ci maybe

* Fix ci maybe + Add way to overide defaults from env vars

* Temp fix for api tests

* Small override fix

* Fix override

* More descriptions

* More descriptions

* Fix led + better typings

* Better format

* Bring back deleted files

* Add all boards in platformio.ini

* Always define Battery Pin and R1, R2 and Resistance

* Checking Boards Default Config:
BOARD_WEMOSD1MINI
BOARD_NODEMCU
BOARD_ESP01
BOARD_TTGO_TBASE

* Format

* Correcting Board Defaults:
- BOARD_WROOM32
- BOARD_LOLIN_C3_MINI
- BOARD_BEETLE32C3
- BOARD_ESP32C3DEVKITM1
- BOARD_ESP32C6DEVKITC1
- BOARD_WEMOSWROOM02
- BOARD_XIAO_ESP32C3
- BOARD_ESP32S3_SUPERMINI

* Change IMU_AUTO to something else on boards that might crash with it.

* remove IMU_UNKNOWN from selection

* Preprocessor fixes

* preprocessor defaults fixes + Make glove not use preprocessor

---------

Co-authored-by: unlogisch04 <98281608+unlogisch04@users.noreply.github.com>
2025-11-01 02:37:38 +02:00
dependabot[bot]
61ab745b38 Bump actions/upload-artifact from 4 to 5 (#479)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 5.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-27 13:11:34 +03:00
lucas lelievre
02df66129c Nixos dev env (#477)
* Basic Nix Flake for dev env

* Remove udev rule command
2025-10-22 16:25:16 +03:00
unlogisch04
f5c9648fbd Make sure wifi is set to persistent (#476) 2025-10-10 09:49:25 +03:00
gorbit99
106f00cf26 WiFi Fix (#473)
* Fix some bugs with wifi rework

* FormattingÅ

* Surprised nothing stopped me from doing that but here we are

* Move comments to their proper place

* Thanks clang for autoimporting that, I'd appreciate if you didn't

* Formatting
2025-10-09 12:02:45 +03:00
Meia
96b6b7acec Optimized VQFParams 2: Electric Boogaloo (#472)
* Adjust IMU parameters

* Format

* Forgot a comment

* Use v2 optimized VQFParams GLOBALLY

* Always calibrate sensor timesteps on startup

* Formatting

---------

Co-authored-by: gorbit99 <gorbitgames@gmail.com>
2025-09-29 19:55:48 +03:00
Summer
1ddbcd4855 reserve positional packet (#471) 2025-09-18 16:07:15 +03:00
JLitewski
c07319f37f Cleanup I2Cscan library (#459)
* Cleanup scanI2C library

* Fixed formatting issues

* First round of changes based on feedback

* Fixed a fat fingered mistake

* Actually fix the formatting issues
2025-09-17 18:06:09 +03:00
gorbit99
23390ddb39 Rework WiFi code (#435)
* Rework WiFi code

* Add missing trySavedAttempt()

* Remap status values to new failure values

* Skip saved credentials properly
2025-09-17 18:03:09 +03:00
dependabot[bot]
1a7619e2e6 Bump actions/setup-python from 5 to 6 (#470)
Bumps [actions/setup-python](https://github.com/actions/setup-python) from 5 to 6.
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](https://github.com/actions/setup-python/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/setup-python
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-16 15:04:02 +03:00
dependabot[bot]
ba1da6054b Bump actions/checkout from 4 to 5 (#469)
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-18 19:02:16 +03:00
Chris D
a8e689784f Update BMI160 bulk read function (#466) 2025-08-12 12:01:00 +03:00
Meia
51c7d15a8b Add IMU_USE_EXTERNAL_CLOCK to debug.h (#464)
* Add IMU_USE_EXTERNAL_CLOCK to debug.h

* Move it to globals.h actually
2025-07-19 15:00:38 +03:00
53 changed files with 2453 additions and 994 deletions

View File

@@ -14,7 +14,7 @@ jobs:
format:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- uses: jidicula/clang-format-action@v4.14.0
with:
clang-format-version: "17"
@@ -33,8 +33,8 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/cache@v4
- uses: actions/checkout@v6
- uses: actions/cache@v5
with:
path: |
~/.cache/pip
@@ -45,7 +45,7 @@ jobs:
run: git fetch --tags origin --recurse-submodules=no --force
- name: Set up Python
uses: actions/setup-python@v5
uses: actions/setup-python@v6
with:
python-version: "3.12"
@@ -58,7 +58,7 @@ jobs:
run: python ./ci/build.py
- name: Upload binaries
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: binaries
path: ./build/*.bin

3
.gitignore vendored
View File

@@ -5,3 +5,6 @@ venv/
cache/
.idea/
compile_commands.json
node_modules/
dist/
.nix-platformio

590
board-defaults.json Normal file
View File

@@ -0,0 +1,590 @@
{
"$schema": "board-defaults.schema.json",
"toolchain": "platformio",
"defaults": {
"BOARD_SLIMEVR": {
"values": {
"SENSORS": [
{
"protocol": "I2C",
"imu": "IMU_AUTO",
"int": "16",
"rotation": "DEG_270",
"scl": "12",
"sda": "14"
},
{
"protocol": "I2C",
"imu": "IMU_AUTO",
"int": "13",
"rotation": "DEG_270",
"scl": "12",
"sda": "14"
}
],
"BATTERY": {
"type": "BAT_EXTERNAL",
"r1": 10,
"r2": 40.2,
"shieldR": 0,
"pin": "17"
},
"LED": {
"LED_PIN": "2",
"LED_INVERTED": true
}
},
"flashingRules": {
"applicationOffset": 0,
"needBootPress": true,
"needManualReboot": true,
"shouldOnlyUseDefaults": true
}
},
"BOARD_SLIMEVR_V1_2": {
"values": {
"SENSORS": [
{
"protocol": "SPI",
"imu": "IMU_AUTO",
"int": "2",
"cs": "15",
"rotation": "DEG_270"
},
{
"protocol": "I2C",
"imu": "IMU_AUTO",
"int": "16",
"rotation": "DEG_270",
"scl": "5",
"sda": "4"
}
],
"BATTERY": {
"type": "BAT_EXTERNAL",
"r1": 10,
"r2": 40.2,
"shieldR": 0,
"pin": "17"
},
"LED": {
"LED_PIN": "2",
"LED_INVERTED": true
}
},
"flashingRules": {
"applicationOffset": 0,
"needBootPress": true,
"needManualReboot": true,
"shouldOnlyUseDefaults": true
}
},
"BOARD_SLIMEVR_DEV": {
"values": {
"SENSORS": [
{
"protocol": "I2C",
"imu": "IMU_AUTO",
"int": "10",
"rotation": "DEG_270",
"scl": "5",
"sda": "4"
},
{
"protocol": "I2C",
"imu": "IMU_AUTO",
"int": "13",
"rotation": "DEG_270",
"scl": "5",
"sda": "4"
}
],
"BATTERY": {
"type": "BAT_EXTERNAL",
"r1": 10,
"r2": 40.2,
"shieldR": 0,
"pin": "17"
},
"LED": {
"LED_PIN": "2",
"LED_INVERTED": true
}
},
"flashingRules": {
"applicationOffset": 0,
"needBootPress": true,
"needManualReboot": true,
"shouldOnlyUseDefaults": true
}
},
"BOARD_WEMOSD1MINI": {
"values": {
"SENSORS": [
{
"protocol": "I2C",
"imu": "IMU_AUTO",
"int": "D5",
"rotation": "DEG_270",
"scl": "D1",
"sda": "D2"
},
{
"protocol": "I2C",
"imu": "IMU_AUTO",
"int": "D6",
"rotation": "DEG_270",
"scl": "D1",
"sda": "D2"
}
],
"BATTERY": {
"type": "BAT_EXTERNAL",
"r1": 100,
"r2": 220,
"shieldR": 180,
"pin": "A0"
},
"LED": {
"LED_PIN": "2",
"LED_INVERTED": true
}
},
"flashingRules": {
"applicationOffset": 0,
"needBootPress": false,
"needManualReboot": false,
"shouldOnlyUseDefaults": false
}
},
"BOARD_NODEMCU": {
"values": {
"SENSORS": [
{
"protocol": "I2C",
"imu": "IMU_BNO085",
"int": "D5",
"rotation": "DEG_270",
"scl": "D1",
"sda": "D2"
},
{
"protocol": "I2C",
"imu": "IMU_BNO085",
"int": "D6",
"rotation": "DEG_270",
"scl": "D1",
"sda": "D2"
}
],
"BATTERY": {
"type": "BAT_EXTERNAL",
"r1": 100,
"r2": 220,
"shieldR": 180,
"pin": "A0"
},
"LED": {
"LED_PIN": "2",
"LED_INVERTED": true
}
},
"flashingRules": {
"applicationOffset": 0,
"needBootPress": false,
"needManualReboot": false,
"shouldOnlyUseDefaults": false
}
},
"BOARD_ESP01": {
"values": {
"SENSORS": [
{
"protocol": "I2C",
"imu": "IMU_BNO085",
"int": "255",
"rotation": "DEG_270",
"scl": "0",
"sda": "2"
},
{
"protocol": "I2C",
"imu": "IMU_BNO085",
"int": "255",
"rotation": "DEG_270",
"scl": "0",
"sda": "2"
}
],
"BATTERY": {
"type": "BAT_INTERNAL",
"r1": 100,
"r2": 220,
"shieldR": 180,
"pin": "255"
},
"LED": {
"LED_PIN": "255",
"LED_INVERTED": true
}
},
"flashingRules": {
"applicationOffset": 0,
"needBootPress": false,
"needManualReboot": false,
"shouldOnlyUseDefaults": false
}
},
"BOARD_TTGO_TBASE": {
"values": {
"SENSORS": [
{
"protocol": "I2C",
"imu": "IMU_BNO085",
"int": "14",
"rotation": "DEG_270",
"scl": "4",
"sda": "5"
},
{
"protocol": "I2C",
"imu": "IMU_BNO085",
"int": "13",
"rotation": "DEG_270",
"scl": "4",
"sda": "5"
}
],
"BATTERY": {
"type": "BAT_EXTERNAL",
"r1": 100,
"r2": 220,
"shieldR": 0,
"pin": "A0"
},
"LED": {
"LED_PIN": "2",
"LED_INVERTED": true
}
},
"flashingRules": {
"applicationOffset": 0,
"needBootPress": false,
"needManualReboot": false,
"shouldOnlyUseDefaults": false
}
},
"BOARD_WROOM32": {
"values": {
"SENSORS": [
{
"protocol": "I2C",
"imu": "IMU_BNO085",
"int": "23",
"rotation": "DEG_270",
"scl": "22",
"sda": "21"
},
{
"protocol": "I2C",
"imu": "IMU_BNO085",
"int": "25",
"rotation": "DEG_270",
"scl": "22",
"sda": "21"
}
],
"BATTERY": {
"type": "BAT_EXTERNAL",
"r1": 100,
"r2": 220,
"shieldR": 180,
"pin": "36"
},
"LED": {
"LED_PIN": "255",
"LED_INVERTED": true
}
},
"flashingRules": {
"applicationOffset": 65536,
"needBootPress": false,
"needManualReboot": false,
"shouldOnlyUseDefaults": false
}
},
"BOARD_LOLIN_C3_MINI": {
"values": {
"SENSORS": [
{
"protocol": "I2C",
"imu": "IMU_ICM45686",
"int": "6",
"rotation": "DEG_270",
"scl": "4",
"sda": "5"
},
{
"protocol": "I2C",
"imu": "IMU_ICM45686",
"int": "8",
"rotation": "DEG_270",
"scl": "4",
"sda": "5"
}
],
"BATTERY": {
"type": "BAT_EXTERNAL",
"r1": 100,
"r2": 220,
"shieldR": 180,
"pin": "3"
},
"LED": {
"LED_PIN": "7",
"LED_INVERTED": true
}
},
"flashingRules": {
"applicationOffset": 65536,
"needBootPress": false,
"needManualReboot": false,
"shouldOnlyUseDefaults": false
}
},
"BOARD_BEETLE32C3": {
"values": {
"SENSORS": [
{
"protocol": "I2C",
"imu": "IMU_ICM45686",
"int": "6",
"rotation": "DEG_270",
"scl": "9",
"sda": "8"
},
{
"protocol": "I2C",
"imu": "IMU_ICM45686",
"int": "7",
"rotation": "DEG_270",
"scl": "9",
"sda": "8"
}
],
"BATTERY": {
"type": "BAT_EXTERNAL",
"r1": 100,
"r2": 220,
"shieldR": 180,
"pin": "3"
},
"LED": {
"LED_PIN": "10",
"LED_INVERTED": false
}
},
"flashingRules": {
"applicationOffset": 65536,
"needBootPress": false,
"needManualReboot": false,
"shouldOnlyUseDefaults": false
}
},
"BOARD_ESP32C3DEVKITM1": {
"values": {
"SENSORS": [
{
"protocol": "I2C",
"imu": "IMU_ICM45686",
"int": "6",
"rotation": "DEG_270",
"scl": "4",
"sda": "5"
},
{
"protocol": "I2C",
"imu": "IMU_ICM45686",
"int": "7",
"rotation": "DEG_270",
"scl": "4",
"sda": "5"
}
],
"BATTERY": {
"type": "BAT_EXTERNAL",
"r1": 100,
"r2": 220,
"shieldR": 180,
"pin": "3"
},
"LED": {
"LED_PIN": "LED_BUILTIN",
"LED_INVERTED": false
}
},
"flashingRules": {
"applicationOffset": 65536,
"needBootPress": false,
"needManualReboot": false,
"shouldOnlyUseDefaults": false
}
},
"BOARD_ESP32C6DEVKITC1": {
"values": {
"SENSORS": [
{
"protocol": "I2C",
"imu": "IMU_ICM45686",
"int": "6",
"rotation": "DEG_270",
"scl": "4",
"sda": "5"
},
{
"protocol": "I2C",
"imu": "IMU_ICM45686",
"int": "7",
"rotation": "DEG_270",
"scl": "4",
"sda": "5"
}
],
"BATTERY": {
"type": "BAT_EXTERNAL",
"r1": 100,
"r2": 220,
"shieldR": 180,
"pin": "3"
},
"LED": {
"LED_PIN": "LED_BUILTIN",
"LED_INVERTED": false
}
},
"flashingRules": {
"applicationOffset": 65536,
"needBootPress": false,
"needManualReboot": false,
"shouldOnlyUseDefaults": false
}
},
"BOARD_WEMOSWROOM02": {
"values": {
"SENSORS": [
{
"protocol": "I2C",
"imu": "IMU_ICM45686",
"int": "0",
"rotation": "DEG_270",
"scl": "14",
"sda": "2"
},
{
"protocol": "I2C",
"imu": "IMU_ICM45686",
"int": "4",
"rotation": "DEG_270",
"scl": "14",
"sda": "2"
}
],
"BATTERY": {
"type": "BAT_EXTERNAL",
"r1": 100,
"r2": 220,
"shieldR": 180,
"pin": "A0"
},
"LED": {
"LED_PIN": "16",
"LED_INVERTED": true
}
},
"flashingRules": {
"applicationOffset": 65536,
"needBootPress": false,
"needManualReboot": false,
"shouldOnlyUseDefaults": false
}
},
"BOARD_XIAO_ESP32C3": {
"values": {
"SENSORS": [
{
"protocol": "I2C",
"imu": "IMU_ICM45686",
"int": "5",
"rotation": "DEG_270",
"scl": "7",
"sda": "6"
},
{
"protocol": "I2C",
"imu": "IMU_ICM45686",
"int": "10",
"rotation": "DEG_270",
"scl": "7",
"sda": "6"
}
],
"BATTERY": {
"type": "BAT_EXTERNAL",
"r1": 100,
"r2": 100,
"shieldR": 0,
"pin": "2"
},
"LED": {
"LED_PIN": "4",
"LED_INVERTED": false
}
},
"flashingRules": {
"applicationOffset": 65536,
"needBootPress": false,
"needManualReboot": false,
"shouldOnlyUseDefaults": false
}
},
"BOARD_ESP32S3_SUPERMINI": {
"values": {
"SENSORS": [
{
"protocol": "I2C",
"imu": "IMU_ICM45686",
"int": "5",
"rotation": "DEG_270",
"scl": "6",
"sda": "7"
},
{
"protocol": "I2C",
"imu": "IMU_ICM45686",
"int": "4",
"rotation": "DEG_270",
"scl": "6",
"sda": "7"
}
],
"BATTERY": {
"type": "BAT_EXTERNAL",
"r1": 10,
"r2": 40.2,
"shieldR": 0,
"pin": "A2"
},
"LED": {
"LED_PIN": "LED_BUILTIN",
"LED_INVERTED": true
}
},
"flashingRules": {
"applicationOffset": 65536,
"needBootPress": false,
"needManualReboot": false,
"shouldOnlyUseDefaults": false
}
}
}
}

244
board-defaults.schema.json Normal file
View File

@@ -0,0 +1,244 @@
{
"$id": "board-defaults.schema.json",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$defs": {
"pin": {
"type": "string",
"pattern": "^[AD]?[0-9]\\d*$"
},
"LED_PIN": {
"type": "string",
"pattern": "^([AD]?[0-9]\\d*|LED_BUILTIN)$"
},
"IMU_TYPE": {
"type": "string",
"enum": [
"IMU_AUTO",
"IMU_MPU9250",
"IMU_MPU6500",
"IMU_BNO080",
"IMU_BNO085",
"IMU_BNO055",
"IMU_MPU6050",
"IMU_BNO086",
"IMU_BMI160",
"IMU_ICM20948",
"IMU_ICM42688",
"IMU_BMI270",
"IMU_LSM6DS3TRC",
"IMU_LSM6DSV",
"IMU_LSM6DSO",
"IMU_LSM6DSR",
"IMU_MPU6050_SF",
"IMU_ICM45686",
"IMU_ICM45605"
],
"description": "Imu Type"
},
"PROTOCOL": {
"type": "string",
"enum": ["I2C", "SPI"],
"description": "Protocol"
},
"IMU_ROTATION": {
"type": "string",
"enum": ["DEG_0", "DEG_90", "DEG_180", "DEG_270"],
"description": "Protocol"
},
"I2C_IMU": {
"type": "object",
"description": "I2C Imu",
"properties": {
"protocol": { "const": "I2C" },
"imu": { "$ref": "#/$defs/IMU_TYPE" },
"sda": { "$ref": "#/$defs/pin", "description": "SDA Pin" },
"scl": { "$ref": "#/$defs/pin", "description": "SCL Pin" },
"int": { "$ref": "#/$defs/pin", "description": "INT Pin" },
"address": {
"type": "number",
"description": "IMU Address"
},
"rotation": {
"$ref": "#/$defs/IMU_ROTATION",
"description": "IMU Rotation"
}
},
"required": ["protocol", "imu", "sda", "scl"]
},
"SPI_IMU": {
"type": "object",
"description": "SPI Imu",
"properties": {
"protocol": { "const": "SPI" },
"imu": { "$ref": "#/$defs/IMU_TYPE" },
"int": { "$ref": "#/$defs/pin", "description": "INT Pin" },
"rotation": {
"$ref": "#/$defs/IMU_ROTATION",
"description": "IMU Rotation"
},
"cs": { "$ref": "#/$defs/pin", "description": "CS Pin" }
},
"required": ["protocol", "imu", "cs"]
},
"IMU": {
"type": "object",
"discriminator": {
"propertyName": "protocol"
},
"oneOf": [
{ "$ref": "#/$defs/I2C_IMU" },
{ "$ref": "#/$defs/SPI_IMU" }
],
"required": ["protocol"]
},
"BOARD_TYPES": {
"type": "string",
"enum": [
"BOARD_SLIMEVR",
"BOARD_SLIMEVR_V1_2",
"BOARD_SLIMEVR_DEV",
"BOARD_NODEMCU",
"BOARD_WEMOSD1MINI",
"BOARD_ESP01",
"BOARD_TTGO_TBASE",
"BOARD_WROOM32",
"BOARD_LOLIN_C3_MINI",
"BOARD_BEETLE32C3",
"BOARD_ESP32C3DEVKITM1",
"BOARD_ESP32C6DEVKITC1",
"BOARD_WEMOSWROOM02",
"BOARD_XIAO_ESP32C3",
"BOARD_ESP32S3_SUPERMINI"
],
"description": "Board Type"
},
"BATTERY": {
"type": "object",
"discriminator": {
"propertyName": "type"
},
"description": "Battery Settings",
"oneOf": [
{
"type": "object",
"properties": {
"type": { "const": "BAT_EXTERNAL" },
"shieldR": {
"type": "number",
"description": "Battery Shield Resistor (Ohms)"
},
"r1": { "type": "number", "description": "R1 (Ohms)" },
"r2": { "type": "number", "description": "R2 (Ohms)" },
"pin": {
"$ref": "#/$defs/pin",
"description": "Battery Pin"
}
}
},
{
"type": "object",
"properties": {
"type": { "const": "BAT_INTERNAL" }
}
},
{
"type": "object",
"properties": {
"type": { "const": "BAT_MCP3021" }
}
},
{
"type": "object",
"properties": {
"type": { "const": "BAT_INTERNAL_MCP3021" }
}
}
],
"required": ["type"]
},
"BASIC_IMU_BOARD_CONFIG": {
"type": "object",
"properties": {
"SENSORS": {
"type": "array",
"items": { "$ref": "#/$defs/IMU" },
"minItems": 1,
"maxItems": 2,
"description": "Sensors List"
},
"LED": {
"type": "object",
"description": "Led Settings",
"properties": {
"LED_PIN": {
"$ref": "#/$defs/LED_PIN",
"description": "Led pin"
},
"LED_INVERTED": {
"type": "boolean",
"description": "Led inverted"
}
},
"required": ["LED_PIN", "LED_INVERTED"]
},
"BATTERY": { "$ref": "#/$defs/BATTERY" }
},
"required": ["SENSORS", "LED", "BATTERY"]
},
"BoardConfig": {
"type": "object",
"properties": {
"values": {
"$ref": "#/$defs/BASIC_IMU_BOARD_CONFIG"
},
"flashingRules": {
"type": "object",
"properties": {
"needBootPress": { "type": "boolean" },
"needManualReboot": { "type": "boolean" },
"shouldOnlyUseDefaults": { "type": "boolean" },
"applicationOffset": { "type": "integer" }
},
"required": [
"needBootPress",
"needManualReboot",
"shouldOnlyUseDefaults",
"applicationOffset"
]
},
"ignored": {
"type": "boolean"
}
},
"required": ["values", "flashingRules"]
}
},
"type": "object",
"properties": {
"toolchain": {
"type": "string",
"enum": ["platformio"]
},
"defaults": {
"type": "object",
"propertyNames": {
"$ref": "#/$defs/BOARD_TYPES"
},
"additionalProperties": { "$ref": "#/$defs/BoardConfig" }
}
},
"required": ["toolchain", "defaults"]
}

View File

@@ -32,12 +32,13 @@ def get_matrix() -> List[DeviceConfiguration]:
matrix: List[DeviceConfiguration] = []
config = configparser.ConfigParser()
config.read("./platformio-tools.ini")
config.read("./platformio.ini")
for section in config.sections():
if section == "env":
split = section.split(":")
if len(split) != 2 or split[0] != 'env':
continue
board = section.split(":")[1]
board = split[1]
platform = config[section]["platform"]
platformio_board = config[section]["board"]
@@ -51,36 +52,15 @@ def get_matrix() -> List[DeviceConfiguration]:
def prepare() -> None:
print(f"🡢 {COLOR_CYAN}Preparation{COLOR_RESET}")
print(f" 🡢 {COLOR_GRAY}Backing up platformio.ini{COLOR_RESET}")
shutil.copy("./platformio.ini", "platformio.ini.bak")
print(
f" 🡢 {COLOR_GRAY}Switching platformio.ini to platformio-tools.ini{COLOR_RESET}")
shutil.copy("./platformio-tools.ini", "platformio.ini")
if os.path.exists("./build"):
print(f" 🡢 {COLOR_GRAY}Removing existing build folder...{COLOR_RESET}")
shutil.rmtree("./build")
print(f" 🡢 {COLOR_GRAY}Creating build folder...{COLOR_RESET}")
os.mkdir("./build")
print(f" 🡢 {COLOR_GREEN}Success!{COLOR_RESET}")
def cleanup() -> None:
print(f"🡢 {COLOR_CYAN}Cleanup{COLOR_RESET}")
print(f" 🡢 {COLOR_GRAY}Restoring platformio.ini...{COLOR_RESET}")
shutil.copy("platformio.ini.bak", "platformio.ini")
print(f" 🡢 {COLOR_GRAY}Removing platformio.ini.bak...{COLOR_RESET}")
os.remove("platformio.ini.bak")
print(f" 🡢 {COLOR_GREEN}Success!{COLOR_RESET}")
def build() -> int:
print(f"🡢 {COLOR_CYAN}Build{COLOR_RESET}")
@@ -135,7 +115,6 @@ def build_for_device(device: DeviceConfiguration) -> bool:
def main() -> None:
prepare()
code = build()
cleanup()
sys.exit(code)

61
flake.lock generated Normal file
View File

@@ -0,0 +1,61 @@
{
"nodes": {
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1760878510,
"narHash": "sha256-K5Osef2qexezUfs0alLvZ7nQFTGS9DL2oTVsIXsqLgs=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "5e2a59a5b1a82f89f2c7e598302a9cacebb72a67",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

80
flake.nix Normal file
View File

@@ -0,0 +1,80 @@
{
description = "PlatformIO development environment";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
flake-utils.url = "github:numtide/flake-utils";
};
outputs = { self, nixpkgs, flake-utils }:
flake-utils.lib.eachDefaultSystem (system:
let
pkgs = nixpkgs.legacyPackages.${system};
in
{
devShells.default = pkgs.mkShell {
buildInputs = with pkgs; [
# PlatformIO
platformio
platformio-core
# Python for PlatformIO with needed packages
python3
python3Packages.pip
python3Packages.virtualenv
# Pre-install Python packages that need compilation
python3Packages.jsonschema
python3Packages.rpds-py
python3Packages.attrs
python3Packages.referencing
# Rust toolchain (in case compilation is needed)
rustc
cargo
# Build tools
gcc
gnumake
cmake
# Serial communication
picocom
minicom
# USB access (for programming devices)
libusb1
pkg-config
];
shellHook = ''
# Set PlatformIO core directory to project-local directory
export PLATFORMIO_CORE_DIR=$PWD/.nix-platformio
# Create and activate Python virtual environment
if [ ! -d .venv ]; then
echo "Creating Python virtual environment..."
python3 -m venv .venv --system-site-packages
fi
source .venv/bin/activate
# Prefer binary wheels over building from source
export PIP_PREFER_BINARY=1
echo "PlatformIO development environment loaded"
echo "Python virtual environment activated: .venv"
echo "PlatformIO version: $(pio --version)"
echo "Python version: $(python --version)"
echo ""
echo "Available commands:"
echo " pio init - Initialize a new PlatformIO project"
echo " pio run - Build the project"
echo " pio run -t upload - Upload to device"
echo " pio device monitor - Open serial monitor"
echo " pip install <package> - Install Python packages in venv"
echo ""
'';
};
}
);
}

View File

@@ -1,116 +1,156 @@
#include "i2cscan.h"
#include <array>
#include <cstdint>
#include <string>
#include "../../src/globals.h"
#include "../../src/consts.h"
#ifdef ESP8266
uint8_t portArray[] = {16, 5, 4, 2, 14, 12, 13};
uint8_t portExclude[] = {LED_PIN};
String portMap[] = {"D0", "D1", "D2", "D4", "D5", "D6", "D7"};
#elif defined(ESP32C3)
uint8_t portArray[] = {2, 3, 4, 5, 6, 7, 8, 9, 10};
uint8_t portExclude[] = {18, 19, 20, 21, LED_PIN};
String portMap[] = {"2", "3", "4", "5", "6", "7", "8", "9", "10"};
#elif defined(ESP32C6)
uint8_t portArray[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 14, 15, 18, 19, 20, 21, 22, 23};
String portMap[] = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "14", "15", "18", "19", "20", "21", "22", "23"};
uint8_t portExclude[] = {12, 13, 16, 17, LED_PIN};
#elif defined(ESP32)
uint8_t portArray[] = {4, 13, 14, 15, 16, 17, 18, 19, 21, 22, 23, 25, 26, 27, 32, 33};
String portMap[] = {"4", "13", "14", "15", "16", "17", "18", "19", "21", "22", "23", "25", "26", "27", "32", "33"};
uint8_t portExclude[] = {LED_PIN};
#endif
namespace I2CSCAN
{
enum class ScanState {
namespace I2CSCAN {
enum class ScanState : uint8_t {
IDLE,
SCANNING,
DONE
};
ScanState scanState = ScanState::IDLE;
uint8_t currentSDA = 0;
uint8_t currentSCL = 0;
uint8_t currentAddress = 1;
bool found = false;
std::vector<uint8_t> validPorts;
namespace {
ScanState scanState = ScanState::IDLE;
uint8_t currentSDA = 0;
uint8_t currentSCL = 0;
uint8_t currentAddress = 1;
bool found = false;
uint8_t txFails = 0;
std::vector<uint8_t> validPorts;
void scani2cports()
{
#ifdef ESP8266
std::array<uint8_t, 7> portArray = {16, 5, 4, 2, 14, 12, 13};
std::array<std::string, 7> portMap = {"D0", "D1", "D2", "D4", "D5", "D6", "D7"};
std::array<uint8_t, 1> portExclude = {LED_PIN};
#elif defined(ESP32C3)
std::array<uint8_t, 9> portArray = {2, 3, 4, 5, 6, 7, 8, 9, 10};
std::array<std::string, 9> portMap = {"2", "3", "4", "5", "6", "7", "8", "9", "10"};
std::array<uint8_t, 5> portExclude = {18, 19, 20, 21, LED_PIN};
#elif defined(ESP32C6)
std::array<uint8_t, 20> portArray = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 14, 15, 18, 19, 20, 21, 22, 23};
std::array<std::string, 20> portMap = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "14", "15", "18", "19", "20", "21", "22", "23"};
std::array<uint8_t, 5> portExclude = {12, 13, 16, 17, LED_PIN};
#elif defined(ESP32)
std::array<uint8_t, 16> portArray = {4, 13, 14, 15, 16, 17, 18, 19, 21, 22, 23, 25, 26, 27, 32, 33};
std::array<std::string, 16> portMap = {"4", "13", "14", "15", "16", "17", "18", "19", "21", "22", "23", "25", "26", "27", "32", "33"};
std::array<uint8_t, 1> portExclude = {LED_PIN};
#endif
bool selectNextPort() {
currentSCL++;
if(validPorts[currentSCL] == validPorts[currentSDA]) currentSCL++;
if (currentSCL < validPorts.size()) {
Wire.begin((int)validPorts[currentSDA], (int)validPorts[currentSCL]); //NOLINT
return true;
}
currentSCL = 0;
currentSDA++;
if (currentSDA >= validPorts.size()) {
if (!found) {
Serial.println("[ERROR] I2C: No I2C devices found"); //NOLINT
}
#ifdef ESP32
Wire.end();
#endif
Wire.begin(static_cast<int>(PIN_IMU_SDA), static_cast<int>(PIN_IMU_SCL));
scanState = ScanState::DONE;
return false;
}
Wire.begin((int)validPorts[currentSDA], (int)validPorts[currentSCL]);
return true;
}
template <uint8_t size1, uint8_t size2>
uint8_t countCommonElements(
const std::array<uint8_t, size1>& array1,
const std::array<uint8_t, size2>& array2) {
uint8_t count = 0;
for (const auto& elem1 : array1) {
for (const auto& elem2 : array2) {
if (elem1 == elem2) {
count++;
}
}
}
return count;
}
} // anonymous namespace
void scani2cports() {
if (scanState != ScanState::IDLE) {
return;
if (scanState == ScanState::DONE) {
Serial.println("[DEBUG] I2C scan finished previously, resetting and scanning again..."); //NOLINT
} else {
return; // Already scanning, do not start again
}
}
// Filter out excluded ports
for (size_t i = 0; i < sizeof(portArray); i++) {
if (!inArray(portArray[i], portExclude, sizeof(portExclude))) {
validPorts.push_back(portArray[i]);
}
}
validPorts.clear();
uint8_t excludes = countCommonElements<portArray.size(), portExclude.size()>(portArray, portExclude);
validPorts.reserve(portArray.size() - excludes); // Reserve space to avoid reallocations
for (const auto& port : portArray) {
if (std::find(portExclude.begin(), portExclude.end(), port) == portExclude.end()) {
validPorts.push_back(port); // Port is valid, add it to the list
}
}
// Reset scan variables and start scanning
found = false;
currentSDA = 0;
currentSCL = 1;
currentAddress = 1;
txFails = 0;
scanState = ScanState::SCANNING;
}
}
bool selectNextPort() {
currentSCL++;
if(validPorts[currentSCL] == validPorts[currentSDA])
currentSCL++;
if (currentSCL < validPorts.size()) {
Wire.begin((int)validPorts[currentSDA], (int)validPorts[currentSCL]);
return true;
}
currentSCL = 0;
currentSDA++;
if (currentSDA >= validPorts.size()) {
if (!found) {
Serial.println("[ERR] I2C: No I2C devices found");
}
#ifdef ESP32
Wire.end();
#endif
Wire.begin(static_cast<int>(PIN_IMU_SDA), static_cast<int>(PIN_IMU_SCL));
scanState = ScanState::DONE;
return false;
}
Wire.begin((int)validPorts[currentSDA], (int)validPorts[currentSCL]);
return true;
}
void update()
{
void update() {
if (scanState != ScanState::SCANNING) {
return;
}
if (currentAddress == 1) {
#ifdef ESP32
if (currentAddress == 1) {
Wire.end();
}
#endif
}
Wire.beginTransmission(currentAddress);
byte error = Wire.endTransmission();
const uint8_t error = Wire.endTransmission();
if (error == 0)
{
Serial.printf("[DBG] I2C (@ %s(%d) : %s(%d)): I2C device found at address 0x%02x !\n",
if (error == 0) {
Serial.printf("[INFO ] I2C (@ %s(%d) : %s(%d)): I2C device found at address 0x%02x!\n",
portMap[currentSDA].c_str(), validPorts[currentSDA], portMap[currentSCL].c_str(), validPorts[currentSCL], currentAddress);
found = true;
}
else if (error == 4)
{
Serial.printf("[ERR] I2C (@ %s(%d) : %s(%d)): Unknown error at address 0x%02x\n",
} else if (error == 4) { // Unable to start transaction, log and warn
Serial.printf("[WARN ] I2C (@ %s(%d) : %s(%d)): Unable to start transaction at address 0x%02x!\n",
portMap[currentSDA].c_str(), validPorts[currentSDA], portMap[currentSCL].c_str(), validPorts[currentSCL], currentAddress);
txFails++;
}
currentAddress++;
if (currentAddress <= 127) {
if (txFails > 5) {
#if BOARD == BOARD_SLIMEVR_LEGACY || BOARD == BOARD_SLIMEVR_DEV || BOARD == BOARD_SLIMEVR || BOARD == BOARD_SLIMEVR_V1_2
Serial.printf("[ERROR] I2C: Too many transaction errors (%d), please power off the tracker and contact SlimeVR support!\n", txFails);
#else
Serial.printf("[ERROR] I2C: Too many transaction errors (%d), please power off the tracker and check the IMU connections!\n", txFails);
#endif
}
return;
}
@@ -118,19 +158,6 @@ namespace I2CSCAN
selectNextPort();
}
bool inArray(uint8_t value, uint8_t* array, size_t arraySize)
{
for (size_t i = 0; i < arraySize; i++)
{
if (value == array[i])
{
return true;
}
}
return false;
}
bool hasDevOnBus(uint8_t addr) {
byte error;
#if ESP32C3
@@ -165,9 +192,9 @@ namespace I2CSCAN
*/
int clearBus(uint8_t SDA, uint8_t SCL) {
#if defined(TWCR) && defined(TWEN)
#if defined(TWCR) && defined(TWEN)
TWCR &= ~(_BV(TWEN)); // Disable the Atmel 2-Wire interface so we can control the SDA and SCL pins directly
#endif
#endif
pinMode(SDA, INPUT_PULLUP);
pinMode(SCL, INPUT_PULLUP);

View File

@@ -11,7 +11,7 @@ namespace I2CSCAN {
bool hasDevOnBus(uint8_t addr);
uint8_t pickDevice(uint8_t addr1, uint8_t addr2, bool scanIfNotFound);
int clearBus(uint8_t SDA, uint8_t SCL);
boolean inArray(uint8_t value, uint8_t* arr, size_t arrSize);
bool inArray(uint8_t value, const uint8_t *array, size_t arraySize);
}
#endif // _I2CSCAN_H_
#endif // _I2CSCAN_H_

View File

@@ -56,7 +56,7 @@ struct VQFParams {
*
* Default value: 3.0 s
*/
vqf_real_t tauAcc = 3.0;
vqf_real_t tauAcc = 4.337983;
/**
* @brief Time constant \f$\tau_\mathrm{mag}\f$ for magnetometer update in seconds.
*
@@ -106,7 +106,7 @@ struct VQFParams {
*
* Default value: 0.5 °/s
*/
vqf_real_t biasSigmaInit = 0.5;
vqf_real_t biasSigmaInit = 3.219453;
/**
* @brief Time in which the bias estimation uncertainty increases from 0 °/s to 0.1
* °/s (in seconds).
@@ -115,7 +115,7 @@ struct VQFParams {
*
* Default value: 100.0 s
*/
vqf_real_t biasForgettingTime = 100.0;
vqf_real_t biasForgettingTime = 136.579346;
/**
* @brief Maximum expected gyroscope bias (in degrees per second).
*
@@ -126,7 +126,7 @@ struct VQFParams {
*
* Default value: 2.0 °/s
*/
vqf_real_t biasClip = 2.0;
vqf_real_t biasClip = 5.0;
#ifndef VQF_NO_MOTION_BIAS_ESTIMATION
/**
* @brief Standard deviation of the converged bias estimation uncertainty during
@@ -137,7 +137,7 @@ struct VQFParams {
*
* Default value: 0.1 °/s
*/
vqf_real_t biasSigmaMotion = 0.1;
vqf_real_t biasSigmaMotion = 0.348501;
/**
* @brief Forgetting factor for unobservable bias in vertical direction during
* motion.
@@ -149,7 +149,7 @@ struct VQFParams {
*
* Default value: 0.0001
*/
vqf_real_t biasVerticalForgettingFactor = 0.0001;
vqf_real_t biasVerticalForgettingFactor = 0.007056;
#endif
/**
* @brief Standard deviation of the converged bias estimation uncertainty during
@@ -160,7 +160,7 @@ struct VQFParams {
*
* Default value: 0.03 °
*/
vqf_real_t biasSigmaRest = 0.03;
vqf_real_t biasSigmaRest = 0.063616;
/**
* @brief Time threshold for rest detection (in seconds).
@@ -170,7 +170,7 @@ struct VQFParams {
*
* Default value: 1.5 s
*/
vqf_real_t restMinT = 1.5;
vqf_real_t restMinT = 2.586910;
/**
* @brief Time constant for the low-pass filter used in rest detection (in seconds).
*
@@ -179,7 +179,7 @@ struct VQFParams {
*
* Default value: 0.5 s
*/
vqf_real_t restFilterTau = 0.5;
vqf_real_t restFilterTau = 1.114532;
/**
* @brief Angular velocity threshold for rest detection (in °/s).
*
@@ -189,7 +189,7 @@ struct VQFParams {
*
* Default value: 2.0 °/s
*/
vqf_real_t restThGyr = 2.0;
vqf_real_t restThGyr = 1.399189;
/**
* @brief Acceleration threshold for rest detection (in m/s²).
*
@@ -198,7 +198,7 @@ struct VQFParams {
*
* Default value: 0.5 m/s²
*/
vqf_real_t restThAcc = 0.5;
vqf_real_t restThAcc = 1.418598;
/**
* @brief Time constant for current norm/dip value in magnetic disturbance detection

View File

@@ -1,176 +0,0 @@
[env]
lib_deps=
https://github.com/SlimeVR/CmdParser.git
https://github.com/SlimeVR/base64_arduino.git
https://github.com/adafruit/Adafruit-MCP23017-Arduino-Library.git
https://github.com/hideakitai/PCA9547.git
monitor_speed = 115200
framework = arduino
build_flags =
!python scripts/get_git_commit.py
-O2
-std=gnu++2a
build_unflags =
-Os
-std=gnu++11 -std=gnu++17
[env:BOARD_SLIMEVR]
platform = espressif8266 @ 4.2.1
board = esp12e
build_flags =
${env.build_flags}
-D BOARD=BOARD_SLIMEVR
-D VENDOR_NAME='"SlimeVR"'
-D VENDOR_URL='"https://slimevr.dev"'
-D PRODUCT_NAME='"SlimeVR Tracker"'
-D UPDATE_ADDRESS='"SlimeVR/SlimeVR-Tracker-ESP"'
-D UPDATE_NAME='"BOARD_SLIMEVR-firmware"'
[env:BOARD_SLIMEVR_V1_2]
platform = espressif8266 @ 4.2.1
board = esp12e
build_flags =
${env.build_flags}
-D BOARD=BOARD_SLIMEVR_V1_2
-D VENDOR_NAME='"SlimeVR"'
-D VENDOR_URL='"https://slimevr.dev"'
-D PRODUCT_NAME='"SlimeVR Tracker v1.2"'
-D UPDATE_ADDRESS='"SlimeVR/SlimeVR-Tracker-ESP"'
-D UPDATE_NAME='"BOARD_SLIMEVR_V1_2-firmware"'
[env:BOARD_SLIMEVR_DEV]
platform = espressif8266 @ 4.2.1
board = esp12e
build_flags =
${env.build_flags}
-D BOARD=BOARD_SLIMEVR_DEV
-D VENDOR_NAME='"SlimeVR"'
-D PRODUCT_NAME='"SlimeVR Tracker (dev)"'
[env:BOARD_GLOVE_IMU_SLIMEVR_DEV]
platform = espressif32 @ 6.7.0
platform_packages =
framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git#3.0.1
framework-arduinoespressif32-libs @ https://github.com/espressif/arduino-esp32/releases/download/3.0.1/esp32-arduino-libs-3.0.1.zip
build_flags =
${env.build_flags}
-DESP32C3
-D BOARD=BOARD_GLOVE_IMU_SLIMEVR_DEV
-D PRODUCT_NAME='"SlimeVR Glove (dev)"'
board = lolin_c3_mini
[env:BOARD_NODEMCU]
platform = espressif8266 @ 4.2.1
board = esp12e
build_flags =
${env.build_flags}
-D BOARD=BOARD_NODEMCU
[env:BOARD_WEMOSD1MINI]
platform = espressif8266 @ 4.2.1
board = esp12e
build_flags =
${env.build_flags}
-D BOARD=BOARD_WEMOSD1MINI
[env:BOARD_TTGO_TBASE]
platform = espressif8266 @ 4.2.1
board = esp12e
build_flags =
${env.build_flags}
-D BOARD=BOARD_TTGO_TBASE
[env:BOARD_WEMOSWROOM02]
platform = espressif8266 @ 4.2.1
board = esp12e
build_flags =
${env.build_flags}
-D BOARD=BOARD_NODEMCU
[env:BOARD_WROOM32]
platform = espressif32 @ 6.7.0
platform_packages =
framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git#3.0.1
framework-arduinoespressif32-libs @ https://github.com/espressif/arduino-esp32/releases/download/3.0.1/esp32-arduino-libs-3.0.1.zip
board = esp32dev
build_flags =
${env.build_flags}
-D BOARD=BOARD_WROOM32
[env:BOARD_ESP01]
platform = espressif32 @ 6.7.0
platform_packages =
framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git#3.0.1
framework-arduinoespressif32-libs @ https://github.com/espressif/arduino-esp32/releases/download/3.0.1/esp32-arduino-libs-3.0.1.zip
board = esp32dev
build_flags =
${env.build_flags}
-D BOARD=BOARD_ESP01
[env:BOARD_LOLIN_C3_MINI]
platform = espressif32 @ 6.7.0
platform_packages =
framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git#3.0.1
framework-arduinoespressif32-libs @ https://github.com/espressif/arduino-esp32/releases/download/3.0.1/esp32-arduino-libs-3.0.1.zip
build_flags =
${env.build_flags}
-DESP32C3
-D BOARD=BOARD_LOLIN_C3_MINI
board = lolin_c3_mini
[env:BOARD_BEETLE32C3]
platform = espressif32 @ 6.7.0
platform_packages =
framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git#3.0.1
framework-arduinoespressif32-libs @ https://github.com/espressif/arduino-esp32/releases/download/3.0.1/esp32-arduino-libs-3.0.1.zip
build_flags =
${env.build_flags}
-DESP32C3
-D BOARD=BOARD_BEETLE32C3
board = dfrobot_beetle_esp32c3
[env:BOARD_ESP32C3DEVKITM1]
platform = espressif32 @ 6.7.0
platform_packages =
framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git#3.0.1
framework-arduinoespressif32-libs @ https://github.com/espressif/arduino-esp32/releases/download/3.0.1/esp32-arduino-libs-3.0.1.zip
build_flags =
${env.build_flags}
-DESP32C3
-D BOARD=BOARD_ESP32C3DEVKITM1
board = esp32-c3-devkitm-1
[env:BOARD_ESP32C6DEVKITC1]
platform = https://github.com/tasmota/platform-espressif32/releases/download/2024.06.11/platform-espressif32.zip
build_flags =
${env.build_flags}
-DESP32C6
-D BOARD=BOARD_ESP32C6DEVKITC1
board = esp32-c6-devkitc-1
[env:BOARD_XIAO_ESP32C3]
platform = espressif32 @ 6.7.0
platform_packages =
framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git#3.0.1
framework-arduinoespressif32-libs @ https://github.com/espressif/arduino-esp32/releases/download/3.0.1/esp32-arduino-libs-3.0.1.zip
build_flags =
${env.build_flags}
-DESP32C3
-D BOARD=BOARD_XIAO_ESP32C3
board = seeed_xiao_esp32c3
[env:BOARD_ESP32S3_SUPERMINI]
platform = espressif32 @ 6.7.0
platform_packages =
framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git#3.0.1
framework-arduinoespressif32-libs @ https://github.com/espressif/arduino-esp32/releases/download/3.0.1/esp32-arduino-libs-3.0.1.zip
build_flags =
${env.build_flags}
-DARDUINO_USB_MODE=1
-DESP32S3
-D BOARD=BOARD_ESP32S3_SUPERMINI
board = esp32s3_supermini
board_upload.use_1200bps_touch = 1
board_upload.wait_for_upload_port = 1
board_upload.require_upload_port = 1
upload_speed = 921600

View File

@@ -13,6 +13,7 @@
[platformio]
build_cache_dir = cache
default_envs = BOARD_WEMOSD1MINI
[env]
lib_deps=
@@ -26,7 +27,8 @@ monitor_filters = colorize
;monitor_rts = 0
;monitor_dtr = 0
framework = arduino
build_flags =
extra_scripts = pre:scripts/preprocessor.py
build_flags =
!python scripts/get_git_commit.py
;If you want to set hardcoded WiFi SSID and password, uncomment and edit the lines below
;To uncomment, only remove ";" and leave the two spaces in front of the tags
@@ -60,39 +62,62 @@ build_unflags = -Os -std=gnu++11 -std=gnu++17
;upload_flags =
; --auth=SlimeVR-OTA
; Settings for different boards
[env:esp12e]
platform = espressif8266 @ 4.2.1
board = esp12e
; Comment out this line below if you have any trouble uploading the firmware
; and if it has a CP2102 on it (a square chip next to the usb port): change to 3000000 (3 million) for even faster upload speed
upload_speed = 921600
; Uncomment below if you want to build for ESP-01
;[env:esp01_1m]
;platform = espressif8266 @ 4.2.1
;board = esp01_1m
;board_build.arduino.ldscript = "eagle.flash.1m64.ld"
; Uncomment below if you want to build for ESP8285 (ESP8266 with embedded Flash)
;[env:esp8285]
;platform = espressif8266 @ 4.2.1
;board = esp8285
;board_build.arduino.ldscript = "eagle.flash.1m64.ld"
;board_build.flash_mode = dout
[env:BOARD_WEMOSD1MINI]
platform = espressif8266 @ 4.2.1
board = esp12e
custom_slime_board = BOARD_WEMOSD1MINI
upload_speed = 921600
; Uncomment below if you want to build for esp32
; Check your board name at https://docs.platformio.org/en/latest/platforms/espressif32.html#boards
; [env:esp32]
; platform = espressif32 @ 6.7.0
; platform_packages =
; framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git#3.0.1
; framework-arduinoespressif32-libs @ https://github.com/espressif/arduino-esp32/releases/download/3.0.1/esp32-arduino-libs-3.0.1.zip
; board = esp32dev
; Comment out this line below if you have any trouble uploading the firmware - and if it has a CP2102 on it (a square chip next to the usb port): change to 3000000 (3 million) for even faster upload speed
;upload_speed = 921600
[env:BOARD_NODEMCU]
platform = espressif8266 @ 4.2.1
board = esp12e
custom_slime_board = BOARD_NODEMCU
upload_speed = 921600
[env:BOARD_TTGO_TBASE]
platform = espressif8266 @ 4.2.1
board = esp12e
custom_slime_board = BOARD_TTGO_TBASE
upload_speed = 921600
[env:BOARD_WEMOSWROOM02]
platform = espressif8266 @ 4.2.1
board = esp12e
custom_slime_board = BOARD_WEMOSWROOM02
upload_speed = 921600
[env:BOARD_WROOM32]
platform = espressif32 @ 6.7.0
platform_packages =
framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git#3.0.1
framework-arduinoespressif32-libs @ https://github.com/espressif/arduino-esp32/releases/download/3.0.1/esp32-arduino-libs-3.0.1.zip
board = esp32dev
custom_slime_board = BOARD_WROOM32
[env:BOARD_ESP01]
platform = espressif8266 @ 4.2.1
board = esp01_1m
board_build.arduino.ldscript = "eagle.flash.1m64.ld"
custom_slime_board = BOARD_ESP01
upload_speed = 921600
[env:BOARD_LOLIN_C3_MINI]
platform = espressif32 @ 6.7.0
platform_packages =
framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git#3.0.1
framework-arduinoespressif32-libs @ https://github.com/espressif/arduino-esp32/releases/download/3.0.1/esp32-arduino-libs-3.0.1.zip
custom_slime_board = BOARD_LOLIN_C3_MINI
build_flags =
${env.build_flags}
-DESP32C3
board = lolin_c3_mini
monitor_filters = colorize, esp32_exception_decoder
; If you want to use a ESP32C3, you can use this (experimental)
; Check your board name at https://docs.platformio.org/en/latest/platforms/espressif32.html#boards
; There are 2 main Boardtypes:
@@ -102,38 +127,126 @@ upload_speed = 921600
; -DARDUINO_USB_MODE=1
; -DARDUINO_USB_CDC_ON_BOOT=1
;[env:esp32c3]
;platform = espressif32 @ 6.7.0
;platform_packages =
; framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git#3.0.1
; framework-arduinoespressif32-libs @ https://github.com/espressif/arduino-esp32/releases/download/3.0.1/esp32-arduino-libs-3.0.1.zip
;build_flags =
; ${env.build_flags}
; -DESP32C3
;board = lolin_c3_mini
;monitor_filters = colorize, esp32_exception_decoder
[env:BOARD_BEETLE32C3]
platform = espressif32 @ 6.7.0
platform_packages =
framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git#3.0.1
framework-arduinoespressif32-libs @ https://github.com/espressif/arduino-esp32/releases/download/3.0.1/esp32-arduino-libs-3.0.1.zip
custom_slime_board = BOARD_BEETLE32C3
build_flags =
${env.build_flags}
-DESP32C3
board = dfrobot_beetle_esp32c3
monitor_filters = colorize, esp32_exception_decoder
; If you want to use a ESP32C3, you can use this (experimental)
; Check your board name at https://docs.platformio.org/en/latest/platforms/espressif32.html#boards
; There are 2 main Boardtypes:
; 1. Boards that use a USB 2 Serial Chipset ( esp32-c3-devkitm-1, ttgo-t-oi-plus )
; 2. Boards that relay on the USB interface of the ESP32C3 ( lolin_c3_mini , dfrobot_beetle_esp32c3)
; On this board there are 2 type some of them need to have set the build flag (menuconfig)
; -DARDUINO_USB_MODE=1
; -DARDUINO_USB_CDC_ON_BOOT=1
; If you want to use a ESP32C6, you can use this (experimental)
;[env:esp32c6]
;platform = https://github.com/tasmota/platform-espressif32/releases/download/2024.06.11/platform-espressif32.zip
;board = esp32-c6-devkitc-1
;build_flags =
; ${env.build_flags}
; -DESP32C6
; -DARDUINO_USB_MODE=1
; -DARDUINO_USB_CDC_ON_BOOT=1
[env:BOARD_ESP32C3DEVKITM1]
platform = espressif32 @ 6.7.0
platform_packages =
framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git#3.0.1
framework-arduinoespressif32-libs @ https://github.com/espressif/arduino-esp32/releases/download/3.0.1/esp32-arduino-libs-3.0.1.zip
custom_slime_board = BOARD_ESP32C3DEVKITM1
build_flags =
${env.build_flags}
-DESP32C3
board = esp32-c3-devkitm-1
monitor_filters = colorize, esp32_exception_decoder
; If you want to use a ESP32C3, you can use this (experimental)
; Check your board name at https://docs.platformio.org/en/latest/platforms/espressif32.html#boards
; There are 2 main Boardtypes:
; 1. Boards that use a USB 2 Serial Chipset ( esp32-c3-devkitm-1, ttgo-t-oi-plus )
; 2. Boards that relay on the USB interface of the ESP32C3 ( lolin_c3_mini , dfrobot_beetle_esp32c3)
; On this board there are 2 type some of them need to have set the build flag (menuconfig)
; -DARDUINO_USB_MODE=1
; -DARDUINO_USB_CDC_ON_BOOT=1
;[env:BOARD_ESP32S3_SUPERMINI]
;platform = espressif32 @ 6.7.0
;platform_packages =
; framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git#3.0.1
; framework-arduinoespressif32-libs @ https://github.com/espressif/arduino-esp32/releases/download/3.0.1/esp32-arduino-libs-3.0.1.zip
;build_flags =
; ${env.build_flags}
; -DARDUINO_USB_MODE=1
; -DESP32S3
;board = esp32s3_supermini
;board_upload.use_1200bps_touch = 1
;board_upload.wait_for_upload_port = 1
;board_upload.require_upload_port = 1
;upload_speed = 921600
[env:BOARD_ESP32C6DEVKITC1]
platform = https://github.com/tasmota/platform-espressif32/releases/download/2024.06.11/platform-espressif32.zip
custom_slime_board = BOARD_ESP32C6DEVKITC1
build_flags =
${env.build_flags}
-DESP32C6
-DARDUINO_USB_MODE=1
-DARDUINO_USB_CDC_ON_BOOT=1
board = esp32-c6-devkitc-1
[env:BOARD_XIAO_ESP32C3]
platform = espressif32 @ 6.7.0
platform_packages =
framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git#3.0.1
framework-arduinoespressif32-libs @ https://github.com/espressif/arduino-esp32/releases/download/3.0.1/esp32-arduino-libs-3.0.1.zip
custom_slime_board = BOARD_XIAO_ESP32C3
build_flags =
${env.build_flags}
-DESP32C3
board = seeed_xiao_esp32c3
monitor_filters = colorize, esp32_exception_decoder
[env:BOARD_ESP32S3_SUPERMINI]
platform = espressif32 @ 6.7.0
platform_packages =
framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git#3.0.1
framework-arduinoespressif32-libs @ https://github.com/espressif/arduino-esp32/releases/download/3.0.1/esp32-arduino-libs-3.0.1.zip
custom_slime_board = BOARD_ESP32S3_SUPERMINI
build_flags =
${env.build_flags}
-DARDUINO_USB_MODE=1
-DESP32S3
board = esp32s3_supermini
board_upload.use_1200bps_touch = 1
board_upload.wait_for_upload_port = 1
board_upload.require_upload_port = 1
upload_speed = 921600
[env:BOARD_SLIMEVR]
platform = espressif8266 @ 4.2.1
board = esp12e
custom_slime_board = BOARD_SLIMEVR
build_flags =
${env.build_flags}
-D VENDOR_NAME='"SlimeVR"'
-D VENDOR_URL='"https://slimevr.dev"'
-D PRODUCT_NAME='"SlimeVR Tracker"'
-D UPDATE_ADDRESS='"SlimeVR/SlimeVR-Tracker-ESP"'
-D UPDATE_NAME='"BOARD_SLIMEVR-firmware"'
[env:BOARD_SLIMEVR_V1_2]
platform = espressif8266 @ 4.2.1
board = esp12e
custom_slime_board = BOARD_SLIMEVR_V1_2
build_flags =
${env.build_flags}
-D VENDOR_NAME='"SlimeVR"'
-D VENDOR_URL='"https://slimevr.dev"'
-D PRODUCT_NAME='"SlimeVR Tracker v1.2"'
-D UPDATE_ADDRESS='"SlimeVR/SlimeVR-Tracker-ESP"'
-D UPDATE_NAME='"BOARD_SLIMEVR_V1_2-firmware"'
[env:BOARD_SLIMEVR_DEV]
platform = espressif8266 @ 4.2.1
board = esp12e
custom_slime_board = BOARD_SLIMEVR_DEV
build_flags =
${env.build_flags}
-D VENDOR_NAME='"SlimeVR"'
-D PRODUCT_NAME='"SlimeVR Tracker (dev)"'
[env:BOARD_GLOVE_IMU_SLIMEVR_DEV]
platform = espressif32 @ 6.7.0
platform_packages =
framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git#3.0.1
framework-arduinoespressif32-libs @ https://github.com/espressif/arduino-esp32/releases/download/3.0.1/esp32-arduino-libs-3.0.1.zip
build_flags =
${env.build_flags}
-D BOARD=BOARD_GLOVE_IMU_SLIMEVR_DEV
-DESP32C3
-D PRODUCT_NAME='"SlimeVR Glove (dev)"'
board = lolin_c3_mini
monitor_filters = colorize, esp32_exception_decoder

View File

@@ -36,7 +36,10 @@ except Exception:
output = f"-DGIT_REV='\"{revision}\"'"
if tag != "":
fwVersion = os.environ.get("FIRMWARE_VERSION")
if fwVersion is not None and fwVersion != "":
output += f" -DFIRMWARE_VERSION='\"{fwVersion}\"'"
elif tag != "":
output += f" -DFIRMWARE_VERSION='\"{tag}\"'"
elif branch != "":
output += f" -DFIRMWARE_VERSION='\"{branch}\"'"

210
scripts/preprocessor.py Normal file
View File

@@ -0,0 +1,210 @@
import json
import re
import os
from pathlib import Path
from typing import Union, Optional, Dict, Any, List
Import("env")
try:
import jsonschema
except:
env.Execute(
env.VerboseAction(
'$PYTHONEXE -m pip install "jsonschema==4.22.0"',
"Installing jsonschema for validation",
)
)
from jsonschema import Draft202012Validator, exceptions as jsonschema_exceptions
def _load_json(maybe_path_or_dict: Union[str, Path, dict]) -> dict:
"""Load JSON file or accept dict directly."""
if isinstance(maybe_path_or_dict, dict):
return maybe_path_or_dict
p = Path(maybe_path_or_dict)
if not p.exists():
raise FileNotFoundError(f"File not found: {p}")
try:
return json.loads(p.read_text(encoding="utf-8"))
except json.JSONDecodeError as e:
raise ValueError(f"Invalid JSON file {p}: {e}")
# Allow:
# - unquoted alphanumerics, underscores, dots, dashes
# - or single-quoted with optional escaped double quotes inside
VALID_DEFINE_VALUE = re.compile(
r"^(?:[A-Za-z0-9_.\-]*|'(\\\"[A-Za-z0-9_.\-\s]*\\\"|[A-Za-z0-9_.\-\s]*)')$"
)
def _validate_define_value(value: str, key: str) -> None:
if key == "SENSOR_DESC_LIST":
return
"""Validate the formatted define value to prevent injection."""
if not VALID_DEFINE_VALUE.fullmatch(value):
raise ValueError(
f"Invalid characters in value for {key!r}: {value!r} "
"(only letters, digits, _, ., -, spaces, and optional quoted forms like '\"text\"' allowed)"
)
def _format_raw_value(value: Any) -> str:
"""Format booleans for C/C++, otherwise str(value)."""
if isinstance(value, bool):
return "true" if value else "false"
return str(value)
def format_value(val: Any, typ: str, key: str = "<unknown>") -> str:
"""Format a value according to type, with built-in validation."""
if typ == "pin":
if isinstance(val, str) and re.search(r"[AD]", val):
result = f'{val}'
else:
result = _format_raw_value(val)
elif typ == "string":
result = f"'\\\"{val}\\\"'"
elif typ in ("raw", "number"):
result = _format_raw_value(val)
else:
raise ValueError(f"Value type '{typ}' is not supported")
_validate_define_value(result, key)
return result
def _build_board_flags(defaults: dict, board_name: str) -> List[str]:
"""Construct list of -D flags for one board."""
if "defaults" not in defaults:
raise ValueError("Missing top-level 'defaults' key in defaults JSON.")
if board_name not in defaults["defaults"]:
raise ValueError(f"Invalid board selected - {board_name}")
board_defaults = defaults["defaults"][board_name]
values = board_defaults.get("values", {})
args: Dict[str, Dict[str, Any]] = {}
def add(key: str, value: Any, value_type: str):
if value is not None:
args[key] = {"value": value, "type": value_type}
add('BOARD', board_name, 'raw')
add('LED_PIN', values.get('LED').get('LED_PIN'), 'pin')
add('LED_INVERTED', values.get('LED').get('LED_INVERTED'), 'raw')
sensors = values.get('SENSORS')
if sensors:
sensor_list = []
add('PIN_IMU_SDA', 255, 'pin') # FIXME fix the I2C Scanner so it use the sensor list and not be called when no I2C sensor
add('PIN_IMU_SCL', 255, 'pin')
add('PIN_IMU_INT_2', 255, 'pin') # FIXME: fix the CONFIG serial command so it use the sensor list
for index, sensor in enumerate(sensors):
if sensor.get('protocol') == 'I2C':
params = [
format_value(sensor.get('imu'), 'raw'),
format_value(sensor.get('address', 'PRIMARY_IMU_ADDRESS_ONE' if index == 0 else 'SECONDARY_IMU_ADDRESS_TWO'), 'number'),
format_value(sensor.get('rotation'), 'raw'),
f"DIRECT_WIRE({format_value(sensor.get('scl'), 'pin')}, {format_value(sensor.get('sda'), 'pin')})",
'false' if index == 0 else 'true',
f"DIRECT_PIN({format_value(sensor.get('int', 255), 'pin')})",
'0'
]
sensor_list.append(f"SENSOR_DESC_ENTRY({','.join(params)})")
add('PIN_IMU_SDA', sensor.get('sda'), 'pin')
add('PIN_IMU_SCL', sensor.get('scl'), 'pin')
if sensor.get('protocol') == 'SPI':
params = [
format_value(sensor.get('imu'), 'raw'),
f"DIRECT_PIN({format_value(sensor.get('cs'), 'pin')})",
format_value(sensor.get('rotation'), 'raw'),
"DIRECT_SPI(24'000'000, MSBFIRST, SPI_MODE3)",
'false' if index == 0 else 'true',
f"DIRECT_PIN({format_value(sensor.get('int', 255), 'pin')})",
'0'
]
sensor_list.append(f"SENSOR_DESC_ENTRY({','.join(params)})")
if index == 0: # FIXME: fix the CONFIG serial command so it use the sensor list
add('PIN_IMU_INT', sensor.get('int'), 'pin')
elif index == 1:
add('PIN_IMU_INT_2', sensor.get('int'), 'pin')
add('SENSOR_DESC_LIST', f"'{' '.join(sensor_list)}'", 'raw')
battery = values.get('BATTERY')
if battery:
add('BATTERY_MONITOR', battery.get('type'), 'raw')
add('PIN_BATTERY_LEVEL', battery.get('pin', 255), 'pin')
add('BATTERY_SHIELD_RESISTANCE', battery.get('shieldR', 180), 'number')
add('BATTERY_SHIELD_R1', battery.get('r1', 100), 'number')
add('BATTERY_SHIELD_R2', battery.get('r2', 220), 'number')
parts: List[str] = []
for key, meta in args.items():
formatted = format_value(meta["value"], meta["type"], key)
parts.append(f"-D{key}={formatted}")
return parts
def build_boards(
schema_obj,
defaults_obj,
board_name: Optional[str] = None,
) -> Dict[str, List[str]]:
"""
Validate defaults.json against board-defaults.schema.json using jsonschema,
and return { board_name: [list of -D flags] }.
"""
validator = Draft202012Validator(schema_obj)
errors = sorted(validator.iter_errors(defaults_obj), key=lambda e: e.path)
if errors:
print("✖ JSON Schema validation failed:")
for err in errors:
path = "/".join(map(str, err.path)) or "(root)"
print(f" • Path: {path}")
print(f" Error: {err.message}")
if err.context:
for ctx in err.context:
print(f"{ctx.message}")
raise ValueError(f"{len(errors)} schema validation errors found.")
out: Dict[str, List[str]] = {}
if board_name:
out[board_name] = _build_board_flags(defaults_obj, board_name)
else:
for name in defaults_obj.get("defaults", {}).keys():
out[name] = _build_board_flags(defaults_obj, name)
return out
schema_obj = _load_json("./board-defaults.schema.json")
defaults_obj = _load_json("./board-defaults.json")
slime_board = env.GetProjectOption("custom_slime_board", None)
if slime_board:
if 'SLIMEVR_OVERRIDE_DEFAULTS' in os.environ and slime_board in defaults_obj['defaults']:
print(">>> OVERIDING BOARD DEFAULTS ", os.environ['SLIMEVR_OVERRIDE_DEFAULTS'])
defaults_obj['defaults'][slime_board]['values'] = json.loads(os.environ['SLIMEVR_OVERRIDE_DEFAULTS'])
output_flags = build_boards(
schema_obj,
defaults_obj,
slime_board,
)
output_flags = output_flags.get(slime_board, []) if isinstance(output_flags, dict) else []
separator = '\n '
print(f">>> Appending build flags:\n {separator.join(output_flags)}")
env.Append(BUILD_FLAGS=output_flags)
else:
print(">>> custom_slime_board not set - skipping")

View File

@@ -28,6 +28,8 @@
#include "configuration/Configuration.h"
#include "network/connection.h"
#include "network/manager.h"
#include "network/wifihandler.h"
#include "network/wifiprovisioning.h"
#include "sensors/SensorManager.h"
#include "status/LEDManager.h"
#include "status/StatusManager.h"
@@ -40,3 +42,5 @@ extern SlimeVR::Sensors::SensorManager sensorManager;
extern SlimeVR::Network::Manager networkManager;
extern SlimeVR::Network::Connection networkConnection;
extern BatteryMonitor battery;
extern SlimeVR::WiFiNetwork wifiNetwork;
extern SlimeVR::WifiProvisioning wifiProvisioning;

View File

@@ -27,166 +27,6 @@
#include "defines_helpers.h"
// Board-specific configurations
#if BOARD == BOARD_SLIMEVR
SDA(14)
SCL(12)
INT(16)
INT2(13)
BATTERY(17)
LED(2)
INVERTED_LED(true)
BATTERY_SHIELD_R(0)
BATTERY_R1(10)
BATTERY_R2(40.2)
#elif BOARD == BOARD_SLIMEVR_V1_2
SDA(4)
SCL(5)
INT(2)
INT2(16)
BATTERY(17)
LED(2)
INVERTED_LED(true)
BATTERY_SHIELD_R(0)
BATTERY_R1(10)
BATTERY_R2(40.2)
#elif BOARD == BOARD_SLIMEVR_LEGACY || BOARD == BOARD_SLIMEVR_DEV
SDA(4)
SCL(5)
INT(10)
INT2(13)
BATTERY(17)
LED(2)
INVERTED_LED(true)
BATTERY_SHIELD_R(0)
BATTERY_R1(10)
BATTERY_R2(40.2)
#elif BOARD == BOARD_NODEMCU || BOARD == BOARD_WEMOSD1MINI
SDA(D2)
SCL(D1)
INT(D5)
INT2(D6)
BATTERY(A0)
BATTERY_SHIELD_R(180)
BATTERY_R1(100)
BATTERY_R2(220)
#elif BOARD == BOARD_ESP01
SDA(2)
SCL(0)
INT(255)
INT2(255)
BATTERY(255)
LED(LED_OFF)
INVERTED_LED(false)
#elif BOARD == BOARD_TTGO_TBASE
SDA(5)
SCL(4)
INT(14)
INT2(13)
BATTERY(A0)
#elif BOARD == BOARD_CUSTOM
// Define pins by the examples above
#elif BOARD == BOARD_WROOM32
SDA(21)
SCL(22)
INT(23)
INT2(25)
BATTERY(36)
#elif BOARD == BOARD_LOLIN_C3_MINI
SDA(5)
SCL(4)
INT(6)
INT2(8)
BATTERY(3)
LED(7)
#elif BOARD == BOARD_BEETLE32C3
SDA(8)
SCL(9)
INT(6)
INT2(7)
BATTERY(3)
LED(10)
INVERTED_LED(false)
#elif BOARD == BOARD_ESP32C3DEVKITM1 || BOARD == BOARD_ESP32C6DEVKITC1
SDA(5)
SCL(4)
INT(6)
INT2(7)
BATTERY(3)
LED(LED_OFF)
#elif BOARD == BOARD_WEMOSWROOM02
SDA(2)
SCL(14)
INT(0)
INT2(4)
BATTERY(A0)
LED(16)
INVERTED_LED(true)
#elif BOARD == BOARD_XIAO_ESP32C3
SDA(6)
SCL(7) // D5
INT(5) // D3
INT2(10) // D10
LED(4) // D2
INVERTED_LED(false)
BATTERY(2) // D0 / A0
BATTERY_SHIELD_R(0)
BATTERY_R1(100)
BATTERY_R2(100)
#elif BOARD == BOARD_GLOVE_IMU_SLIMEVR_DEV
SDA(1)
SCL(0)
#define PCA_ADDR 0x70
INT(16)
INT2(13)
BATTERY(3)
LED(2)
INVERTED_LED(true)
BATTERY_SHIELD_R(0)
BATTERY_R1(10)
BATTERY_R2(40.2)
#elif BOARD == BOARD_ESP32S3_SUPERMINI
SDA(7)
SCL(6)
INT(5)
INT2(4)
BATTERY(A2) // IO3
BATTERY_SHIELD_R(0)
BATTERY_R1(10)
BATTERY_R2(40.2)
LED(LED_BUILTIN)
#endif
// Default IMU pinouts and definitions for default tracker types
#if BOARD != BOARD_GLOVE_IMU_SLIMEVR_DEV
@@ -253,6 +93,18 @@ PIN_IMU_SDA, PRIMARY_IMU_OPTIONAL, BMI160_QMC_REMAP) \
#endif
#else // BOARD == BOARD_GLOVE_IMU_SLIMEVR_DEV
SDA(1)
SCL(0)
#define PCA_ADDR 0x70
INT(16)
INT2(13)
BATTERY(3)
LED(2)
INVERTED_LED(true)
BATTERY_SHIELD_R(0)
BATTERY_R1(10)
BATTERY_R2(40.2)
#include "glove_default.h"
#endif // BOARD != BOARD_GLOVE_IMU_SLIMEVR_DEV

View File

@@ -29,5 +29,10 @@
#define LED_BUILTIN LED_OFF
#endif
#ifndef LED_PIN
extern const uint8_t __attribute__((weak)) LED_PIN = LED_BUILTIN;
#endif
#ifndef LED_INVERTED
extern const bool __attribute__((weak)) LED_INVERTED = true;
#endif

View File

@@ -25,13 +25,18 @@
#include <LittleFS.h>
#include <cstdint>
#include <cstring>
#include "../FSHelper.h"
#include "consts.h"
#include "sensors/SensorToggles.h"
#include "utils.h"
#define DIR_CALIBRATIONS "/calibrations"
#define DIR_TEMPERATURE_CALIBRATIONS "/tempcalibrations"
#define DIR_TOGGLES "/toggles"
#define DIR_TOGGLES_OLD "/toggles"
#define DIR_TOGGLES "/sensortoggles"
namespace SlimeVR::Configuration {
void Configuration::setup() {
@@ -118,13 +123,21 @@ void Configuration::save() {
file.write((uint8_t*)&config, sizeof(SensorConfig));
file.close();
sprintf(path, DIR_TOGGLES "/%zu", i);
if (i < m_SensorToggles.size()) {
sprintf(path, DIR_TOGGLES "/%zu", i);
m_Logger.trace("Saving sensor toggle state for %d", i);
m_Logger.trace("Saving sensor toggle state for %d", i);
file = LittleFS.open(path, "w");
file.write((uint8_t*)&m_SensorToggles[i], sizeof(SensorToggleState));
file.close();
file = LittleFS.open(path, "w");
auto toggleValues = m_SensorToggles[i].getValues();
file.write((uint8_t*)&toggleValues, sizeof(SensorToggleValues));
file.close();
} else {
m_Logger.trace(
"Skipping saving toggles for sensor %d, no toggles present",
i
);
}
}
{
@@ -133,6 +146,18 @@ void Configuration::save() {
file.close();
}
// Clean up old toggles directory
if (LittleFS.exists(DIR_TOGGLES_OLD)) {
char path[17] = DIR_TOGGLES_OLD;
char* end = path + strlen(DIR_TOGGLES_OLD);
Utils::forEachFile(DIR_TOGGLES_OLD, [&](SlimeVR::Utils::File file) {
sprintf(end, "/%s", file.name());
LittleFS.remove(path);
file.close();
});
LittleFS.rmdir(DIR_TOGGLES_OLD);
}
m_Logger.debug("Saved configuration");
}
@@ -226,14 +251,34 @@ void Configuration::loadSensors() {
setSensor(sensorId, sensorConfig);
});
if (LittleFS.exists(DIR_TOGGLES_OLD)) {
SlimeVR::Utils::forEachFile(DIR_TOGGLES_OLD, [&](SlimeVR::Utils::File f) {
SensorToggleValues values;
// Migration for pre 0.7.0 togglestate, the values started at offset 20 and
// there were 3 of them
f.seek(20);
f.read(reinterpret_cast<uint8_t*>(&values), 3);
uint8_t sensorId = strtoul(f.name(), nullptr, 10);
m_Logger.debug("Found sensor toggle state at index %d", sensorId);
setSensorToggles(sensorId, SensorToggleState{values});
});
}
SlimeVR::Utils::forEachFile(DIR_TOGGLES, [&](SlimeVR::Utils::File f) {
SensorToggleState sensorToggleState;
f.read((uint8_t*)&sensorToggleState, sizeof(SensorToggleState));
if (f.size() > sizeof(SensorToggleValues)) {
return;
}
SensorToggleValues values;
// With the magic of C++ default initialization, the rest of the values should
// be their default after reading
f.read(reinterpret_cast<uint8_t*>(&values), f.size());
uint8_t sensorId = strtoul(f.name(), nullptr, 10);
m_Logger.debug("Found sensor toggle state at index %d", sensorId);
setSensorToggles(sensorId, sensorToggleState);
setSensorToggles(sensorId, SensorToggleState{values});
});
}

View File

@@ -39,6 +39,8 @@
// disable if problems. Server does nothing with value so disabled atm
#define SEND_ACCELERATION true // send linear acceleration to the server
#define EXT_SERIAL_COMMANDS false // Set to true to enable extra serial debug commands
// Debug information
#define LOG_LEVEL LOG_LEVEL_DEBUG
@@ -94,7 +96,7 @@
// Not recommended for production
#define ENABLE_INSPECTION false
#define PROTOCOL_VERSION 21
#define PROTOCOL_VERSION 22
#ifndef FIRMWARE_VERSION
#define FIRMWARE_VERSION "UNKNOWN"

View File

@@ -53,6 +53,10 @@
#define EXPERIMENTAL_BNO_DISABLE_ACCEL_CALIBRATION true
#endif
#ifndef IMU_USE_EXTERNAL_CLOCK
#define IMU_USE_EXTERNAL_CLOCK true // Use external clock for IMU (ICM-45686 only)
#endif
#ifndef VENDOR_NAME
#define VENDOR_NAME "Unknown"
#endif

110
src/logging/LogBuffer.cpp Normal file
View File

@@ -0,0 +1,110 @@
#include "LogBuffer.h"
namespace SlimeVR::Logging {
bool LogBuffer::addLog(const char* message) {
size_t len = strlen(message);
// Need space for message + null terminator + length prefix (2 bytes)
size_t required = len + 3;
if (required > getAvailableSpace()) {
// Buffer full, drop the message or overwrite oldest
return false;
}
// Write length as 2-byte prefix (little endian)
m_Buffer[m_WritePos] = (len >> 8) & 0xFF;
m_WritePos = (m_WritePos + 1) % MAX_BUFFER_SIZE;
m_Buffer[m_WritePos] = len & 0xFF;
m_WritePos = (m_WritePos + 1) % MAX_BUFFER_SIZE;
// Write message
for (size_t i = 0; i < len; i++) {
m_Buffer[m_WritePos] = message[i];
m_WritePos = (m_WritePos + 1) % MAX_BUFFER_SIZE;
}
// Write null terminator
m_Buffer[m_WritePos] = '\0';
m_WritePos = (m_WritePos + 1) % MAX_BUFFER_SIZE;
return true;
}
void LogBuffer::processCycle() {
size_t bytesWritten = 0;
while (m_WritePos != m_ReadPos && bytesWritten < MAX_BYTES_PER_CYCLE) {
size_t msgLen = getNextMessageLength();
if (msgLen == 0 || bytesWritten + msgLen > MAX_BYTES_PER_CYCLE) {
// Message too large for this cycle or corrupted
break;
}
// Skip length prefix
m_ReadPos = (m_ReadPos + 2) % MAX_BUFFER_SIZE;
// Read and print message
for (size_t i = 0; i < msgLen; i++) {
Serial.write(m_Buffer[m_ReadPos]);
m_ReadPos = (m_ReadPos + 1) % MAX_BUFFER_SIZE;
}
// Skip null terminator
m_ReadPos = (m_ReadPos + 1) % MAX_BUFFER_SIZE;
bytesWritten += msgLen;
}
}
size_t LogBuffer::getPendingBytes() const {
if (m_WritePos >= m_ReadPos) {
return m_WritePos - m_ReadPos;
} else {
return MAX_BUFFER_SIZE - m_ReadPos + m_WritePos;
}
}
void LogBuffer::flushAll() {
while (m_WritePos != m_ReadPos) {
size_t msgLen = getNextMessageLength();
if (msgLen == 0) {
break; // Corrupted buffer
}
// Skip length prefix
m_ReadPos = (m_ReadPos + 2) % MAX_BUFFER_SIZE;
// Read and print message
for (size_t i = 0; i < msgLen; i++) {
Serial.write(m_Buffer[m_ReadPos]);
m_ReadPos = (m_ReadPos + 1) % MAX_BUFFER_SIZE;
}
// Skip null terminator
m_ReadPos = (m_ReadPos + 1) % MAX_BUFFER_SIZE;
}
}
size_t LogBuffer::getAvailableSpace() const {
size_t used = getPendingBytes();
return MAX_BUFFER_SIZE - used - 1; // -1 to distinguish full from empty
}
size_t LogBuffer::getNextMessageLength() const {
if (m_WritePos == m_ReadPos) {
return 0;
}
size_t pos = m_ReadPos;
uint8_t highByte = m_Buffer[pos];
pos = (pos + 1) % MAX_BUFFER_SIZE;
uint8_t lowByte = m_Buffer[pos];
return (highByte << 8) | lowByte;
}
} // namespace SlimeVR::Logging

46
src/logging/LogBuffer.h Normal file
View File

@@ -0,0 +1,46 @@
#ifndef LOGGING_LOGBUFFER_H
#define LOGGING_LOGBUFFER_H
#include <Arduino.h>
namespace SlimeVR::Logging {
class LogBuffer {
public:
static constexpr size_t MAX_BUFFER_SIZE = 4096; // Total buffer size
static constexpr size_t MAX_BYTES_PER_CYCLE = 512; // Max bytes to output per cycle
static LogBuffer& getInstance() {
static LogBuffer instance;
return instance;
}
// Add a log message to the buffer
bool addLog(const char* message);
// Process buffered logs up to MAX_BYTES_PER_CYCLE
void processCycle();
// Check if there are pending logs
bool hasPendingLogs() const { return m_WritePos != m_ReadPos; }
// Get number of bytes pending
size_t getPendingBytes() const;
// Force flush all pending logs (for critical errors or shutdown)
void flushAll();
private:
LogBuffer() : m_Buffer{0}, m_WritePos(0), m_ReadPos(0) {}
char m_Buffer[MAX_BUFFER_SIZE];
volatile size_t m_WritePos;
volatile size_t m_ReadPos;
size_t getAvailableSpace() const;
size_t getNextMessageLength() const;
};
} // namespace SlimeVR::Logging
#endif

View File

@@ -63,6 +63,23 @@ void Logger::log(Level level, const char* format, va_list args) const {
strcat(buf, m_Tag);
}
Serial.printf("[%-5s] [%s] %s\n", levelToString(level), buf, buffer);
// Format complete log message
char fullMessage[384]; // Enough for header + message
snprintf(fullMessage, sizeof(fullMessage), "[%-5s] [%s] %s\n", levelToString(level), buf, buffer);
// Add to buffer or write directly for critical messages
if (level >= ERROR) {
// Critical messages: write immediately and flush buffer
LogBuffer::getInstance().flushAll();
Serial.print(fullMessage);
} else {
// Normal messages: add to buffer
if (!LogBuffer::getInstance().addLog(fullMessage)) {
// Buffer full, try to make space
LogBuffer::getInstance().processCycle();
// Try again
LogBuffer::getInstance().addLog(fullMessage);
}
}
}
} // namespace SlimeVR::Logging

View File

@@ -4,6 +4,7 @@
#include <Arduino.h>
#include "Level.h"
#include "LogBuffer.h"
#include "debug.h"
namespace SlimeVR::Logging {
@@ -79,12 +80,20 @@ private:
strcat(buf, m_Tag);
}
Serial.printf("[%-5s] [%s] %s", levelToString(level), buf, str);
// Build array log message
char header[256];
snprintf(header, sizeof(header), "[%-5s] [%s] %s", levelToString(level), buf, str);
// For arrays, we'll output directly to avoid excessive buffering
// But for critical levels, flush first
if (level >= ERROR) {
LogBuffer::getInstance().flushAll();
}
Serial.print(header);
for (size_t i = 0; i < size; i++) {
Serial.print(array[i]);
}
Serial.println();
}

View File

@@ -42,6 +42,8 @@ SlimeVR::Status::StatusManager statusManager;
SlimeVR::Configuration::Configuration configuration;
SlimeVR::Network::Manager networkManager;
SlimeVR::Network::Connection networkConnection;
SlimeVR::WiFiNetwork wifiNetwork;
SlimeVR::WifiProvisioning wifiProvisioning;
#if DEBUG_MEASURE_SENSOR_TIME_TAKEN
SlimeVR::Debugging::TimeTakenMeasurer sensorMeasurer{"Sensors"};
@@ -157,6 +159,9 @@ void loop() {
OTA::otaUpdate();
networkManager.update();
// Process buffered logs
SlimeVR::Logging::LogBuffer::getInstance().processCycle();
#if DEBUG_MEASURE_SENSOR_TIME_TAKEN
sensorMeasurer.before();
#endif

View File

@@ -620,6 +620,9 @@ void Connection::reset() {
m_UDP.begin(m_ServerPort);
// Reset server address to broadcast if disconnected
m_ServerHost = IPAddress(255, 255, 255, 255);
statusManager.setStatus(SlimeVR::Status::SERVER_CONNECTING, true);
}
@@ -650,6 +653,9 @@ void Connection::update() {
);
m_Logger.warn("Connection to server timed out");
// Reset server address to broadcast if disconnected
m_ServerHost = IPAddress(255, 255, 255, 255);
return;
}
@@ -658,7 +664,6 @@ void Connection::update() {
return;
}
m_LastPacketTimestamp = millis();
int len = m_UDP.read(m_Packet, sizeof(m_Packet));
#ifdef DEBUG_NETWORK
@@ -673,6 +678,12 @@ void Connection::update() {
(void)packetSize;
#endif
if (static_cast<ReceivePacketType>(m_Packet[3]) == ReceivePacketType::Handshake) {
m_Logger.warn("Handshake received again, ignoring");
return;
}
m_LastPacketTimestamp = millis();
switch (static_cast<ReceivePacketType>(m_Packet[3])) {
case ReceivePacketType::HeartBeat:
sendHeartbeat();
@@ -682,8 +693,7 @@ void Connection::update() {
break;
case ReceivePacketType::Handshake:
// Assume handshake successful
m_Logger.warn("Handshake received again, ignoring");
// handled above
break;
case ReceivePacketType::Command:

View File

@@ -26,14 +26,14 @@
namespace SlimeVR::Network {
void Manager::setup() { ::WiFiNetwork::setUp(); }
void Manager::setup() { wifiNetwork.setUp(); }
void Manager::update() {
WiFiNetwork::upkeep();
wifiNetwork.upkeep();
auto wasConnected = m_IsConnected;
m_IsConnected = ::WiFiNetwork::isConnected();
m_IsConnected = wifiNetwork.isConnected();
if (!m_IsConnected) {
return;

View File

@@ -54,6 +54,7 @@ enum class SendPacketType : uint8_t {
// RotationAcceleration = 23,
AcknowledgeConfigChange = 24,
FlexData = 26,
// PositionData = 27,
Bundle = 100,
Inspection = 105,
};

View File

@@ -20,31 +20,25 @@
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#include "network/wifihandler.h"
#include "GlobalVars.h"
#include "globals.h"
#include "logging/Logger.h"
#if !ESP8266
#include "esp_wifi.h"
#include "esp_wifi_types.h"
#endif
unsigned long lastWifiReportTime = 0;
unsigned long wifiConnectionTimeout = millis();
bool isWifiConnected = false;
uint8_t wifiState = SLIME_WIFI_NOT_SETUP;
bool hadWifi = false;
unsigned long last_rssi_sample = 0;
namespace SlimeVR {
// TODO: Cleanup with proper classes
SlimeVR::Logging::Logger wifiHandlerLogger("WiFiHandler");
void reportWifiError() {
void WiFiNetwork::reportWifiProgress() {
if (lastWifiReportTime + 1000 < millis()) {
lastWifiReportTime = millis();
Serial.print(".");
}
}
void setStaticIPIfDefined() {
void WiFiNetwork::setStaticIPIfDefined() {
#ifdef WIFI_USE_STATICIP
const IPAddress ip(WIFI_STATIC_IP);
const IPAddress gateway(WIFI_STATIC_GATEWAY);
@@ -53,16 +47,17 @@ void setStaticIPIfDefined() {
#endif
}
bool WiFiNetwork::isConnected() { return isWifiConnected; }
bool WiFiNetwork::isConnected() const {
return wifiState == WiFiReconnectionStatus::Success;
}
void WiFiNetwork::setWiFiCredentials(const char* SSID, const char* pass) {
stopProvisioning();
setStaticIPIfDefined();
WiFi.begin(SSID, pass);
wifiProvisioning.stopProvisioning();
tryConnecting(false, SSID, pass);
retriedOnG = false;
// Reset state, will get back into provisioning if can't connect
hadWifi = false;
wifiState = SLIME_WIFI_SERVER_CRED_ATTEMPT;
wifiConnectionTimeout = millis();
wifiState = WiFiReconnectionStatus::ServerCredAttempt;
}
IPAddress WiFiNetwork::getAddress() { return WiFi.localIP(); }
@@ -71,25 +66,14 @@ void WiFiNetwork::setUp() {
wifiHandlerLogger.info("Setting up WiFi");
WiFi.persistent(true);
WiFi.mode(WIFI_STA);
#if ESP8266
#if USE_ATTENUATION
WiFi.setOutputPower(20.0 - ATTENUATION_N);
#endif
WiFi.setPhyMode(WIFI_PHY_MODE_11N);
#endif
WiFi.hostname("SlimeVR FBT Tracker");
wifiHandlerLogger.info(
"Loaded credentials for SSID '%s' and pass length %d",
WiFi.SSID().c_str(),
WiFi.psk().length()
getSSID().c_str(),
getPassword().length()
);
setStaticIPIfDefined();
wl_status_t status = WiFi.begin(
); // Should connect to last used access point, see
// https://arduino-esp8266.readthedocs.io/en/latest/esp8266wifi/station-class.html#begin
wifiHandlerLogger.debug("Status: %d", status);
wifiState = SLIME_WIFI_SAVED_ATTEMPT;
wifiConnectionTimeout = millis();
trySavedCredentials();
#if ESP8266
#if POWERSAVING_MODE == POWER_SAVING_NONE
@@ -121,156 +105,283 @@ void WiFiNetwork::setUp() {
#endif
}
void onConnected() {
WiFiNetwork::stopProvisioning();
void WiFiNetwork::onConnected() {
wifiState = WiFiReconnectionStatus::Success;
wifiProvisioning.stopProvisioning();
statusManager.setStatus(SlimeVR::Status::WIFI_CONNECTING, false);
isWifiConnected = true;
hadWifi = true;
wifiHandlerLogger.info(
"Connected successfully to SSID '%s', IP address %s",
WiFi.SSID().c_str(),
getSSID().c_str(),
WiFi.localIP().toString().c_str()
);
// Reset it, in case we just connected with server creds
}
uint8_t WiFiNetwork::getWiFiState() { return wifiState; }
String WiFiNetwork::getSSID() {
#if ESP8266
return WiFi.SSID();
#else
// Necessary, because without a WiFi.begin(), ESP32 is not kind enough to load the
// SSID on its own, for whatever reason
wifi_config_t wifiConfig;
esp_wifi_get_config((wifi_interface_t)ESP_IF_WIFI_STA, &wifiConfig);
return {reinterpret_cast<char*>(wifiConfig.sta.ssid)};
#endif
}
String WiFiNetwork::getPassword() {
#if ESP8266
return WiFi.psk();
#else
// Same as above
wifi_config_t wifiConfig;
esp_wifi_get_config((wifi_interface_t)ESP_IF_WIFI_STA, &wifiConfig);
return {reinterpret_cast<char*>(wifiConfig.sta.password)};
#endif
}
WiFiNetwork::WiFiReconnectionStatus WiFiNetwork::getWiFiState() { return wifiState; }
void WiFiNetwork::upkeep() {
upkeepProvisioning();
if (WiFi.status() != WL_CONNECTED) {
if (isWifiConnected) {
wifiHandlerLogger.warn("Connection to WiFi lost, reconnecting...");
isWifiConnected = false;
wifiProvisioning.upkeepProvisioning();
if (WiFi.status() == WL_CONNECTED) {
if (!isConnected()) {
onConnected();
return;
}
statusManager.setStatus(SlimeVR::Status::WIFI_CONNECTING, true);
reportWifiError();
if (wifiConnectionTimeout + 11000 < millis()) {
switch (wifiState) {
case SLIME_WIFI_NOT_SETUP: // Wasn't set up
return;
case SLIME_WIFI_SAVED_ATTEMPT: // Couldn't connect with first set of
// credentials
#if ESP8266
// Try again but with 11G but only if there are credentials,
// otherwise we just waste time before switching to hardcoded
// credentials.
if (WiFi.SSID().length() > 0) {
#if USE_ATTENUATION
WiFi.setOutputPower(20.0 - ATTENUATION_G);
#endif
WiFi.setPhyMode(WIFI_PHY_MODE_11G);
setStaticIPIfDefined();
WiFi.begin();
wifiConnectionTimeout = millis();
wifiHandlerLogger.error(
"Can't connect from saved credentials, status: %d.",
WiFi.status()
);
wifiHandlerLogger.debug(
"Trying saved credentials with PHY Mode G..."
);
} else {
wifiHandlerLogger.debug(
"Skipping PHY Mode G attempt on 0-length SSID..."
);
}
#endif
wifiState = SLIME_WIFI_SAVED_G_ATTEMPT;
return;
case SLIME_WIFI_SAVED_G_ATTEMPT: // Couldn't connect with first set of
// credentials with PHY Mode G
#if defined(WIFI_CREDS_SSID) && defined(WIFI_CREDS_PASSWD)
// Try hardcoded credentials now
#if ESP8266
#if USE_ATTENUATION
WiFi.setOutputPower(20.0 - ATTENUATION_N);
#endif
WiFi.setPhyMode(WIFI_PHY_MODE_11N);
#endif
setStaticIPIfDefined();
WiFi.begin(WIFI_CREDS_SSID, WIFI_CREDS_PASSWD);
wifiConnectionTimeout = millis();
wifiHandlerLogger.error(
"Can't connect from saved credentials, status: %d.",
WiFi.status()
);
wifiHandlerLogger.debug("Trying hardcoded credentials...");
#endif
wifiState = SLIME_WIFI_HARDCODE_ATTEMPT;
return;
case SLIME_WIFI_HARDCODE_ATTEMPT: // Couldn't connect with second set
// of credentials
#if defined(WIFI_CREDS_SSID) && defined(WIFI_CREDS_PASSWD) && ESP8266
// Try hardcoded credentials again,
// but with PHY Mode G
#if USE_ATTENUATION
WiFi.setOutputPower(20.0 - ATTENUATION_G);
#endif
WiFi.setPhyMode(WIFI_PHY_MODE_11G);
setStaticIPIfDefined();
WiFi.begin(WIFI_CREDS_SSID, WIFI_CREDS_PASSWD);
wifiConnectionTimeout = millis();
wifiHandlerLogger.error(
"Can't connect from saved credentials, status: %d.",
WiFi.status()
);
wifiHandlerLogger.debug(
"Trying hardcoded credentials with WiFi PHY Mode G..."
);
#endif
wifiState = SLIME_WIFI_HARDCODE_G_ATTEMPT;
return;
case SLIME_WIFI_SERVER_CRED_ATTEMPT: // Couldn't connect with
// server-sent credentials.
#if ESP8266
// Try again silently but with 11G
#if USE_ATTENUATION
WiFi.setOutputPower(20.0 - ATTENUATION_G);
#endif
WiFi.setPhyMode(WIFI_PHY_MODE_11G);
setStaticIPIfDefined();
WiFi.begin();
wifiConnectionTimeout = millis();
wifiState = SLIME_WIFI_SERVER_CRED_G_ATTEMPT;
#endif
return;
case SLIME_WIFI_HARDCODE_G_ATTEMPT: // Couldn't connect with second set
// of credentials with PHY Mode G.
case SLIME_WIFI_SERVER_CRED_G_ATTEMPT: // Or if couldn't connect with
// server-sent credentials
// Return to the default PHY Mode N.
#if ESP8266
#if USE_ATTENUATION
WiFi.setOutputPower(20.0 - ATTENUATION_N);
#endif
WiFi.setPhyMode(WIFI_PHY_MODE_11N);
#endif
// Start smart config
if (!hadWifi && !WiFi.smartConfigDone()
&& wifiConnectionTimeout + 11000 < millis()) {
if (WiFi.status() != WL_IDLE_STATUS) {
wifiHandlerLogger.error(
"Can't connect from any credentials, status: %d.",
WiFi.status()
);
wifiConnectionTimeout = millis();
}
startProvisioning();
}
return;
}
}
return;
}
if (!isWifiConnected) {
onConnected();
return;
} else {
if (millis() - last_rssi_sample >= 2000) {
last_rssi_sample = millis();
if (millis() - lastRssiSample >= 2000) {
lastRssiSample = millis();
uint8_t signalStrength = WiFi.RSSI();
networkConnection.sendSignalStrength(signalStrength);
}
return;
}
if (isConnected()) {
statusManager.setStatus(SlimeVR::Status::WIFI_CONNECTING, true);
wifiHandlerLogger.warn("Connection to WiFi lost, reconnecting...");
trySavedCredentials();
return;
}
if (wifiState != WiFiReconnectionStatus::Failed) {
reportWifiProgress();
}
if (millis() - wifiConnectionTimeout
< static_cast<uint32_t>(WiFiTimeoutSeconds * 1000)
&& WiFi.status() == WL_DISCONNECTED) {
return;
}
switch (wifiState) {
case WiFiReconnectionStatus::NotSetup: // Wasn't set up
return;
case WiFiReconnectionStatus::SavedAttempt: // Couldn't connect with
// first set of
// credentials
if (!trySavedCredentials()) {
tryHardcodedCredentials();
}
return;
case WiFiReconnectionStatus::HardcodeAttempt: // Couldn't connect with
// second set of credentials
if (!tryHardcodedCredentials()) {
wifiState = WiFiReconnectionStatus::Failed;
}
return;
case WiFiReconnectionStatus::ServerCredAttempt: // Couldn't connect with
// server-sent credentials.
if (!tryServerCredentials()) {
wifiState = WiFiReconnectionStatus::Failed;
}
return;
case WiFiReconnectionStatus::Failed: // Couldn't connect with second set of
// credentials or server credentials
// Return to the default PHY Mode N.
#if ESP8266
if constexpr (USE_ATTENUATION) {
WiFi.setOutputPower(20.0 - ATTENUATION_N);
}
WiFi.setPhyMode(WIFI_PHY_MODE_11N);
#endif
// Start smart config
if (!hadWifi && !WiFi.smartConfigDone()
&& millis() - wifiConnectionTimeout
>= static_cast<uint32_t>(WiFiTimeoutSeconds * 1000)) {
if (WiFi.status() != WL_IDLE_STATUS) {
wifiHandlerLogger.error(
"Can't connect from any credentials, error: %d, reason: %s.",
static_cast<int>(statusToFailure(WiFi.status())),
statusToReasonString(WiFi.status())
);
wifiConnectionTimeout = millis();
}
wifiProvisioning.startProvisioning();
}
return;
}
return;
}
const char* WiFiNetwork::statusToReasonString(wl_status_t status) {
switch (status) {
case WL_DISCONNECTED:
return "Timeout";
#ifdef ESP8266
case WL_WRONG_PASSWORD:
return "Wrong password";
case WL_CONNECT_FAILED:
return "Connection failed";
#elif ESP32
case WL_CONNECT_FAILED:
return "Wrong password";
#endif
case WL_NO_SSID_AVAIL:
return "SSID not found";
default:
return "Unknown";
}
}
WiFiNetwork::WiFiFailureReason WiFiNetwork::statusToFailure(wl_status_t status) {
switch (status) {
case WL_DISCONNECTED:
return WiFiFailureReason::Timeout;
#ifdef ESP8266
case WL_WRONG_PASSWORD:
return WiFiFailureReason::WrongPassword;
#elif ESP32
case WL_CONNECT_FAILED:
return WiFiFailureReason::WrongPassword;
#endif
case WL_NO_SSID_AVAIL:
return WiFiFailureReason::SSIDNotFound;
default:
return WiFiFailureReason::Unknown;
}
}
void WiFiNetwork::showConnectionAttemptFailed(const char* type) const {
wifiHandlerLogger.error(
"Can't connect from %s credentials, error: %d, reason: %s.",
type,
static_cast<int>(statusToFailure(WiFi.status())),
statusToReasonString(WiFi.status())
);
}
bool WiFiNetwork::trySavedCredentials() {
if (getSSID().length() == 0) {
wifiHandlerLogger.debug("Skipping saved credentials attempt on 0-length SSID..."
);
wifiState = WiFiReconnectionStatus::HardcodeAttempt;
return false;
}
if (wifiState == WiFiReconnectionStatus::SavedAttempt) {
showConnectionAttemptFailed("saved");
if (WiFi.status() != WL_DISCONNECTED) {
return false;
}
if (retriedOnG) {
return false;
}
retriedOnG = true;
wifiHandlerLogger.debug("Trying saved credentials with PHY Mode G...");
return tryConnecting(true);
}
retriedOnG = false;
wifiState = WiFiReconnectionStatus::SavedAttempt;
return tryConnecting();
}
bool WiFiNetwork::tryHardcodedCredentials() {
#if defined(WIFI_CREDS_SSID) && defined(WIFI_CREDS_PASSWD)
if (wifiState == WiFiReconnectionStatus::HardcodeAttempt) {
showConnectionAttemptFailed("hardcoded");
if (WiFi.status() != WL_DISCONNECTED) {
return false;
}
if (retriedOnG) {
return false;
}
retriedOnG = true;
wifiHandlerLogger.debug("Trying hardcoded credentials with PHY Mode G...");
// Don't need to save hardcoded credentials
WiFi.persistent(false);
auto result = tryConnecting(true, WIFI_CREDS_SSID, WIFI_CREDS_PASSWD);
WiFi.persistent(true);
return result;
}
retriedOnG = false;
wifiState = WiFiReconnectionStatus::HardcodeAttempt;
// Don't need to save hardcoded credentials
WiFi.persistent(false);
auto result = tryConnecting(false, WIFI_CREDS_SSID, WIFI_CREDS_PASSWD);
WiFi.persistent(true);
return result;
#else
wifiState = WiFiReconnectionStatus::HardcodeAttempt;
return false;
#endif
}
bool WiFiNetwork::tryServerCredentials() {
if (WiFi.status() != WL_DISCONNECTED) {
return false;
}
if (retriedOnG) {
return false;
}
retriedOnG = true;
return tryConnecting(true);
}
bool WiFiNetwork::tryConnecting(bool phyModeG, const char* SSID, const char* pass) {
#if ESP8266
if (phyModeG) {
WiFi.setPhyMode(WIFI_PHY_MODE_11G);
if constexpr (USE_ATTENUATION) {
WiFi.setOutputPower(20.0 - ATTENUATION_G);
}
} else {
WiFi.setPhyMode(WIFI_PHY_MODE_11N);
if constexpr (USE_ATTENUATION) {
WiFi.setOutputPower(20.0 - ATTENUATION_N);
}
}
#else
if (phyModeG) {
return false;
}
#endif
setStaticIPIfDefined();
if (SSID == nullptr) {
WiFi.begin();
} else {
WiFi.begin(SSID, pass);
}
wifiConnectionTimeout = millis();
return true;
}
} // namespace SlimeVR

View File

@@ -20,33 +20,78 @@
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#ifndef SLIMEVR_WIFI_H_
#define SLIMEVR_WIFI_H_
#pragma once
#include "logging/Logger.h"
#ifdef ESP8266
#include <ESP8266WiFi.h>
#else
#include <WiFi.h>
#endif
namespace WiFiNetwork {
bool isConnected();
void setUp();
void upkeep();
void setWiFiCredentials(const char* SSID, const char* pass);
IPAddress getAddress();
uint8_t getWiFiState();
} // namespace WiFiNetwork
namespace SlimeVR {
class WiFiNetwork {
public:
enum class WiFiReconnectionStatus {
NotSetup = 0,
SavedAttempt,
HardcodeAttempt,
ServerCredAttempt,
Failed,
Success
};
enum class WiFiFailureReason {
Timeout = 0,
SSIDNotFound = 1,
WrongPassword = 2,
Unknown = 3,
};
[[nodiscard]] bool isConnected() const;
void setUp();
void upkeep();
void setWiFiCredentials(const char* SSID, const char* pass);
static IPAddress getAddress();
WiFiReconnectionStatus getWiFiState();
private:
static constexpr float WiFiTimeoutSeconds = 11;
void reportWifiProgress();
void setStaticIPIfDefined();
void onConnected();
static String getSSID();
static String getPassword();
bool trySavedCredentials();
bool tryHardcodedCredentials();
bool tryServerCredentials();
bool tryConnecting(
bool phyModeG = false,
const char* SSID = nullptr,
const char* pass = nullptr
);
void showConnectionAttemptFailed(const char* type) const;
static const char* statusToReasonString(wl_status_t status);
static WiFiFailureReason statusToFailure(wl_status_t status);
unsigned long lastWifiReportTime = 0;
unsigned long wifiConnectionTimeout = millis();
bool isWifiConnected = false;
WiFiReconnectionStatus wifiState = WiFiReconnectionStatus::NotSetup;
bool retriedOnG = false;
bool hadWifi = false;
unsigned long lastRssiSample = 0;
uint8_t lastFailStatus = 0;
SlimeVR::Logging::Logger wifiHandlerLogger{"WiFiHandler"};
};
/** Wifi Reconnection Statuses **/
typedef enum {
SLIME_WIFI_NOT_SETUP = 0,
SLIME_WIFI_SAVED_ATTEMPT,
SLIME_WIFI_SAVED_G_ATTEMPT,
SLIME_WIFI_HARDCODE_ATTEMPT,
SLIME_WIFI_HARDCODE_G_ATTEMPT,
SLIME_WIFI_SERVER_CRED_ATTEMPT,
SLIME_WIFI_SERVER_CRED_G_ATTEMPT
} wifi_reconnection_statuses;
#endif // SLIMEVR_WIFI_H_
} // namespace SlimeVR

View File

@@ -29,29 +29,31 @@
// it sucks.
// TODO: New implementation: https://github.com/SlimeVR/SlimeVR-Tracker-ESP/issues/71
// TODO: Cleanup with proper classes
SlimeVR::Logging::Logger wifiProvisioningLogger("WiFiProvisioning");
bool provisioning = false;
namespace SlimeVR {
void WiFiNetwork::upkeepProvisioning() {
void WifiProvisioning::upkeepProvisioning() {
// Called even when not provisioning to do things like provide neighbours or other
// upkeep
}
void WiFiNetwork::startProvisioning() {
void WifiProvisioning::startProvisioning() {
if (WiFi.beginSmartConfig()) {
provisioning = true;
wifiProvisioningLogger.info("SmartConfig started");
}
}
void WiFiNetwork::stopProvisioning() {
void WifiProvisioning::stopProvisioning() {
WiFi.stopSmartConfig();
provisioning = false;
}
void WiFiNetwork::provideNeighbours() {
void WifiProvisioning::provideNeighbours() {
// TODO: SmartConfig can't do this, created for future
}
bool WiFiNetwork::isProvisioning() { return provisioning && !WiFi.smartConfigDone(); }
bool WifiProvisioning::isProvisioning() const {
return provisioning && !WiFi.smartConfigDone();
}
} // namespace SlimeVR

View File

@@ -23,12 +23,23 @@
#ifndef SLIMEVR_WIFIPROVISIONING_H_
#define SLIMEVR_WIFIPROVISIONING_H_
namespace WiFiNetwork {
void upkeepProvisioning();
void startProvisioning();
void stopProvisioning();
bool isProvisioning();
void provideNeighbours();
} // namespace WiFiNetwork
#include "logging/Logger.h"
namespace SlimeVR {
class WifiProvisioning {
public:
void upkeepProvisioning();
void startProvisioning();
void stopProvisioning();
bool isProvisioning() const;
void provideNeighbours();
private:
SlimeVR::Logging::Logger wifiProvisioningLogger{"WiFiProvisioning"};
bool provisioning = false;
};
} // namespace SlimeVR
#endif // SLIMEVR_WIFIPROVISIONING_H_

View File

@@ -1,15 +1,18 @@
#include "SensorToggles.h"
SensorToggleState::SensorToggleState(SensorToggleValues values)
: values{values} {}
void SensorToggleState::setToggle(SensorToggles toggle, bool state) {
switch (toggle) {
case SensorToggles::MagEnabled:
magEnabled = state;
values.magEnabled = state;
break;
case SensorToggles::CalibrationEnabled:
calibrationEnabled = state;
values.calibrationEnabled = state;
break;
case SensorToggles::TempGradientCalibrationEnabled:
tempGradientCalibrationEnabled = state;
values.tempGradientCalibrationEnabled = state;
break;
}
}
@@ -17,11 +20,11 @@ void SensorToggleState::setToggle(SensorToggles toggle, bool state) {
bool SensorToggleState::getToggle(SensorToggles toggle) const {
switch (toggle) {
case SensorToggles::MagEnabled:
return magEnabled;
return values.magEnabled;
case SensorToggles::CalibrationEnabled:
return calibrationEnabled;
return values.calibrationEnabled;
case SensorToggles::TempGradientCalibrationEnabled:
return tempGradientCalibrationEnabled;
return values.tempGradientCalibrationEnabled;
}
return false;
}
@@ -32,6 +35,8 @@ void SensorToggleState::onToggleChange(
this->callback = callback;
}
SensorToggleValues SensorToggleState::getValues() const { return values; }
void SensorToggleState::emitToggleChange(SensorToggles toggle, bool state) const {
if (callback) {
(*callback)(toggle, state);

View File

@@ -35,8 +35,17 @@ enum class SensorToggles : uint16_t {
TempGradientCalibrationEnabled = 3,
};
struct SensorToggleValues {
bool magEnabled = !USE_6_AXIS;
bool calibrationEnabled = true;
bool tempGradientCalibrationEnabled
= false; // disable by default, it is not clear that it really helps
};
class SensorToggleState {
public:
SensorToggleState() = default;
explicit SensorToggleState(SensorToggleValues values);
void setToggle(SensorToggles toggle, bool state);
[[nodiscard]] bool getToggle(SensorToggles toggle) const;
@@ -44,12 +53,12 @@ public:
static const char* toggleToString(SensorToggles toggle);
[[nodiscard]] SensorToggleValues getValues() const;
private:
std::optional<std::function<void(SensorToggles, bool)>> callback;
void emitToggleChange(SensorToggles toggle, bool state) const;
bool magEnabled = !USE_6_AXIS;
bool calibrationEnabled = true;
bool tempGradientCalibrationEnabled
= false; // disable by default, it is not clear that it really helps
SensorToggleValues values;
};

View File

@@ -33,6 +33,7 @@ SensorStatus Sensor::getSensorState() {
void Sensor::setAcceleration(Vector3 a) {
acceleration = a;
sensorOffset.sandwich(acceleration);
newAcceleration = true;
}

View File

@@ -87,6 +87,7 @@ public:
virtual const uint8_t* getMotionlessCalibrationData() = 0;
virtual void signalOverwhelmed() {}
virtual void provideAccelSample(const RawSensorT accelSample[3]) {}
virtual void provideGyroSample(const RawSensorT gyroSample[3]) {}
virtual void provideTempSample(float tempSample) {}

View File

@@ -25,18 +25,20 @@
#include <algorithm>
#include <array>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <limits>
#include "../../../sensorinterface/RegisterInterface.h"
#include "callbacks.h"
#include "vqf.h"
namespace SlimeVR::Sensors::SoftFusion::Drivers {
// Driver uses acceleration range at 4G
// and gyroscope range at 1000DPS
// Gyroscope ODR = 400Hz, accel ODR = 100Hz
// Gyroscope ODR = 200Hz, accel ODR = 100Hz
// Timestamps reading are not used
// Sensorhub to be implemented
@@ -46,7 +48,7 @@ struct BMI160 {
static constexpr auto Name = "BMI160";
static constexpr auto Type = SensorTypeID::BMI160;
static constexpr float GyrTs = 1.0 / 400.0;
static constexpr float GyrTs = 1.0 / 200.0;
static constexpr float AccTs = 1.0 / 100.0;
static constexpr float MagTs = 1.0 / 100;
@@ -57,14 +59,7 @@ struct BMI160 {
static constexpr float TemperatureZROChange
= 2.0f; // wow maybe BMI270 isn't that bad actually
static constexpr VQFParams SensorVQFParams{
// need to be refined, this IMU sucks
.motionBiasEstEnabled = true,
.biasSigmaInit = 0.5f,
.biasClip = 2.0f,
.restThGyr = 0.5f,
.restThAcc = 0.196f,
};
static constexpr VQFParams SensorVQFParams{};
RegisterInterface& m_RegisterInterface;
SlimeVR::Logging::Logger& m_Logger;
@@ -101,7 +96,7 @@ struct BMI160 {
struct GyrConf {
static constexpr uint8_t reg = 0x42;
static constexpr uint8_t value = 0b0101010; // 400Hz, filter mode normal
static constexpr uint8_t value = 0b0101001; // 200Hz, filter mode normal
};
struct GyrRange {
@@ -182,12 +177,7 @@ struct BMI160 {
return to_ret;
}
template <typename AccelCall, typename GyroCall, typename TempCall>
void bulkRead(
AccelCall&& processAccelSample,
GyroCall&& processGyroSample,
TempCall&& processTempSample
) {
bool bulkRead(DriverCallbacks<int16_t>&& callbacks) {
const auto fifo_bytes = m_RegisterInterface.readReg16(Regs::FifoLength) & 0x7FF;
const auto bytes_to_read = std::min(
@@ -218,7 +208,7 @@ struct BMI160 {
gyro[0] = getFromFifo<uint16_t>(i, read_buffer);
gyro[1] = getFromFifo<uint16_t>(i, read_buffer);
gyro[2] = getFromFifo<uint16_t>(i, read_buffer);
processGyroSample(gyro, GyrTs);
callbacks.processGyroSample(gyro, GyrTs);
}
if (header & Fifo::AccelDataBit) {
@@ -226,10 +216,11 @@ struct BMI160 {
accel[0] = getFromFifo<uint16_t>(i, read_buffer);
accel[1] = getFromFifo<uint16_t>(i, read_buffer);
accel[2] = getFromFifo<uint16_t>(i, read_buffer);
processAccelSample(accel, AccTs);
callbacks.processAccelSample(accel, AccTs);
}
}
}
return static_cast<size_t>(fifo_bytes) > static_cast<size_t>(bytes_to_read);
}
};

View File

@@ -38,7 +38,7 @@ namespace SlimeVR::Sensors::SoftFusion::Drivers {
// Driver uses acceleration range at 16g
// and gyroscope range at 1000dps
// Gyroscope ODR = 400Hz, accel ODR = 100Hz
// Gyroscope ODR = 200Hz, accel ODR = 100Hz
// Timestamps reading are not used
struct BMI270 {
@@ -46,7 +46,7 @@ struct BMI270 {
static constexpr auto Name = "BMI270";
static constexpr auto Type = SensorTypeID::BMI270;
static constexpr float GyrTs = 1.0 / 400.0;
static constexpr float GyrTs = 1.0 / 200.0;
static constexpr float AccTs = 1.0 / 100.0;
static constexpr float MagTs = 1.0 / 100;
@@ -56,13 +56,7 @@ struct BMI270 {
static constexpr float TemperatureZROChange = 6.667f;
static constexpr VQFParams SensorVQFParams{
.motionBiasEstEnabled = true,
.biasSigmaInit = 0.5f,
.biasClip = 1.0f,
.restThGyr = 0.5f,
.restThAcc = 0.196f,
};
static constexpr VQFParams SensorVQFParams{};
struct MotionlessCalibrationData {
bool valid;
@@ -138,7 +132,7 @@ struct BMI270 {
static constexpr uint8_t filterHighPerfMode = 1 << 7;
static constexpr uint8_t value
= rate400Hz | DLPFModeNorm | noisePerfMode | filterHighPerfMode;
= rate200Hz | DLPFModeNorm | noisePerfMode | filterHighPerfMode;
};
struct GyrRange {
@@ -434,7 +428,7 @@ struct BMI270 {
return to_ret;
}
void bulkRead(DriverCallbacks<int16_t>&& callbacks) {
bool bulkRead(DriverCallbacks<int16_t>&& callbacks) {
const auto fifo_bytes = m_RegisterInterface.readReg16(Regs::FifoCount);
const auto bytes_to_read = std::min(
@@ -488,6 +482,8 @@ struct BMI270 {
}
}
}
return fifo_bytes > bytes_to_read;
}
};

View File

@@ -30,11 +30,13 @@
#include "callbacks.h"
#include "vqf.h"
constexpr static bool DEBUG_ICM42688_HIRES = false;
namespace SlimeVR::Sensors::SoftFusion::Drivers {
// Driver uses acceleration range at 8g
// and gyroscope range at 1000dps
// Gyroscope ODR = 500Hz, accel ODR = 100Hz
// Gyroscope ODR = 200Hz, accel ODR = 100Hz
// Timestamps reading not used, as they're useless (constant predefined increment)
struct ICM42688 {
@@ -42,27 +44,27 @@ struct ICM42688 {
static constexpr auto Name = "ICM-42688";
static constexpr auto Type = SensorTypeID::ICM42688;
static constexpr float GyrTs = 1.0 / 500.0;
static constexpr float GyrTs = 1.0 / 200.0;
static constexpr float AccTs = 1.0 / 100.0;
static constexpr float TempTs = 1.0 / 500.0;
static constexpr float TempTs = 1.0 / 200.0;
static constexpr float MagTs = 1.0 / 100;
static constexpr float GyroSensitivity = 32.8f;
static constexpr float AccelSensitivity = 4096.0f;
// When 20-bits data format is used, the only FSR settings that are
// operational are ±2000dps for gyroscope and ±16g for accelerometer, even if the
// FSR selection register settings are configured for other FSR values. The
// corresponding sensitivity scale factor values are 131 LSB/dps for gyroscope and
// 8192 LSB/g for accelerometer.
static constexpr float GyroSensitivity = (DEBUG_ICM42688_HIRES ? 131.0f : 32.8f);
static constexpr float AccelSensitivity
= (DEBUG_ICM42688_HIRES ? 8192.0f : 4096.0f);
static constexpr float TemperatureBias = 25.0f;
static constexpr float TemperatureSensitivity = 2.07f;
static constexpr float TemperatureSensitivity
= (DEBUG_ICM42688_HIRES ? 132.48f : 2.07f);
static constexpr float TemperatureZROChange = 20.0f;
static constexpr VQFParams SensorVQFParams{
.motionBiasEstEnabled = true,
.biasSigmaInit = 0.5f,
.biasClip = 1.0f,
.restThGyr = 0.5f,
.restThAcc = 0.196f,
};
static constexpr VQFParams SensorVQFParams{};
RegisterInterface& m_RegisterInterface;
SlimeVR::Logging::Logger& m_Logger;
@@ -94,12 +96,14 @@ struct ICM42688 {
static constexpr uint8_t reg = 0x5f;
static constexpr uint8_t value
= 0b1 | (0b1 << 1) | (0b1 << 2)
| (0b0 << 4); // fifo accel en=1, gyro=1, temp=1, hires=1
| (DEBUG_ICM42688_HIRES ? (0b1 << 4) : (0b0 << 4));
// fifo accel en=1, gyro=1, temp=1,
// hires=DEBUG_ICM42688_HIRES
};
struct GyroConfig {
static constexpr uint8_t reg = 0x4f;
static constexpr uint8_t value
= (0b001 << 5) | 0b1111; // 1000dps, odr=500Hz
= (0b001 << 5) | 0b0111; // 1000dps, odr=200Hz
};
struct AccelConfig {
static constexpr uint8_t reg = 0x50;
@@ -121,7 +125,7 @@ struct ICM42688 {
};
#pragma pack(push, 1)
struct FifoEntryAligned {
struct FifoEntryAlignedHires {
union {
struct {
int16_t accel[3];
@@ -134,10 +138,65 @@ struct ICM42688 {
} part;
uint8_t raw[19];
};
void getGyro(int32_t out[3]) {
// 6.1 Packet Structure for high resolution mode
// https://invensense.tdk.com/wp-content/uploads/2020/04/ds-000347_icm-42688-p-datasheet.pdf
// When 20-bits data format is used, gyroscope data consists of 19-bits
// of actual data and the LSB is always set to 0
out[0] = static_cast<int32_t>(part.gyro[0]) << 3 | ((part.xlsb & 0xe) >> 1);
out[1] = static_cast<int32_t>(part.gyro[1]) << 3 | ((part.ylsb & 0xe) >> 1);
out[2] = static_cast<int32_t>(part.gyro[2]) << 3 | ((part.zlsb & 0xe) >> 1);
}
void getAccel(int32_t out[3]) {
// accelerometer data consists of 18-bits of actual data and the two
// lowest order bits are always set to 0
out[0] = static_cast<int32_t>(part.accel[0]) << 2
| (static_cast<int32_t>((part.xlsb) & 0xf0) >> 6);
out[1] = static_cast<int32_t>(part.accel[1]) << 2
| (static_cast<int32_t>((part.ylsb) & 0xf0) >> 6);
out[2] = static_cast<int32_t>(part.accel[2]) << 2
| (static_cast<int32_t>((part.zlsb) & 0xf0) >> 6);
}
};
struct FifoEntryAlignedDefault {
union {
struct {
int16_t accel[3];
int16_t gyro[3];
int8_t temp;
uint16_t timestamp;
} part;
uint8_t raw[15];
};
void getGyro(int32_t out[3]) {
out[0] = static_cast<int32_t>(part.gyro[0]);
out[1] = static_cast<int32_t>(part.gyro[1]);
out[2] = static_cast<int32_t>(part.gyro[2]);
}
void getAccel(int32_t out[3]) {
out[0] = static_cast<int32_t>(part.accel[0]);
out[1] = static_cast<int32_t>(part.accel[1]);
out[2] = static_cast<int32_t>(part.accel[2]);
}
};
#pragma pack(pop)
static_assert(sizeof(FifoEntryAlignedHires) == 19);
static_assert(sizeof(FifoEntryAlignedDefault) == 15);
using FifoEntryAligned = std::conditional<
DEBUG_ICM42688_HIRES,
FifoEntryAlignedHires,
FifoEntryAlignedDefault>::type;
static constexpr size_t FullFifoEntrySize = sizeof(FifoEntryAligned) + 1;
// max 4 readings in highres mode 8 readings delay too high 6 seems to be the
// edge to work reliably. Tested on ESP8266 with 2 IMU
static constexpr size_t MaxReadings = DEBUG_ICM42688_HIRES ? 4 : 8;
bool initialize() {
// perform initialization step
@@ -158,10 +217,11 @@ struct ICM42688 {
return true;
}
void bulkRead(DriverCallbacks<int32_t>&& callbacks) {
bool bulkRead(DriverCallbacks<int32_t>&& callbacks) {
const auto fifo_bytes = m_RegisterInterface.readReg16(Regs::FifoCount);
std::array<uint8_t, FullFifoEntrySize * 8> read_buffer; // max 8 readings
std::array<uint8_t, FullFifoEntrySize * MaxReadings> read_buffer;
const auto bytes_to_read = std::min(
static_cast<size_t>(read_buffer.size()),
static_cast<size_t>(fifo_bytes)
@@ -177,22 +237,13 @@ struct ICM42688 {
sizeof(FifoEntryAligned)
); // skip fifo header
const int32_t gyroData[3]{
static_cast<int32_t>(entry.part.gyro[0]) << 4 | (entry.part.xlsb & 0xf),
static_cast<int32_t>(entry.part.gyro[1]) << 4 | (entry.part.ylsb & 0xf),
static_cast<int32_t>(entry.part.gyro[2]) << 4 | (entry.part.zlsb & 0xf),
};
int32_t gyroData[3];
entry.getGyro(gyroData);
callbacks.processGyroSample(gyroData, GyrTs);
if (entry.part.accel[0] != -32768) {
const int32_t accelData[3]{
static_cast<int32_t>(entry.part.accel[0]) << 4
| (static_cast<int32_t>(entry.part.xlsb) & 0xf0 >> 4),
static_cast<int32_t>(entry.part.accel[1]) << 4
| (static_cast<int32_t>(entry.part.ylsb) & 0xf0 >> 4),
static_cast<int32_t>(entry.part.accel[2]) << 4
| (static_cast<int32_t>(entry.part.zlsb) & 0xf0 >> 4),
};
int32_t accelData[3];
entry.getAccel(accelData);
callbacks.processAccelSample(accelData, AccTs);
}
@@ -203,6 +254,7 @@ struct ICM42688 {
);
}
}
return fifo_bytes > bytes_to_read;
}
};

View File

@@ -32,20 +32,14 @@ namespace SlimeVR::Sensors::SoftFusion::Drivers {
// and gyroscope range at 4000dps
// using high resolution mode
// Uses 32.768kHz clock
// Gyroscope ODR = 409.6Hz, accel ODR = 204.8Hz
// Gyroscope ODR = 204.8Hz, accel ODR = 102.4Hz
// Timestamps reading not used, as they're useless (constant predefined increment)
struct ICM45605 : public ICM45Base {
static constexpr auto Name = "ICM-45605";
static constexpr auto Type = SensorTypeID::ICM45605;
static constexpr VQFParams SensorVQFParams{
.motionBiasEstEnabled = true,
.biasSigmaInit = 0.3f,
.biasClip = 0.6f,
.restThGyr = 0.3f,
.restThAcc = 0.0098f,
};
static constexpr VQFParams SensorVQFParams{};
ICM45605(RegisterInterface& registerInterface, SlimeVR::Logging::Logger& logger)
: ICM45Base{registerInterface, logger} {}

View File

@@ -32,26 +32,14 @@ namespace SlimeVR::Sensors::SoftFusion::Drivers {
// and gyroscope range at 4000dps
// using high resolution mode
// Uses 32.768kHz clock
// Gyroscope ODR = 409.6Hz, accel ODR = 102.4Hz
// Gyroscope ODR = 204.8Hz, accel ODR = 102.4Hz
// Timestamps reading not used, as they're useless (constant predefined increment)
struct ICM45686 : public ICM45Base {
static constexpr auto Name = "ICM-45686";
static constexpr auto Type = SensorTypeID::ICM45686;
static constexpr VQFParams SensorVQFParams{
.tauAcc = 7.171490,
.biasSigmaInit = 0.337976,
.biasForgettingTime = 352.235500,
.biasClip = 5.0,
.biasSigmaMotion = 0.985346,
.biasVerticalForgettingFactor = 0.007959,
.biasSigmaRest = 0.028897,
.restMinT = 4.648680,
.restFilterTau = 1.900166,
.restThGyr = 2.620598,
.restThAcc = 2.142593,
};
static constexpr VQFParams SensorVQFParams{};
ICM45686(RegisterInterface& registerInterface, SlimeVR::Logging::Logger& logger)
: ICM45Base{registerInterface, logger} {}
@@ -75,8 +63,10 @@ struct ICM45686 : public ICM45Base {
bool initialize() {
ICM45Base::softResetIMU();
#if IMU_USE_EXTERNAL_CLOCK
m_RegisterInterface.writeReg(Regs::Pin9Config::reg, Regs::Pin9Config::value);
m_RegisterInterface.writeReg(Regs::RtcConfig::reg, Regs::RtcConfig::value);
#endif
return ICM45Base::initializeBase();
}
};

View File

@@ -34,13 +34,13 @@ namespace SlimeVR::Sensors::SoftFusion::Drivers {
// and gyroscope range at 4000dps
// using high resolution mode
// Uses 32.768kHz clock
// Gyroscope ODR = 409.6Hz, accel ODR = 102.4Hz
// Gyroscope ODR = 204.8Hz, accel ODR = 102.4Hz
// Timestamps reading not used, as they're useless (constant predefined increment)
struct ICM45Base {
static constexpr uint8_t Address = 0x68;
static constexpr float GyrTs = 1.0 / 409.6;
static constexpr float GyrTs = 1.0 / 204.8;
static constexpr float AccTs = 1.0 / 102.4;
static constexpr float TempTs = 1.0 / 409.6;
@@ -71,7 +71,7 @@ struct ICM45Base {
struct GyroConfig {
static constexpr uint8_t reg = 0x1c;
static constexpr uint8_t value
= (0b0000 << 4) | 0b0111; // 4000dps, odr=409.6Hz
= (0b0000 << 4) | 0b1000; // 4000dps, odr=204.8Hz
};
struct AccelConfig {
@@ -238,13 +238,13 @@ struct ICM45Base {
// stack overflow and panic
std::vector<uint8_t> read_buffer;
void bulkRead(DriverCallbacks<int32_t>&& callbacks) {
bool bulkRead(DriverCallbacks<int32_t>&& callbacks) {
constexpr int16_t InvalidReading = -32768;
size_t fifo_packets = m_RegisterInterface.readReg16(BaseRegs::FifoCount);
if (fifo_packets <= 1) {
return;
return false;
}
// AN-000364
@@ -264,9 +264,9 @@ struct ICM45Base {
// can cause FIFO data corruption, from happening.
--fifo_packets;
fifo_packets = std::min(fifo_packets, MaxReadings);
auto packets_to_read = std::min(fifo_packets, MaxReadings);
size_t bytes_to_read = fifo_packets * FullFifoEntrySize;
size_t bytes_to_read = packets_to_read * FullFifoEntrySize;
m_RegisterInterface
.readBytes(BaseRegs::FifoData, bytes_to_read, read_buffer.data());
@@ -307,6 +307,8 @@ struct ICM45Base {
callbacks.processTempSample(static_cast<int16_t>(entry.temp), TempTs);
}
}
return fifo_packets > MaxReadings;
}
template <typename Reg>

View File

@@ -55,7 +55,7 @@ struct LSM6DSOutputHandler {
static constexpr size_t FullFifoEntrySize = sizeof(FifoEntryAligned) + 1;
template <typename Regs>
void bulkRead(
bool bulkRead(
DriverCallbacks<int16_t>&& callbacks,
float GyrTs,
float AccTs,
@@ -103,6 +103,7 @@ struct LSM6DSOutputHandler {
break;
}
}
return fifo_bytes > bytes_to_read;
}
};

View File

@@ -33,37 +33,35 @@
namespace SlimeVR::Sensors::SoftFusion::Drivers {
// Driver uses acceleration range at 8g
// Driver uses acceleration range at 4g
// and gyroscope range at 1000dps
// Gyroscope ODR = 416Hz, accel ODR = 416Hz
// Gyroscope ODR = 208Hz, accel ODR = 104Hz
struct LSM6DS3TRC {
static constexpr uint8_t Address = 0x6a;
static constexpr auto Name = "LSM6DS3TR-C";
static constexpr auto Type = SensorTypeID::LSM6DS3TRC;
static constexpr float Freq = 416;
static constexpr float GyrFreq = 208.0f;
static constexpr float AccFreq = 104.0f;
static constexpr float MagFreq = 100.0f;
static constexpr float TempFreq
= 416.0f; // I guess it's just output at the FIFO ODR?
static constexpr float GyrTs = 1.0 / Freq;
static constexpr float AccTs = 1.0 / Freq;
static constexpr float MagTs = 1.0 / Freq;
static constexpr float TempTs = 1.0 / Freq;
static constexpr float GyrTs = 1.0 / GyrFreq;
static constexpr float AccTs = 1.0 / AccFreq;
static constexpr float MagTs = 1.0 / MagFreq;
static constexpr float TempTs = 1.0 / TempFreq;
static constexpr float GyroSensitivity = 28.571428571f;
static constexpr float AccelSensitivity = 4098.360655738f;
static constexpr float AccelSensitivity = 1000 / 0.122f;
static constexpr float TemperatureBias = 25.0f;
static constexpr float TemperatureSensitivity = 256.0f;
static constexpr float TemperatureZROChange = 2.0f;
static constexpr VQFParams SensorVQFParams{
.motionBiasEstEnabled = true,
.biasSigmaInit = 3.0f,
.biasClip = 6.0f,
.restThGyr = 3.0f,
.restThAcc = 0.392f,
};
static constexpr VQFParams SensorVQFParams{};
RegisterInterface& m_RegisterInterface;
SlimeVR::Logging::Logger m_Logger;
@@ -79,12 +77,12 @@ struct LSM6DS3TRC {
};
struct Ctrl1XL {
static constexpr uint8_t reg = 0x10;
static constexpr uint8_t value = (0b11 << 2) | (0b0110 << 4); // 8g, 416Hz
static constexpr uint8_t value = (0b10 << 2) | (0b0100 << 4); // 4g, 104Hz
};
struct Ctrl2G {
static constexpr uint8_t reg = 0x11;
static constexpr uint8_t value
= (0b10 << 2) | (0b0110 << 4); // 1000dps, 416Hz
= (0b10 << 2) | (0b0101 << 4); // 1000dps, 208Hz
};
struct Ctrl3C {
static constexpr uint8_t reg = 0x12;
@@ -104,7 +102,7 @@ struct LSM6DS3TRC {
struct FifoCtrl5 {
static constexpr uint8_t reg = 0x0a;
static constexpr uint8_t value
= 0b110 | (0b0111 << 3); // continuous mode, odr = 833Hz
= 0b110 | (0b0110 << 3); // continuous mode, odr = 416Hz
};
static constexpr uint8_t FifoStatus = 0x3a;
@@ -124,14 +122,14 @@ struct LSM6DS3TRC {
return true;
}
void bulkRead(DriverCallbacks<int16_t>&& callbacks) {
bool bulkRead(DriverCallbacks<int16_t>&& callbacks) {
const auto read_result = m_RegisterInterface.readReg16(Regs::FifoStatus);
if (read_result & 0x4000) { // overrun!
// disable and re-enable fifo to clear it
m_Logger.debug("Fifo overrun, resetting...");
m_RegisterInterface.writeReg(Regs::FifoCtrl5::reg, 0);
m_RegisterInterface.writeReg(Regs::FifoCtrl5::reg, Regs::FifoCtrl5::value);
return;
return true;
}
const auto unread_entries = read_result & 0x7ff;
constexpr auto single_measurement_words = 6;
@@ -158,6 +156,8 @@ struct LSM6DS3TRC {
callbacks.processAccelSample(&read_buffer[i + 3], AccTs);
callbacks.processTempSample(read_buffer[i + 9], TempTs);
}
return static_cast<size_t>(unread_entries) > read_buffer.size();
}
}; // namespace SlimeVR::Sensors::SoftFusion::Drivers

View File

@@ -33,7 +33,7 @@
namespace SlimeVR::Sensors::SoftFusion::Drivers {
// Driver uses acceleration range at 8g
// Driver uses acceleration range at 4g
// and gyroscope range at 1000dps
// Gyroscope ODR = 416Hz, accel ODR = 104Hz
@@ -42,7 +42,7 @@ struct LSM6DSO : LSM6DSOutputHandler {
static constexpr auto Name = "LSM6DSO";
static constexpr auto Type = SensorTypeID::LSM6DSO;
static constexpr float GyrFreq = 416;
static constexpr float GyrFreq = 208;
static constexpr float AccFreq = 104;
static constexpr float MagFreq = 120;
static constexpr float TempFreq = 52;
@@ -53,20 +53,14 @@ struct LSM6DSO : LSM6DSOutputHandler {
static constexpr float TempTs = 1.0 / TempFreq;
static constexpr float GyroSensitivity = 1000 / 35.0f;
static constexpr float AccelSensitivity = 1000 / 0.244f;
static constexpr float AccelSensitivity = 1000 / 0.122f;
static constexpr float TemperatureBias = 25.0f;
static constexpr float TemperatureSensitivity = 256.0f;
static constexpr float TemperatureZROChange = 10.0;
static constexpr VQFParams SensorVQFParams{
.motionBiasEstEnabled = true,
.biasSigmaInit = 1.0f,
.biasClip = 2.0f,
.restThGyr = 1.0f,
.restThAcc = 0.192f,
};
static constexpr VQFParams SensorVQFParams{};
struct Regs {
struct WhoAmI {
@@ -75,11 +69,11 @@ struct LSM6DSO : LSM6DSOutputHandler {
};
struct Ctrl1XL {
static constexpr uint8_t reg = 0x10;
static constexpr uint8_t value = (0b01001100); // XL at 104 Hz, 8g FS
static constexpr uint8_t value = (0b01001000); // XL at 104 Hz, 4g FS
};
struct Ctrl2GY {
static constexpr uint8_t reg = 0x11;
static constexpr uint8_t value = (0b01101000); // GY at 416 Hz, 1000dps FS
static constexpr uint8_t value = (0b01011000); // GY at 208 Hz, 1000dps FS
};
struct Ctrl3C {
static constexpr uint8_t reg = 0x12;
@@ -90,7 +84,7 @@ struct LSM6DSO : LSM6DSOutputHandler {
struct FifoCtrl3BDR {
static constexpr uint8_t reg = 0x09;
static constexpr uint8_t value
= (0b0110) | (0b0110 << 4); // gyro and accel batched at 417Hz
= 0b01010100; // Gyroscope batched into FIFO at 208Hz, Accel at 104Hz
};
struct FifoCtrl4Mode {
static constexpr uint8_t reg = 0x0a;
@@ -123,8 +117,8 @@ struct LSM6DSO : LSM6DSOutputHandler {
return true;
}
void bulkRead(DriverCallbacks<int16_t>&& callbacks) {
LSM6DSOutputHandler::template bulkRead<Regs>(
bool bulkRead(DriverCallbacks<int16_t>&& callbacks) {
return LSM6DSOutputHandler::template bulkRead<Regs>(
std::move(callbacks),
GyrTs,
AccTs,

View File

@@ -31,16 +31,16 @@
namespace SlimeVR::Sensors::SoftFusion::Drivers {
// Driver uses acceleration range at 8g
// Driver uses acceleration range at 4g
// and gyroscope range at 1000dps
// Gyroscope ODR = 416Hz, accel ODR = 104Hz
// Gyroscope ODR = 208Hz, accel ODR = 104Hz
struct LSM6DSR : LSM6DSOutputHandler {
static constexpr uint8_t Address = 0x6a;
static constexpr auto Name = "LSM6DSR";
static constexpr auto Type = SensorTypeID::LSM6DSR;
static constexpr float GyrFreq = 416;
static constexpr float GyrFreq = 208;
static constexpr float AccFreq = 104;
static constexpr float MagFreq = 120;
static constexpr float TempFreq = 52;
@@ -51,20 +51,14 @@ struct LSM6DSR : LSM6DSOutputHandler {
static constexpr float TempTs = 1.0 / TempFreq;
static constexpr float GyroSensitivity = 1000 / 35.0f;
static constexpr float AccelSensitivity = 1000 / 0.244f;
static constexpr float AccelSensitivity = 1000 / 0.122f;
static constexpr float TemperatureBias = 25.0f;
static constexpr float TemperatureSensitivity = 256.0f;
static constexpr float TemperatureZROChange = 20.0f;
static constexpr VQFParams SensorVQFParams{
.motionBiasEstEnabled = true,
.biasSigmaInit = 1.0f,
.biasClip = 2.0f,
.restThGyr = 1.0f,
.restThAcc = 0.192f,
};
static constexpr VQFParams SensorVQFParams{};
struct Regs {
struct WhoAmI {
@@ -73,11 +67,11 @@ struct LSM6DSR : LSM6DSOutputHandler {
};
struct Ctrl1XL {
static constexpr uint8_t reg = 0x10;
static constexpr uint8_t value = (0b01001100); // XL at 104 Hz, 8g FS
static constexpr uint8_t value = (0b01001000); // XL at 104 Hz, 4g FS
};
struct Ctrl2GY {
static constexpr uint8_t reg = 0x11;
static constexpr uint8_t value = (0b01101000); // GY at 416 Hz, 1000dps FS
static constexpr uint8_t value = (0b01011000); // GY at 208 Hz, 1000dps FS
};
struct Ctrl3C {
static constexpr uint8_t reg = 0x12;
@@ -88,7 +82,7 @@ struct LSM6DSR : LSM6DSOutputHandler {
struct FifoCtrl3BDR {
static constexpr uint8_t reg = 0x09;
static constexpr uint8_t value
= (0b0110) | (0b0110 << 4); // gyro and accel batched at 417Hz
= 0b01010100; // Gyroscope batched into FIFO at 208Hz, Accel at 104Hz
};
struct FifoCtrl4Mode {
static constexpr uint8_t reg = 0x0a;
@@ -121,8 +115,8 @@ struct LSM6DSR : LSM6DSOutputHandler {
return true;
}
void bulkRead(DriverCallbacks<int16_t>&& callbacks) {
LSM6DSOutputHandler::template bulkRead<Regs>(
bool bulkRead(DriverCallbacks<int16_t>&& callbacks) {
return LSM6DSOutputHandler::template bulkRead<Regs>(
std::move(callbacks),
GyrTs,
AccTs,

View File

@@ -32,16 +32,16 @@
namespace SlimeVR::Sensors::SoftFusion::Drivers {
// Driver uses acceleration range at 8g
// Driver uses acceleration range at 4g
// and gyroscope range at 1000dps
// Gyroscope ODR = 480Hz, accel ODR = 120Hz
// Gyroscope ODR = 240Hz, accel ODR = 120Hz
struct LSM6DSV : LSM6DSOutputHandler {
static constexpr uint8_t Address = 0x6a;
static constexpr auto Name = "LSM6DSV";
static constexpr auto Type = SensorTypeID::LSM6DSV;
static constexpr float GyrFreq = 480;
static constexpr float GyrFreq = 240;
static constexpr float AccFreq = 120;
static constexpr float MagFreq = 120;
static constexpr float TempFreq = 60;
@@ -52,20 +52,14 @@ struct LSM6DSV : LSM6DSOutputHandler {
static constexpr float TempTs = 1.0 / TempFreq;
static constexpr float GyroSensitivity = 1000 / 35.0f;
static constexpr float AccelSensitivity = 1000 / 0.244f;
static constexpr float AccelSensitivity = 1000 / 0.122f;
static constexpr float TemperatureBias = 25.0f;
static constexpr float TemperatureSensitivity = 256.0f;
static constexpr float TemperatureZROChange = 16.667f;
static constexpr VQFParams SensorVQFParams{
.motionBiasEstEnabled = true,
.biasSigmaInit = 1.0f,
.biasClip = 2.0f,
.restThGyr = 1.0f,
.restThAcc = 0.192f,
};
static constexpr VQFParams SensorVQFParams{};
struct Regs {
struct WhoAmI {
@@ -82,7 +76,7 @@ struct LSM6DSV : LSM6DSOutputHandler {
};
struct Ctrl2GODR {
static constexpr uint8_t reg = 0x11;
static constexpr uint8_t value = (0b0011000); // 480Hz, HAODR
static constexpr uint8_t value = (0b0010111); // 240Hz, HAODR
};
struct Ctrl3C {
static constexpr uint8_t reg = 0x12;
@@ -96,12 +90,12 @@ struct LSM6DSV : LSM6DSOutputHandler {
};
struct Ctrl8XLFS {
static constexpr uint8_t reg = 0x17;
static constexpr uint8_t value = (0b10); // 8g
static constexpr uint8_t value = (0b01); // 4g
};
struct FifoCtrl3BDR {
static constexpr uint8_t reg = 0x09;
static constexpr uint8_t value
= (0b1000) | (0b1000 << 4); // gyro and accel batched at 480Hz
= 0b01110110; // Gyroscope batched into FIFO at 240Hz, Accel at 120Hz
};
struct FifoCtrl4Mode {
static constexpr uint8_t reg = 0x0a;
@@ -137,8 +131,8 @@ struct LSM6DSV : LSM6DSOutputHandler {
return true;
}
void bulkRead(DriverCallbacks<int16_t>&& callbacks) {
LSM6DSOutputHandler::template bulkRead<Regs>(
bool bulkRead(DriverCallbacks<int16_t>&& callbacks) {
return LSM6DSOutputHandler::template bulkRead<Regs>(
std::move(callbacks),
GyrTs,
AccTs,

View File

@@ -70,13 +70,7 @@ struct MPU6050 {
static constexpr float TemperatureZROChange = 1.6f;
static constexpr VQFParams SensorVQFParams{
.motionBiasEstEnabled = true,
.biasSigmaInit = 20.0f,
.biasClip = 40.0f,
.restThGyr = 20.0f,
.restThAcc = 0.784f,
};
static constexpr VQFParams SensorVQFParams{};
RegisterInterface& m_RegisterInterface;
SlimeVR::Logging::Logger& m_Logger;
@@ -182,7 +176,7 @@ struct MPU6050 {
return result;
}
void bulkRead(DriverCallbacks<int16_t>&& callbacks) {
bool bulkRead(DriverCallbacks<int16_t>&& callbacks) {
const auto status = m_RegisterInterface.readReg(Regs::IntStatus);
if (status & (1 << MPU6050_INTERRUPT_FIFO_OFLOW_BIT)) {
@@ -190,7 +184,7 @@ struct MPU6050 {
// This necessitates a reset
m_Logger.debug("Fifo overrun, resetting...");
resetFIFO();
return;
return true;
}
std::array<uint8_t, 12 * 10>
@@ -200,7 +194,7 @@ struct MPU6050 {
auto readBytes = min(static_cast<size_t>(byteCount), readBuffer.size())
/ sizeof(FifoSample) * sizeof(FifoSample);
if (!readBytes) {
return;
return false;
}
m_RegisterInterface.readBytes(Regs::FifoData, readBytes, readBuffer.data());
@@ -219,6 +213,8 @@ struct MPU6050 {
xyz[2] = MPU6050_FIFO_VALUE(sample, gyro_z);
callbacks.processGyroSample(xyz, GyrTs);
}
return byteCount > readBytes;
}
}; // namespace SlimeVR::Sensors::SoftFusion::Drivers

View File

@@ -46,6 +46,9 @@ public:
virtual void cancel() = 0;
virtual bool requiresRest() { return true; }
// Signals that the sensor had more packets than the MCU could read, which can
// compromise calibration
virtual void signalOverwhelmed() {}
virtual void processAccelSample(const SensorRawT accelSample[3]) {}
virtual void processGyroSample(const SensorRawT accelSample[3]) {}
virtual void processTempSample(float tempSample) {}

View File

@@ -99,7 +99,10 @@ public:
gyroBiasCalibrationStep.swapCalibrationIfNecessary();
computeNextCalibrationStep();
currentStep = &sampleRateCalibrationStep;
currentStep->start();
nextCalibrationStep = CalibrationStepEnum::SAMPLING_RATE;
calculateZROChange();
printCalibration();
@@ -139,10 +142,14 @@ public:
switch (result) {
case CalibrationStep<RawSensorT>::TickResult::DONE:
if (nextCalibrationStep == CalibrationStepEnum::SAMPLING_RATE) {
stepCalibrationForward(true, false);
break;
}
stepCalibrationForward();
break;
case CalibrationStep<RawSensorT>::TickResult::SKIP:
stepCalibrationForward(false);
stepCalibrationForward(false, false);
break;
case CalibrationStep<RawSensorT>::TickResult::CONTINUE:
break;
@@ -179,6 +186,12 @@ public:
return activeCalibration.MotionlessData;
}
void signalOverwhelmed() final {
if (isCalibrating) {
currentStep->signalOverwhelmed();
}
}
void provideAccelSample(const RawSensorT accelSample[3]) final {
if (isCalibrating) {
currentStep->processAccelSample(accelSample);
@@ -229,10 +242,7 @@ private:
};
void computeNextCalibrationStep() {
if (!calibration.sensorTimestepsCalibrated) {
nextCalibrationStep = CalibrationStepEnum::SAMPLING_RATE;
currentStep = &sampleRateCalibrationStep;
} else if (!calibration.motionlessCalibrated && Base::HasMotionlessCalib) {
if (!calibration.motionlessCalibrated && Base::HasMotionlessCalib) {
nextCalibrationStep = CalibrationStepEnum::MOTIONLESS;
currentStep = &motionlessCalibrationStep;
} else if (calibration.gyroPointsCalibrated == 0) {
@@ -247,7 +257,7 @@ private:
}
}
void stepCalibrationForward(bool save = true) {
void stepCalibrationForward(bool print = true, bool save = true) {
currentStep->cancel();
switch (nextCalibrationStep) {
case CalibrationStepEnum::NONE:
@@ -255,14 +265,14 @@ private:
case CalibrationStepEnum::SAMPLING_RATE:
nextCalibrationStep = CalibrationStepEnum::MOTIONLESS;
currentStep = &motionlessCalibrationStep;
if (save) {
if (print) {
printCalibration(CalibrationPrintFlags::TIMESTEPS);
}
break;
case CalibrationStepEnum::MOTIONLESS:
nextCalibrationStep = CalibrationStepEnum::GYRO_BIAS;
currentStep = &gyroBiasCalibrationStep;
if (save) {
if (print) {
printCalibration(CalibrationPrintFlags::MOTIONLESS);
}
break;
@@ -274,7 +284,7 @@ private:
currentStep = &gyroBiasCalibrationStep;
}
if (save) {
if (print) {
printCalibration(CalibrationPrintFlags::GYRO_BIAS);
}
break;
@@ -282,7 +292,7 @@ private:
nextCalibrationStep = CalibrationStepEnum::GYRO_BIAS;
currentStep = &gyroBiasCalibrationStep;
if (save) {
if (print) {
printCalibration(CalibrationPrintFlags::ACCEL_BIAS);
}
@@ -306,8 +316,6 @@ private:
calibration.data.runtimeCalibration = this->calibration;
configuration.setSensor(sensorId, calibration);
configuration.save();
ledManager.blink(100);
}
enum class CalibrationPrintFlags {
@@ -323,12 +331,12 @@ private:
void printCalibration(CalibrationPrintFlags toPrint = PrintAllCalibration) {
if (any(toPrint & CalibrationPrintFlags::TIMESTEPS)) {
if (calibration.sensorTimestepsCalibrated) {
if (activeCalibration.sensorTimestepsCalibrated) {
logger.info(
"Calibrated timesteps: Accel %f, Gyro %f, Temperature %f",
calibration.A_Ts,
calibration.G_Ts,
calibration.T_Ts
activeCalibration.A_Ts,
activeCalibration.G_Ts,
activeCalibration.T_Ts
);
} else {
logger.info("Sensor timesteps not calibrated");
@@ -389,12 +397,12 @@ private:
}
}
CalibrationStepEnum nextCalibrationStep = CalibrationStepEnum::MOTIONLESS;
CalibrationStepEnum nextCalibrationStep = CalibrationStepEnum::SAMPLING_RATE;
static constexpr float initialStartupDelaySeconds = 5;
uint64_t startupMillis = millis();
SampleRateCalibrationStep<RawSensorT> sampleRateCalibrationStep{calibration};
SampleRateCalibrationStep<RawSensorT> sampleRateCalibrationStep{activeCalibration};
MotionlessCalibrationStep<IMU, RawSensorT> motionlessCalibrationStep{
calibration,
sensor

View File

@@ -67,6 +67,14 @@ public:
void cancel() override final { calibrationData.reset(); }
bool requiresRest() override final { return false; }
void signalOverwhelmed() override final {
// Not good, restart
calibrationData.value().accelSamples = 0;
calibrationData.value().gyroSamples = 0;
calibrationData.value().tempSamples = 0;
calibrationData.value().startMillis = millis();
}
void processAccelSample(const SensorRawT accelSample[3]) override final {
calibrationData.value().accelSamples++;
}

View File

@@ -241,7 +241,7 @@ public:
constexpr uint32_t sendInterval = 1.0f / maxSendRateHz * 1e6f;
elapsed = now - m_lastRotationPacketSent;
if (elapsed >= sendInterval) {
m_sensor.bulkRead({
auto overwhelmed = m_sensor.bulkRead({
[&](const auto sample[3], float AccTs) {
processAccelSample(sample, AccTs);
},
@@ -252,6 +252,9 @@ public:
processTempSample(sample, TempTs);
},
});
if (overwhelmed) {
calibrator.signalOverwhelmed();
}
if (!m_fusion.isUpdated()) {
checkSensorTimeout();
return;

View File

@@ -35,10 +35,34 @@
#include "nvs_flash.h"
#endif
#ifdef EXT_SERIAL_COMMANDS
#define CALLBACK_SIZE 7 // Increase callback size to allow for debug commands
#include "i2cscan.h"
#endif
#ifndef CALLBACK_SIZE
#define CALLBACK_SIZE 6 // Default callback size
#endif
#if defined(VENDOR_URL) && defined(VENDOR_NAME) && defined(PRODUCT_NAME) \
&& defined(UPDATE_ADDRESS) && defined(UPDATE_NAME)
constexpr const char* FULL_VENDOR_STR
= "Vendor: " VENDOR_NAME " (" VENDOR_URL "), product: " PRODUCT_NAME
", firmware update url: " UPDATE_ADDRESS ", name: " UPDATE_NAME;
#elif defined(VENDOR_URL) && defined(VENDOR_NAME) && defined(PRODUCT_NAME)
constexpr const char* FULL_VENDOR_STR
= "Vendor: " VENDOR_NAME " (" VENDOR_URL "), product: " PRODUCT_NAME;
#elif defined(VENDOR_NAME) && defined(PRODUCT_NAME)
constexpr const char* FULL_VENDOR_STR
= "Vendor: " VENDOR_NAME ", product: " PRODUCT_NAME;
#else
constexpr const char* FULL_VENDOR_STR = "Vendor: Unknown, product: Unknown";
#endif
namespace SerialCommands {
SlimeVR::Logging::Logger logger("SerialCommands");
CmdCallback<6> cmdCallbacks;
CmdCallback<CALLBACK_SIZE> cmdCallbacks;
CmdParser cmdParser;
CmdBuffer<256> cmdBuffer;
@@ -85,7 +109,7 @@ void cmdSet(CmdParser* parser) {
return;
}
WiFiNetwork::setWiFiCredentials(sc_ssid, sc_pw);
wifiNetwork.setWiFiCredentials(sc_ssid, sc_pw);
logger.info("CMD SET WIFI OK: New wifi credentials set, reconnecting");
}
} else if (parser->equalCmdParam(1, "BWIFI")) {
@@ -131,7 +155,7 @@ void cmdSet(CmdParser* parser) {
// set the pointer for pass to null for no password
ppass = NULL;
}
WiFiNetwork::setWiFiCredentials(ssid, ppass);
wifiNetwork.setWiFiCredentials(ssid, ppass);
logger.info("CMD SET BWIFI OK: New wifi credentials set, reconnecting");
}
} else {
@@ -150,43 +174,13 @@ void printState() {
HARDWARE_MCU,
PROTOCOL_VERSION,
FIRMWARE_VERSION,
WiFiNetwork::getAddress().toString().c_str(),
wifiNetwork.getAddress().toString().c_str(),
WiFi.macAddress().c_str(),
statusManager.getStatus(),
WiFiNetwork::getWiFiState()
wifiNetwork.getWiFiState()
);
char vendorBuffer[512];
size_t writtenLength;
if (strlen(VENDOR_URL) == 0) {
sprintf(
vendorBuffer,
"Vendor: %s, product: %s%n",
VENDOR_NAME,
PRODUCT_NAME,
&writtenLength
);
} else {
sprintf(
vendorBuffer,
"Vendor: %s (%s), product: %s%n",
VENDOR_NAME,
VENDOR_URL,
PRODUCT_NAME,
&writtenLength
);
}
if (strlen(UPDATE_ADDRESS) > 0 && strlen(UPDATE_NAME) > 0) {
sprintf(
vendorBuffer + writtenLength,
", firmware update url: %s, name: %s",
UPDATE_ADDRESS,
UPDATE_NAME
);
}
logger.info("%s", vendorBuffer);
logger.info("%s", FULL_VENDOR_STR);
for (auto& sensor : sensorManager.getSensors()) {
logger.info(
@@ -311,10 +305,10 @@ void cmdGet(CmdParser* parser) {
HARDWARE_MCU,
PROTOCOL_VERSION,
FIRMWARE_VERSION,
WiFiNetwork::getAddress().toString().c_str(),
wifiNetwork.getAddress().toString().c_str(),
WiFi.macAddress().c_str(),
statusManager.getStatus(),
WiFiNetwork::getWiFiState()
wifiNetwork.getWiFiState()
);
auto& sensor0 = sensorManager.getSensors()[0];
sensor0->motionLoop();
@@ -347,8 +341,8 @@ void cmdGet(CmdParser* parser) {
if (WiFi.status() != WL_CONNECTED) {
WiFi.disconnect();
}
if (WiFiNetwork::isProvisioning()) {
WiFiNetwork::stopProvisioning();
if (wifiProvisioning.isProvisioning()) {
wifiProvisioning.stopProvisioning();
}
WiFi.scanNetworks();
@@ -457,6 +451,13 @@ void cmdDeleteCalibration(CmdParser* parser) {
configuration.eraseSensors();
}
#if EXT_SERIAL_COMMANDS
void cmdScanI2C(CmdParser* parser) {
logger.info("Forcing I2C scan...");
I2CSCAN::scani2cports();
}
#endif
void setUp() {
cmdCallbacks.addCmd("SET", &cmdSet);
cmdCallbacks.addCmd("GET", &cmdGet);
@@ -464,6 +465,9 @@ void setUp() {
cmdCallbacks.addCmd("REBOOT", &cmdReboot);
cmdCallbacks.addCmd("DELCAL", &cmdDeleteCalibration);
cmdCallbacks.addCmd("TCAL", &cmdTemperatureCalibration);
#if EXT_SERIAL_COMMANDS
cmdCallbacks.addCmd("SCANI2C", &cmdScanI2C);
#endif
}
void update() { cmdCallbacks.updateCmdProcessing(&cmdParser, &cmdBuffer, &Serial); }