Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ jobs:

- name: Coverage report
run: |
output=$(gcovr --root . --filter 'src/' --exclude 'src/SnipsController\.ino' --exclude 'src/oled\.cpp' --exclude 'src/rgb_led\.cpp' --exclude 'src/calibration_store\.cpp' --exclude 'src/droid_persistence\.cpp' --exclude 'src/xbee_spi\.cpp' --exclude 'src/xbee_control\.cpp' --print-summary --fail-under-line 90)
output=$(gcovr --root . --filter 'src/' --exclude 'src/SnipsController\.ino' --exclude 'src/oled\.cpp' --exclude 'src/rgb_led\.cpp' --exclude 'src/calibration_store\.cpp' --exclude 'src/droid_persistence\.cpp' --exclude 'src/xbee_spi\.cpp' --exclude 'src/xbee_control\.cpp' --exclude 'src/complication_persistence\.cpp' --print-summary --fail-under-line 90)
status=$?
{
echo '### Coverage (src/, excluding SnipsController.ino and hardware adapters)'
Expand Down
17 changes: 17 additions & 0 deletions include/complication_persistence.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#pragma once

#include "complications.h"

// Thin adapter persisting a ComplicationRegistry's slot assignments to
// NVS (ESP32 Preferences). No complication logic lives here — see
// complications.h. Excluded from native build/coverage (see
// platformio.ini's [env:native] build_src_filter).
namespace ComplicationPersistence {

// Applies any previously-saved slot assignments onto `registry` (leaving
// its defaults in place for any slot that's never been saved).
void load(ComplicationRegistry *registry);

void save(const ComplicationRegistry &registry);

} // namespace ComplicationPersistence
62 changes: 62 additions & 0 deletions include/complications.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#pragma once

#include <cstddef>

#include "packet.h"
#include "screen.h"

// Maps OLED display slots to data sources for the "normal operating"
// screen (shown whenever the on-device menu is closed) — smartwatch-style
// complications, user-assignable via the Display Config menu screen.
// Pure logic: knows nothing about how the underlying data is obtained
// (XBee queries, ADC reads, etc.) — SnipsController.ino feeds current
// values in every tick via setData().
enum class ComplicationSource {
kBattery,
kLeftSlot,
kRightSlot,
kSignal,
kDroidName,
kHandedness,
kCount,
};

const char *complicationSourceLabel(ComplicationSource source);

struct ComplicationData {
int batteryPercent = 0;
char leftLabel[DownlinkPacket::kFieldLength + 1] = {};
char leftValue[DownlinkPacket::kFieldLength + 1] = {};
char rightLabel[DownlinkPacket::kFieldLength + 1] = {};
char rightValue[DownlinkPacket::kFieldLength + 1] = {};
int signalDbm = 0;
bool signalKnown = false; // distinguishes "0dBm" from "never queried"
char droidName[17] = {};
DownlinkPacket::Handedness handedness =
DownlinkPacket::Handedness::kUnknown;
};

class ComplicationRegistry {
public:
static constexpr int kSlotCount = 4;

ComplicationSource slotSource(int slotIndex) const;

// Both are bounds-checked no-ops on an out-of-range slotIndex.
void setSlotSource(int slotIndex, ComplicationSource source);
void cycleSlotSource(int slotIndex);

void setData(const ComplicationData &data) { data_ = data; }

// Renders every slot into consecutive ScreenBuffer lines, one per slot.
void render(ScreenBuffer *screen) const;

private:
ComplicationSource slots_[kSlotCount] = {
ComplicationSource::kBattery,
ComplicationSource::kDroidName,
ComplicationSource::kLeftSlot,
ComplicationSource::kRightSlot,
};
ComplicationData data_;
};
26 changes: 23 additions & 3 deletions include/menu.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#pragma once

#include "calibration.h"
#include "complications.h"
#include "droid_store.h"
#include "screen.h"
#include "text_entry.h"
Expand All @@ -11,9 +12,6 @@
// the nav calls below (and detects the open combo via updateOpenCombo()),
// and renderMenuScreen() turns the current state into a ScreenBuffer for
// the (already-existing, hardware-touching) OledDisplay to draw.
//
// Display Config (PR 9) isn't part of this menu tree yet — it needs the
// packet protocol's label/value data, which doesn't exist yet.
enum class MenuScreen {
kInactive,
kMainMenu,
Expand All @@ -25,6 +23,7 @@ enum class MenuScreen {
kManageDroidsDeleteConfirm,
kCalibrateStick,
kCalibrateTrigger,
kDisplayConfig,
kDeviceInfo,
kFactoryResetConfirm,
};
Expand All @@ -34,6 +33,7 @@ enum class MainMenuItem {
kManageDroids,
kCalibrateStick,
kCalibrateTrigger,
kDisplayConfig,
kDeviceInfo,
kFactoryReset,
kCount,
Expand Down Expand Up @@ -111,6 +111,21 @@ class MenuController {
const TextEntryWidget &nameEntry() const { return nameEntry_; }
const TextEntryWidget &panIdEntry() const { return panIdEntry_; }

// The most recently successfully-switched-to droid's name, for the
// complications system's "Droid Name" source — "(none)" until a switch
// has actually succeeded this session (not persisted; resets on boot).
const char *currentDroidName() const { return currentDroidName_; }

// Display Config edits an externally-owned ComplicationRegistry rather
// than duplicating its slot-assignment state here — set once at boot.
// May be left null (the menu screen then just does nothing on Enter).
void setComplications(ComplicationRegistry *registry) {
complications_ = registry;
}
const ComplicationRegistry *complications() const { return complications_; }
int selectedDisplayConfigSlot() const { return displayConfigSlotIndex_; }
bool consumeComplicationsChanged();

private:
static constexpr unsigned long kOpenComboHoldMs = 1000;

Expand Down Expand Up @@ -149,6 +164,11 @@ class MenuController {
DroidSwitchResult lastSwitchResult_ = DroidSwitchResult::kSuccess;
XbeeTransport *xbeeTransport_ = nullptr;
const char *deviceSerialLow_ = "(unknown)";
char currentDroidName_[DroidEntry::kMaxNameLength + 1] = "(none)";

ComplicationRegistry *complications_ = nullptr;
int displayConfigSlotIndex_ = 0;
bool complicationsChanged_ = false;
};

// Decides what text should be on screen for the menu's current state.
Expand Down
8 changes: 7 additions & 1 deletion include/packet.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,18 @@
// gesture classification — see the rewrite plan's Context section) plus
// calibrated analog and battery/charge status.
struct UplinkPacket {
static constexpr uint8_t kFlagShuttingDown = 0x01;

uint16_t buttonMask = 0; // bit i = Buttons::Index i is pressed
uint8_t triggerPercent = 0; // 0-100
int8_t stickXPercent = 0; // -100..100
int8_t stickYPercent = 0; // -100..100
uint8_t batteryPercent = 0; // 0-100
ChargeState chargeState = ChargeState::kDone;
// Bitfield, currently just kFlagShuttingDown — set on the final few
// uplinks before power cuts so Amidala can mark this controller
// disconnected immediately instead of waiting out a timeout.
uint8_t flags = 0;
};

// Amidala -> controller. Handedness is sent once at connect and is static
Expand All @@ -46,7 +52,7 @@ struct DownlinkPacket {

namespace Packet {

constexpr size_t kUplinkEncodedSize = 7;
constexpr size_t kUplinkEncodedSize = 8;
constexpr size_t kDownlinkEncodedSize =
1 + 4 * DownlinkPacket::kFieldLength; // 33

Expand Down
7 changes: 7 additions & 0 deletions include/xbee_control.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,13 @@ class XbeeControl : public XbeeTransport {
// query failure, leaving outHex untouched.
bool querySerialLow(char *outHex, size_t outHexCapacity);

// Queries the local module's own last-hop received signal strength
// ("DB" AT command — a single byte, the RSSI magnitude in dBm, e.g. a
// response of 0x2A means -42dBm). Purely local: no round trip to
// Amidala needed, unlike everything else this controller displays.
// Returns false on query failure, leaving outDbm untouched.
bool queryLocalRssiDbm(int *outDbm);

// Passthroughs to the owned XbeeSpi — see xbee_spi.h. XbeeControl is
// kept as the single facade over the one physical XBee connection
// rather than SnipsController.ino owning a second XbeeSpi instance.
Expand Down
10 changes: 5 additions & 5 deletions platformio.ini
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,14 @@ lib_deps =
; adapter files (thin wrappers over I2C/SPI/ADC/RMT/NVS), added here
; alongside as new subsystems land — see CLAUDE.md's testing-approach note.
; Currently: oled.cpp (Adafruit_SSD1306/Wire), rgb_led.cpp
; (Adafruit_NeoPixel/RMT), calibration_store.cpp and droid_persistence.cpp
; (both Preferences/NVS), xbee_spi.cpp (SPI) and xbee_control.cpp (calls
; through to XbeeSpi, hardware-dependent now that it's a real transport
; rather than a stub).
; (Adafruit_NeoPixel/RMT), calibration_store.cpp, droid_persistence.cpp,
; and complication_persistence.cpp (all Preferences/NVS), xbee_spi.cpp
; (SPI) and xbee_control.cpp (calls through to XbeeSpi, hardware-dependent
; now that it's a real transport rather than a stub).
[env:native]
platform = native
test_build_src = yes
build_src_filter = +<*> -<*.ino> -<oled.cpp> -<rgb_led.cpp> -<calibration_store.cpp> -<droid_persistence.cpp> -<xbee_spi.cpp> -<xbee_control.cpp>
build_src_filter = +<*> -<*.ino> -<oled.cpp> -<rgb_led.cpp> -<calibration_store.cpp> -<droid_persistence.cpp> -<xbee_spi.cpp> -<xbee_control.cpp> -<complication_persistence.cpp>
test_framework = unity
build_flags = --coverage
extra_scripts = pre:scripts/native_coverage_linkflags.py
Loading
Loading