Skip to content
Merged
27 changes: 26 additions & 1 deletion lib/steami_config/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,12 +145,35 @@ config.apply_accelerometer_calibration(imu)

---

## Boot counter
Store and restore amount of boot of the card.

### Set boot counter
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new Boot counter section wording is unclear/incorrect (“amount of boot of the card”) and has trailing spaces in headings. Please rephrase to clearly describe the board boot count (e.g., “number of board boots/power cycles”) and remove the trailing whitespace.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 369cd25: reworded to "Track how many times the board has booted." and removed trailing spaces.

```python
config.set_boot_count(0)
```

### Get boot counter
```python
config.get_boot_count()
print("Boot count:", count)
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The “Get boot counter” README snippet calls config.get_boot_count() but then prints count, which is never assigned. Assign the return value to count (and consider showing the None case since get_boot_count() can return None when unset).

Suggested change
config.get_boot_count()
print("Boot count:", count)
count = config.get_boot_count()
if count is None:
print("Boot count is not set")
else:
print("Boot count:", count)

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 05a5d81: added count = before config.get_boot_count().

# -> Boot count: 0
```

### Increment boot counter
```python
config.increment_boot_count()
# -> boot_count = boot_count + 1
```

---

# JSON Format

Data is stored as compact JSON to fit within 1 KB:

```json
{"rev":3,"name":"STeaMi-01","tc":{"hts":{"g":1.0,"o":-0.5}},"cm":{"hx":12.3,"hy":-5.1,"hz":0.8,"sx":1.01,"sy":0.98,"sz":1.0},"ca":{"ox":0.01,"oy":-0.02,"oz":0.03}}
{"rev":3,"name":"STeaMi-01","tc":{"hts":{"g":1.0,"o":-0.5}},"cm":{"hx":12.3,"hy":-5.1,"hz":0.8,"sx":1.01,"sy":0.98,"sz":1.0},"ca":{"ox":0.01,"oy":-0.02,"oz":0.03},"boot_count":0}
```

| Key | Content |
Expand All @@ -165,6 +188,7 @@ Data is stored as compact JSON to fit within 1 KB:
| `cm.sx/sy/sz` | Soft-iron scale factors (X, Y, Z) |
| `ca` | Accelerometer calibration dict |
| `ca.ox/oy/oz` | Bias offsets in g (X, Y, Z) |
| `boot_count` | Count of the amount of boot |
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The JSON key description for boot_count (“Count of the amount of boot”) is grammatically incorrect/unclear. Please update this table entry to something unambiguous like “Boot count (number of power cycles)”.

Suggested change
| `boot_count` | Count of the amount of boot |
| `boot_count` | Boot count (number of power cycles) |

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 95a2d36: key renamed to "bc" and table entry updated to "Boot counter".


Sensor short keys: `hts` (HTS221), `mag` (LIS2MDL), `ism` (ISM330DL),
`hid` (WSEN-HIDS), `pad` (WSEN-PADS).
Expand All @@ -179,6 +203,7 @@ Sensor short keys: `hts` (HTS221), `mag` (LIS2MDL), `ism` (ISM330DL),
| `calibrate_temperature.py` | Calibrate all sensors against WSEN-HIDS reference |
| `calibrate_magnetometer.py` | Calibrate LIS2MDL with OLED display and persistent storage |
| `calibrate_accelerometer.py` | Calibrate ISM330DL accelerometer bias and persist it |
| `boot_counter.py` | Display the amount of boot and increment it |

Run with mpremote:

Expand Down
28 changes: 28 additions & 0 deletions lib/steami_config/examples/boot_counter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
"""
Boot counter example.
Each time the board boots,
it increments the boot count
and saves it to non-volatile storage.
"""

from daplink_bridge import DaplinkBridge
from machine import I2C
from steami_config import SteamiConfig

# --- Hardware init ---
i2c = I2C(1)
bridge = DaplinkBridge(i2c)

config = SteamiConfig(bridge)
config.load()

# --- Boot counter logic ---
config.increment_boot_count()

# Save updated value
config.save()
Comment on lines +19 to +23
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This example increments the counter and immediately calls config.save(). Since SteamiConfig.save() erases the config zone (bridge.clear_config()) before rewriting JSON, doing this on every boot can significantly increase flash erase/write cycles (and is especially risky in reboot loops). Consider adding a note about flash wear / optionally persisting less frequently (e.g., only every N boots or on graceful shutdown).

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Valid point about flash wear. However, this is a pedagogical example — simplicity is the priority. The STeaMi DAPLink config zone uses a dedicated EEPROM-like region, not the main flash, so the write endurance is higher. Adding wear-leveling logic to a 28-line example would obscure the intent. A comment warning about frequent writes could be added, but it's not blocking.


# Read and display
count = config.get_boot_count()

print("Boot count:", count)
19 changes: 19 additions & 0 deletions lib/steami_config/steami_config/device.py
Original file line number Diff line number Diff line change
Expand Up @@ -254,3 +254,22 @@ def apply_accelerometer_calibration(self, ism330dl_instance):
cal["oy"],
cal["oz"],
)

