mirror of
https://github.com/mattintech/pyBTMCP.git
synced 2026-07-11 13:01:53 +00:00
v1.2.0: Firmware refactor to service-oriented architecture + WiFi fixes
Firmware: - Fixed WiFi reconnection bug after power cycle by adding WiFi.persistent(false) and disabling auto-connect/auto-reconnect - Fixed AP mode not broadcasting by properly handling WIFI_AP_STA mode transitions - Refactored to service-oriented architecture: - device_state: Central state management with event callbacks - config_service: NVS persistence (renamed from config_manager) - wifi_service: WiFi STA/AP management (extracted from main.cpp) - mqtt_service: MQTT client and message routing (extracted from main.cpp) - ble_service: BLE GATT services (renamed from ble_services) - web_service: HTTP configuration portal (renamed from web_portal) - main.cpp reduced from ~470 lines to ~60 lines (thin orchestrator) - Each service is self-contained with setup()/loop() pattern Backend: - Added heart rate variation endpoint - UI improvements Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,70 +0,0 @@
|
||||
#ifndef BLE_SERVICES_H
|
||||
#define BLE_SERVICES_H
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
// ============================================
|
||||
// BLE Service UUIDs (Bluetooth SIG standard)
|
||||
// ============================================
|
||||
|
||||
// Heart Rate Service
|
||||
#define HEART_RATE_SERVICE_UUID "180D"
|
||||
#define HEART_RATE_MEASUREMENT_UUID "2A37"
|
||||
#define BODY_SENSOR_LOCATION_UUID "2A38"
|
||||
|
||||
// Battery Service
|
||||
#define BATTERY_SERVICE_UUID "180F"
|
||||
#define BATTERY_LEVEL_UUID "2A19"
|
||||
|
||||
// Fitness Machine Service
|
||||
#define FITNESS_MACHINE_SERVICE_UUID "1826"
|
||||
#define TREADMILL_DATA_UUID "2ACD"
|
||||
#define FITNESS_MACHINE_FEATURE_UUID "2ACC"
|
||||
|
||||
// ============================================
|
||||
// Function Prototypes
|
||||
// ============================================
|
||||
|
||||
/**
|
||||
* Initialize NimBLE stack (call once at startup)
|
||||
*/
|
||||
void initBLE();
|
||||
|
||||
/**
|
||||
* Stop BLE advertising and deinit
|
||||
*/
|
||||
void stopBLE();
|
||||
|
||||
/**
|
||||
* Setup BLE as Heart Rate Monitor
|
||||
* Creates Heart Rate Service with measurement characteristic
|
||||
*/
|
||||
void setupBLE_HeartRate();
|
||||
|
||||
/**
|
||||
* Setup BLE as Treadmill (Fitness Machine)
|
||||
* Creates Fitness Machine Service with treadmill data
|
||||
*/
|
||||
void setupBLE_Treadmill();
|
||||
|
||||
/**
|
||||
* Send heart rate notification
|
||||
* @param bpm Heart rate in beats per minute
|
||||
*/
|
||||
void notifyHeartRate(uint8_t bpm);
|
||||
|
||||
/**
|
||||
* Update battery level
|
||||
* @param level Battery level 0-100%
|
||||
*/
|
||||
void updateBatteryLevel(uint8_t level);
|
||||
|
||||
/**
|
||||
* Send treadmill data notification
|
||||
* @param speed Speed in 0.01 km/h units
|
||||
* @param incline Incline in 0.1% units
|
||||
* @param distance Total distance in meters
|
||||
*/
|
||||
void notifyTreadmill(uint16_t speed, int16_t incline, uint32_t distance);
|
||||
|
||||
#endif // BLE_SERVICES_H
|
||||
@@ -4,7 +4,7 @@
|
||||
// ============================================
|
||||
// Firmware Version
|
||||
// ============================================
|
||||
#define FIRMWARE_VERSION "1.0.5"
|
||||
#define FIRMWARE_VERSION "1.0.6"
|
||||
|
||||
// ============================================
|
||||
// AP Mode Configuration
|
||||
|
||||
105
firmware/esp32_ble_sim/include/device_state.h
Normal file
105
firmware/esp32_ble_sim/include/device_state.h
Normal file
@@ -0,0 +1,105 @@
|
||||
#ifndef DEVICE_STATE_H
|
||||
#define DEVICE_STATE_H
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <functional>
|
||||
|
||||
// ============================================
|
||||
// Device Types
|
||||
// ============================================
|
||||
enum class DeviceType {
|
||||
NONE,
|
||||
HEART_RATE,
|
||||
TREADMILL
|
||||
};
|
||||
|
||||
// ============================================
|
||||
// Connection State
|
||||
// ============================================
|
||||
struct ConnectionState {
|
||||
bool wifiConnected = false;
|
||||
bool mqttConnected = false;
|
||||
bool bleClientConnected = false;
|
||||
String ipAddress = "";
|
||||
};
|
||||
|
||||
// ============================================
|
||||
// Simulated Device Values
|
||||
// ============================================
|
||||
struct SimulatedValues {
|
||||
// Heart Rate Monitor
|
||||
uint8_t heartRate = 70;
|
||||
uint8_t batteryLevel = 100;
|
||||
|
||||
// Treadmill
|
||||
uint16_t treadmillSpeed = 0; // 0.01 km/h resolution
|
||||
int16_t treadmillIncline = 0; // 0.1% resolution
|
||||
uint32_t treadmillDistance = 0; // meters
|
||||
float distanceAccumulator = 0.0; // fractional distance
|
||||
};
|
||||
|
||||
// ============================================
|
||||
// Event Callbacks
|
||||
// ============================================
|
||||
using DeviceTypeChangedCallback = std::function<void(DeviceType newType)>;
|
||||
using ValuesChangedCallback = std::function<void(const SimulatedValues& values)>;
|
||||
using ConnectionChangedCallback = std::function<void(const ConnectionState& state)>;
|
||||
|
||||
// ============================================
|
||||
// Device State Manager (Singleton)
|
||||
// ============================================
|
||||
class DeviceState {
|
||||
public:
|
||||
static DeviceState& getInstance();
|
||||
|
||||
// Prevent copying
|
||||
DeviceState(const DeviceState&) = delete;
|
||||
DeviceState& operator=(const DeviceState&) = delete;
|
||||
|
||||
// Device type
|
||||
DeviceType getDeviceType() const { return deviceType; }
|
||||
void setDeviceType(DeviceType type);
|
||||
bool isBleStarted() const { return deviceType != DeviceType::NONE; }
|
||||
const char* getDeviceTypeString() const;
|
||||
|
||||
// Simulated values
|
||||
const SimulatedValues& getValues() const { return values; }
|
||||
void setHeartRate(uint8_t bpm);
|
||||
void setBatteryLevel(uint8_t level);
|
||||
void setTreadmillSpeed(float speedKmh);
|
||||
void setTreadmillIncline(float inclinePercent);
|
||||
void setTreadmillDistance(uint32_t meters);
|
||||
void resetTreadmillDistance();
|
||||
void accumulateTreadmillDistance(float deltaSeconds);
|
||||
|
||||
// Connection state
|
||||
const ConnectionState& getConnectionState() const { return connectionState; }
|
||||
void setWifiConnected(bool connected, const String& ip = "");
|
||||
void setMqttConnected(bool connected);
|
||||
void setBleClientConnected(bool connected);
|
||||
|
||||
// Event registration
|
||||
void onDeviceTypeChanged(DeviceTypeChangedCallback callback);
|
||||
void onValuesChanged(ValuesChangedCallback callback);
|
||||
void onConnectionChanged(ConnectionChangedCallback callback);
|
||||
|
||||
private:
|
||||
DeviceState() = default;
|
||||
|
||||
DeviceType deviceType = DeviceType::NONE;
|
||||
SimulatedValues values;
|
||||
ConnectionState connectionState;
|
||||
|
||||
// Callbacks
|
||||
DeviceTypeChangedCallback deviceTypeCallback = nullptr;
|
||||
ValuesChangedCallback valuesCallback = nullptr;
|
||||
ConnectionChangedCallback connectionCallback = nullptr;
|
||||
|
||||
void notifyValuesChanged();
|
||||
void notifyConnectionChanged();
|
||||
};
|
||||
|
||||
// Convenience macro for accessing singleton
|
||||
#define deviceState DeviceState::getInstance()
|
||||
|
||||
#endif // DEVICE_STATE_H
|
||||
58
firmware/esp32_ble_sim/include/services/ble_service.h
Normal file
58
firmware/esp32_ble_sim/include/services/ble_service.h
Normal file
@@ -0,0 +1,58 @@
|
||||
#ifndef BLE_SERVICE_H
|
||||
#define BLE_SERVICE_H
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
// ============================================
|
||||
// BLE Service UUIDs (Bluetooth SIG standard)
|
||||
// ============================================
|
||||
#define HEART_RATE_SERVICE_UUID "180D"
|
||||
#define HEART_RATE_MEASUREMENT_UUID "2A37"
|
||||
#define BODY_SENSOR_LOCATION_UUID "2A38"
|
||||
#define BATTERY_SERVICE_UUID "180F"
|
||||
#define BATTERY_LEVEL_UUID "2A19"
|
||||
#define FITNESS_MACHINE_SERVICE_UUID "1826"
|
||||
#define TREADMILL_DATA_UUID "2ACD"
|
||||
#define FITNESS_MACHINE_FEATURE_UUID "2ACC"
|
||||
|
||||
// ============================================
|
||||
// BLE Service
|
||||
// Manages BLE advertising and GATT services
|
||||
// ============================================
|
||||
class BleService {
|
||||
public:
|
||||
static BleService& getInstance();
|
||||
|
||||
// Prevent copying
|
||||
BleService(const BleService&) = delete;
|
||||
BleService& operator=(const BleService&) = delete;
|
||||
|
||||
// Lifecycle
|
||||
void setup();
|
||||
void loop();
|
||||
|
||||
// Configuration
|
||||
void setupHeartRate();
|
||||
void setupTreadmill();
|
||||
void stop();
|
||||
|
||||
// Notifications
|
||||
void notifyHeartRate(uint8_t bpm);
|
||||
void notifyTreadmill(uint16_t speed, int16_t incline, uint32_t distance);
|
||||
void updateBattery(uint8_t level);
|
||||
|
||||
// State
|
||||
bool isClientConnected() const { return deviceConnected; }
|
||||
|
||||
private:
|
||||
BleService() = default;
|
||||
|
||||
bool deviceConnected = false;
|
||||
unsigned long lastNotify = 0;
|
||||
|
||||
void initBLE();
|
||||
};
|
||||
|
||||
#define bleService BleService::getInstance()
|
||||
|
||||
#endif // BLE_SERVICE_H
|
||||
@@ -1,27 +1,20 @@
|
||||
#ifndef CONFIG_MANAGER_H
|
||||
#define CONFIG_MANAGER_H
|
||||
#ifndef CONFIG_SERVICE_H
|
||||
#define CONFIG_SERVICE_H
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <Preferences.h>
|
||||
|
||||
// ============================================
|
||||
// Configuration Structure
|
||||
// Config Service
|
||||
// Manages persistent configuration in NVS
|
||||
// ============================================
|
||||
struct DeviceConfig {
|
||||
bool configured = false;
|
||||
String wifiSsid = "";
|
||||
String wifiPassword = "";
|
||||
String mqttHost = "";
|
||||
uint16_t mqttPort = 1883;
|
||||
String deviceId = "";
|
||||
};
|
||||
|
||||
// ============================================
|
||||
// Configuration Manager Class
|
||||
// ============================================
|
||||
class ConfigManager {
|
||||
class ConfigService {
|
||||
public:
|
||||
ConfigManager();
|
||||
static ConfigService& getInstance();
|
||||
|
||||
// Prevent copying
|
||||
ConfigService(const ConfigService&) = delete;
|
||||
ConfigService& operator=(const ConfigService&) = delete;
|
||||
|
||||
// Load config from NVS
|
||||
bool load();
|
||||
@@ -33,14 +26,14 @@ public:
|
||||
void clear();
|
||||
|
||||
// Check if configured
|
||||
bool isConfigured() const { return config.configured; }
|
||||
bool isConfigured() const { return configured; }
|
||||
|
||||
// Getters
|
||||
const String& getWifiSsid() const { return config.wifiSsid; }
|
||||
const String& getWifiPassword() const { return config.wifiPassword; }
|
||||
const String& getMqttHost() const { return config.mqttHost; }
|
||||
uint16_t getMqttPort() const { return config.mqttPort; }
|
||||
const String& getDeviceId() const { return config.deviceId; }
|
||||
const String& getWifiSsid() const { return wifiSsid; }
|
||||
const String& getWifiPassword() const { return wifiPassword; }
|
||||
const String& getMqttHost() const { return mqttHost; }
|
||||
uint16_t getMqttPort() const { return mqttPort; }
|
||||
const String& getDeviceId() const { return deviceId; }
|
||||
|
||||
// Setters
|
||||
void setWifiCredentials(const String& ssid, const String& password);
|
||||
@@ -54,11 +47,18 @@ public:
|
||||
String getDefaultDeviceId() const;
|
||||
|
||||
private:
|
||||
DeviceConfig config;
|
||||
ConfigService() = default;
|
||||
|
||||
bool configured = false;
|
||||
String wifiSsid = "";
|
||||
String wifiPassword = "";
|
||||
String mqttHost = "";
|
||||
uint16_t mqttPort = 1883;
|
||||
String deviceId = "";
|
||||
|
||||
Preferences preferences;
|
||||
};
|
||||
|
||||
// Global instance
|
||||
extern ConfigManager configManager;
|
||||
#define configService ConfigService::getInstance()
|
||||
|
||||
#endif // CONFIG_MANAGER_H
|
||||
#endif // CONFIG_SERVICE_H
|
||||
45
firmware/esp32_ble_sim/include/services/mqtt_service.h
Normal file
45
firmware/esp32_ble_sim/include/services/mqtt_service.h
Normal file
@@ -0,0 +1,45 @@
|
||||
#ifndef MQTT_SERVICE_H
|
||||
#define MQTT_SERVICE_H
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
// ============================================
|
||||
// MQTT Service
|
||||
// Manages MQTT connection and message routing
|
||||
// ============================================
|
||||
class MqttService {
|
||||
public:
|
||||
static MqttService& getInstance();
|
||||
|
||||
// Prevent copying
|
||||
MqttService(const MqttService&) = delete;
|
||||
MqttService& operator=(const MqttService&) = delete;
|
||||
|
||||
// Lifecycle
|
||||
void setup();
|
||||
void loop();
|
||||
|
||||
// State
|
||||
bool isConnected() const { return mqttConnected; }
|
||||
|
||||
// Publishing
|
||||
void publishStatus();
|
||||
void publishValues();
|
||||
|
||||
private:
|
||||
MqttService() = default;
|
||||
|
||||
bool mqttConnected = false;
|
||||
unsigned long lastMqttAttempt = 0;
|
||||
unsigned long lastStatusReport = 0;
|
||||
|
||||
void connectToMQTT();
|
||||
void handleMessage(char* topic, byte* payload, unsigned int length);
|
||||
|
||||
// Static callback wrapper for PubSubClient
|
||||
static void messageCallback(char* topic, byte* payload, unsigned int length);
|
||||
};
|
||||
|
||||
#define mqttService MqttService::getInstance()
|
||||
|
||||
#endif // MQTT_SERVICE_H
|
||||
35
firmware/esp32_ble_sim/include/services/web_service.h
Normal file
35
firmware/esp32_ble_sim/include/services/web_service.h
Normal file
@@ -0,0 +1,35 @@
|
||||
#ifndef WEB_SERVICE_H
|
||||
#define WEB_SERVICE_H
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
// ============================================
|
||||
// Web Service
|
||||
// Manages HTTP server for configuration portal
|
||||
// ============================================
|
||||
class WebService {
|
||||
public:
|
||||
static WebService& getInstance();
|
||||
|
||||
// Prevent copying
|
||||
WebService(const WebService&) = delete;
|
||||
WebService& operator=(const WebService&) = delete;
|
||||
|
||||
// Lifecycle
|
||||
void setup();
|
||||
void loop();
|
||||
|
||||
private:
|
||||
WebService() = default;
|
||||
|
||||
void handleRoot();
|
||||
void handleGetStatus();
|
||||
void handlePostConfig();
|
||||
void handleReset();
|
||||
void handleResetDistance();
|
||||
void handleSetBattery();
|
||||
};
|
||||
|
||||
#define webService WebService::getInstance()
|
||||
|
||||
#endif // WEB_SERVICE_H
|
||||
49
firmware/esp32_ble_sim/include/services/wifi_service.h
Normal file
49
firmware/esp32_ble_sim/include/services/wifi_service.h
Normal file
@@ -0,0 +1,49 @@
|
||||
#ifndef WIFI_SERVICE_H
|
||||
#define WIFI_SERVICE_H
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
// ============================================
|
||||
// WiFi Service
|
||||
// Manages WiFi STA and AP modes
|
||||
// ============================================
|
||||
class WiFiService {
|
||||
public:
|
||||
static WiFiService& getInstance();
|
||||
|
||||
// Prevent copying
|
||||
WiFiService(const WiFiService&) = delete;
|
||||
WiFiService& operator=(const WiFiService&) = delete;
|
||||
|
||||
// Lifecycle
|
||||
void setup();
|
||||
void loop();
|
||||
|
||||
// State queries
|
||||
bool isConnected() const { return wifiConnected; }
|
||||
bool isApActive() const { return apModeActive; }
|
||||
String getIP() const;
|
||||
|
||||
// Actions
|
||||
void startAP();
|
||||
void stopAP();
|
||||
void reconnect(); // Trigger reconnection attempt
|
||||
|
||||
private:
|
||||
WiFiService() = default;
|
||||
|
||||
bool wifiConnected = false;
|
||||
bool apModeActive = false;
|
||||
unsigned long lastWifiAttempt = 0;
|
||||
int wifiRetryCount = 0;
|
||||
|
||||
static const int MAX_WIFI_RETRIES = 5;
|
||||
|
||||
void connectToWiFi();
|
||||
void startAPMode();
|
||||
void stopAPMode();
|
||||
};
|
||||
|
||||
#define wifiService WiFiService::getInstance()
|
||||
|
||||
#endif // WIFI_SERVICE_H
|
||||
@@ -1,40 +0,0 @@
|
||||
#ifndef WEB_PORTAL_H
|
||||
#define WEB_PORTAL_H
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
/**
|
||||
* Initialize and start the web portal
|
||||
* Runs on AP interface at 192.168.4.1
|
||||
*/
|
||||
void setupWebPortal();
|
||||
|
||||
/**
|
||||
* Handle web portal requests (call in loop)
|
||||
*/
|
||||
void handleWebPortal();
|
||||
|
||||
/**
|
||||
* Get status info for web portal display
|
||||
*/
|
||||
struct PortalStatus {
|
||||
bool wifiConnected;
|
||||
bool mqttConnected;
|
||||
String ipAddress;
|
||||
String deviceType;
|
||||
bool bleStarted;
|
||||
uint32_t treadmillDistance;
|
||||
uint8_t batteryLevel;
|
||||
};
|
||||
|
||||
// Callback for resetting treadmill distance
|
||||
typedef void (*ResetDistanceCallback)();
|
||||
void setResetDistanceCallback(ResetDistanceCallback callback);
|
||||
|
||||
// Callback for setting battery level
|
||||
typedef void (*SetBatteryCallback)(uint8_t level);
|
||||
void setSetBatteryCallback(SetBatteryCallback callback);
|
||||
|
||||
void updatePortalStatus(const PortalStatus& status);
|
||||
|
||||
#endif // WEB_PORTAL_H
|
||||
Reference in New Issue
Block a user