Skip to content

Commit 341ac7d

Browse files
committed
Merge branch 'develop' into copilot/handle-wifi-issues-after-ota-update
2 parents 0080a41 + b7441b0 commit 341ac7d

23 files changed

+817
-784
lines changed

.github/copilot-instructions.md

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
# SmartSpin2k - ESP32 Smart Trainer Firmware
2+
3+
SmartSpin2k is an ESP32-based DIY smart trainer project that converts any spin bike into a connected fitness device compatible with Zwift, TrainerRoad, and other training apps. The firmware controls stepper motor resistance, handles BLE communication, serves a web interface, and manages sensor data.
4+
5+
Always reference these instructions first and fallback to search or bash commands only when you encounter unexpected information that does not match the info here.
6+
7+
## Working Effectively
8+
9+
### Bootstrap Environment
10+
Install required tools and dependencies:
11+
- `sudo apt-get update && sudo apt-get install -y build-essential git python3 python3-pip`
12+
- `pip install platformio pre-commit`
13+
- `pre-commit install --hook-type pre-push`
14+
15+
### Build the Firmware
16+
- **CRITICAL**: Build takes 15-45 minutes depending on network connectivity. NEVER CANCEL. Set timeout to 60+ minutes.
17+
- `pio run --environment release` -- builds ESP32 firmware. NEVER CANCEL: Build takes 15-45 minutes on first run due to platform/toolchain downloads.
18+
- `pio run --target buildfs` -- builds filesystem. Takes 2-5 minutes.
19+
- **Network Issues**: If platform downloads fail with HTTPClientError, this is due to firewall/network restrictions. The build cannot proceed without internet access to download ESP32 toolchain.
20+
21+
### Testing
22+
- **CRITICAL**: Native tests take 5-15 minutes. NEVER CANCEL. Set timeout to 30+ minutes.
23+
- `pio test --environment native` -- runs unit tests using Unity framework. NEVER CANCEL: Takes 5-15 minutes on first run.
24+
- Tests validate sensor data parsing, power calculations, BLE communication, and stepper motor control.
25+
- **Network Issues**: Native platform download may fail with HTTPClientError due to firewall restrictions.
26+
27+
### Code Quality and Validation
28+
- `pre-commit run --all-files` -- runs license header insertion. Takes 1-2 minutes.
29+
- `pio check -e debug` -- runs cppcheck static analysis on debug environment. Takes 2-5 minutes. **Network Issues**: May fail with HTTPClientError due to platform download restrictions.
30+
- Python build scripts (always work):
31+
- `python git_tag_macro.py` -- generates firmware version from git tags
32+
- `python build_date_macro.py` -- generates build timestamp
33+
- `python cert_updater.py` -- updates SSL certificates (may fail with network issues)
34+
35+
### Run the Application
36+
- **Build First**: Always complete the bootstrap and build steps before attempting to run.
37+
- The application runs on ESP32 hardware - cannot be executed in the sandbox environment.
38+
- Web interface available at device IP on port 80 when running on hardware.
39+
- BLE services broadcast as "SmartSpin2k" when running on hardware.
40+
41+
## Validation
42+
43+
### Manual Testing Scenarios
44+
After making code changes, always validate:
45+
1. **Build Validation**: Ensure `pio run --environment release` completes successfully.
46+
2. **Test Validation**: Ensure `pio test --environment native` passes all Unity tests.
47+
3. **Code Quality**: Run `pre-commit run --all-files` and fix any license header issues.
48+
4. **BLE Service Changes**: When modifying BLE services, verify characteristic UUIDs match the CustomCharacteristic.md specification.
49+
5. **Power Calculations**: When changing power table or ERG mode code, run tests in test_pt_lookup_*.cpp files.
50+
6. **Sensor Data**: When modifying sensor parsing, validate with tests in test_*Data.cpp files.
51+
52+
### Critical Areas to Test
53+
- **Power Table**: Always validate power lookup and resistance calculations after changes to Power_Table.cpp or PowerTable_Helpers.cpp
54+
- **BLE Services**: Test characteristic read/write operations when modifying BLE_*_Service.cpp files
55+
- **ERG Mode**: Validate resistance control when changing ERG_Mode.cpp
56+
- **Stepper Control**: Test motor control when modifying stepper-related code in Main.cpp
57+
58+
## Common Tasks
59+
60+
### Repository Structure
61+
Key directories and their purpose:
62+
```
63+
/src -- Main ESP32 firmware source code
64+
/lib/SS2K/src -- Core library with sensor parsing and data structures
65+
/include -- Header files and configuration
66+
/test -- Unity unit tests for native environment
67+
/data -- Web interface HTML/CSS files
68+
/Hardware -- 3D printing files and PCB designs
69+
/.github/workflows -- CI/CD pipeline definitions
70+
```
71+
72+
### Important Files
73+
- `platformio.ini` -- Build configuration for ESP32 and native environments
74+
- `include/settings.h` -- Hardware pin definitions and configuration constants
75+
- `CustomCharacteristic.md` -- BLE characteristic specification and usage
76+
- `src/Main.cpp` -- Main firmware entry point and setup
77+
- `lib/SS2K/src/sensors/` -- Sensor data parsing classes
78+
79+
### Build Dependencies
80+
External libraries loaded automatically by PlatformIO:
81+
- NimBLE-ESP32 for Bluetooth Low Energy
82+
- TMCStepper for stepper motor control
83+
- FastAccelStepper for smooth motor movement
84+
- ArduinoJson for configuration and web API
85+
- ArduinoWebsockets for real-time web communication
86+
87+
### Configuration
88+
- Default device name: "SmartSpin2k"
89+
- Default WiFi password: "password"
90+
- Web interface served on port 80
91+
- BLE service UUID: "77776277-7877-7774-4466-896665500000"
92+
- Over-the-air update URL: configured in settings.h
93+
94+
### Hardware Compatibility
95+
- ESP32 DevKit v1 board (primary target)
96+
- TMC2209 stepper motor driver
97+
- Custom PCB designs in Hardware/ directory
98+
- Support for multiple bike mount configurations
99+
100+
### Known Issues and Limitations
101+
- **Network Connectivity**: Platform and toolchain downloads may fail due to firewall restrictions. All build commands (`pio run`, `pio test`, `pio check`) require internet access on first run.
102+
- **SSL Certificates**: cert_updater.py may fail to fetch current certificates due to network restrictions
103+
- **Hardware Testing**: Cannot test actual motor control or BLE communication without physical hardware
104+
- **Build Times**: Initial builds require internet access and take 15-45 minutes due to large platform downloads
105+
106+
### Troubleshooting Common Issues
107+
- **HTTPClientError during build**: This indicates network/firewall restrictions preventing platform downloads. No workaround available in restricted environments.
108+
- **Platform not found**: Run `pio platform install espressif32` to manually install the ESP32 platform (requires internet).
109+
- **Test failures**: Ensure you're running tests in native environment: `pio test -e native`
110+
- **SSL certificate warnings**: Update certificates with `python cert_updater.py` or manually update `include/cert.h`
111+
- **Build flag errors**: The Python scripts in build_flags must execute successfully. Test them individually if build fails.
112+
113+
### Environment Verification
114+
Before working on the project, verify your environment:
115+
```bash
116+
# Check tools are installed
117+
which python3 pio pre-commit
118+
# Verify project configuration
119+
pio project config
120+
# Test build scripts
121+
python git_tag_macro.py && python build_date_macro.py
122+
```
123+
124+
### Debugging Tips
125+
- Use `pio device monitor` to view serial output when connected to ESP32 hardware
126+
- Check `include/cert.h` if experiencing SSL errors during firmware updates
127+
- Monitor memory usage with DEBUG_STACK enabled in settings.h
128+
- BLE debugging available through web interface at `/develop.html`
129+
130+
Always run `pre-commit run --all-files` before completing changes to ensure code meets project standards.
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
name: "Copilot Setup Steps"
2+
3+
on:
4+
workflow_dispatch:
5+
push:
6+
paths:
7+
- .github/workflows/copilot-setup-steps.yml
8+
pull_request:
9+
paths:
10+
- .github/workflows/copilot-setup-steps.yml
11+
12+
jobs:
13+
# IMPORTANT: Job name must be exactly this.
14+
copilot-setup-steps:
15+
# You can upgrade to a larger Ubuntu runner label later if needed (e.g. ubuntu-4-core)
16+
runs-on: ubuntu-latest
17+
timeout-minutes: 40
18+
19+
# Minimize permissions; Copilot will get its own token later.
20+
permissions:
21+
contents: read
22+
23+
steps:
24+
- name: Checkout repository
25+
uses: actions/checkout@v5
26+
with:
27+
# fetch-depth overridden internally for Copilot anyway; keep explicit for normal runs
28+
fetch-depth: 0
29+
30+
- name: Set up Python
31+
uses: actions/setup-python@v6
32+
with:
33+
python-version: '3.x'
34+
35+
- name: Cache PlatformIO core and packages
36+
uses: actions/cache@v4
37+
with:
38+
path: |
39+
~/.platformio
40+
key: pio-${{ runner.os }}-${{ hashFiles('platformio.ini', 'dependencies.lock') }}
41+
restore-keys: |
42+
pio-${{ runner.os }}-
43+
44+
- name: Install PlatformIO Core
45+
run: |
46+
python -m pip install --upgrade pip
47+
pip install --upgrade platformio
48+
49+
- name: Show PlatformIO info
50+
run: |
51+
platformio --version
52+
platformio system info
53+
54+
- name: Pre-download native test dependencies
55+
run: |
56+
platformio pkg install -e native || echo "Native env install attempted"
57+
58+
- name: Pre-download ESP32 toolchains and libraries (release env)
59+
run: |
60+
# Only install packages (faster + ensures offline availability later)
61+
platformio pkg install -e release
62+
63+
- name: Warm build cache (lightweight compile) for release env
64+
run: |
65+
# A full build ensures toolchains, frameworks, and libs are all present
66+
# If this becomes too slow, you can remove this step and rely on pkg install only.
67+
platformio run -e release || exit 0
68+
69+
- name: Warm build cache for native env (tests)
70+
run: |
71+
platformio run -e native || exit 0
72+
73+
- name: Validate native tests (non-blocking)
74+
run: |
75+
set +e
76+
platformio test -e native
77+
# Do not fail setup if tests fail; Copilot will handle fixes.
78+
exit 0
79+
80+
- name: Summarize cached packages
81+
run: |
82+
du -sh ~/.platformio/packages/* || true
83+
du -sh ~/.platformio/platforms/* || true
84+
85+
- name: Guidance for future adjustments
86+
shell: bash
87+
run: |
88+
echo "Setup steps completed. You can:"
89+
echo " - Upgrade runner: change runs-on to ubuntu-4-core if builds are slow." \
90+
"\n - Enable LFS: add 'with: lfs: true' to checkout step if using Git LFS." \
91+
"\n - Trim steps: remove warm build steps if time exceeds limits." \
92+
"\n - Add more pkg installs for other environments if created later."

.gitignore

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,4 @@ C/Users/
2222
/managed_components
2323
dependencies.lock
2424
.vscode/settings.json
25-
dependencies.lock
26-
dependencies.lock
25+
*.map

CHANGELOG.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,29 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3232

3333
### Hardware
3434

35+
36+
## [25.9.17]
37+
38+
### Added
39+
40+
### Changed
41+
- Fixed incline mode handling negative numbers.
42+
43+
### Hardware
44+
45+
46+
## [25.9.8]
47+
48+
### Added
49+
50+
### Changed
51+
- Only check battery level on initial connection. This is to fix Tempo power meter drops.
52+
- Moved battery information to the SpinBLEAdvertisedDevice class.
53+
- When adding to SpinBLEAdvertisedDevice, check adevname as well as address to prevent duplicates.
54+
- Stopped reusing BLE clients for better connection reliability.
55+
56+
### Hardware
57+
3558
## [25.8.26]
3659

3760
### Added

dependencies.lock

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ dependencies:
7575
idf:
7676
source:
7777
type: idf
78-
version: 5.4.1
78+
version: 5.4.2
7979
joltwallet/littlefs:
8080
component_hash: fe3d04a59a4c370408b0e0b69d9096c06371b9ee12ad8e06b9d52ac63ab1570c
8181
dependencies:
@@ -96,6 +96,6 @@ direct_dependencies:
9696
- espressif/network_provisioning
9797
- idf
9898
- joltwallet/littlefs
99-
manifest_hash: e871ea1e1aff9421b8e57e1c5a4db8daeef71b071a2a17f239e5feebc4acdfc3
99+
manifest_hash: e7ce7a886dfdb3cc5cd59acbdb4ff333c0a91c6e4de55b9fd6323d866c7cc691
100100
target: esp32
101101
version: 2.0.0

include/BLE_Common.h

Lines changed: 19 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
// maxInterval – [in] The maximum connection interval in 1.25ms units.
3030
// latency – [in] The number of packets allowed to skip (extends max interval).
3131
// timeout – [in] The timeout time in 10ms units before disconnecting.
32-
const uint16_t connectionParams[] = {24, 168, 1, 200};
32+
const uint16_t connectionParams[] = {24, 48, 0, 200};
3333

3434
// Vector of supported BLE services and their corresponding characteristic UUIDs
3535
struct BLEServiceInfo {
@@ -42,8 +42,8 @@ namespace BLEServices {
4242
const std::vector<BLEServiceInfo> SUPPORTED_SERVICES = {{CYCLINGPOWERSERVICE_UUID, CYCLINGPOWERMEASUREMENT_UUID, "Cycling Power Service"},
4343
{CSCSERVICE_UUID, CSCMEASUREMENT_UUID, "Cycling Speed And Cadence Service"},
4444
{HEARTSERVICE_UUID, HEARTCHARACTERISTIC_UUID, "Heart Rate Service"},
45-
{ECHELON_DEVICE_UUID, ECHELON_SERVICE_UUID, "Echelon Device"}, // Two lines for Echelon
46-
{ECHELON_SERVICE_UUID, ECHELON_DATA_UUID, "Echelon Service"}, // Because one is for search, the other for data
45+
{ECHELON_DEVICE_UUID, ECHELON_SERVICE_UUID, "Echelon Device"}, // Two lines for Echelon
46+
{ECHELON_SERVICE_UUID, ECHELON_DATA_UUID, "Echelon Service"}, // Because one is for search, the other for data
4747
{FITNESSMACHINESERVICE_UUID, FITNESSMACHINEINDOORBIKEDATA_UUID, "Fitness Machine Service"},
4848
{HID_SERVICE_UUID, HID_REPORT_DATA_UUID, "HID Service"},
4949
{FLYWHEEL_UART_SERVICE_UUID, FLYWHEEL_UART_TX_UUID, "Flywheel UART Service"}};
@@ -145,33 +145,29 @@ class SpinBLEAdvertisedDevice {
145145
private:
146146
QueueHandle_t dataBufferQueue = nullptr;
147147

148-
bool isPostConnected = false;
148+
void clearState(bool resetAdvertisedDevice); // NEW
149149

150-
public: // eventually these should be made private
151-
// // TODO: Do we dispose of this object? Is so, we need to de-allocate the queue.
152-
// // This destructor was called too early and the queue was deleted out from
153-
// // under us.
154-
// ~SpinBLEAdvertisedDevice() {
155-
// if (dataBuffer != nullptr) {
156-
// Serial.println("Deleting queue");
157-
// vQueueDelete(dataBuffer);
158-
// }
159-
// }
150+
public:
151+
SpinBLEAdvertisedDevice() { clearState(true); } // NEW
160152

161153
const NimBLEAdvertisedDevice* advertisedDevice = nullptr;
162154
NimBLEAddress peerAddress;
163155

156+
std::string uniqueName = ""; // Stable identifier using adevName2UniqueName()
157+
164158
int connectedClientID = BLE_HS_CONN_HANDLE_NONE;
165159
BLEUUID serviceUUID = (uint16_t)0x0000;
166160
BLEUUID charUUID = (uint16_t)0x0000;
167-
bool isHRM = false;
168-
bool isPM = false;
169-
bool isCSC = false;
170-
bool isCT = false;
171-
bool isRemote = false;
172-
bool doConnect = false;
173-
void setPostConnected(bool pc) { isPostConnected = pc; }
174-
bool getPostConnected() { return isPostConnected; }
161+
Measurement batt;
162+
bool isHRM = false;
163+
bool isPM = false;
164+
bool isCSC = false;
165+
bool isCT = false;
166+
bool isRemote = false;
167+
bool doConnect = false;
168+
bool isPostConnected = false;
169+
unsigned long lastDataUpdateTime = 0; // Reset disconnect detection timestamp
170+
175171
void set(const NimBLEAdvertisedDevice* device, int id = BLE_HS_CONN_HANDLE_NONE, BLEUUID inServiceUUID = (uint16_t)0x0000, BLEUUID inCharUUID = (uint16_t)0x0000);
176172
void reset(bool resetAdvertisedDevice = true);
177173
bool enqueueData(uint8_t* data, size_t length, NimBLEUUID serviceUUID, NimBLEUUID charUUID);
@@ -188,21 +184,18 @@ class SpinBLEClient {
188184
boolean connectedCT = false;
189185
boolean connectedSpeed = false;
190186
boolean connectedRemote = false;
191-
boolean doScan = false;
192-
int intentionalDisconnect = 0;
187+
boolean doScan = true; //Set to true so there's an initial scan on startup
193188
long int cscCumulativeCrankRev = 0;
194189
double cscLastCrankEvtTime = 0.0;
195190
long int cscCumulativeWheelRev = 0;
196191
double cscLastWheelEvtTime = 0.0;
197-
int reconnectTries = MAX_RECONNECT_TRIES;
198192

199193
BLERemoteCharacteristic* pRemoteCharacteristic = nullptr;
200194

201195
// BLEDevices myBLEDevices;
202196
SpinBLEAdvertisedDevice myBLEDevices[NUM_BLE_DEVICES];
203197

204198
void start();
205-
// void serverScan(bool connectRequest);
206199
bool connectToServer();
207200
// Check for duplicate services of BLEClient and remove the previously
208201
// connected one.

include/BLE_Fitness_Machine_Service.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,9 @@ class BLE_Fitness_Machine_Service {
1717
void update();
1818
bool spinDown(uint8_t response);
1919
void processFTMSWrite();
20-
20+
2121
private:
22+
int calculateResistanceFromPosition();
2223
BLEService *pFitnessMachineService;
2324
BLECharacteristic *fitnessMachineFeature;
2425
BLECharacteristic *fitnessMachineIndoorBikeData;

include/ERG_Mode.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ class ErgMode {
1919
// What used to be in the ERGTaskLoop(). This is the main control function for ERG Mode and the powertable operations.
2020
void runERG();
2121
void computeErg();
22-
void computeResistance();
2322
void _writeLog(float currentIncline, float newIncline, int currentSetPoint, int newSetPoint, int currentWatts, int newWatts, int currentCadence, int newCadence);
2423

2524
private:

include/Main.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ class SS2K {
7474
void FTMSModeShiftModifier();
7575
static void rxSerial(void);
7676
void txSerial();
77-
void pelotonConnected();
77+
bool pelotonConnected();
7878
void goHome(bool bothDirections = false);
7979

8080
SS2K() {

0 commit comments

Comments
 (0)