# --------------------------------------------------
# Boot counter
# --------------------------------------------------

def set_boot_count(self, count):
"""Store the number of times the board has booted."""
self._data["boot_count"] = int(count)

def get_boot_count(self):
"""Return the stored boot count, or None."""
return self._data.get("boot_count")

def increment_boot_count(self):
"""Increment the stored boot count by 1."""
count = self.get_boot_count()
if count is None:
count = 0
self.set_boot_count(count + 1)
147 changes: 147 additions & 0 deletions tests/scenarios/steami_config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,72 @@ tests:
expect_true: true
mode: [mock]

# -- Mock boot counter --
- name: "Set and get boot counter"
action: script
script: |
dev._data = {}
dev.set_boot_count(427)
result = dev.get_boot_count() == 427
expect_true: true
mode: [mock]

- name: "Get boot counter returns None when not set"
action: script
script: |
dev._data = {}
result = dev.get_boot_count() is None
expect_true: true
mode: [mock]

- name: "Increment boot counter from empty starts at 1"
action: script
script: |
dev._data = {}
dev.increment_boot_count()
result = dev.get_boot_count() == 1
expect_true: true
mode: [mock]

- name: "Increment boot counter increases existing value"
action: script
script: |
dev._data = {}
dev.set_boot_count(41)
dev.increment_boot_count()
result = dev.get_boot_count() == 42
expect_true: true
mode: [mock]

- name: "Boot counter survives save/load"
action: script
script: |
dev._data = {}
dev.set_boot_count(427)
dev.save()
dev2 = SteamiConfig(dev._bridge)
dev2.load()
result = dev2.get_boot_count() == 427
expect_true: true
mode: [mock]

- name: "Boot counter coexists with other config values"
action: script
script: |
dev._data = {}
dev.board_revision = 3
dev.board_name = "STeaMi-Test"
dev.set_boot_count(12)
dev.set_accelerometer_calibration(ox=0.01, oy=-0.02, oz=0.03)
result = (
dev.board_revision == 3
and dev.board_name == "STeaMi-Test"
and dev.get_boot_count() == 12
and dev.get_accelerometer_calibration()["ox"] == 0.01
)
expect_true: true
mode: [mock]

# ----- Hardware -----

- name: "Save and load config on hardware"
Expand Down Expand Up @@ -541,3 +607,84 @@ tests:
)
expect_true: true
mode: [hardware]

# -- Hardware Boot Counter --
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The “Hardware Boot Counter” section header comment is indented under the previous test case (same indentation as mode:), which makes the YAML harder to scan. Align this comment with the other section headers at the list indentation level for consistency/readability.

Suggested change
# -- Hardware Boot Counter --
# -- Hardware Boot Counter --

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 591af42: comment realigned from 4 spaces to 2 spaces, consistent with all other section headers.

- name: "Save and load boot counter on hardware"
action: script
script: |
from time import sleep_ms
dev._bridge.clear_config()
dev._data = {}
dev.set_boot_count(427)
dev.save()
sleep_ms(200)
dev2 = SteamiConfig(dev._bridge)
dev2.load()
result = dev2.get_boot_count() == 427
expect_true: true
mode: [hardware]

- name: "Increment boot counter on hardware"
action: script
script: |
from time import sleep_ms
dev._bridge.clear_config()
dev._data = {}
dev.set_boot_count(5)
dev.save()
sleep_ms(200)

dev2 = SteamiConfig(dev._bridge)
dev2.load()
dev2.increment_boot_count()
dev2.save()
sleep_ms(200)

dev3 = SteamiConfig(dev._bridge)
dev3.load()
result = dev3.get_boot_count() == 6
expect_true: true
mode: [hardware]

- name: "Boot counter survives clear_flash"
action: script
script: |
from time import sleep_ms
from daplink_flash import DaplinkFlash
dev._bridge.clear_config()
dev._data = {}
dev.set_boot_count(99)
dev.save()
sleep_ms(200)

flash = DaplinkFlash(dev._bridge)
flash.clear_flash()
sleep_ms(500)

dev2 = SteamiConfig(dev._bridge)
dev2.load()
result = dev2.get_boot_count() == 99
expect_true: true
mode: [hardware]

- name: "Boot counter coexists with temperature calibration on hardware"
action: script
script: |
from time import sleep_ms
dev._bridge.clear_config()
dev._data = {}
dev.set_boot_count(12)
dev.set_temperature_calibration("hts221", gain=1.05, offset=-0.3)
dev.save()
sleep_ms(200)

dev2 = SteamiConfig(dev._bridge)
dev2.load()
tc = dev2.get_temperature_calibration("hts221")
result = (
dev2.get_boot_count() == 12
and tc["gain"] == 1.05
and tc["offset"] == -0.3
)
expect_true: true
mode: [hardware]
Loading