This commit is contained in:
moyongming
2025-08-13 17:15:40 +08:00
commit 41e04e2a7a
15 changed files with 4574 additions and 0 deletions
+167
View File
@@ -0,0 +1,167 @@
---
Language: Cpp
BasedOnStyle: Google
AccessModifierOffset: -4
AlignAfterOpenBracket: Align
AlignConsecutiveMacros: true
AlignConsecutiveAssignments: true
AlignConsecutiveDeclarations: false
AlignEscapedNewlines: Left
AlignOperands: true
AlignTrailingComments: true
AllowAllArgumentsOnNextLine: true
AllowAllConstructorInitializersOnNextLine: true
AllowAllParametersOfDeclarationOnNextLine: true
AllowShortBlocksOnASingleLine: Never
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: false
AllowShortLambdasOnASingleLine: All
AllowShortIfStatementsOnASingleLine: WithoutElse
AllowShortLoopsOnASingleLine: true
AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: true
AlwaysBreakTemplateDeclarations: Yes
BinPackArguments: true
BinPackParameters: true
BraceWrapping:
AfterCaseLabel: false
AfterClass: false
AfterControlStatement: false
AfterEnum: false
AfterFunction: true
AfterNamespace: false
AfterObjCDeclaration: false
AfterStruct: false
AfterUnion: false
AfterExternBlock: false
BeforeCatch: false
BeforeElse: false
IndentBraces: false
SplitEmptyFunction: true
SplitEmptyRecord: true
SplitEmptyNamespace: true
BreakBeforeBinaryOperators: None
BreakBeforeBraces: Custom
BreakBeforeInheritanceComma: false
BreakInheritanceList: BeforeColon
BreakBeforeTernaryOperators: true
BreakConstructorInitializersBeforeComma: false
BreakConstructorInitializers: BeforeColon
BreakAfterJavaFieldAnnotations: false
BreakStringLiterals: true
ColumnLimit: 120
CommentPragmas: '^ IWYU pragma:'
CompactNamespaces: false
ConstructorInitializerAllOnOneLineOrOnePerLine: true
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: true
DeriveLineEnding: true
DerivePointerAlignment: true
DisableFormat: false
ExperimentalAutoDetectBinPacking: false
FixNamespaceComments: true
ForEachMacros:
- foreach
- Q_FOREACH
- BOOST_FOREACH
IncludeBlocks: Regroup
IncludeCategories:
- Regex: '^<ext/.*\.h>'
Priority: 2
SortPriority: 0
- Regex: '^<.*\.h>'
Priority: 1
SortPriority: 0
- Regex: '^<.*'
Priority: 2
SortPriority: 0
- Regex: '.*'
Priority: 3
SortPriority: 0
IncludeIsMainRegex: '([-_](test|unittest))?$'
IncludeIsMainSourceRegex: ''
IndentCaseLabels: true
IndentGotoLabels: true
IndentPPDirectives: None
IndentWidth: 4
IndentWrappedFunctionNames: false
JavaScriptQuotes: Leave
JavaScriptWrapImports: true
KeepEmptyLinesAtTheStartOfBlocks: false
MacroBlockBegin: ''
MacroBlockEnd: ''
MaxEmptyLinesToKeep: 1
NamespaceIndentation: None
ObjCBinPackProtocolList: Never
ObjCBlockIndentWidth: 2
ObjCSpaceAfterProperty: false
ObjCSpaceBeforeProtocolList: true
PenaltyBreakAssignment: 2
PenaltyBreakBeforeFirstCallParameter: 1
PenaltyBreakComment: 300
PenaltyBreakFirstLessLess: 120
PenaltyBreakString: 1000
PenaltyBreakTemplateDeclaration: 10
PenaltyExcessCharacter: 1000000
PenaltyReturnTypeOnItsOwnLine: 200
PointerAlignment: Left
RawStringFormats:
- Language: Cpp
Delimiters:
- cc
- CC
- cpp
- Cpp
- CPP
- 'c++'
- 'C++'
CanonicalDelimiter: ''
BasedOnStyle: google
- Language: TextProto
Delimiters:
- pb
- PB
- proto
- PROTO
EnclosingFunctions:
- EqualsProto
- EquivToProto
- PARSE_PARTIAL_TEXT_PROTO
- PARSE_TEST_PROTO
- PARSE_TEXT_PROTO
- ParseTextOrDie
- ParseTextProtoOrDie
CanonicalDelimiter: ''
BasedOnStyle: google
ReflowComments: true
SortIncludes: false
SortUsingDeclarations: true
SpaceAfterCStyleCast: false
SpaceAfterLogicalNot: false
SpaceAfterTemplateKeyword: true
SpaceBeforeAssignmentOperators: true
SpaceBeforeCpp11BracedList: false
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeParens: ControlStatements
SpaceBeforeRangeBasedForLoopColon: true
SpaceInEmptyBlock: false
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 2
SpacesInAngles: false
SpacesInConditionalStatement: false
SpacesInContainerLiterals: true
SpacesInCStyleCastParentheses: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
SpaceBeforeSquareBrackets: false
Standard: Auto
StatementMacros:
- Q_UNUSED
- QT_REQUIRE_VERSION
TabWidth: 4
UseCRLF: false
UseTab: Never
...
+32
View File
@@ -0,0 +1,32 @@
# Prerequisites
*.d
# Compiled Object files
*.slo
*.lo
*.o
*.obj
# Precompiled Headers
*.gch
*.pch
# Compiled Dynamic libraries
*.so
*.dylib
*.dll
# Fortran module files
*.mod
*.smod
# Compiled Static libraries
*.lai
*.la
*.a
*.lib
# Executables
*.exe
*.out
*.app
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025 M5Stack Technology CO LTD
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+21
View File
@@ -0,0 +1,21 @@
# M5SwitchC6
## Overview
### SKU:K140
ESP-NOW based library to control and manage Switch-C6 smart switch devices.
## Related Link
- [Function Guide (CN)](./README_FUNCTION_CN.md)
- [Function Guide (EN)](./README_FUNCTION_EN.md)
## Required Libraries:
- [Arduino-ESP32](https://github.com/espressif/arduino-esp32)
- [M5Unified (optional)](https://github.com/m5stack/M5Unified)
## License
- [M5SwitchC6 - MIT](LICENSE)
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+221
View File
@@ -0,0 +1,221 @@
# M5SwitchC6 软件流程图(基于 src/M5Switchc6.cpp
更新时间:2025-08-13
本文基于 `src/M5Switchc6.cpp``src/M5Switchc6.h` 提炼核心流程,给出总览流程图与收发应答时序图,帮助理解 ESP-NOW 初始化、数据接收/解析、命令发送与应答等待、设备清理等关键路径。
提示:VS Code Markdown 预览已原生支持 Mermaid。打开本文件后,使用“在侧边打开预览”即可查看图表。
---
## 一、总览流程图
### 1.1 初始化流程 (begin)
```mermaid
flowchart TB
A[begin] --> B{Platform ESP32 or ESP8266}
B -->|No| A0[Not supported return false]
B -->|Yes| C{mutexLock == NULL}
C -->|Yes| C1[Create mutex]
C1 --> D{dataQueue == nullptr}
C -->|No| D
D -->|Yes| D1[Create queue 10]
D1 --> E{parseTaskHandle == nullptr}
D -->|No| E
E -->|Yes| E1[Create task parseDataTask]
E1 --> F{peers == nullptr}
E -->|No| F
F -->|Yes| F1[Alloc init peers max_broadcast_peers]
F1 --> G{WiFi.getMode}
F -->|No| G
G -->|WIFI_OFF| A1[WiFi is off return false]
G -->|WIFI_AP| A2[WiFi is AP mode return false]
G -->|WIFI_STA or WIFI_AP_STA| H[Continue WiFi OK]
H --> I[acquireMutex]
I --> J[esp_now_init]
J -->|ESP_OK| K[Register recv cb lambda]
J -->|fail| J1[releaseMutex return false]
K --> L[Register send cb lambda]
L --> M[releaseMutex return true]
```
### 1.2 接收路径 (回调 -> 队列 -> 解析)
```mermaid
flowchart TB
R0[recv_cb with esp_now_info data len] --> R1{esp_now_info and src_addr and instance}
R1 -->|Yes| R2[acquireMutex]
R1 -->|No| R0A[Log unknown MAC]
R2 --> R3[Extract MAC channel rssi from rx_ctrl]
R3 --> R4[Find or add peer in peers array]
R4 --> R5[Update lastData status lastReceiveTime]
R5 --> R6[releaseMutex]
R6 --> R7[enqueueData with senderMac]
R0A --> R7B[enqueueData without senderMac]
R7 --> Q[(Queue size 10)]
R7B --> Q
Q -->|xQueueReceive 100ms| T[parseDataTask infinite loop]
T --> T1{queuedData received}
T1 -->|Yes| P0[parseData with data len senderMac]
T1 -->|No| T2[vTaskDelay 1ms continue]
P0 --> P1[parseDataStructured]
P1 -->|isValid true| P2[handleReceivedResponse with senderMac]
P1 -->|isValid false| P3[log Parse error]
T2 --> T
```
### 1.3 发送路径 (构建命令 -> 发送广播)
```mermaid
flowchart TB
S1[sendSwitchCommand / sendStatusQuery / sendVersionQuery / sendCustomMessage] --> S2[Validate channel 1-14]
S2 -->|invalid| S2A[return false]
S2 -->|valid| S3[Validate MAC format]
S3 -->|invalid| S3A[return false]
S3 -->|valid| S4[Build command string with channel]
S4 --> S5[acquireMutex]
S5 --> S6[Check broadcast peer exists]
S6 -->|not exist| S6A[Add broadcast peer FF:FF:FF:FF:FF:FF]
S6 -->|exist| S7
S6A --> S7[esp_now_send broadcast]
S7 --> S8[releaseMutex]
S8 -->|ESP_OK| S9[Log success]
S8 -->|fail| S10[Log fail]
S7 --> S11[send_cb status log]
```
### 1.4 应答等待机制 (可选)
```mermaid
flowchart TB
W0[sendSwitchCommandWithResponse] --> W1[addResponseWait deviceMac timeoutMs]
W1 -->|success| W2[Send with needResponse true]
W1 -->|fail| W1A[return false]
W2 -->|success| W3[millis startTime lastSendTime]
W2 -->|fail| W2A[removeResponseWait return false]
W3 --> W4{millis - startTime < timeoutMs}
W4 -->|Yes| W5[checkResponseTimeouts]
W5 --> W6[findResponseWait index]
W6 -->|hasResponse true| W7[Copy response removeResponseWait return true]
W6 -->|hasResponse false| W8{millis - lastSendTime >= resendInterval}
W8 -->|Yes| W9[Resend command update lastSendTime]
W8 -->|No| W10[delay 1ms]
W9 --> W4
W10 --> W4
W4 -->|No timeout| W11[removeResponseWait return false]
%% 从接收路径的响应匹配
P2[handleReceivedResponse from RX path] -->|match normalized senderMAC| W6
```
### 1.5 自动清理机制 (可选)
```mermaid
flowchart TB
GC0[setAutoCleanup intervalMs deviceTimeoutMs] --> GC1[Save autoCleanupInterval deviceTimeout lastCleanupTime]
GC2[checkAutoCleanup] --> GC3{autoCleanupInterval == 0}
GC3 -->|Yes disabled| GC5[return 0]
GC3 -->|No| GC4{currentTime - lastCleanupTime >= autoCleanupInterval}
GC4 -->|No| GC5
GC4 -->|Yes| GC6[clearExpiredDevices deviceTimeout]
GC6 --> GC7[Remove expired devices and response waits]
GC7 --> GC8[Update lastCleanupTime return removedCount]
```
说明:
- **初始化流程**:严格按照源码顺序,包含所有条件检查(mutex/queue/task/peers的nullptr检查,WiFi模式的具体枚举值检查)
- **接收路径**:体现了 ESP-NOW 回调的完整处理逻辑,包括 esp_now_info 检查、MAC提取、peers更新、队列入队、解析任务循环
- **发送路径**:包含参数验证(信道范围、MAC格式)、命令字符串构建、广播peer确保存在等完整步骤
- **WithResponse机制**:详细展示了超时循环、重发间隔检查、响应匹配的完整逻辑,以及各种失败情况的处理
- **自动清理**:体现了间隔检查、过期设备移除的完整流程,包括禁用状态的处理
---
## 二、收发与应答时序图
```mermaid
sequenceDiagram
participant App
participant M5 as M5SwitchC6
participant ESP as ESP_NOW
participant Q as dataQueue
participant T as parseTask
App->>M5: sendSwitchCommandWithResponse(deviceMac, switchOn, timeoutMs)
M5->>M5: addResponseWait(deviceMac, timeoutMs)
alt addResponseWait success
M5->>M5: sendSwitchCommand(deviceMac, switchOn, needResponse=true)
alt sendSwitchCommand success
M5->>M5: normalizeMacAddress + build command string
M5->>M5: acquireMutex
M5->>ESP: esp_now_send(FF:FF:FF:FF:FF:FF, command)
ESP-->>M5: send_cb(mac, status)
M5->>M5: releaseMutex
Note over M5: Start timeout loop with resend logic
loop while millis() - startTime < timeoutMs
M5->>M5: checkResponseTimeouts()
M5->>M5: findResponseWait(deviceMac)
alt hasResponse == true
M5->>App: return response data (success)
else resend interval reached
M5->>ESP: esp_now_send (resend command)
else
M5->>M5: delay(1)
end
end
M5->>App: timeout - return false
else
M5->>M5: removeResponseWait(deviceMac)
M5->>App: send failed - return false
end
else
M5->>App: addResponseWait failed - return false
end
Note over ESP,M5: Remote device receives and replies
ESP-->>M5: recv_cb(esp_now_info, data, len)
M5->>M5: acquireMutex
M5->>M5: extract senderMAC from esp_now_info.src_addr
M5->>M5: find/add peer in peers array
M5->>M5: update peers MAC/channel/RSSI/lastData/status/time
M5->>M5: releaseMutex
M5->>Q: enqueueData(data, len, senderMAC)
T->>Q: xQueueReceive(dataQueue, queuedData, 100ms)
Q-->>T: data, len, senderMAC
T->>M5: parseData(data, len, senderMAC)
M5->>M5: parseDataStructured(data, len) -> parsed
alt parsed.isValid == true
M5->>M5: handleReceivedResponse(parsed, senderMacStr)
M5->>M5: findResponseWait(senderMacStr)
alt waiting device found
M5->>M5: set hasResponse = true, copy parsed data
Note over M5: This breaks the timeout loop above
end
else
M5->>M5: log parse error
end
```
---
## 参考与扩展
- 关键接口:
- 初始化与通道:`begin()`, `setChannel()`, `getChannel()`
- 发送:`sendSwitchCommand()`, `sendStatusQuery()`, `sendVersionQuery()`, `sendCustomMessage()`
- 带应答:`sendSwitchCommandWithResponse()`, `sendStatusQueryWithResponse()`, `sendVersionQueryWithResponse()`
- 解析:`parseData()`, `parseDataStructured()`
- 等待/匹配:`addResponseWait()`, `findResponseWait()`, `removeResponseWait()`, `checkResponseTimeouts()`, `handleReceivedResponse()`
- 清理:`clearPeersData()`, `clearResponseWaits()`, `clearAllData()`, `clearDeviceData()`, `clearExpiredDevices()`, `setAutoCleanup()`, `checkAutoCleanup()`
- 预览方式:
1) 在 VS Code 中打开本文件
2) 右上角“在侧边打开预览”(或快捷键 Ctrl+K V)
@@ -0,0 +1,200 @@
# SwitchC6 ESP-NOW控制协议
## SwitchC6开(不返回)
* 命令
主机发送
```C
"XXXX-XXXX-XXXX=1;ch=n"
```
* 例子
主机发送
```C
"AABB-CCDD-EEFF=1;ch=6" 或 "aabb-ccdd-eeff=1;ch=6" 或 "aaBB-CCdd-eeFF=1;ch=6"
```
## SwitchC6开(带返回)
* 命令
主机发送
```C
"XXXX-XXXX-XXXX=1;ch=n;"
```
SwitchC6返回
```C
"YYYY-YYYY-YYYY;1;z.zzV"
```
* 例子
主机发送
```C
"AABB-CCDD-EEFF=1;ch=6;" 或 "aabb-ccdd-eeff=1;ch=6;" 或 "aaBB-CCdd-eeFF=1;ch=6;"
```
SwitchC6返回
```C
"1122-AABB-CCGG;1;3.30V" 或 "1122-aabb-ccgg;1;3.30V" 或 "1122-aaBB-ccgg;1;3.30V"
```
## SwitchC6关(不返回)
* 命令
主机发送
```C
"YYYY-YYYY-YYYY=0;ch=n"
```
* 例子
主机发送
```C
"AABB-CCDD-EEFF=0;ch=6" 或 "aabb-ccdd-eeff=0;ch=6" 或 "aaBB-CCdd-eeFF=0;ch=6"
```
## SwitchC6关(带返回)
* 命令
主机发送
```C
"XXXX-XXXX-XXXX=0;ch=n;"
```
SwitchC6返回
```C
"YYYY-YYYY-YYYY;0;z.zzV"
```
* 例子
主机发送
```C
"AABB-CCDD-EEFF=0;ch=6;" 或 "aabb-ccdd-eeff=0;ch=6;" 或 "aaBB-CCdd-eeFF=0;ch=6;"
```
SwitchC6返回
```C
"1122-AABB-CCGG;0;3.30V" 或 "1122-aabb-ccgg;0;3.30V" 或 "1122-aaBB-ccgg;0;3.30V"
```
## 读取设备状态
* 命令
主机发送
```C
"XXXX-XXXX-XXXX=?;ch=n;"
```
SwitchC6返回
```C
"YYYY-YYYY-YYYY;0;z.zzV" 或 "YYYY-YYYY-YYYY;1;z.zzV"
```
* 例子
主机发送
```C
"AABB-CCDD-EEFF=?;ch=6;" 或 "aabb-ccdd-eeff=?;ch=6;" 或 "aaBB-CCdd-eeFF=?;ch=6;"
```
SwitchC6返回
```C
"1122-AABB-CCGG;0;3.30V" 或 "1122-aabb-ccgg;0;3.30V" 或 "1122-aaBB-ccgg;0;3.30V"
"1122-AABB-CCGG;1;3.30V" 或 "1122-aabb-ccgg;1;3.30V" 或 "1122-aaBB-ccgg;1;3.30V"
```
## 版本查询
* 命令
主机发送
```C
"XXXX-XXXX-XXXX=V;ch=n;"
```
SwitchC6返回
```C
"YYYY-YYYY-YYYY;ver"
```
* 例子
主机发送
```C
"AABB-CCDD-EEFF=V;ch=6;" 或 "aabb-ccdd-eeff=V;ch=6;" 或 "aaBB-CCdd-eeFF=V;ch=6;"
```
SwitchC6返回
```C
"1122-AABB-CCGG;1.0.0" 或 "1122-aabb-ccgg;1.0.0" 或 "1122-aaBB-ccgg;1.0.0"
```
## Button广播
* 单击一次
SwitchC6广播并且开关状态切换一次
```C
"FFFF-FFFF-FFFF;0;z.zzV" 或 "FFFF-FFFF-FFFF;1;z.zzV"
```
* 长按5s
SwitchC6广播
```C
"FFFF-FFFF-FFFF;0;z.zzV" 或 "FFFF-FFFF-FFFF;1;z.zzV"
```
### 说明:
* 数据发送和接收均采用字符串格式
* "XXXX-XXXX-XXXX" 表示单火设备的 MAC 地址,使用字符形式,兼容大小写
* "YYYY-YYYY-YYYY" 表示主机的 MAC 地址,使用字符形式,兼容大小写
* 设备的通信采用广播方式,主机需要将广播地址(FF:FF:FF:FF:FF:FF)添加为对等节点。SwitchC6 也通过广播发送返回数据。主机可以通过 "YYYY-YYYY-YYYY" 判断接收到的数据包是否属于本设备,而 SwitchC6 则通过 "XXXX-XXXX-XXXX" 判断是否为需要自己处理的数据包
* "ch=n" 中的 n 表示主机的 Wi-Fi 通道,取值范围为 1~14,主机发送的命令中必须填入正确的通道
* 主机发送的命令中,MAC 地址后紧跟的第一个字符为 "="
* SwitchC6 返回的命令中,MAC 地址后紧跟的第一个字符为 ";"
* "z.zzV" 表示电容电压
* 返回数据中的继电器状态与电容电压使用 ";" 分隔
* SwitchC6 是否需要回复由主机发送数据包的最后一位是否为 ";" 来判断:如果末尾是 ";",则需要返回数据
* LED 灯状态与继电器状态绑定:继电器闭合时,LED 灯点亮;继电器断开时,LED 灯熄灭
* 按下按钮时,设备会广播自身状态。Switch-C6 会在 1~14 通道各发送一次数据包,且数据包中携带的地址为 FFFF-FFFF-FFFF(广播地址)。
## ESP32-IDF使用示例
* esp now 发送
```C
// 广播 MAC 地址(表示将数据发送给所有接收范围内的 ESP-NOW 设备)
const uint8_t broadcast_mac_addr[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
// 目标设备的 MAC 地址(此处为 SwitchC6 的 MAC 地址)
const uint8_t switchc6_mac[6] = {0xE4, 0xB3, 0x23, 0x86, 0x18, 0xC4};
/**
* @brief 需要发送的控制指令列表(字符串格式)
*
* 格式:MAC=操作;ch=信道;
* - 操作 1 表示开,0 表示关,? 表示读取状态
* - ch=6 表示当前 Wi-Fi 工作信道为 6
* - 最后的分号(;)代表是否需要设备响应(有则需要响应)
*/
const char *send_cmd[] = {
"E4B3-2386-18C4=1;ch=6", // 开启继电器,不需要设备响应
"E4B3-2386-18C4=1;ch=6;", // 开启继电器,需要设备响应
"E4B3-2386-18C4=0;ch=6", // 关闭继电器,不需要设备响应
"E4B3-2386-18C4=0;ch=6;", // 关闭继电器,需要设备响应
"E4B3-2386-18C4=?;ch=6;", // 查询继电器状态,需要设备响应
};
// 使用 ESP-NOW 发送其中一条控制指令
// 此处发送第 0 条指令,即:开启继电器,且不等待响应
esp_now_send(broadcast_mac_addr, (const uint8_t *)send_cmd[0], strlen(send_cmd[0]));
```
Binary file not shown.
+108
View File
@@ -0,0 +1,108 @@
// Switch-C6 ESP-NOW 广播扫描器 / Switch-C6 ESP-NOW Broadcast Scanner
// 使用 M5SwitchC6 库监听和解析广播数据 / Listen and parse broadcast frames using M5SwitchC6
// 串口输出可见: MAC地址 / RSSI / 信道 / 状态 / 电压 / Serial output: MAC / RSSI / Channel / State / Voltage
#include <Arduino.h>
#include <WiFi.h>
#include <M5Switchc6.h>
// ===== 可调参数 / Configurable parameters =====
static const uint8_t FIXED_CHANNEL = 6; // 目标信道,需与对端一致;设为 0 则不改 / Target channel; must match the peer; set to 0 to keep unchanged
static const uint16_t MAX_DEVICES = 20; // 最大设备数量 / Maximum number of devices
// ===================
// 创建 M5SwitchC6 实例 / Create M5SwitchC6 instance
M5SwitchC6 switchC6(MAX_DEVICES);
// 上次显示设备列表的时间 / Timestamp of last device list print
unsigned long lastDisplayTime = 0;
const unsigned long DISPLAY_INTERVAL = 5000; // 每5秒显示一次设备列表 / Print the device list every 5 seconds
void setup() {
Serial.begin(115200);
delay(200);
Serial.println("========================================");
Serial.println("Switch-C6 Broadcast Scanner Starting");
Serial.println("========================================");
// 1) 启动 Wi-FiSTAStation,工作站)模式 / Start Wi-Fi in STA (station) mode
WiFi.mode(WIFI_STA);
WiFi.begin(); // 仅初始化协议栈,不必真的连 AP / Initialize stack only; no real AP connection
WiFi.disconnect(false, true); // 不关闭无线,仅清理历史热点 / Keep radio on; clear saved hotspots
// 2) 初始化 M5SwitchC6 库 / Initialize M5SwitchC6 library
if (!switchC6.begin()) {
Serial.println("ERROR: M5SwitchC6 initialization failed!");
while (true) delay(1000);
}
// 3) 设置信道 / Set Wi-Fi channel
if (FIXED_CHANNEL >= 1 && FIXED_CHANNEL <= 14) {
if (switchC6.setChannel(FIXED_CHANNEL)) {
Serial.printf("Channel set successfully: %u\n", FIXED_CHANNEL);
} else {
Serial.printf("Channel set failed: %u\n", FIXED_CHANNEL);
}
}
uint8_t currentChannel = switchC6.getChannel();
Serial.printf("Current channel: %u\n", currentChannel);
// 4) 设置自动清理过期设备(每60秒检查一次,设备超5分钟无响应则清理) / Enable auto cleanup of expired devices (check every 60 s; remove if silent > 5 min)
switchC6.setAutoCleanup(60000, 300000);
Serial.println("ESP-NOW broadcast scanner ready");
Serial.println("========================================");
}
void loop() {
// 检查是否有自动清理过期设备 / Check whether expired devices were auto-cleaned
uint16_t removedCount = switchC6.checkAutoCleanup();
if (removedCount > 0) {
Serial.printf("Auto cleaned %u expired devices\n", removedCount);
}
// 定期显示设备列表 / Periodically display the device list
unsigned long currentTime = millis();
if (currentTime - lastDisplayTime >= DISPLAY_INTERVAL) {
lastDisplayTime = currentTime;
uint16_t peerCount = switchC6.getPeerCount();
if (peerCount > 0) {
Serial.println("========================================");
Serial.printf("Discovered devices count: %u\n", peerCount);
Serial.println("----------------------------------------");
for (uint16_t i = 0; i < peerCount; i++) {
broadcast_packet_with_mac_t peerInfo;
if (switchC6.getPeerInfo(i, &peerInfo)) {
// 转换MAC地址为字符串格式 / Convert MAC address to string
char macStr[18];
snprintf(macStr, sizeof(macStr), "%02X:%02X:%02X:%02X:%02X:%02X",
peerInfo.senderMac[0], peerInfo.senderMac[1], peerInfo.senderMac[2],
peerInfo.senderMac[3], peerInfo.senderMac[4], peerInfo.senderMac[5]);
// 计算设备静默时间 / Calculate device silent time
unsigned long silentTime = (currentTime - peerInfo.lastReceiveTime) / 1000;
Serial.printf("Device %d:\n", i + 1);
Serial.printf(" MAC: %s\n", macStr);
Serial.printf(" Channel: %u\n", peerInfo.channel);
Serial.printf(" RSSI: %d dBm\n", peerInfo.rssi);
Serial.printf(" Status: %s\n", peerInfo.status ? "ON" : "OFF");
Serial.printf(" Last data: %s\n", peerInfo.lastData.c_str());
Serial.printf(" Silent time: %lu seconds\n", silentTime);
Serial.println();
}
}
Serial.println("========================================");
} else {
Serial.println("No devices discovered yet...");
}
}
// 主循环延迟 / Main loop delay
delay(100);
}
+319
View File
@@ -0,0 +1,319 @@
#include <M5Unified.h>
#include <WiFi.h>
#include <M5Switchc6.h>
// 创建 M5SwitchC6 实例,最大节点数量为16,可自定义大小 / Create M5SwitchC6 instance with max peers set to 16, customizable size
M5SwitchC6 switchC6(16);
//填入你的SwitchC6设备MAC地址 / Fill in your SwitchC6 device MAC address
const String DEVICE_MAC = "E4B3-2386-18C4";
//是否启用wifi连接 / Enable Wi-Fi connection
// #define USE_WIFI
#ifdef USE_WIFI
const char *ssid = "your_ssid";
const char *password = "your_password";
#endif
// 等待动画控制变量 / Control flag for waiting animation
volatile bool showAnimation = false;
TaskHandle_t animationTaskHandle = NULL;
SemaphoreHandle_t displayMutex = NULL;
// 等待动画任务 / Waiting animation task
void animationTask(void *parameter) {
while (true) {
if (showAnimation) {
// 获取互斥锁 / Acquire mutex
if (xSemaphoreTake(displayMutex, pdMS_TO_TICKS(50)) == pdTRUE) {
int centerX = M5.Display.width() / 2;
int centerY = M5.Display.height() / 2;
static int dots = 1;
// 清除点号区域 - 只清除动画区域 / Clear dots area (only the animation region)
M5.Display.fillRect(centerX - 30, centerY, 60, 20, BLACK);
// 显示当前数量的点号 / Draw current number of dots
M5.Display.setCursor(centerX - dots * 6, centerY);
M5.Display.setTextColor(WHITE);
for (int i = 0; i < dots; i++) {
M5.Display.print(".");
}
// 释放互斥锁 / Release mutex
xSemaphoreGive(displayMutex);
dots++;
if (dots > 5) {
dots = 1;
}
}
vTaskDelay(200 / portTICK_PERIOD_MS); // 200ms延迟 / 200 ms delay
} else {
// 清除动画时也需要获取锁 / Also need the lock when clearing animation
if (xSemaphoreTake(displayMutex, pdMS_TO_TICKS(50)) == pdTRUE) {
int centerX = M5.Display.width() / 2;
int centerY = M5.Display.height() / 2 + 20;
// 清除动画区域 / Clear animation region
M5.Display.fillRect(centerX - 30, centerY - 10, 60, 30, BLACK);
xSemaphoreGive(displayMutex);
}
vTaskDelay(100 / portTICK_PERIOD_MS);
}
}
}
// 开始等待动画 / Start waiting animation
void startWaitingAnimation() {
showAnimation = true;
}
// 停止等待动画 / Stop waiting animation
void stopWaitingAnimation() {
showAnimation = false;
}
void setup() {
Serial.begin(115200);
M5.begin();
// M5.Display.setRotation(1);
M5.Display.setTextSize(2);
WiFi.mode(WIFI_STA);
#ifdef USE_WIFI
// 连接到Wi-Fi网络 / Connect to Wi-Fi network
WiFi.begin(ssid, password);
if (WiFi.status() != WL_CONNECTED) {
Serial.print("Connecting to Wi-Fi");
while (WiFi.status() != WL_CONNECTED) {
Serial.print(".");
delay(500);
}
Serial.println("\nConnected to Wi-Fi!");
// 串口打印连接信息 / Print connection info to serial
Serial.printf("SSID: %s\r\n", WiFi.SSID().c_str());
Serial.printf("IP Address: %s\r\n", WiFi.localIP().toString().c_str());
Serial.printf("MAC Address: %s\r\n", WiFi.macAddress().c_str());
Serial.printf("Channel: %d\r\n", WiFi.channel());
} else {
Serial.println("Already connected to Wi-Fi.");
}
#else
WiFi.begin();
switchC6.setChannel(11); // 设置信道为11 / Set channel to 11
#endif
M5.Display.clear();
// 创建互斥锁 / Create mutex
displayMutex = xSemaphoreCreateMutex();
// 创建canvas,使用正确的屏幕尺寸 / Prepare display with proper size
M5.Display.fillScreen(BLACK);
M5.Display.setTextSize(2);
M5.Display.setTextColor(WHITE);
M5.Display.setCursor(10, 10);
M5.Display.println("M5Switchc6 Controller");
if (switchC6.begin()) {
M5.Display.setCursor(10, 60);
M5.Display.println("A:ON B:OFF C:STATUS");
}
// 创建等待动画任务 / Create waiting animation task
xTaskCreate(
animationTask, // 任务函数 / Task function
"AnimationTask", // 任务名称 / Task name
4096, // 增加堆栈大小 / Stack size
NULL, // 参数 / Parameter
1, // 优先级 / Priority
&animationTaskHandle // 任务句柄 / Task handle
);
}
void loop() {
M5.update();
// 先定义按钮变量,在互斥锁外部 / Define button layout variables outside the mutex
int screenWidth = M5.Display.width();
int screenHeight = M5.Display.height();
int buttonWidth = screenWidth / 3;
int buttonHeight = 40;
int buttonY = screenHeight - buttonHeight;
int button3Width = screenWidth - buttonWidth * 2; // 剩余宽度 / Remaining width
// 获取互斥锁后再操作canvas / Lock before drawing on the display
if (xSemaphoreTake(displayMutex, pdMS_TO_TICKS(100)) == pdTRUE) {
// 绘制3个按钮 / Draw 3 buttons
// 按钮A - ON / Button A - ON
M5.Display.fillRect(0, buttonY, buttonWidth, buttonHeight, GREEN);
M5.Display.drawRect(0, buttonY, buttonWidth, buttonHeight, WHITE);
M5.Display.setTextColor(BLACK);
M5.Display.setCursor(buttonWidth/2 - 10, buttonY + 15);
M5.Display.println("ON");
// 按钮B - OFF / Button B - OFF
M5.Display.fillRect(buttonWidth, buttonY, buttonWidth, buttonHeight, RED);
M5.Display.drawRect(buttonWidth, buttonY, buttonWidth, buttonHeight, WHITE);
M5.Display.setTextColor(WHITE);
M5.Display.setCursor(buttonWidth + buttonWidth/2 - 15, buttonY + 15);
M5.Display.println("OFF");
// 按钮C - STATUS(使用计算后的宽度避免溢出) / Button C - STATUS (use computed width to avoid overflow)
M5.Display.fillRect(buttonWidth * 2, buttonY, button3Width, buttonHeight, BLUE);
M5.Display.drawRect(buttonWidth * 2, buttonY, button3Width, buttonHeight, WHITE);
M5.Display.setTextColor(WHITE);
M5.Display.setCursor(buttonWidth * 2 + button3Width/2 - 25, buttonY + 15);
M5.Display.println("STATUS");
M5.Display.setTextColor(WHITE); // 恢复默认文本颜色 / Restore default text color
// 释放互斥锁 / Release mutex
xSemaphoreGive(displayMutex);
}
bool deviceOn = false;
bool deviceOff = false;
bool deviceStatus = false;
// 检测物理按键(适用于有按键的设备) / Handle physical buttons (if available)
if (M5.BtnA.wasPressed()) {
deviceOn = true;
}
if (M5.BtnB.wasPressed()) {
deviceOff = true;
}
if (M5.BtnC.wasPressed()) {
deviceStatus = true;
}
// 检测触摸事件(适用于有触摸屏的设备) / Handle touch events (if touch screen)
auto touch = M5.Touch.getDetail();
if (touch.wasPressed()) {
int touchX = touch.x;
int touchY = touch.y;
// 检查是否点击了按钮区域 / Check whether a button area is tapped
if (touchY >= buttonY) {
if (touchX < buttonWidth) {
deviceOn = true;
} else if (touchX < buttonWidth * 2) {
deviceOff = true;
} else {
deviceStatus = true;
}
}
}
// 执行相应的设备控制逻辑 / Execute device control logic
if (deviceOn) {
// 开始等待动画 / Start waiting animation
startWaitingAnimation();
// 开启设备 / Turn device ON
if (switchC6.sendSwitchCommandWithResponse(DEVICE_MAC, true, 10000)) {
Serial.println("开启命令发送成功"); // ON command sent successfully
stopWaitingAnimation();
// 获取互斥锁后更新状态显示 / Update status display with mutex
if (xSemaphoreTake(displayMutex, pdMS_TO_TICKS(100)) == pdTRUE) {
M5.Display.fillRect(10, 160, 300, 30, BLACK);
M5.Display.setCursor(10, 160);
M5.Display.setTextColor(WHITE);
M5.Display.println("Device ON sent");
xSemaphoreGive(displayMutex);
}
}
else {
Serial.println("开启命令发送失败"); // Failed to send ON command
stopWaitingAnimation();
if (xSemaphoreTake(displayMutex, pdMS_TO_TICKS(100)) == pdTRUE) {
M5.Display.fillRect(10, 160, 300, 30, BLACK);
M5.Display.setCursor(10, 160);
M5.Display.setTextColor(WHITE);
M5.Display.println("Device ON failed");
xSemaphoreGive(displayMutex);
}
}
}
if (deviceOff) {
// 开始等待动画 / Start waiting animation
startWaitingAnimation();
// 关闭设备 / Turn device OFF
if (switchC6.sendSwitchCommandWithResponse(DEVICE_MAC, false, 10000)) {
Serial.println("关闭命令发送成功"); // OFF command sent successfully
stopWaitingAnimation();
if (xSemaphoreTake(displayMutex, pdMS_TO_TICKS(100)) == pdTRUE) {
M5.Display.fillRect(10, 160, 300, 30, BLACK);
M5.Display.setCursor(10, 160);
M5.Display.setTextColor(WHITE);
M5.Display.println("Device OFF sent");
xSemaphoreGive(displayMutex);
}
}
else {
Serial.println("关闭命令发送失败"); // Failed to send OFF command
stopWaitingAnimation();
if (xSemaphoreTake(displayMutex, pdMS_TO_TICKS(100)) == pdTRUE) {
M5.Display.fillRect(10, 160, 300, 30, BLACK);
M5.Display.setCursor(10, 160);
M5.Display.setTextColor(WHITE);
M5.Display.println("Device OFF failed");
xSemaphoreGive(displayMutex);
}
}
}
if (deviceStatus) {
// 开始等待动画 / Start waiting animation
startWaitingAnimation();
// 查询状态 / Query status
SwitchC6ParsedData_t response;
if (switchC6.sendStatusQueryWithResponse(DEVICE_MAC, 10000, &response)) {
Serial.printf("设备状态: %s, 电压: %.2fV\n",
response.switchState ? "开启" : "关闭", response.voltage); // Device status and voltage
stopWaitingAnimation();
if (xSemaphoreTake(displayMutex, pdMS_TO_TICKS(100)) == pdTRUE) {
M5.Display.fillRect(10, 160, 300, 30, BLACK);
M5.Display.setCursor(10, 160);
M5.Display.setTextColor(WHITE);
M5.Display.printf("S:%s V:%.2fV",
response.switchState ? "ON" : "OFF", response.voltage);
xSemaphoreGive(displayMutex);
}
} else {
stopWaitingAnimation();
if (xSemaphoreTake(displayMutex, pdMS_TO_TICKS(100)) == pdTRUE) {
M5.Display.fillRect(10, 160, 300, 30, BLACK);
M5.Display.setCursor(10, 160);
M5.Display.setTextColor(WHITE);
M5.Display.println("Query failed");
xSemaphoreGive(displayMutex);
}
}
}
delay(20); // 减少循环频率,降低系统负载 / Slow down the loop to reduce load
}
+24
View File
@@ -0,0 +1,24 @@
{
"name": "M5SwitchC6-ESP-NOW",
"description": "M5Stack Library of M5SwitchC6",
"keywords": "M5Stack,M5SwitchC6",
"authors": {
"name": "M5Stack",
"url": "http://www.m5stack.com"
},
"repository": {
"type": "git",
"url": "https://github.com/m5stack/M5SwitchC6-ESP-NOW.git"
},
"version": "1.0.0",
"frameworks": [
"arduino"
],
"platforms": [
"espressif32",
"native"
],
"headers": "M5Switchc6.h",
"dependencies": {
}
}
+10
View File
@@ -0,0 +1,10 @@
name=M5SwitchC6-ESP-NOW
version=1.1.2
author=M5Stack
maintainer=M5Stack
sentence=M5Stack Library of M5SwitchC6
paragraph=M5Stack,M5SwitchC6, See more on http://M5Stack.com
category=Device Control
url=https://github.com/m5stack/M5SwitchC6-ESP-NOW.git
architectures=esp32
includes=M5Switchc6.h
+1463
View File
File diff suppressed because it is too large Load Diff
+509
View File
File diff suppressed because it is too large Load Diff