1. 项目概述1.1 设计目标与工程定位spaiot-lib是一款面向 Intex PxxxSPA 系列便携式水疗设备Spa的嵌入式远程监控与控制中间件库。其核心工程目标是在不修改原厂控制面板与电机模块硬件结构的前提下通过非侵入式信号桥接方式实现对 Spa 设备的全状态感知与全功能遥控。该库并非设备固件替代方案而是作为“信号层代理”部署于控制面板与主控模块之间的专用接口板上通过解析并注入原始通信帧完成双向交互。这一设计严格遵循工业级 retrofit翻新改造原则——所有硬件改动仅限于外挂式电路板无需焊接、剪线或拆解原设备外壳。物理连接通过 Spa 原生的 5 针专用接口实现该接口在 Intex SSP/SJB 系列中定义为同步串行总线Synchronous Serial Bus信号包括SDATA数据线、SCLK时钟线、nWR写使能低有效、VCC和GND。1.2 硬件拓扑与信号链路系统物理架构分为三层前端设备层Intex Spa 控制面板含 LED 指示灯、触摸/机械按键与电机控制模块含加热器、水泵、气泡发生器等执行单元桥接层基于 ESP8266/ESP32 的定制 PCB 板通过 5 针排线直连 Spa 主板承担帧解析、状态映射、指令注入三大职能云端/本地应用层通过 Wi-Fi 连接家庭局域网接入 SinricPro、Home Assistant、Blynk 等 IoT 平台支持语音控制Google Assistant/Alexa、手机 App 远程操作、自动化场景联动。关键信号路径如下图所示逻辑示意[Control Panel] ────┬── SDATA ────► [ESP32 GPIO12] ├── SCLK ────► [ESP32 GPIO14] ├── nWR ────► [ESP32 GPIO13] ├── VCC ────► [ESP32 3.3V] └── GND ────► [ESP32 GND] │ [Motor Block] ◄─────────────┘该总线本质为类 SPI 协议16 位帧结构MSB First高位字节B先传低位字节A后传nWR下降沿触发帧锁存SCLK提供同步时序SDATA为双向信号线在nWR0时由主控驱动输出在nWR1时由 Spa 内部电路驱动反馈输入。2. 核心架构与模块化设计2.1 整体分层架构spaiot-lib采用清晰的四层抽象模型符合嵌入式软件工程最佳实践层级模块职责实现特点硬件抽象层HALBusSettings,LedSettings,ButtonSettings,HardwareSettings定义物理引脚、信号时序、寄存器位映射等底层参数支持运行时配置避免硬编码协议处理层FrameDecoder,FrameEncoder解析/构造 16 位同步帧提取 LED 状态、温度值、错误码等语义信息基于中断驱动SCLK/nWR引脚配置为外部中断源设备控制层Button,ButtonController,Multiplexer,PCF8574实现按键模拟逻辑通过 MOSFET 或多路复用器注入电平变化抽象控制器接口支持自定义硬件方案应用接口层ControlPanel提供面向用户的高阶 API封装状态读取、按钮触发、事件回调等操作继承自FrameDecoder单例模式确保线程安全2.2 关键类关系与继承体系class FrameDecoder { // 帧解码基类单例禁止直接实例化 protected: static FrameDecoder* instance; virtual void onFrameDecoded(uint16_t frame) 0; // 帧接收回调 public: static FrameDecoder getInstance(); // 获取单例引用 uint16_t lastFrame() const; // 获取最新完整帧 bool isPowerOn() const; // 解析 Power LED 状态 uint8_t waterTemp() const; // 解析当前水温℃ uint8_t desiredTemp() const; // 解析设定温度℃ uint8_t error() const; // 解析错误代码0无错误 }; class ControlPanel : public FrameDecoder { // 应用层主类继承解码能力 private: HardwareSettings hwConfig; // 硬件配置对象 std::mapint, Button buttons; // 按键映射表 public: ControlPanel(const char* model); // 构造函数指定 Spa 型号 void begin(); // 初始化硬件与中断 void pushButton(ButtonId id); // 触发指定按键 uint8_t waterTemp(); // 重载获取水温带缓存优化 // ... 其他状态访问接口 };ControlPanel类通过组合HardwareSettings实现硬件无关性开发者可自由替换BusSettings总线引脚、LedSettingsLED 位定义、ButtonSettings按键控制器而无需修改业务逻辑。3. 同步串行总线协议深度解析3.1 帧结构与时序规范Intex Spa 总线采用固定长度 16 位帧每帧周期约 2ms由控制面板以恒定速率广播。帧格式如下bit 15 为 MSBBit Position1514131211109876543210Byte B (MSB)A7A6A5A4A3A2A1A0————————Byte A (LSB)————————B7B6B5B4B3B2B1B0LED 状态映射A0对应 Power LEDB7对应 Heater LEDB1对应 HeatReached LEDB2对应 Bubble LEDB4对应 Filter LED温度数据waterTemp()返回A1:A0低位与B3:B0高位拼接的 8 位值范围 0–50℃实际值 raw - 20desiredTemp()返回A3:A2与B5:B4拼接值错误码error()提取A5:A4两位常见值0x00正常0x01水流故障0x02温度传感器异常0x03加热器过热。3.2 帧解码实现机制FrameDecoder通过双中断协同捕获完整帧nWR引脚配置为下降沿中断标志新帧开始清空内部缓冲区SCLK引脚配置为上升沿中断每来一个时钟沿从SDATA读取 1 bit按 MSB First 规则移入 16 位寄存器当累计接收 16 个 bit 后调用onFrameDecoded(frame)通知子类处理。关键代码片段简化版// 在 begin() 中注册中断 attachInterrupt(digitalPinToInterrupt(nWR_pin), []{ frameBuffer 0; bitCount 0; }, FALLING); attachInterrupt(digitalPinToInterrupt(sclk_pin), []{ if (bitCount 16) { bool bit digitalRead(sdata_pin); frameBuffer (frameBuffer 1) | bit; bitCount; if (bitCount 16) { getInstance().onFrameDecoded(frameBuffer); } } }, RISING);此设计避免轮询开销CPU 利用率低于 2%满足实时性要求。4. 按键模拟技术原理与硬件实现4.1 按键检测与注入的电气原理Spa 控制面板按键采用矩阵扫描架构但spaiot-lib不依赖扫描时序而是利用其内部锁存器特性实现“硬件级按键注入”。根据文档描述其核心原理如下锁存器结构控制面板内部集成两片 8 位锁存器 U1存 A 字节、U2存 B 字节SDATA线连接至所有按键一端另一端分别接 U1/U2 的输出引脚按键状态判定当某按键未按下时其两端电平相同Ux 输出 SDATA按下后形成通路SDATA 被强制拉至 Ux 输出电平模拟按下流程步骤1向 U1/U2 写入特定位为0的 16 位字如Filter键对应 A10使目标按键一端为低电平步骤2拉高nWR释放锁存此时 U1/U2 输出保持步骤1写入值步骤3激活SDATA上拉电阻ESP32 内部启用INPUT_PULLUP步骤4读取SDATA电平——若为低则证明按键已物理闭合即被“按下”步骤5恢复nWR为低更新锁存器状态。该过程本质是“电平反射测试”无需理解 Spa 内部扫描逻辑具有强鲁棒性。4.2 按键控制器抽象与多路复用实现spaiot-lib将按键注入抽象为ButtonController接口class ButtonController { public: virtual void selectButton(uint8_t bitPos) 0; // 选择目标按键位 virtual bool readSdata() 0; // 读取 SDATA 电平 virtual void release() 0; // 释放控制 };针对不同硬件方案提供具体实现Multiplexer类适配 CD4051 八选一模拟开关使用 3 位地址线A/B/C加使能端INH控制PCF8574类适配 I²C 扩展 IO通过写入 8 位寄存器设置输出电平DirectGPIO类适用于简单场景直接映射 GPIO 到锁存器输入引脚。典型Multiplexer使用示例// U3 控制 A 组按键A0-A7U4 控制 B 组B0-B7 Multiplexer btnCtrlA(U3, {GPIO5, GPIO4, GPIO15}, GPIO16); // A/B/C, INH Multiplexer btnCtrlB(U4, {GPIO5, GPIO4, GPIO15}, GPIO0); // 复用地址线独立使能 // 定义按键映射Filter 键位于 U3 的通道 1A1 const ButtonSettings filterBtn(btnCtrlA, 1); const std::mapint, ButtonSettings myButtons { {Filter, filterBtn} };5. 硬件配置与自定义开发指南5.1 预置型号配置说明库内置 12 种标准配置覆盖主流硬件组合。命名规则为SPAIOTCHIPMODELMUX例如SPAIOT32SSPESP32 SSP 系列 Spa 直连模式无 MUXSPAIOT328574SJBESP32 SJB 系列 Spa PCF8574 I²C MUXSPAIOTS38574SSPESP32-S3 SSP 系列 PCF8574。各配置差异主要体现在LedSettings与ButtonSettings的位定义上因 SSP 与 SJB 系列 LED/按键物理布局不同。5.2 自定义硬件配置全流程当预置配置不匹配时需手动构建HardwareSettings。以下为完整步骤以 ESP32 SSP CD4051 方案为例步骤1定义总线引脚// SDATAGPIO12, SCLKGPIO14, nWRGPIO13 const BusSettings myBus(12, 14, 13);步骤2定义 LED 位映射// SSP 系列 LED 位定义参考 Spa 主板丝印 const std::mapint, LedSettings myLeds { {Power, LedSettings(0)}, // A0 {Heater, LedSettings(7)}, // A7 {HeatReached,LedSettings(9)}, // B1 {Bubble, LedSettings(10)}, // B2 {Filter, LedSettings(12)} // B4 };步骤3定义按键控制器// U3: CD4051 控制 A 组按键地址线 GPIO5/4/15使能 GPIO16 Multiplexer btnCtrlA(U3, {5, 4, 15}, 16); // U4: CD4051 控制 B 组按键使能 GPIO0 Multiplexer btnCtrlB(U4, {5, 4, 15}, 0);步骤4定义按键位映射const std::mapint, ButtonSettings myButtons { {Filter, ButtonSettings(btnCtrlA, 1)}, // A1 {Bubble, ButtonSettings(btnCtrlA, 3)}, // A3 {TempDown, ButtonSettings(btnCtrlA, 7)}, // A7 {Power, ButtonSettings(btnCtrlB, 2)}, // B2 {TempUp, ButtonSettings(btnCtrlB, 4)}, // B4 {TempUnit, ButtonSettings(btnCtrlB, 5)}, // B5 {Heater, ButtonSettings(btnCtrlB, 7)} // B7 };步骤5组装硬件配置const HardwareSettings myConfig(myBus, myLeds, myButtons); // 在 ControlPanel 构造时传入 ControlPanel spa(myConfig);6. 快速上手与典型应用示例6.1 最小可行示例SpaSimple#include SpaIot.h using namespace SpaIot; // 使用预置配置ESP32 SSP ControlPanel spa(SPAIOT32SSP); uint8_t lastWaterTemp 0; void setup() { Serial.begin(115200); spa.begin(); // 初始化总线与中断 } void loop() { uint8_t temp spa.waterTemp(); if (temp ! lastWaterTemp) { Serial.printf(Water Temp: %d°C\n, temp); lastWaterTemp temp; } // 串口命令触发按键 if (Serial.available()) { char cmd Serial.read(); switch(cmd) { case P: case p: spa.pushButton(Power); break; case B: case b: spa.pushButton(Bubble); break; case F: case f: spa.pushButton(Filter); break; } } delay(500); }编译前需在 Arduino IDE 中设置开发板ESP32 Dev ModuleCPU 频率240MHz确保时序精度Flash 频率80MHzUpload Speed921600。6.2 与 SinricPro 云平台集成结合SinricPro库可实现语音控制#include SinricPro.h #include SinricProSpa.h SinricProSpa mySpa SinricPro[DEVICE_ID]; void onPowerState(const String deviceId, bool state) { if (state) spa.pushButton(Power); else spa.pushButton(Power); // Power 键为切换式 } void onTemperature(const String deviceId, int temperature) { // 温度调节需连续按 TempUp/TempDown int delta temperature - spa.desiredTemp(); for (int i 0; i abs(delta); i) { spa.pushButton(delta 0 ? TempUp : TempDown); delay(300); // 避免连击 } } void setup() { SinricPro.onPowerState(onPowerState); SinricPro.onTargetTemperature(onTemperature); SinricPro.begin(APP_KEY, APP_SECRET); }6.3 FreeRTOS 多任务优化方案在 ESP32 上推荐使用 FreeRTOS 分离时间敏感任务TaskHandle_t spaTaskHandle; void spaTask(void *pvParameters) { for(;;) { uint8_t temp spa.waterTemp(); xQueueSend(tempQueue, temp, portMAX_DELAY); vTaskDelay(1000 / portTICK_PERIOD_MS); } } void setup() { // ... 初始化代码 xTaskCreate(spaTask, SpaMonitor, 2048, NULL, 1, spaTaskHandle); }7. 安全声明与工程风险提示重要法律与安全警示本项目与 Intex 公司无任何关联亦非其授权产品。所有硬件连接、固件烧录、功能调试均需用户自行承担全部责任。保修失效风险在 Spa 设备上安装本系统将导致原厂保修自动失效电气安全风险5 针接口工作电压为 3.3V严禁接入 5V 或 12V 电源否则将永久损坏 Spa 主板热失控风险远程控制加热器时必须确保 Spa 水位传感器正常工作避免干烧固件兼容性仅验证适用于 Intex PxxxSPA 2018–2023 款 SSP/SJB 系列其他型号需自行确认协议一致性。所有开发者须在首次通电前使用万用表确认VCC-GND间电压为 3.3V±0.1V并验证SDATA信号在nWR1时呈高阻态浮空。建议初始调试阶段全程使用 USB 逻辑分析仪抓取SCLK/SDATA/nWR三线波形比对文档时序图。本库的工程价值在于其严谨的信号层抽象能力——它不试图“理解” Spa 固件而是忠实扮演一个高速、可靠的物理层代理。这种设计哲学使其具备极强的跨平台适应性未来可无缝迁移至 Raspberry Pi PicoRP2040、Nordic nRF52840 等 Cortex-M 微控制器平台只需重写BusSettings与中断服务例程。