mirror of
https://github.com/esphome/esphome.git
synced 2026-08-30 17:46:01 +00:00
Merge remote-tracking branch 'origin/dev' into esp8266-native-library-backend
This commit is contained in:
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"hooks": {
|
||||
"SessionStart": [
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "d=\"${CLAUDE_PROJECT_DIR:-.}\"; [ -x \"$d/venv/bin/python\" ] || { mkdir -p \"$d/.temp\"; env -u VIRTUAL_ENV \"$d/script/setup\" >\"$d/.temp/setup.log\" 2>&1 || echo '{\"systemMessage\":\"script/setup failed; see .temp/setup.log\"}'; }",
|
||||
"statusMessage": "Setting up dev environment (script/setup)...",
|
||||
"timeout": 900
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -7,146 +7,146 @@ namespace esphome::captive_portal {
|
||||
|
||||
#ifdef USE_CAPTIVE_PORTAL_GZIP
|
||||
constexpr uint8_t INDEX_GZ[] PROGMEM = {
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x95, 0x16, 0x6b, 0x8f, 0xdb, 0x36, 0xf2, 0x7b, 0x7f,
|
||||
0x05, 0x8f, 0x4d, 0x1b, 0xa9, 0xb1, 0xa8, 0x87, 0xd7, 0xde, 0x44, 0x96, 0x54, 0xa4, 0x7b, 0x2d, 0x5a, 0xa0, 0x69,
|
||||
0x03, 0xec, 0x36, 0xf7, 0x21, 0x08, 0xb0, 0x34, 0x39, 0xb2, 0x98, 0xa5, 0x48, 0x1d, 0x49, 0xbf, 0x62, 0xf8, 0x7e,
|
||||
0xfb, 0x81, 0x92, 0xec, 0xf5, 0x2e, 0x9a, 0x03, 0x0e, 0x86, 0x85, 0x19, 0xce, 0x7b, 0x38, 0x0f, 0x16, 0xff, 0xe0,
|
||||
0x9a, 0xb9, 0x7d, 0x07, 0xa8, 0x71, 0xad, 0xac, 0x0a, 0xff, 0x45, 0x92, 0xaa, 0x55, 0x09, 0xaa, 0x2a, 0x1a, 0xa0,
|
||||
0xbc, 0x2a, 0x5a, 0x70, 0x14, 0xb1, 0x86, 0x1a, 0x0b, 0xae, 0xfc, 0xeb, 0xee, 0x97, 0xe8, 0x75, 0x55, 0x48, 0xa1,
|
||||
0x1e, 0x90, 0x01, 0x59, 0x0a, 0xa6, 0x15, 0x6a, 0x0c, 0xd4, 0x25, 0xa7, 0x8e, 0xe6, 0xa2, 0xa5, 0x2b, 0x18, 0x45,
|
||||
0x14, 0x6d, 0xa1, 0xdc, 0x08, 0xd8, 0x76, 0xda, 0x38, 0xc4, 0xb4, 0x72, 0xa0, 0x5c, 0x89, 0xb7, 0x82, 0xbb, 0xa6,
|
||||
0xe4, 0xb0, 0x11, 0x0c, 0xa2, 0x1e, 0x99, 0x08, 0x25, 0x9c, 0xa0, 0x32, 0xb2, 0x8c, 0x4a, 0x28, 0xd3, 0xc9, 0xda,
|
||||
0x82, 0xe9, 0x11, 0xba, 0x94, 0x50, 0x2a, 0x8d, 0xab, 0xc2, 0x32, 0x23, 0x3a, 0x87, 0xbc, 0xab, 0x65, 0xab, 0xf9,
|
||||
0x5a, 0x42, 0x15, 0xc7, 0xd4, 0x5a, 0x70, 0x36, 0x16, 0x8a, 0xc3, 0x8e, 0xd0, 0x6b, 0xb8, 0xa6, 0x2c, 0x4d, 0xc8,
|
||||
0x67, 0xfb, 0x0d, 0xd7, 0x6c, 0xdd, 0x82, 0x72, 0x44, 0x6a, 0x46, 0x9d, 0xd0, 0x8a, 0x58, 0xa0, 0x86, 0x35, 0x65,
|
||||
0x59, 0xe2, 0x1f, 0x2d, 0xdd, 0x00, 0xfe, 0xfe, 0xfb, 0xe0, 0xcc, 0xb4, 0x02, 0xf7, 0xb3, 0x04, 0x0f, 0xda, 0x9f,
|
||||
0xf6, 0x77, 0x74, 0xf5, 0x07, 0x6d, 0x21, 0xc0, 0xd4, 0x0a, 0x0e, 0x38, 0xfc, 0x98, 0x7c, 0x22, 0xd6, 0xed, 0x25,
|
||||
0x10, 0x2e, 0x6c, 0x27, 0xe9, 0xbe, 0xc4, 0x4b, 0xa9, 0xd9, 0x03, 0x0e, 0x17, 0xf5, 0x5a, 0x31, 0xaf, 0x1c, 0xe9,
|
||||
0x00, 0xc2, 0x83, 0x04, 0x87, 0x5c, 0xf9, 0x8e, 0xba, 0x86, 0xb4, 0x74, 0x17, 0x0c, 0x80, 0x50, 0x41, 0xf6, 0x43,
|
||||
0x00, 0xaf, 0xd2, 0x24, 0x09, 0x27, 0xfd, 0x27, 0x09, 0xe3, 0x34, 0x49, 0x16, 0x06, 0xdc, 0xda, 0x28, 0x44, 0x83,
|
||||
0xfb, 0xa2, 0xa3, 0xae, 0x41, 0xbc, 0xc4, 0xef, 0xd2, 0x0c, 0xa5, 0x6f, 0x48, 0x36, 0xfb, 0x9d, 0x5c, 0xa3, 0x2b,
|
||||
0x92, 0xcd, 0xd8, 0x75, 0x34, 0x43, 0xe9, 0x55, 0x34, 0x43, 0x59, 0x46, 0x66, 0x28, 0xf9, 0x82, 0x51, 0x2d, 0xa4,
|
||||
0x2c, 0xb1, 0xd2, 0x0a, 0x30, 0xb2, 0xce, 0xe8, 0x07, 0x28, 0x31, 0x5b, 0x1b, 0x03, 0xca, 0xdd, 0x68, 0xa9, 0x0d,
|
||||
0x8e, 0xab, 0x6f, 0xfe, 0x2f, 0x85, 0xce, 0x50, 0x65, 0x6b, 0x6d, 0xda, 0x12, 0xf7, 0xd9, 0x0f, 0x5e, 0x1c, 0xdc,
|
||||
0x11, 0xf9, 0x4f, 0x78, 0x41, 0x8c, 0xb4, 0x11, 0x2b, 0xa1, 0x4a, 0xec, 0x35, 0xbe, 0xc6, 0x71, 0x75, 0x1f, 0x1e,
|
||||
0xcf, 0xd1, 0x53, 0x1f, 0xfd, 0x18, 0x0f, 0x0f, 0x3e, 0xde, 0x17, 0x76, 0xb3, 0x42, 0xbb, 0x56, 0x2a, 0x5b, 0xe2,
|
||||
0xc6, 0xb9, 0x2e, 0x8f, 0xe3, 0xed, 0x76, 0x4b, 0xb6, 0x53, 0xa2, 0xcd, 0x2a, 0xce, 0x92, 0x24, 0x89, 0xed, 0x66,
|
||||
0x85, 0xd1, 0x50, 0x08, 0x38, 0xbb, 0xc2, 0xa8, 0x01, 0xb1, 0x6a, 0x5c, 0x0f, 0x57, 0x2f, 0x0e, 0x70, 0x2c, 0x3c,
|
||||
0x47, 0x75, 0xff, 0xe9, 0xc2, 0x8a, 0xb9, 0xb0, 0x02, 0x3f, 0xd2, 0x00, 0x9f, 0xc2, 0x7c, 0xd9, 0x87, 0x79, 0x4d,
|
||||
0x33, 0x94, 0xa1, 0xa4, 0xff, 0x65, 0x91, 0x87, 0x47, 0x2c, 0x7a, 0x86, 0xa1, 0x0b, 0xcc, 0x43, 0xed, 0x3c, 0x7a,
|
||||
0x73, 0x96, 0x4d, 0xfd, 0xc9, 0x26, 0x4d, 0x1e, 0x0f, 0xbc, 0xc0, 0xaf, 0xf3, 0x4b, 0x3c, 0xca, 0x3e, 0x5c, 0x32,
|
||||
0x78, 0x6b, 0x4d, 0xfa, 0x61, 0x4e, 0x67, 0x68, 0x36, 0x9e, 0xcc, 0x22, 0x0f, 0x9f, 0x31, 0x34, 0xdb, 0x64, 0x4d,
|
||||
0xda, 0x46, 0xf3, 0x68, 0x46, 0xa7, 0x68, 0x3a, 0x3a, 0x32, 0x45, 0xd3, 0x4d, 0xd6, 0xcc, 0x3f, 0xcc, 0x2f, 0xcf,
|
||||
0xa2, 0xe9, 0x97, 0x97, 0x71, 0x85, 0xc3, 0x1c, 0xe3, 0xc7, 0xc8, 0xf9, 0x65, 0xe4, 0xe4, 0xb3, 0x16, 0x2a, 0xc0,
|
||||
0x38, 0x3c, 0xd6, 0xe0, 0x58, 0x13, 0xe0, 0x98, 0x69, 0x55, 0x8b, 0x15, 0xf9, 0x6c, 0xb5, 0xc2, 0x21, 0x71, 0x0d,
|
||||
0xa8, 0xe0, 0x24, 0xea, 0x05, 0xa1, 0xa7, 0x04, 0xcf, 0x29, 0x2e, 0x3c, 0x9c, 0xeb, 0xdf, 0x09, 0x27, 0xa1, 0x74,
|
||||
0xc4, 0x37, 0xec, 0xe4, 0x6f, 0xba, 0xe2, 0xa7, 0xfd, 0x6f, 0x3c, 0xc0, 0x2d, 0x65, 0x38, 0x24, 0x42, 0x29, 0x30,
|
||||
0x77, 0xb0, 0x73, 0x25, 0x7e, 0xf7, 0xf6, 0x06, 0xbd, 0xe5, 0xdc, 0x80, 0xb5, 0x39, 0xc2, 0xaf, 0x1c, 0x69, 0x29,
|
||||
0xfb, 0xba, 0x78, 0x93, 0x3e, 0x95, 0xfe, 0x97, 0xf8, 0x45, 0xa0, 0x3f, 0xc0, 0x6d, 0xb5, 0x79, 0x18, 0xe5, 0xbd,
|
||||
0xfd, 0x85, 0x6f, 0x23, 0x56, 0x7e, 0x55, 0x8d, 0x02, 0x87, 0xc3, 0x89, 0xf8, 0x3a, 0x83, 0xb5, 0x82, 0xe3, 0x70,
|
||||
0x22, 0xbf, 0xce, 0xd1, 0x59, 0xdf, 0xbc, 0x8e, 0xd0, 0xce, 0x12, 0x2b, 0x05, 0x83, 0x20, 0x0d, 0x49, 0xad, 0xcd,
|
||||
0xcf, 0x94, 0x35, 0x8f, 0x09, 0xb2, 0x43, 0x47, 0xab, 0x47, 0x3d, 0xcc, 0x00, 0x75, 0x30, 0xaa, 0x0a, 0x30, 0x17,
|
||||
0x1b, 0x1c, 0x2e, 0x14, 0x61, 0x92, 0x5a, 0xeb, 0x47, 0x46, 0xe9, 0x9d, 0xf3, 0xe1, 0xe0, 0x89, 0x1a, 0x22, 0xfd,
|
||||
0xf5, 0xee, 0xdd, 0xef, 0xe5, 0x7d, 0x41, 0x87, 0x01, 0x89, 0xbf, 0xc5, 0xa8, 0x67, 0x3e, 0x33, 0x46, 0x12, 0x6a,
|
||||
0xe7, 0x2b, 0x5e, 0x07, 0x96, 0x18, 0x6b, 0x45, 0x78, 0x2c, 0x6c, 0x47, 0xd5, 0x73, 0xb6, 0x3e, 0xa6, 0xaa, 0x88,
|
||||
0x3d, 0xad, 0x2a, 0x62, 0x5a, 0xbd, 0x38, 0x98, 0xc0, 0xfa, 0xe1, 0xf6, 0x10, 0x1e, 0xef, 0x27, 0x8a, 0xfc, 0x7b,
|
||||
0x0d, 0x66, 0x7f, 0x0b, 0x12, 0x98, 0xd3, 0x26, 0xc0, 0xe4, 0x89, 0x60, 0x48, 0x1c, 0xec, 0xdc, 0xcd, 0x38, 0x7f,
|
||||
0x2d, 0xf1, 0x87, 0x13, 0x45, 0xb4, 0x62, 0x52, 0xb0, 0x87, 0xf2, 0x1c, 0x71, 0x78, 0x10, 0x64, 0x43, 0xe5, 0x1a,
|
||||
0x4e, 0x3c, 0x92, 0xd4, 0x9a, 0xad, 0x6d, 0x10, 0x1e, 0x27, 0x8c, 0xd0, 0xae, 0x03, 0xc5, 0x6f, 0x1a, 0x21, 0x79,
|
||||
0xa0, 0xc2, 0x63, 0xf8, 0x78, 0xd3, 0xcf, 0x8c, 0xfb, 0xd5, 0xf0, 0xd1, 0x80, 0xfc, 0x4f, 0xf9, 0xd2, 0x2f, 0x87,
|
||||
0x97, 0x9f, 0x70, 0x48, 0xfa, 0xf8, 0xef, 0x1f, 0x37, 0x84, 0x6f, 0xef, 0x57, 0xbb, 0x56, 0x4e, 0x7c, 0xe8, 0xd1,
|
||||
0x7c, 0x16, 0x1e, 0xef, 0x8f, 0xe1, 0x31, 0x5c, 0x14, 0xf1, 0x30, 0xe7, 0xab, 0xa2, 0x1f, 0xb9, 0xd5, 0x0f, 0x87,
|
||||
0xa5, 0xde, 0x45, 0x56, 0x7c, 0x11, 0x6a, 0x95, 0x0b, 0xd5, 0x80, 0x11, 0xee, 0xc8, 0xc5, 0x66, 0x22, 0x54, 0xb7,
|
||||
0x76, 0x87, 0x8e, 0x72, 0xee, 0x29, 0xb3, 0x6e, 0xb7, 0xa8, 0xb5, 0x72, 0x9e, 0x13, 0xf2, 0x14, 0xda, 0xe3, 0x40,
|
||||
0xef, 0x27, 0x4c, 0xfe, 0x66, 0xf6, 0xdd, 0x71, 0xa9, 0xf9, 0xfe, 0xe0, 0xd3, 0x10, 0x51, 0x29, 0x56, 0x2a, 0x67,
|
||||
0xa0, 0x1c, 0x98, 0x41, 0xa8, 0xa6, 0xad, 0x90, 0xfb, 0xdc, 0x52, 0x65, 0x23, 0x0b, 0x46, 0xd4, 0xc7, 0xe5, 0xda,
|
||||
0x39, 0xad, 0x0e, 0x4b, 0x6d, 0x38, 0x98, 0x3c, 0x59, 0x0c, 0x40, 0x64, 0x28, 0x17, 0x6b, 0x9b, 0x93, 0xa9, 0x81,
|
||||
0x76, 0xb1, 0xa4, 0xec, 0x61, 0x65, 0xf4, 0x5a, 0xf1, 0x88, 0xf9, 0xc9, 0x9b, 0x7f, 0x9b, 0xd6, 0x74, 0x0a, 0x6c,
|
||||
0x31, 0x62, 0x75, 0x5d, 0x2f, 0xa4, 0x50, 0x10, 0x0d, 0xb3, 0x2d, 0xcf, 0xc8, 0x95, 0x17, 0xbb, 0x70, 0x93, 0x64,
|
||||
0xfe, 0x60, 0xf0, 0x31, 0x4d, 0x92, 0xef, 0x16, 0xa7, 0x70, 0x92, 0x05, 0x5b, 0x1b, 0xab, 0x4d, 0xde, 0x69, 0xe1,
|
||||
0xdd, 0x3c, 0xb6, 0x54, 0xa8, 0x4b, 0xef, 0x7d, 0xd9, 0x2c, 0xc6, 0x75, 0x94, 0x0b, 0xd5, 0x9b, 0xe9, 0x97, 0xd2,
|
||||
0xa2, 0x15, 0x6a, 0xd8, 0xa9, 0x79, 0x36, 0x4f, 0xba, 0xdd, 0xf1, 0x54, 0x09, 0x87, 0x13, 0x77, 0x2d, 0x61, 0xb7,
|
||||
0xf8, 0xbc, 0xb6, 0x4e, 0xd4, 0xfb, 0x68, 0xdc, 0xc9, 0xb9, 0xed, 0x28, 0x83, 0x68, 0x09, 0x6e, 0x0b, 0xa0, 0x16,
|
||||
0xbd, 0x8d, 0x48, 0x38, 0x68, 0xed, 0x98, 0xa7, 0xb3, 0x9a, 0xbe, 0x60, 0x9f, 0xea, 0xfa, 0x5f, 0xdc, 0xbe, 0x8a,
|
||||
0x0e, 0x2d, 0x35, 0x2b, 0xa1, 0xa2, 0xa5, 0x76, 0x4e, 0xb7, 0x79, 0x74, 0xdd, 0xed, 0x16, 0xe3, 0x91, 0x57, 0x96,
|
||||
0xa7, 0xde, 0xcd, 0x7e, 0xd7, 0x9e, 0xf2, 0x9d, 0x76, 0x3b, 0x64, 0xb5, 0x14, 0x7c, 0xe4, 0xeb, 0x59, 0x50, 0x72,
|
||||
0x4e, 0x4f, 0x3a, 0xeb, 0x76, 0xc8, 0x9f, 0x9d, 0x52, 0x7d, 0x55, 0xbf, 0xa6, 0x69, 0xf2, 0x37, 0x37, 0xc2, 0xeb,
|
||||
0x3a, 0x5b, 0xd6, 0xe7, 0x4c, 0xf9, 0xb5, 0xe9, 0x57, 0x4b, 0x5f, 0x5a, 0x45, 0x3c, 0xbc, 0x6e, 0x7c, 0x65, 0x54,
|
||||
0x85, 0xcf, 0x70, 0x55, 0x34, 0x29, 0x12, 0xbc, 0x6c, 0x29, 0xab, 0x2e, 0x66, 0x5b, 0x11, 0x37, 0xe9, 0x89, 0xd4,
|
||||
0xa4, 0xd5, 0x93, 0xb9, 0x35, 0xd0, 0x7a, 0xef, 0xab, 0x1b, 0xad, 0x14, 0x30, 0x27, 0xd4, 0x0a, 0x39, 0x8d, 0xc6,
|
||||
0x14, 0x10, 0x42, 0x8a, 0xa5, 0xa9, 0xde, 0x4b, 0xa0, 0x16, 0xd0, 0x96, 0x0a, 0x47, 0x8a, 0x78, 0xe0, 0x1f, 0x3a,
|
||||
0x5d, 0xf0, 0x52, 0x81, 0x3b, 0xf7, 0x76, 0x33, 0x1d, 0x0c, 0xdc, 0x82, 0xf3, 0x9a, 0xbc, 0x81, 0x69, 0x55, 0xf8,
|
||||
0x15, 0x8c, 0x68, 0xdf, 0xa5, 0x65, 0xbc, 0x15, 0xb5, 0xf0, 0x4f, 0x98, 0xaa, 0xe8, 0x8b, 0xdc, 0x6b, 0xf0, 0x79,
|
||||
0x1e, 0x9e, 0x5b, 0x3d, 0x24, 0x41, 0xad, 0x5c, 0x53, 0x4e, 0x33, 0xd4, 0x49, 0xca, 0xa0, 0xd1, 0x92, 0x83, 0x29,
|
||||
0x6f, 0x6f, 0x7f, 0xfb, 0x67, 0xe5, 0x9d, 0x79, 0x94, 0xeb, 0xec, 0xc3, 0x20, 0xe6, 0x81, 0x51, 0x6a, 0x7e, 0x35,
|
||||
0x3c, 0xb2, 0x3a, 0x6a, 0xed, 0x56, 0x1b, 0xfe, 0x44, 0xc7, 0xfb, 0xf1, 0x70, 0xd0, 0xd3, 0xff, 0xfb, 0x56, 0xa9,
|
||||
0x6e, 0xe9, 0x06, 0x8a, 0x78, 0x44, 0x8a, 0xd8, 0x3b, 0x3c, 0xd0, 0x9b, 0x91, 0xaf, 0x49, 0xab, 0x3f, 0xef, 0xde,
|
||||
0xa2, 0xbf, 0x3a, 0x4e, 0x1d, 0x0c, 0x69, 0xeb, 0xa3, 0x6a, 0xc1, 0x35, 0x9a, 0x97, 0xef, 0xff, 0xbc, 0xbd, 0x3b,
|
||||
0x47, 0xb8, 0xee, 0x99, 0x10, 0x28, 0x36, 0x3c, 0xf7, 0xd6, 0xd2, 0x89, 0x8e, 0x1a, 0xd7, 0xab, 0x8d, 0xfc, 0x14,
|
||||
0x39, 0xc5, 0xd0, 0xd3, 0x6b, 0x21, 0x61, 0x08, 0x63, 0x10, 0xac, 0xd0, 0xc9, 0xab, 0x93, 0xb5, 0x67, 0x7e, 0xc5,
|
||||
0xc3, 0x6d, 0xc7, 0xc3, 0xd5, 0xc7, 0xfd, 0xcb, 0xf7, 0xbf, 0x81, 0xdb, 0x13, 0xb5, 0x09, 0x0b, 0x00, 0x00};
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x95, 0x56, 0x6d, 0x8f, 0xdb, 0x36, 0x0c, 0xfe, 0xbe,
|
||||
0x5f, 0xa1, 0x79, 0xdd, 0x6a, 0xaf, 0xb1, 0xfc, 0x92, 0x4b, 0xda, 0x3a, 0x96, 0x8b, 0xee, 0xd6, 0x62, 0x03, 0xd6,
|
||||
0xad, 0xc0, 0xdd, 0xba, 0x0f, 0x45, 0x01, 0x2b, 0x32, 0x1d, 0xab, 0x27, 0x4b, 0x9e, 0xa4, 0xbc, 0x35, 0xc8, 0x7e,
|
||||
0xfb, 0x20, 0xdb, 0xc9, 0xe5, 0x8a, 0x16, 0xd8, 0x10, 0xc4, 0xa0, 0x44, 0xf2, 0xe1, 0x8b, 0x28, 0x52, 0xf9, 0xb7,
|
||||
0x95, 0x62, 0x76, 0xdf, 0x01, 0x6a, 0x6c, 0x2b, 0x8a, 0xdc, 0x7d, 0x91, 0xa0, 0x72, 0x45, 0x40, 0x16, 0x79, 0x03,
|
||||
0xb4, 0x2a, 0xf2, 0x16, 0x2c, 0x45, 0xac, 0xa1, 0xda, 0x80, 0x25, 0x7f, 0xde, 0xbe, 0x0e, 0x9f, 0x15, 0xb9, 0xe0,
|
||||
0xf2, 0x0e, 0x69, 0x10, 0x84, 0x33, 0x25, 0x51, 0xa3, 0xa1, 0x26, 0x15, 0xb5, 0x34, 0xe3, 0x2d, 0x5d, 0xc1, 0xa8,
|
||||
0x22, 0x69, 0x0b, 0x64, 0xc3, 0x61, 0xdb, 0x29, 0x6d, 0x11, 0x53, 0xd2, 0x82, 0xb4, 0xc4, 0xdb, 0xf2, 0xca, 0x36,
|
||||
0xa4, 0x82, 0x0d, 0x67, 0x10, 0xf6, 0x8b, 0x09, 0x97, 0xdc, 0x72, 0x2a, 0x42, 0xc3, 0xa8, 0x00, 0x92, 0x4c, 0xd6,
|
||||
0x06, 0x74, 0xbf, 0xa0, 0x4b, 0x01, 0x44, 0x2a, 0xaf, 0xc8, 0x0d, 0xd3, 0xbc, 0xb3, 0xc8, 0xb9, 0x4a, 0x5a, 0x55,
|
||||
0xad, 0x05, 0x20, 0xa6, 0x95, 0x31, 0x4a, 0xf3, 0x15, 0x97, 0x45, 0xa5, 0xd8, 0xba, 0x05, 0x69, 0xb1, 0x50, 0x8c,
|
||||
0x5a, 0xae, 0x24, 0x36, 0x40, 0x35, 0x6b, 0x08, 0x21, 0xe5, 0x0b, 0x43, 0x37, 0x50, 0xfe, 0xf0, 0x83, 0x7f, 0x16,
|
||||
0x5a, 0x81, 0x7d, 0x25, 0xc0, 0x91, 0xe6, 0xa7, 0xfd, 0x2d, 0x5d, 0xfd, 0x4e, 0x5b, 0xf0, 0x4b, 0x6a, 0x78, 0x05,
|
||||
0x65, 0xf0, 0x3e, 0xfe, 0x80, 0x8d, 0xdd, 0x0b, 0xc0, 0x15, 0x37, 0x9d, 0xa0, 0x7b, 0x52, 0x2e, 0x85, 0x62, 0x77,
|
||||
0x65, 0xb0, 0xa8, 0xd7, 0x92, 0x39, 0x70, 0x04, 0x3e, 0x04, 0x07, 0x01, 0x16, 0x49, 0xf2, 0x86, 0xda, 0x06, 0xb7,
|
||||
0x74, 0xe7, 0x0f, 0x04, 0x97, 0x7e, 0xfa, 0xa3, 0x0f, 0x4f, 0x92, 0x38, 0x0e, 0x26, 0xfd, 0x27, 0x0e, 0xa2, 0x24,
|
||||
0x8e, 0x17, 0x1a, 0xec, 0x5a, 0x4b, 0x64, 0xfd, 0x32, 0xef, 0xa8, 0x6d, 0x50, 0x45, 0xbc, 0x37, 0x49, 0x8a, 0x92,
|
||||
0xe7, 0x38, 0x9d, 0xfd, 0x86, 0x9f, 0xa2, 0x2b, 0x9c, 0xce, 0xd8, 0xd3, 0x70, 0x86, 0x92, 0xab, 0x70, 0x86, 0xd2,
|
||||
0x14, 0xcf, 0x50, 0xfc, 0xc9, 0x43, 0x35, 0x17, 0x82, 0x78, 0x52, 0x49, 0xf0, 0x90, 0xb1, 0x5a, 0xdd, 0x01, 0xf1,
|
||||
0xd8, 0x5a, 0x6b, 0x90, 0xf6, 0x5a, 0x09, 0xa5, 0xbd, 0xa8, 0xf8, 0xe6, 0x7f, 0x01, 0x5a, 0x4d, 0xa5, 0xa9, 0x95,
|
||||
0x6e, 0x89, 0xd7, 0xa7, 0xdb, 0x7f, 0x74, 0x90, 0x47, 0xe4, 0x3e, 0xc1, 0x05, 0x33, 0x1c, 0xf2, 0x4a, 0x3c, 0x87,
|
||||
0xf8, 0xcc, 0x8b, 0x8a, 0x32, 0x38, 0x9e, 0xa3, 0xb7, 0x2e, 0xfa, 0x31, 0x1e, 0xed, 0xbf, 0x2f, 0x73, 0xb3, 0x59,
|
||||
0xa1, 0x5d, 0x2b, 0xa4, 0x21, 0x5e, 0x63, 0x6d, 0x97, 0x45, 0xd1, 0x76, 0xbb, 0xc5, 0xdb, 0x29, 0x56, 0x7a, 0x15,
|
||||
0xa5, 0x71, 0x1c, 0x47, 0x66, 0xb3, 0xf2, 0xd0, 0x70, 0xf2, 0x5e, 0x7a, 0xe5, 0xa1, 0x06, 0xf8, 0xaa, 0xb1, 0x3d,
|
||||
0x5d, 0x3c, 0x3a, 0xc0, 0x31, 0x77, 0x12, 0x45, 0xf9, 0xe1, 0xc2, 0x8a, 0xbc, 0xb0, 0x02, 0x2f, 0x2e, 0xf2, 0xf6,
|
||||
0xb8, 0x0f, 0xf3, 0x29, 0x4d, 0x51, 0x8a, 0xe2, 0xfe, 0x97, 0x86, 0x8e, 0x1e, 0x57, 0xe1, 0x67, 0x2b, 0x74, 0xb1,
|
||||
0x72, 0x54, 0x3b, 0x0f, 0x9f, 0x9f, 0x75, 0x13, 0xb7, 0xb3, 0x49, 0xe2, 0xfb, 0x0d, 0xa7, 0xf0, 0xcb, 0xfc, 0x72,
|
||||
0x1d, 0xa6, 0xef, 0x2e, 0x05, 0x9c, 0xb5, 0x26, 0x79, 0x37, 0xa7, 0x33, 0x34, 0x1b, 0x77, 0x66, 0xa1, 0xa3, 0xcf,
|
||||
0x2b, 0x34, 0xdb, 0xa4, 0x4d, 0xd2, 0x86, 0xf3, 0x70, 0x46, 0xa7, 0x68, 0x3a, 0x3a, 0x32, 0x45, 0xd3, 0x4d, 0xda,
|
||||
0xcc, 0xdf, 0xcd, 0x2f, 0xf7, 0xc2, 0xe9, 0xa7, 0xc7, 0x2e, 0xb9, 0x59, 0x59, 0xde, 0x47, 0xae, 0x2f, 0x23, 0xc7,
|
||||
0x1f, 0x15, 0x97, 0x7e, 0xe9, 0xf2, 0x0f, 0x96, 0x35, 0x7e, 0x19, 0x31, 0x25, 0x6b, 0xbe, 0xc2, 0x1f, 0x8d, 0x92,
|
||||
0x65, 0x80, 0x6d, 0x03, 0xd2, 0x3f, 0xa9, 0xfa, 0x36, 0x38, 0xd8, 0x9e, 0xe3, 0x7f, 0x81, 0x73, 0xae, 0x7f, 0xcb,
|
||||
0xad, 0x00, 0x62, 0xb1, 0xbb, 0xa1, 0x93, 0x2f, 0xdc, 0x8a, 0x9f, 0xf6, 0xbf, 0x56, 0x7e, 0xd9, 0x52, 0x56, 0x06,
|
||||
0x98, 0x4b, 0x09, 0xfa, 0x16, 0x76, 0x96, 0x94, 0x6f, 0x5e, 0x5e, 0xa3, 0x97, 0x55, 0xa5, 0xc1, 0x98, 0x0c, 0x95,
|
||||
0x4f, 0x2c, 0x6e, 0x29, 0xfb, 0xba, 0x7a, 0x93, 0x3c, 0xd4, 0xfe, 0x8b, 0xbf, 0xe6, 0xe8, 0x77, 0xb0, 0x5b, 0xa5,
|
||||
0xef, 0x46, 0x7d, 0x67, 0x7f, 0xe1, 0xae, 0x91, 0x26, 0x5f, 0x85, 0x91, 0x60, 0xcb, 0x60, 0xc2, 0xbf, 0x2e, 0x60,
|
||||
0x0c, 0xaf, 0xca, 0x60, 0x42, 0xbf, 0x2e, 0xd1, 0x19, 0x77, 0x79, 0x2d, 0xa6, 0x9d, 0xc1, 0x46, 0x70, 0x06, 0x7e,
|
||||
0x12, 0xe0, 0x5a, 0xe9, 0x57, 0x94, 0x35, 0x0f, 0x12, 0xe4, 0x5c, 0x51, 0xf7, 0x38, 0x4c, 0x03, 0xb5, 0x30, 0x42,
|
||||
0xf9, 0x65, 0xc5, 0x37, 0x65, 0xb0, 0x50, 0x98, 0x09, 0x6a, 0x8c, 0x6b, 0x19, 0xc4, 0x39, 0xe7, 0xc2, 0x29, 0x27,
|
||||
0x6a, 0x88, 0xf4, 0x97, 0xdb, 0x37, 0xbf, 0x91, 0x32, 0xa7, 0x43, 0x47, 0xf4, 0xbe, 0xf3, 0x50, 0x2f, 0x4c, 0xbc,
|
||||
0x51, 0x30, 0x14, 0x50, 0xdb, 0xbe, 0xe2, 0x7d, 0x8b, 0xb5, 0x31, 0x3c, 0x38, 0xe6, 0xa6, 0xa3, 0xf2, 0x73, 0x31,
|
||||
0x17, 0x93, 0x57, 0xe4, 0x91, 0xe3, 0x15, 0x79, 0x44, 0x8b, 0x47, 0x07, 0xe9, 0xf7, 0xcd, 0xed, 0x2e, 0x38, 0x3a,
|
||||
0x6b, 0x7f, 0xaf, 0x41, 0xef, 0x6f, 0x40, 0x00, 0xb3, 0x4a, 0xfb, 0x25, 0xbe, 0x54, 0x74, 0x45, 0x01, 0x3b, 0x7b,
|
||||
0x3d, 0x36, 0x5c, 0x8b, 0xdd, 0xe6, 0x44, 0x61, 0x25, 0x99, 0xe0, 0xec, 0x8e, 0x9c, 0x23, 0x0e, 0x0e, 0x1c, 0x6f,
|
||||
0xa8, 0x58, 0xc3, 0x49, 0x86, 0xe2, 0x5a, 0xb1, 0xb5, 0xf1, 0x83, 0xe3, 0x44, 0x63, 0xda, 0x75, 0x20, 0xab, 0xeb,
|
||||
0x86, 0x8b, 0xca, 0x57, 0xc1, 0x31, 0xb8, 0x3f, 0xe9, 0xcf, 0x8c, 0xbb, 0x59, 0xf0, 0x5e, 0x83, 0xf8, 0x87, 0x3c,
|
||||
0x76, 0xd3, 0xe0, 0xf1, 0x87, 0x32, 0xc0, 0x7d, 0xfc, 0xe5, 0xfd, 0x48, 0x70, 0xd7, 0xfb, 0xc9, 0xae, 0x15, 0x13,
|
||||
0x17, 0x7a, 0x38, 0x9f, 0x05, 0xc7, 0xf2, 0x18, 0x1c, 0x83, 0x45, 0x1e, 0x0d, 0x8d, 0xbd, 0xc8, 0xfb, 0x96, 0xdb,
|
||||
0x8f, 0x94, 0x9e, 0x32, 0x0d, 0x80, 0x7d, 0xd0, 0xe2, 0x7f, 0x3c, 0x2c, 0xd5, 0x2e, 0x34, 0xfc, 0x13, 0x97, 0xab,
|
||||
0x8c, 0xcb, 0x06, 0x34, 0xb7, 0xc7, 0x8a, 0x6f, 0x26, 0x5c, 0x76, 0x6b, 0x7b, 0xe8, 0x68, 0x55, 0x39, 0xce, 0xac,
|
||||
0xdb, 0x2d, 0x6a, 0x25, 0xad, 0x93, 0x84, 0x2c, 0x81, 0xf6, 0x38, 0xf0, 0xfb, 0xe6, 0x93, 0x3d, 0x9f, 0x7d, 0x7f,
|
||||
0x5c, 0xaa, 0x6a, 0x7f, 0x70, 0x19, 0x0a, 0xa9, 0xe0, 0x2b, 0x99, 0x31, 0x90, 0x16, 0xf4, 0xa0, 0x54, 0xd3, 0x96,
|
||||
0x8b, 0x7d, 0x66, 0xa8, 0x34, 0xa1, 0x01, 0xcd, 0xeb, 0xe3, 0x72, 0x6d, 0xad, 0x92, 0x07, 0xe6, 0x9a, 0x6d, 0xf6,
|
||||
0x5d, 0x5d, 0xd7, 0x0b, 0xb6, 0xd6, 0x46, 0xe9, 0xac, 0x53, 0xbc, 0xd7, 0x5b, 0x52, 0x76, 0xb7, 0xd2, 0x6a, 0x2d,
|
||||
0xab, 0x70, 0x14, 0x4a, 0x6a, 0x3a, 0x05, 0xb6, 0x58, 0x2a, 0x5d, 0x81, 0xce, 0xe2, 0x91, 0x08, 0x35, 0xad, 0xf8,
|
||||
0xda, 0x64, 0x78, 0xaa, 0xa1, 0x5d, 0x0c, 0xee, 0x24, 0x71, 0xfc, 0xfd, 0xe2, 0xe4, 0x79, 0x7c, 0xe9, 0x37, 0x4e,
|
||||
0x9d, 0x94, 0xe0, 0x12, 0xc2, 0xa1, 0x57, 0x66, 0x29, 0xbe, 0xd2, 0xd0, 0x1e, 0x5b, 0xca, 0xe5, 0xa5, 0xf7, 0xae,
|
||||
0xa2, 0x16, 0x2d, 0x97, 0xc3, 0x28, 0xcd, 0xd2, 0x79, 0xdc, 0xed, 0x16, 0xe3, 0xe4, 0xca, 0xb8, 0xec, 0x11, 0xfa,
|
||||
0xf9, 0x75, 0x3c, 0x15, 0xc9, 0xe1, 0xe3, 0xda, 0x58, 0x5e, 0xef, 0xc3, 0x71, 0x24, 0x67, 0xa6, 0xa3, 0x0c, 0xc2,
|
||||
0x25, 0xd8, 0x2d, 0x80, 0x5c, 0xf4, 0xb0, 0x21, 0xb7, 0xd0, 0x9a, 0x53, 0x6a, 0x4e, 0x70, 0xb5, 0x80, 0xdd, 0x19,
|
||||
0xa6, 0xaf, 0xe5, 0xc3, 0x7f, 0x96, 0x76, 0x05, 0x76, 0x68, 0xa9, 0x5e, 0x71, 0x19, 0x2e, 0x95, 0xb5, 0xaa, 0xcd,
|
||||
0xc2, 0xa7, 0xdd, 0x6e, 0x31, 0x6e, 0x39, 0xb0, 0x2c, 0x89, 0xbb, 0xdd, 0xb1, 0x1f, 0xc3, 0xa7, 0x7c, 0x5f, 0xd5,
|
||||
0xcf, 0x68, 0x12, 0x7f, 0x21, 0xc7, 0x55, 0x5d, 0xa7, 0xcb, 0xfa, 0x94, 0xe3, 0xa4, 0xdb, 0x21, 0xa3, 0x04, 0xaf,
|
||||
0x46, 0xb8, 0x1e, 0x09, 0xc5, 0xe7, 0xd4, 0x26, 0xb3, 0x6e, 0x87, 0x92, 0xcb, 0xcc, 0xb8, 0x89, 0xea, 0xa6, 0x8e,
|
||||
0xab, 0xb5, 0x22, 0x8f, 0x86, 0x97, 0x8e, 0xab, 0x8c, 0x22, 0x77, 0x19, 0x2e, 0xf2, 0x26, 0x41, 0xbc, 0x22, 0x2d,
|
||||
0x65, 0xc5, 0x45, 0xdb, 0xcb, 0xa3, 0x26, 0x39, 0xb1, 0x9a, 0xa4, 0x78, 0xd0, 0xd2, 0x06, 0x5e, 0xef, 0x7d, 0x71,
|
||||
0xad, 0xa4, 0x04, 0x66, 0xb9, 0x5c, 0x21, 0xab, 0xd0, 0x98, 0x02, 0x8c, 0x71, 0xbe, 0xd4, 0xc5, 0x5b, 0x01, 0xd4,
|
||||
0x00, 0xda, 0x52, 0x6e, 0x71, 0x1e, 0x0d, 0xf2, 0x43, 0x13, 0xe0, 0x15, 0x91, 0x60, 0xcf, 0xd7, 0xbe, 0x99, 0x0e,
|
||||
0x06, 0x6e, 0xc0, 0x3a, 0x24, 0x67, 0x60, 0x5a, 0xe4, 0x6e, 0x3a, 0x23, 0xda, 0x5f, 0x60, 0x12, 0x6d, 0x79, 0xcd,
|
||||
0xdd, 0xeb, 0xa6, 0xc8, 0xfb, 0x22, 0x77, 0x08, 0x2e, 0xcf, 0xc3, 0xd3, 0xab, 0xa7, 0x04, 0xc8, 0x95, 0x6d, 0xc8,
|
||||
0x34, 0x45, 0x9d, 0xa0, 0x0c, 0x1a, 0x25, 0x2a, 0xd0, 0xe4, 0xe6, 0xe6, 0xd7, 0x9f, 0x0b, 0xe7, 0xcc, 0xbd, 0x5e,
|
||||
0x67, 0xee, 0x06, 0x35, 0x47, 0x8c, 0x5a, 0xf3, 0xab, 0xe1, 0xc1, 0xd5, 0x51, 0x63, 0xb6, 0x4a, 0x57, 0x0f, 0x30,
|
||||
0xde, 0x8e, 0x9b, 0x03, 0x4e, 0xff, 0xef, 0xaf, 0x4a, 0x71, 0x43, 0x37, 0x90, 0x47, 0xe3, 0x22, 0x8f, 0x9c, 0xc3,
|
||||
0x03, 0xbf, 0x19, 0xe5, 0x9a, 0xa4, 0xf8, 0xe3, 0xf6, 0x25, 0xfa, 0xb3, 0xab, 0xa8, 0x85, 0x21, 0x6d, 0x7d, 0x54,
|
||||
0x2d, 0xd8, 0x46, 0x55, 0xe4, 0xed, 0x1f, 0x37, 0xb7, 0xe7, 0x08, 0xd7, 0xbd, 0x10, 0x02, 0xc9, 0x86, 0xa7, 0xdf,
|
||||
0x5a, 0x58, 0xde, 0x51, 0x6d, 0x7b, 0xd8, 0xd0, 0x35, 0x98, 0x53, 0x0c, 0x3d, 0xbf, 0xe6, 0x02, 0x86, 0x30, 0x06,
|
||||
0xc5, 0x02, 0x9d, 0xbc, 0x3a, 0x59, 0xfb, 0xcc, 0xaf, 0x68, 0x38, 0xed, 0x68, 0x38, 0xfa, 0xa8, 0x7f, 0x05, 0xff,
|
||||
0x0b, 0x54, 0xcf, 0x54, 0x8b, 0x15, 0x0b, 0x00, 0x00};
|
||||
|
||||
#else // Brotli (default, smaller)
|
||||
constexpr uint8_t INDEX_BR[] PROGMEM = {
|
||||
0x1b, 0x08, 0x0b, 0x00, 0xe4, 0x7f, 0x9b, 0xad, 0xbb, 0x97, 0x53, 0xde, 0xb7, 0x25, 0x0e, 0x69, 0xd4, 0x69, 0x89,
|
||||
0xba, 0xa5, 0x55, 0x22, 0x04, 0x27, 0xeb, 0x00, 0x52, 0xac, 0xbc, 0xec, 0x5f, 0xfb, 0xb5, 0x7a, 0x12, 0x0a, 0xd6,
|
||||
0x48, 0x84, 0x4a, 0x48, 0x5a, 0xcb, 0xbd, 0xb7, 0x72, 0x66, 0x4e, 0x62, 0xd8, 0xbd, 0x7f, 0x88, 0x68, 0x86, 0x28,
|
||||
0xd6, 0xf1, 0xda, 0x2c, 0xa2, 0x32, 0x5c, 0xdd, 0xab, 0xb2, 0x15, 0xbc, 0x24, 0x13, 0xe6, 0xbd, 0x0c, 0x52, 0x63,
|
||||
0x82, 0x88, 0x6d, 0x74, 0x71, 0x51, 0x9c, 0xe2, 0x40, 0x56, 0xea, 0xb0, 0x5c, 0xb7, 0x1d, 0x81, 0x3a, 0x0c, 0xf2,
|
||||
0xd7, 0xa8, 0x5c, 0x35, 0x2d, 0x43, 0xb3, 0x42, 0x37, 0x7c, 0x60, 0xd8, 0xc1, 0x95, 0x91, 0x94, 0x3c, 0x29, 0x20,
|
||||
0x96, 0x20, 0xf6, 0x24, 0xb7, 0x83, 0x4a, 0x06, 0xf1, 0xc3, 0x2f, 0x88, 0x50, 0x55, 0x35, 0x2d, 0xe8, 0xb6, 0x21,
|
||||
0x38, 0x61, 0x8e, 0x44, 0x2a, 0xac, 0x39, 0xcf, 0x74, 0xaa, 0x31, 0x7f, 0x62, 0x32, 0x9b, 0x99, 0x42, 0x0a, 0xf6,
|
||||
0x6f, 0xd8, 0x89, 0x3f, 0x49, 0xb1, 0xa2, 0x52, 0x0a, 0x8e, 0xb3, 0x27, 0x0b, 0x07, 0x07, 0x58, 0xb0, 0x8f, 0xaa,
|
||||
0xdc, 0x68, 0x12, 0x0f, 0x95, 0xbf, 0xc7, 0x36, 0xd5, 0x60, 0x5a, 0xc7, 0x80, 0xac, 0x52, 0xf7, 0xa7, 0x2d, 0xb6,
|
||||
0x64, 0xda, 0xda, 0x11, 0x8d, 0x6a, 0xe5, 0xba, 0xe9, 0xda, 0xdc, 0x62, 0xc3, 0xcb, 0xae, 0xc1, 0xe1, 0x11, 0xb6,
|
||||
0x33, 0x29, 0x04, 0x09, 0xfe, 0x09, 0x08, 0xc2, 0x1f, 0x98, 0x16, 0x26, 0x0d, 0xce, 0xd7, 0x75, 0xfb, 0xd7, 0xa5,
|
||||
0x82, 0xd7, 0x32, 0x44, 0x72, 0xc1, 0xc2, 0xe4, 0x15, 0xcb, 0x50, 0xfc, 0xd3, 0x58, 0x64, 0x34, 0x41, 0x32, 0xbe,
|
||||
0x1f, 0x08, 0x43, 0x16, 0x14, 0xf7, 0x70, 0x2c, 0x1c, 0x61, 0x09, 0x78, 0x73, 0xd1, 0x30, 0xf6, 0xed, 0x85, 0x55,
|
||||
0x50, 0x02, 0xf0, 0x68, 0x50, 0x5c, 0x1a, 0xd7, 0x3b, 0x8e, 0xf2, 0x23, 0xc8, 0x88, 0x39, 0xd0, 0x84, 0xf7, 0xa6,
|
||||
0xd1, 0xa3, 0xfb, 0xe5, 0x44, 0xc0, 0x4c, 0x2f, 0x6f, 0x19, 0x70, 0x75, 0xf6, 0x1c, 0xb8, 0xce, 0x89, 0x4f, 0x01,
|
||||
0x8d, 0xa1, 0x71, 0xf2, 0x97, 0xf8, 0xe7, 0xd3, 0xf1, 0xe1, 0xfa, 0xfc, 0xc8, 0x03, 0x78, 0x14, 0xa0, 0x3d, 0x28,
|
||||
0xb7, 0xeb, 0x26, 0x52, 0x59, 0xa8, 0xb5, 0x0d, 0x5c, 0x8a, 0x6b, 0xe5, 0xf6, 0x30, 0xd6, 0xf7, 0x71, 0x31, 0xe8,
|
||||
0xbd, 0xc9, 0xfa, 0xf5, 0x71, 0x9d, 0xff, 0xf6, 0x69, 0x62, 0x5f, 0xb5, 0xc7, 0x06, 0x43, 0x54, 0xb5, 0x87, 0xf1,
|
||||
0xcc, 0x84, 0xe8, 0xb7, 0x56, 0x38, 0x43, 0x6a, 0xa6, 0x2f, 0x53, 0xd1, 0x89, 0x48, 0x8d, 0x72, 0x75, 0x4a, 0x17,
|
||||
0xf6, 0x8d, 0xb2, 0xeb, 0xb1, 0x6b, 0x29, 0x1e, 0xd5, 0x74, 0xc7, 0xb3, 0xf4, 0xf1, 0x04, 0x0d, 0xbf, 0x08, 0x81,
|
||||
0x8f, 0xfe, 0x8d, 0xfc, 0xf2, 0x35, 0x92, 0xa0, 0x24, 0x88, 0x12, 0x6a, 0xe6, 0x2f, 0x01, 0x94, 0x5c, 0x4b, 0xf9,
|
||||
0x6b, 0x9a, 0xf6, 0x1a, 0x35, 0x11, 0x8a, 0xc6, 0x04, 0x8d, 0x50, 0x64, 0x48, 0x2d, 0x8b, 0xdf, 0xdf, 0xa1, 0xd1,
|
||||
0xfd, 0x21, 0xd7, 0x40, 0x96, 0x00, 0xb1, 0x9f, 0x54, 0xc2, 0xe1, 0x00, 0x79, 0x15, 0x80, 0xf8, 0xca, 0x8e, 0xc5,
|
||||
0x06, 0x03, 0xdf, 0x72, 0x49, 0xc0, 0x08, 0x27, 0x90, 0xe3, 0x14, 0xc2, 0xac, 0xc3, 0x83, 0xc9, 0xf6, 0x9d, 0x12,
|
||||
0xab, 0x46, 0xae, 0x03, 0x74, 0x1d, 0x27, 0xce, 0x91, 0x1d, 0x4e, 0xb4, 0xbe, 0x80, 0xe7, 0x6a, 0x6d, 0x0a, 0x20,
|
||||
0x47, 0x6b, 0x00, 0x4e, 0x25, 0x52, 0xe6, 0xf5, 0xe9, 0x43, 0x84, 0xc1, 0x0d, 0x4b, 0x04, 0xb3, 0x91, 0x49, 0xf5,
|
||||
0xe9, 0xc4, 0x2e, 0x1b, 0xf9, 0x98, 0xf9, 0xea, 0x9e, 0xb8, 0xf6, 0x53, 0xf8, 0x42, 0xc2, 0x60, 0x5c, 0xa9, 0xd2,
|
||||
0xfe, 0x0a, 0xe5, 0xd4, 0x7d, 0x36, 0x76, 0x04, 0x12, 0x38, 0xa1, 0xc4, 0x30, 0xb8, 0xf2, 0x69, 0x86, 0x5b, 0xe7,
|
||||
0xe5, 0xa0, 0xc0, 0xfe, 0x91, 0x19, 0x03, 0x1e, 0xa9, 0x93, 0x26, 0x49, 0x58, 0xd5, 0xf6, 0x33, 0x4e, 0x0a, 0x89,
|
||||
0x64, 0x1e, 0xb4, 0x7a, 0x5d, 0x0d, 0x12, 0xcf, 0x2b, 0xdd, 0x35, 0x90, 0x55, 0xc3, 0x0e, 0xcd, 0x0e, 0xad, 0x6b,
|
||||
0x8f, 0x43, 0xd0, 0xb4, 0x6a, 0xdb, 0x4b, 0xe5, 0xa4, 0x9b, 0x09, 0x23, 0x1a, 0x43, 0xa9, 0xff, 0x61, 0x8b, 0x07,
|
||||
0xd6, 0x0f, 0x83, 0x23, 0xbe, 0x8a, 0x66, 0xa2, 0x10, 0x2f, 0x11, 0x01, 0x0d, 0xc6, 0x96, 0xba, 0x87, 0xaa, 0xa8,
|
||||
0x44, 0x7c, 0x1e, 0x34, 0xdb, 0xba, 0x41, 0x90, 0xf0, 0x7a, 0xdb, 0x63, 0x60, 0x96, 0x8d, 0x64, 0xcb, 0x2f, 0x28,
|
||||
0x96, 0x04, 0xd5, 0xc0, 0x7a, 0xe8, 0xec, 0xd1, 0x61, 0x1b, 0x09, 0x4b, 0x4c, 0x6e, 0x1b, 0x31, 0x98, 0x9c, 0x6d,
|
||||
0xdb, 0xd0, 0xec, 0xa4, 0x0f, 0x0a, 0xe0, 0xc8, 0x35, 0xc4, 0x93, 0xdc, 0xee, 0x19, 0x00, 0x80, 0x07, 0xf5, 0xcf,
|
||||
0xe0, 0x7f, 0x75, 0xf8, 0x52, 0x3a, 0xfc, 0x0d, 0x84, 0xa5, 0xc1, 0xb2, 0x1c, 0x25, 0x40, 0xc5, 0x8b, 0xb3, 0xdb,
|
||||
0x7a, 0x1b, 0x44, 0xff, 0x79, 0x9a, 0x26, 0xc4, 0xe7, 0x9e, 0x78, 0x4c, 0xa5, 0x94, 0xab, 0x78, 0x34, 0x9d, 0xb5,
|
||||
0xb7, 0x24, 0x26, 0xe7, 0x9a, 0xf3, 0xe5, 0x2a, 0x35, 0x4b, 0xbe, 0x74, 0xd7, 0x01, 0xbc, 0x71, 0x72, 0x03, 0x5c,
|
||||
0x40, 0x24, 0x17, 0x61, 0x5b, 0x7b, 0x19, 0xc2, 0x7f, 0x4e, 0x5b, 0x24, 0xfb, 0x8b, 0xc3, 0x51, 0x00, 0x3d, 0x09,
|
||||
0x04, 0x05, 0x72, 0x64, 0xf6, 0xf0, 0x6b, 0x99, 0x38, 0x93, 0x5b, 0xac, 0x0c, 0xff, 0x40, 0x7b, 0x53, 0xba, 0xab,
|
||||
0x61, 0xc9, 0xa2, 0xde, 0xd6, 0x2b, 0x0c, 0xbd, 0x85, 0xac, 0x4c, 0x64, 0x8b, 0xe6, 0x69, 0x2b, 0x56, 0x10, 0x1b,
|
||||
0x08, 0x59, 0x6c, 0x55, 0x0a, 0xaa, 0x93, 0x85, 0x1d, 0x45, 0xda, 0xc6, 0x39, 0xe6, 0x6a, 0xab, 0x71, 0x73, 0x46,
|
||||
0x0f, 0xf7, 0xab, 0x4e, 0x88, 0xae, 0x8c, 0xe0, 0x91, 0xda, 0x35, 0x8c, 0xd3, 0x18, 0x92, 0x3d, 0xab, 0x2f, 0x0d,
|
||||
0xd6, 0xc9, 0x06, 0xdb, 0xc2, 0x62, 0xcb, 0x8a, 0x23, 0x5b, 0x28, 0x2e, 0x9b, 0x96, 0xc4, 0xc1, 0x42, 0xa9, 0x8d,
|
||||
0x69, 0xe5, 0x8f, 0x89, 0x12, 0x11, 0x9a, 0x56, 0x3b, 0x3c, 0x2b, 0xb4, 0xb2, 0x7b, 0xfd, 0xdb, 0x80, 0x92, 0xb4,
|
||||
0xca, 0x44, 0x37, 0x8c, 0x94, 0x2f, 0x4a, 0xb4, 0xc4, 0x28, 0x83, 0x8a, 0x78, 0xcb, 0xd2, 0x5c, 0x5c, 0x35, 0x29,
|
||||
0x95, 0x35, 0x2f, 0x99, 0x28, 0x4f, 0x22, 0x18, 0x70, 0x85, 0xee, 0x2c, 0xb9, 0xef, 0x14, 0x57, 0x73, 0x23, 0x45,
|
||||
0xae, 0xdc, 0x54, 0x56, 0x55, 0x78, 0x56, 0xad, 0x48, 0x26, 0x27, 0xbf, 0xcb, 0xd7, 0x54, 0xa9, 0xa8, 0xd7, 0xb5,
|
||||
0x71, 0x9c, 0xe7, 0x79, 0x89, 0x5c, 0xa9, 0x6a, 0x53, 0xe8, 0xfa, 0x0d, 0x69, 0x36, 0xed, 0xad, 0x33, 0xc9, 0x75,
|
||||
0x17, 0xe9, 0x8f, 0x31, 0x58, 0xa6, 0x27, 0xfc, 0x79, 0xc6, 0xb6, 0xd5, 0x65, 0x90, 0x31, 0xc6, 0x9d, 0x29, 0x95,
|
||||
0x83, 0xe5, 0x4e, 0xbb, 0xd7, 0xdc, 0x8e, 0xd0, 0x3a, 0x54, 0x27, 0xce, 0xf5, 0xdb, 0xbd, 0x89, 0x3c, 0x61, 0xb3,
|
||||
0x71, 0x23, 0xc7, 0x55, 0x9e, 0xea, 0x50, 0xe4, 0x37, 0xae, 0x72, 0x34, 0x66, 0xb9, 0x6e, 0x2c, 0x0a, 0x69, 0x8d,
|
||||
0x94, 0x8b, 0x98, 0x08, 0x05, 0x3c, 0x45, 0x45, 0xe1, 0x6c, 0x22, 0xc5, 0x8f, 0x1f, 0x9f, 0x3f, 0xd2, 0x01, 0x12,
|
||||
0xec, 0x86, 0x2f, 0x87, 0x8b, 0x47, 0x30, 0xd0, 0x77, 0x77, 0x1a, 0x13, 0x2d, 0xc6, 0x31, 0x65, 0x77, 0xd8, 0x8c,
|
||||
0xe7, 0xe0, 0x16, 0xf9, 0x4b, 0xd4, 0xc5, 0xa8, 0x67, 0x79, 0xe7, 0xc3, 0x07, 0x94, 0x46, 0xa3, 0x8c, 0x67, 0xd3,
|
||||
0xff, 0x5f, 0x96, 0xfa, 0xed, 0xa7, 0xd3, 0xdd, 0x4f, 0x27, 0x49, 0x27, 0xcf, 0xa4, 0x02, 0xc3, 0x27, 0x3c, 0x96,
|
||||
0x64, 0x24, 0x55, 0xc8, 0x36, 0x45, 0x68, 0x90, 0xea, 0x03, 0x0b, 0x46, 0xa7, 0x8d, 0xb4, 0x26, 0x91, 0x07, 0x4c,
|
||||
0x80, 0x7b, 0x93, 0xa8, 0x10, 0xcb, 0x5e, 0x8d, 0x42, 0x86, 0x3e, 0x2a, 0x7c, 0x99, 0x79, 0x8e, 0xcb, 0x43, 0x28};
|
||||
0x1b, 0x14, 0x0b, 0x00, 0xe4, 0x6f, 0xcd, 0xfc, 0x7b, 0x2e, 0x27, 0xd2, 0x21, 0x2b, 0x58, 0xea, 0x16, 0xf8, 0xa5,
|
||||
0xb6, 0x47, 0x14, 0xb3, 0x4c, 0x56, 0xcc, 0x20, 0x5b, 0x1d, 0xfe, 0x7d, 0xfb, 0xb5, 0x7a, 0x12, 0x0a, 0xd6, 0x48,
|
||||
0x84, 0xa2, 0x21, 0x69, 0x0d, 0x77, 0x33, 0xf3, 0x77, 0xcf, 0x4c, 0x21, 0xf1, 0xde, 0xee, 0x7d, 0x44, 0xbc, 0xd3,
|
||||
0xc4, 0x33, 0x89, 0x1a, 0x69, 0x68, 0x48, 0x57, 0xa7, 0x17, 0x0b, 0x4d, 0x91, 0xac, 0x30, 0x48, 0x21, 0x4d, 0x4c,
|
||||
0x10, 0x57, 0x46, 0x14, 0x17, 0xd5, 0x21, 0x0e, 0xb4, 0x50, 0x87, 0xad, 0x62, 0xda, 0x11, 0x68, 0xc3, 0x20, 0x7f,
|
||||
0x2d, 0xdc, 0x57, 0xeb, 0x2c, 0x36, 0xdb, 0xc4, 0x84, 0x0f, 0xac, 0xec, 0x08, 0x91, 0xa3, 0x92, 0x27, 0x45, 0xc4,
|
||||
0x1e, 0xa4, 0x9e, 0x72, 0x3b, 0xc2, 0xe3, 0x20, 0x7d, 0xf8, 0x05, 0x09, 0xea, 0xe3, 0xa6, 0x3f, 0x11, 0x8b, 0x16,
|
||||
0x11, 0x6b, 0x26, 0x91, 0x1a, 0x2c, 0x19, 0x24, 0xf7, 0x5d, 0x44, 0x82, 0x49, 0x44, 0x15, 0xce, 0x39, 0xdc, 0xcb,
|
||||
0x8f, 0x5b, 0xe1, 0xe2, 0x42, 0x96, 0xa2, 0x10, 0x3c, 0x20, 0xa5, 0x65, 0x85, 0x49, 0x6a, 0x06, 0x08, 0xd1, 0xcb,
|
||||
0x41, 0xb8, 0x49, 0x20, 0x73, 0x71, 0xfe, 0x55, 0x61, 0x45, 0xc6, 0x95, 0x72, 0xc8, 0xf0, 0xa5, 0xe9, 0xe6, 0x3a,
|
||||
0xb9, 0xc3, 0x8a, 0x9f, 0xb5, 0xc1, 0xc9, 0x15, 0x56, 0x93, 0x38, 0x8a, 0x48, 0xf0, 0x4f, 0x38, 0x22, 0xe1, 0x8d,
|
||||
0x55, 0xbb, 0x8c, 0xc3, 0xb0, 0x68, 0xcc, 0x7f, 0x6f, 0xf8, 0xc9, 0x9b, 0x38, 0x41, 0xf1, 0x94, 0x25, 0xf9, 0x6b,
|
||||
0x56, 0xa2, 0xec, 0xa7, 0xbd, 0x2e, 0x69, 0x8e, 0xe2, 0xec, 0x36, 0x94, 0x24, 0x2c, 0x12, 0x1d, 0x4e, 0x76, 0x7e,
|
||||
0x23, 0xcc, 0xf2, 0x6e, 0x35, 0x1a, 0x9c, 0xed, 0x6f, 0x15, 0x3f, 0xc9, 0x72, 0xdc, 0xfd, 0x13, 0x0f, 0x16, 0x8a,
|
||||
0x23, 0x4f, 0xf9, 0x2e, 0x63, 0x44, 0x91, 0x77, 0xe0, 0xb3, 0xd1, 0x78, 0x74, 0xdb, 0x4a, 0x2c, 0xd8, 0xe8, 0xf9,
|
||||
0x2c, 0x03, 0xbe, 0xae, 0xac, 0x4e, 0x42, 0x01, 0xc4, 0x4b, 0x40, 0xe7, 0x94, 0x34, 0x85, 0x2c, 0xfe, 0xf5, 0x74,
|
||||
0x75, 0xd8, 0xdc, 0xec, 0x6a, 0x00, 0x3f, 0x3f, 0x81, 0x77, 0xa8, 0xcd, 0xde, 0x6d, 0x5a, 0x47, 0xa1, 0x99, 0x36,
|
||||
0x87, 0xb6, 0x78, 0x35, 0x3c, 0x9a, 0x64, 0x15, 0x7c, 0x56, 0x76, 0x22, 0xce, 0x46, 0xe5, 0x17, 0x57, 0x05, 0xfc,
|
||||
0x09, 0x69, 0xae, 0x39, 0xaa, 0xee, 0xc9, 0x4e, 0x7f, 0x99, 0x2a, 0x65, 0x82, 0x7e, 0xeb, 0x23, 0x4f, 0x42, 0xd5,
|
||||
0xca, 0xcb, 0x42, 0x74, 0x2e, 0xd2, 0xa2, 0x62, 0x57, 0xd0, 0xa9, 0x7b, 0x4b, 0xac, 0x7b, 0x65, 0x13, 0x47, 0x0f,
|
||||
0x2d, 0x3d, 0xf6, 0xbc, 0x78, 0x5c, 0xa3, 0xc9, 0x57, 0x4b, 0x10, 0x62, 0x68, 0x19, 0x7f, 0xfd, 0x1a, 0xcb, 0x51,
|
||||
0x1e, 0x41, 0x39, 0x55, 0xf3, 0x97, 0x30, 0xca, 0x37, 0xb6, 0x42, 0x1d, 0x2d, 0x8c, 0xc6, 0x65, 0x8a, 0xd2, 0x89,
|
||||
0x88, 0xa6, 0x28, 0xbd, 0x56, 0x7c, 0x2d, 0x5e, 0x0d, 0x2f, 0x9e, 0xc3, 0xa5, 0x80, 0xaf, 0xcc, 0x00, 0xde, 0xe6,
|
||||
0x5a, 0x8b, 0xda, 0xff, 0x1f, 0x16, 0xc8, 0x83, 0x5e, 0xe5, 0xea, 0x25, 0x86, 0x70, 0x55, 0x25, 0x01, 0x14, 0x0c,
|
||||
0x77, 0xd8, 0x31, 0x21, 0xcc, 0x79, 0x15, 0x3b, 0x32, 0x3a, 0xd3, 0xc5, 0x30, 0xaf, 0x03, 0xd8, 0x3e, 0x44, 0xb8,
|
||||
0x63, 0xb5, 0x74, 0x4f, 0x41, 0x4b, 0xf0, 0x24, 0x74, 0xb2, 0x06, 0xb2, 0x7b, 0x06, 0x60, 0xdc, 0xc1, 0x3e, 0x0e,
|
||||
0x6f, 0x1e, 0x3c, 0x42, 0xa0, 0xdb, 0x36, 0x43, 0x30, 0x71, 0xcc, 0xd6, 0x1a, 0xbd, 0x38, 0x61, 0x19, 0x3f, 0xf2,
|
||||
0xdf, 0xf4, 0x53, 0x3d, 0xc1, 0x14, 0xbe, 0x50, 0x1c, 0x2c, 0xf3, 0xaa, 0x74, 0x36, 0xcb, 0xbd, 0x7a, 0x4b, 0xa3,
|
||||
0x1c, 0x90, 0x40, 0x5b, 0x4a, 0x0f, 0x83, 0x6e, 0x9e, 0x96, 0x28, 0x3d, 0x77, 0x43, 0x05, 0x0e, 0x39, 0x26, 0x15,
|
||||
0xb8, 0x6b, 0x4e, 0x3a, 0x62, 0xc2, 0xda, 0xde, 0x8e, 0x29, 0x29, 0x0b, 0x09, 0xa2, 0xb3, 0xa7, 0x1e, 0x9a, 0x0c,
|
||||
0x8f, 0xa1, 0x46, 0x6f, 0x92, 0xf3, 0x9e, 0xb5, 0xc5, 0x7e, 0x0e, 0x8d, 0xeb, 0x55, 0x08, 0xfa, 0xc9, 0xd8, 0x4e,
|
||||
0xe3, 0xd0, 0xca, 0x2a, 0x96, 0x11, 0x7e, 0xb1, 0xd4, 0x7f, 0x8f, 0x1d, 0xb3, 0xc3, 0xa0, 0x89, 0x6f, 0x93, 0x99,
|
||||
0x55, 0x48, 0x97, 0x08, 0x79, 0x66, 0xe9, 0x4a, 0x6b, 0xa0, 0x29, 0xf2, 0x10, 0x1f, 0x22, 0xa2, 0x1b, 0x41, 0xdf,
|
||||
0xa3, 0xde, 0x62, 0x60, 0x8e, 0xa1, 0x60, 0xc4, 0xd5, 0xce, 0x81, 0x47, 0x84, 0x3b, 0x66, 0x60, 0x74, 0xa7, 0x74,
|
||||
0xcc, 0x48, 0xe0, 0xe1, 0xd5, 0x0c, 0x15, 0x98, 0x3d, 0xa7, 0x9c, 0xb2, 0xec, 0xba, 0x0f, 0x2c, 0x52, 0x14, 0x7b,
|
||||
0xe2, 0x49, 0x6e, 0x0f, 0x8c, 0x00, 0xe0, 0x81, 0xf6, 0x57, 0xe4, 0x3f, 0xbf, 0x7c, 0xa9, 0x5e, 0xfe, 0x01, 0xc2,
|
||||
0x64, 0xb0, 0x05, 0x60, 0x01, 0xaa, 0x78, 0x65, 0xb2, 0xeb, 0x56, 0x41, 0xf2, 0xbf, 0xa3, 0x45, 0x4e, 0x3c, 0x78,
|
||||
0xe2, 0xa1, 0x0d, 0xa9, 0x2a, 0xc0, 0x8a, 0xc0, 0x6f, 0xe4, 0x66, 0xbe, 0x72, 0x35, 0x5e, 0xf7, 0x3b, 0x42, 0x53,
|
||||
0xd4, 0xe6, 0x66, 0xb6, 0x78, 0xcd, 0xaa, 0x6f, 0xf4, 0x26, 0x80, 0x3a, 0x4e, 0x74, 0x80, 0x17, 0x88, 0x44, 0x23,
|
||||
0xa6, 0x3a, 0x6f, 0x87, 0xb8, 0xd0, 0x4d, 0xd3, 0xfc, 0x7c, 0xd6, 0x38, 0x2a, 0x00, 0x28, 0x01, 0xa2, 0x40, 0x94,
|
||||
0x6c, 0x1e, 0x8a, 0xed, 0xe3, 0x92, 0x9d, 0x90, 0xe3, 0x0d, 0x02, 0x4e, 0x15, 0x30, 0xed, 0x8f, 0x5b, 0x99, 0xaa,
|
||||
0x7a, 0x4e, 0xb9, 0xec, 0x91, 0xe2, 0x9f, 0xd4, 0xca, 0x46, 0xaf, 0x87, 0x19, 0x4b, 0xad, 0xea, 0xe6, 0x6c, 0x8d,
|
||||
0x53, 0x4b, 0x29, 0xee, 0x1e, 0x96, 0xd8, 0x94, 0x30, 0x3a, 0x9c, 0xb0, 0x4c, 0xdb, 0xe2, 0xa1, 0x7f, 0xc7, 0x11,
|
||||
0xdd, 0xe3, 0x9d, 0x36, 0x44, 0xd3, 0x93, 0x14, 0x9c, 0x4c, 0x9d, 0xdd, 0x3a, 0x7c, 0x41, 0xb1, 0x8f, 0x14, 0xd9,
|
||||
0x4e, 0x61, 0xd9, 0x3a, 0xe3, 0x0d, 0x76, 0xca, 0x6c, 0xac, 0x73, 0xaf, 0xad, 0x94, 0x87, 0x30, 0xf1, 0x30, 0x2f,
|
||||
0x8b, 0xed, 0x4a, 0xed, 0xbc, 0xc2, 0xf2, 0x7c, 0x30, 0xa3, 0x0b, 0x28, 0x64, 0x3b, 0x8c, 0xd4, 0xc3, 0x42, 0xb9,
|
||||
0xa3, 0x44, 0x51, 0x80, 0x07, 0x5a, 0x3d, 0x14, 0x33, 0x99, 0xbf, 0x2a, 0x6b, 0x2b, 0x19, 0x47, 0x72, 0x9e, 0xd4,
|
||||
0xb4, 0x6d, 0x72, 0xdd, 0x8a, 0x4b, 0x33, 0x55, 0xbc, 0xb4, 0xcd, 0xc8, 0x2b, 0x17, 0x2f, 0x74, 0xeb, 0x22, 0x17,
|
||||
0x94, 0x08, 0x27, 0x27, 0xc2, 0x5b, 0x17, 0xb4, 0xa9, 0x22, 0x16, 0x9d, 0xd4, 0xfc, 0xc7, 0x15, 0xa3, 0x9b, 0x86,
|
||||
0x1f, 0xad, 0x45, 0xd3, 0x87, 0x94, 0x5b, 0x31, 0x36, 0xaa, 0xe4, 0x66, 0x8d, 0xcc, 0x31, 0x05, 0x5b, 0xc4, 0x40,
|
||||
0xc0, 0xb8, 0xeb, 0x91, 0x18, 0x22, 0x8c, 0x31, 0x1e, 0xad, 0xd0, 0x3a, 0x98, 0x07, 0xb5, 0x6f, 0x11, 0xba, 0x11,
|
||||
0xa6, 0x14, 0x35, 0x5a, 0xe7, 0x55, 0xdf, 0xb7, 0x4c, 0x03, 0x61, 0xa3, 0x74, 0x23, 0xdf, 0x55, 0x1f, 0x02, 0x51,
|
||||
0x09, 0xb7, 0xba, 0xd5, 0x0c, 0x67, 0xab, 0x98, 0x70, 0x14, 0x64, 0x8d, 0xf4, 0x8b, 0x54, 0x44, 0x07, 0x6f, 0xe0,
|
||||
0x69, 0x32, 0xca, 0x48, 0xe5, 0xd3, 0xa7, 0x17, 0x8f, 0x45, 0x84, 0x04, 0xb7, 0xd1, 0xbb, 0xe1, 0xf6, 0x01, 0x0a,
|
||||
0xf6, 0xee, 0x2b, 0x32, 0xd2, 0xc5, 0xf8, 0xa6, 0xec, 0x0f, 0x1b, 0x09, 0x1d, 0xfc, 0xa2, 0xbf, 0x54, 0x5d, 0x2c,
|
||||
0x62, 0xf4, 0x77, 0xde, 0xad, 0xa0, 0x50, 0x6a, 0xb4, 0xe3, 0x5f, 0xda, 0xff, 0x6b, 0xb1, 0x78, 0xf7, 0xf9, 0xc1,
|
||||
0xa6, 0xa8, 0x93, 0xe8, 0xe4, 0x11, 0x56, 0xa0, 0x5b, 0x85, 0xa7, 0x92, 0x7a, 0x58, 0x45, 0x95, 0xa9, 0x63, 0x83,
|
||||
0xb4, 0x1f, 0x18, 0x31, 0x7a, 0x6d, 0xa1, 0x8d, 0x8c, 0xdc, 0x91, 0x02, 0x3c, 0x9c, 0x92, 0x42, 0x8e, 0x03, 0x02,
|
||||
0xc5, 0x0c, 0x43, 0x54, 0xf9, 0xb2, 0x85, 0x39, 0x2e, 0x77, 0xad, 0x00};
|
||||
|
||||
// Backwards compatibility alias
|
||||
#define INDEX_GZ INDEX_BR
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components.esp32 import add_idf_component
|
||||
from esphome.components.esp32 import add_idf_component, add_idf_sdkconfig_option
|
||||
from esphome.config_helpers import filter_source_files_from_platform, get_logger_level
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
@@ -208,7 +208,9 @@ async def to_code(config: ConfigType) -> None:
|
||||
ethernet.request_ethernet_ip_state_listener()
|
||||
|
||||
if CORE.is_esp32:
|
||||
add_idf_component(name="espressif/mdns", ref="1.11.3")
|
||||
add_idf_component(name="espressif/mdns", ref="1.12.0")
|
||||
# ESPHome only advertises; the browse APIs are unused
|
||||
add_idf_sdkconfig_option("CONFIG_MDNS_ENABLE_BROWSE", False)
|
||||
|
||||
cg.add_define("USE_MDNS")
|
||||
|
||||
|
||||
@@ -220,6 +220,26 @@ inline bool value_type_is_float(SensorValueType v) {
|
||||
return v == SensorValueType::FP32 || v == SensorValueType::FP32_R;
|
||||
}
|
||||
|
||||
/// Number of 16-bit registers a value of this type occupies (RAW counts as one register).
|
||||
inline uint16_t register_width_for(SensorValueType v) {
|
||||
switch (v) {
|
||||
case SensorValueType::U_DWORD:
|
||||
case SensorValueType::S_DWORD:
|
||||
case SensorValueType::U_DWORD_R:
|
||||
case SensorValueType::S_DWORD_R:
|
||||
case SensorValueType::FP32:
|
||||
case SensorValueType::FP32_R:
|
||||
return 2;
|
||||
case SensorValueType::U_QWORD:
|
||||
case SensorValueType::S_QWORD:
|
||||
case SensorValueType::U_QWORD_R:
|
||||
case SensorValueType::S_QWORD_R:
|
||||
return 4;
|
||||
default:
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/// Coils and discrete inputs are the bit-addressed entity tables; the other types are 16-bit registers.
|
||||
inline bool is_entity_type_binary(EntityType type) {
|
||||
return type == EntityType::COIL || type == EntityType::DISCRETE_INPUT;
|
||||
|
||||
@@ -41,6 +41,7 @@ from .const import (
|
||||
CONF_REGISTER_COUNT,
|
||||
CONF_REGISTER_TYPE,
|
||||
CONF_RESPONSE_SIZE,
|
||||
CONF_REUSE_PREVIOUS_RANGE,
|
||||
CONF_SERVER_COURTESY_RESPONSE,
|
||||
CONF_SERVER_REGISTERS,
|
||||
CONF_SKIP_UPDATES,
|
||||
@@ -60,6 +61,13 @@ ModbusController = modbus_controller_ns.class_("ModbusController", cg.PollingCom
|
||||
|
||||
SensorItem = modbus_controller_ns.struct("SensorItem")
|
||||
|
||||
RangeReuse = modbus_controller_ns.enum("RangeReuse", is_class=True)
|
||||
RANGE_REUSE = {
|
||||
"auto": RangeReuse.AUTO,
|
||||
True: RangeReuse.ALWAYS,
|
||||
False: RangeReuse.NEVER,
|
||||
}
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -184,13 +192,88 @@ ModbusItemBaseSchema = cv.Schema(
|
||||
): cv.positive_int,
|
||||
cv.Optional(CONF_BITMASK, default=0xFFFFFFFF): cv.hex_uint32_t,
|
||||
cv.Optional(CONF_SKIP_UPDATES): validate_skip_updates_deprecated,
|
||||
cv.Optional(CONF_FORCE_NEW_RANGE, default=False): cv.boolean,
|
||||
cv.Optional(CONF_REUSE_PREVIOUS_RANGE, default="auto"): cv.Any(
|
||||
cv.boolean, cv.one_of("auto", lower=True)
|
||||
),
|
||||
# Deprecated options, migrated by validate_range_reuse_migration(). Remove before 2027.3.0
|
||||
cv.Optional(CONF_FORCE_NEW_RANGE): cv.boolean,
|
||||
cv.Optional(CONF_REGISTER_COUNT): cv.positive_int,
|
||||
cv.Optional(CONF_LAMBDA): cv.returning_lambda,
|
||||
cv.Optional(CONF_RESPONSE_SIZE, default=0): cv.positive_int,
|
||||
cv.Optional(CONF_RESPONSE_SIZE, default=0): cv.int_range(min=0, max=250),
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def _derived_register_widths(config: ConfigType) -> set[int]:
|
||||
"""Register widths an item derives on its own; a matching register_count is redundant."""
|
||||
response_size = config.get(CONF_RESPONSE_SIZE, 0)
|
||||
if (value_type := config.get(CONF_VALUE_TYPE)) is not None:
|
||||
widths = {TYPE_REGISTER_MAP[value_type]}
|
||||
if value_type == "RAW" and response_size > 0:
|
||||
widths.add((response_size + 1) // 2)
|
||||
return widths
|
||||
if response_size > 0:
|
||||
# text sensors: the old default was floor(response_size / 2); the derived width is now ceil
|
||||
return {response_size // 2, (response_size + 1) // 2}
|
||||
return {1}
|
||||
|
||||
|
||||
def entity_label(config: ConfigType) -> str:
|
||||
"""The entity's name or id, so migration messages say which entry to edit."""
|
||||
label = config.get(CONF_NAME) or config.get(CONF_ID)
|
||||
return str(label) if label is not None else "<unnamed>"
|
||||
|
||||
|
||||
# Remove before 2027.3.0
|
||||
def validate_range_reuse_migration(config: ConfigType) -> ConfigType:
|
||||
"""Migrate the removed force_new_range/register_count options to reuse_previous_range."""
|
||||
if (force_new_range := config.pop(CONF_FORCE_NEW_RANGE, None)) is not None:
|
||||
if config[CONF_REUSE_PREVIOUS_RANGE] != "auto":
|
||||
raise cv.Invalid(
|
||||
f"'{CONF_FORCE_NEW_RANGE}' and '{CONF_REUSE_PREVIOUS_RANGE}' can't be used together; "
|
||||
f"remove '{CONF_FORCE_NEW_RANGE}'"
|
||||
)
|
||||
if force_new_range:
|
||||
_LOGGER.warning(
|
||||
"%s: '%s' is deprecated; '%s: false' replaces it but only stops this entity joining "
|
||||
"the PREVIOUS range - set it on the following entity too if the range must stay "
|
||||
"isolated. Removed in 2027.3.0",
|
||||
entity_label(config),
|
||||
CONF_FORCE_NEW_RANGE,
|
||||
CONF_REUSE_PREVIOUS_RANGE,
|
||||
)
|
||||
config[CONF_REUSE_PREVIOUS_RANGE] = False
|
||||
else:
|
||||
_LOGGER.warning(
|
||||
"%s: '%s: false' has no effect; remove it. Removed in 2027.3.0",
|
||||
entity_label(config),
|
||||
CONF_FORCE_NEW_RANGE,
|
||||
)
|
||||
if (register_count := config.pop(CONF_REGISTER_COUNT, None)) is not None:
|
||||
if (
|
||||
register_count not in _derived_register_widths(config)
|
||||
and register_count != 0
|
||||
):
|
||||
raise cv.Invalid(
|
||||
f"'{CONF_REGISTER_COUNT}' has been removed; the number of registers to read is now "
|
||||
f"derived from '{CONF_VALUE_TYPE}' (or '{CONF_RESPONSE_SIZE}' for RAW values and text "
|
||||
f"sensors). To make one request span extra registers up to the next sensor, set "
|
||||
f"'{CONF_REUSE_PREVIOUS_RANGE}: true' on the NEXT sensor instead; for RAW or text block "
|
||||
f"reads set '{CONF_RESPONSE_SIZE}' to the byte count; to force multi-register writes set "
|
||||
f"'use_write_multiple: true'. See "
|
||||
"https://esphome.io/components/modbus_controller/"
|
||||
)
|
||||
_LOGGER.warning(
|
||||
"%s: '%s' is now derived from '%s' (or '%s' for RAW values and text sensors) and has no "
|
||||
"effect; remove it. Removed in 2027.3.0",
|
||||
entity_label(config),
|
||||
CONF_REGISTER_COUNT,
|
||||
CONF_VALUE_TYPE,
|
||||
CONF_RESPONSE_SIZE,
|
||||
)
|
||||
return config
|
||||
|
||||
|
||||
def validate_modbus_register(config: ConfigType) -> ConfigType:
|
||||
# custom_command is the deprecated alias for custom_pdu (migrated later in final validate); treat
|
||||
# either as "a custom frame is configured" so the address/register_type rules match.
|
||||
@@ -293,20 +376,13 @@ def reject_odd_holding_write_offset(config: ConfigType) -> ConfigType:
|
||||
return config
|
||||
|
||||
|
||||
def modbus_calc_properties(config: ConfigType) -> tuple[int, int]:
|
||||
def modbus_calc_properties(config: ConfigType) -> int:
|
||||
byte_offset = 0
|
||||
reg_count = 0
|
||||
if CONF_OFFSET in config:
|
||||
byte_offset = config[CONF_OFFSET]
|
||||
# A CONF_BYTE_OFFSET setting overrides CONF_OFFSET
|
||||
if CONF_BYTE_OFFSET in config:
|
||||
byte_offset = config[CONF_BYTE_OFFSET]
|
||||
if CONF_REGISTER_COUNT in config:
|
||||
reg_count = config[CONF_REGISTER_COUNT]
|
||||
if CONF_VALUE_TYPE in config:
|
||||
value_type = config[CONF_VALUE_TYPE]
|
||||
if reg_count == 0:
|
||||
reg_count = TYPE_REGISTER_MAP[value_type]
|
||||
if CONF_CUSTOM_PDU in config:
|
||||
if CONF_ADDRESS not in config:
|
||||
# generate a unique modbus address using the hash of the name
|
||||
@@ -317,8 +393,7 @@ def modbus_calc_properties(config: ConfigType) -> tuple[int, int]:
|
||||
value = value.encode()
|
||||
config[CONF_ADDRESS] = binascii.crc_hqx(value, 0)
|
||||
config[CONF_REGISTER_TYPE] = cv.enum(MODBUS_REGISTER_TYPE)("custom")
|
||||
config[CONF_FORCE_NEW_RANGE] = True
|
||||
return byte_offset, reg_count
|
||||
return byte_offset
|
||||
|
||||
|
||||
async def add_modbus_base_properties(
|
||||
|
||||
@@ -5,6 +5,7 @@ import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ADDRESS, CONF_ID
|
||||
|
||||
from .. import (
|
||||
RANGE_REUSE,
|
||||
ModbusItemBaseSchema,
|
||||
SensorItem,
|
||||
add_modbus_base_properties,
|
||||
@@ -12,12 +13,13 @@ from .. import (
|
||||
modbus_controller_ns,
|
||||
validate_custom_pdu_item,
|
||||
validate_modbus_register,
|
||||
validate_range_reuse_migration,
|
||||
)
|
||||
from ..const import (
|
||||
CONF_BITMASK,
|
||||
CONF_FORCE_NEW_RANGE,
|
||||
CONF_MODBUS_CONTROLLER_ID,
|
||||
CONF_REGISTER_TYPE,
|
||||
CONF_REUSE_PREVIOUS_RANGE,
|
||||
)
|
||||
|
||||
DEPENDENCIES = ["modbus_controller"]
|
||||
@@ -38,20 +40,21 @@ CONFIG_SCHEMA = cv.All(
|
||||
}
|
||||
),
|
||||
validate_modbus_register,
|
||||
validate_range_reuse_migration,
|
||||
)
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = validate_custom_pdu_item
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
byte_offset, _ = modbus_calc_properties(config)
|
||||
byte_offset = modbus_calc_properties(config)
|
||||
var = cg.new_Pvariable(
|
||||
config[CONF_ID],
|
||||
config[CONF_REGISTER_TYPE],
|
||||
config[CONF_ADDRESS],
|
||||
byte_offset,
|
||||
config[CONF_BITMASK],
|
||||
config[CONF_FORCE_NEW_RANGE],
|
||||
RANGE_REUSE[config[CONF_REUSE_PREVIOUS_RANGE]],
|
||||
)
|
||||
await cg.register_component(var, config)
|
||||
await binary_sensor.register_binary_sensor(var, config)
|
||||
|
||||
@@ -11,19 +11,22 @@ namespace esphome::modbus_controller {
|
||||
class ModbusBinarySensor final : public Component, public binary_sensor::BinarySensor, public SensorItem {
|
||||
public:
|
||||
ModbusBinarySensor(modbus::EntityType register_type, uint16_t start_address, uint8_t offset, uint32_t bitmask,
|
||||
bool force_new_range) {
|
||||
RangeReuse reuse_previous_range) {
|
||||
this->register_type = register_type;
|
||||
this->set_address(start_address);
|
||||
this->set_offset_from_start_address(offset);
|
||||
this->bitmask = bitmask;
|
||||
this->sensor_value_type = SensorValueType::BIT;
|
||||
this->force_new_range = force_new_range;
|
||||
this->reuse_previous_range = reuse_previous_range;
|
||||
}
|
||||
|
||||
if (modbus::helpers::is_entity_type_binary(register_type)) {
|
||||
this->register_count = offset + 1;
|
||||
} else {
|
||||
this->register_count = 1;
|
||||
/// On the bit-addressed tables the bit sits at start_address + offset, so the read must span offset + 1
|
||||
/// bits. Uses the offset as configured: `offset` itself is overwritten with the position in the range.
|
||||
uint16_t entity_count() const override {
|
||||
if (modbus::helpers::is_entity_type_binary(this->register_type)) {
|
||||
return this->offset_from_start_address + 1;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
void parse_and_publish(std::span<const uint8_t> data) override;
|
||||
|
||||
@@ -18,6 +18,7 @@ CONF_REGISTER_LAST_ADDRESS = "register_last_address"
|
||||
CONF_REGISTER_TYPE = "register_type"
|
||||
CONF_REGISTER_VALUE = "register_value"
|
||||
CONF_RESPONSE_SIZE = "response_size"
|
||||
CONF_REUSE_PREVIOUS_RANGE = "reuse_previous_range"
|
||||
CONF_SERVER_COURTESY_RESPONSE = "server_courtesy_response"
|
||||
CONF_SERVER_REGISTERS = "server_registers"
|
||||
CONF_SKIP_UPDATES = "skip_updates"
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
|
||||
namespace esphome::modbus_controller {
|
||||
|
||||
@@ -137,7 +138,7 @@ ModbusCommandItem::ModbusCommandItem(ModbusController &controller, modbus::Modbu
|
||||
SensorItem *sensor)
|
||||
: modbus::ModbusClientDevice(parent, address),
|
||||
start_address_(sensor->start_address),
|
||||
register_count_(sensor->register_count),
|
||||
register_count_(sensor->entity_count()),
|
||||
custom_pdu_(&sensor->custom_pdu),
|
||||
controller_(&controller) {
|
||||
// The PDU's first byte is its real function code; carry it so dump_config, the on_command_sent
|
||||
@@ -350,129 +351,176 @@ void ModbusController::update() {
|
||||
}
|
||||
|
||||
// walk through the sensors and determine the register ranges to read
|
||||
namespace {
|
||||
|
||||
class RangeBuilder {
|
||||
public:
|
||||
explicit RangeBuilder(FixedVector<RegisterRange> &ranges) : ranges_(ranges) {}
|
||||
|
||||
bool can_join(const SensorItem *curr) const {
|
||||
return this->have_range_ && curr->reuse_previous_range != RangeReuse::NEVER &&
|
||||
this->r_.register_type == curr->register_type && curr->register_type != modbus::EntityType::CUSTOM;
|
||||
}
|
||||
|
||||
// A sensor that joined mid-range must never anchor this - hence both address tests.
|
||||
bool try_reuse_register(SensorItem *curr) {
|
||||
const uint32_t range_end = this->range_end_();
|
||||
if (curr->start_address != range_end - this->prev_->entity_count() ||
|
||||
this->prev_->start_address + this->prev_->entity_count() != range_end ||
|
||||
curr->entity_count() != this->prev_->entity_count() ||
|
||||
curr->get_register_size() != this->prev_->get_register_size()) {
|
||||
return false;
|
||||
}
|
||||
if (!place_offset(curr, static_cast<uint32_t>(this->prev_->offset) + curr->offset_from_start_address))
|
||||
return false;
|
||||
ESP_LOGV(TAG, "Re-use previous register 0x%X", curr->start_address);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool try_extend(SensorItem *curr) {
|
||||
const uint32_t range_end = this->range_end_();
|
||||
const bool reachable =
|
||||
curr->reuse_previous_range == RangeReuse::ALWAYS
|
||||
? curr->start_address >= range_end
|
||||
: curr->start_address == range_end && (curr->addresses_bits() || !this->range_custom_size_);
|
||||
if (!reachable)
|
||||
return false;
|
||||
const uint16_t gap = static_cast<uint16_t>(curr->start_address - range_end);
|
||||
const uint32_t new_count = this->r_.register_count + gap + curr->entity_count();
|
||||
const uint16_t max_quantity =
|
||||
curr->addresses_bits() ? modbus::MAX_NUM_OF_COILS_TO_READ : modbus::MAX_NUM_OF_REGISTERS_TO_READ;
|
||||
const uint32_t prospective_offset =
|
||||
(curr->addresses_bits() ? static_cast<uint32_t>(curr->start_address - this->r_.start_address)
|
||||
: static_cast<uint32_t>(this->range_bytes_) + gap * 2) +
|
||||
curr->offset_from_start_address;
|
||||
if (new_count > max_quantity || !place_offset(curr, prospective_offset)) {
|
||||
return false;
|
||||
}
|
||||
if (!curr->addresses_bits())
|
||||
this->range_bytes_ += static_cast<size_t>(gap) * 2;
|
||||
this->range_bytes_ += curr->get_register_size();
|
||||
this->range_custom_size_ = this->range_custom_size_ || has_custom_size(curr);
|
||||
this->r_.register_count = static_cast<uint16_t>(new_count);
|
||||
ESP_LOGV(TAG, "Extend range to include 0x%X", curr->start_address);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool try_cover(SensorItem *curr) {
|
||||
if (!this->range_shared_ || this->range_forced_ || curr->start_address < this->r_.start_address ||
|
||||
curr->start_address + curr->entity_count() > this->range_end_() || this->range_custom_size_ ||
|
||||
has_custom_size(curr)) {
|
||||
return false;
|
||||
}
|
||||
const uint32_t addr_delta = curr->start_address - this->r_.start_address;
|
||||
if (!place_offset(curr, (curr->addresses_bits() ? addr_delta : addr_delta * 2) + curr->offset_from_start_address))
|
||||
return false;
|
||||
ESP_LOGV(TAG, "Register 0x%X already covered by range 0x%X", curr->start_address, this->r_.start_address);
|
||||
return true;
|
||||
}
|
||||
|
||||
// A response dispatches to a single range per (start address, register type), so same-address items
|
||||
// must share - even reuse_previous_range: false and custom entities.
|
||||
bool try_share(SensorItem *curr) {
|
||||
if (!this->have_range_ || this->r_.register_type != curr->register_type ||
|
||||
this->r_.start_address != curr->start_address) {
|
||||
return false;
|
||||
}
|
||||
curr->offset = curr->offset_from_start_address;
|
||||
this->r_.register_count = std::max(this->r_.register_count, curr->entity_count());
|
||||
this->range_bytes_ = std::max(this->range_bytes_, curr->get_register_size());
|
||||
this->range_custom_size_ = this->range_custom_size_ || has_custom_size(curr);
|
||||
this->range_shared_ = true;
|
||||
this->range_forced_ = this->range_forced_ || curr->reuse_previous_range == RangeReuse::NEVER;
|
||||
ESP_LOGV(TAG, "Share range start 0x%X", curr->start_address);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool always_declined(const SensorItem *curr) const {
|
||||
return this->have_range_ && curr->reuse_previous_range == RangeReuse::ALWAYS &&
|
||||
this->r_.register_type == curr->register_type && curr->start_address != this->r_.start_address;
|
||||
}
|
||||
|
||||
void open(SensorItem *curr) {
|
||||
this->close();
|
||||
this->r_ = {};
|
||||
this->range_bytes_ = curr->get_register_size();
|
||||
this->range_custom_size_ = has_custom_size(curr);
|
||||
this->range_forced_ = curr->reuse_previous_range == RangeReuse::NEVER;
|
||||
this->range_shared_ = false;
|
||||
curr->offset = curr->offset_from_start_address;
|
||||
this->r_.start_address = curr->start_address;
|
||||
this->r_.register_count = curr->entity_count();
|
||||
this->r_.register_type = curr->register_type;
|
||||
if (curr->register_type == modbus::EntityType::CUSTOM)
|
||||
this->r_.custom_pdu = &curr->custom_pdu;
|
||||
this->have_range_ = true;
|
||||
}
|
||||
|
||||
void record(SensorItem *curr) {
|
||||
curr->range_start_address = this->r_.start_address;
|
||||
this->r_.sensors.insert(curr);
|
||||
this->prev_ = curr;
|
||||
}
|
||||
|
||||
void close() {
|
||||
if (!this->have_range_)
|
||||
return;
|
||||
ESP_LOGV(TAG, "Add range 0x%X %d", this->r_.start_address, this->r_.register_count);
|
||||
this->ranges_.push_back(std::move(this->r_));
|
||||
this->have_range_ = false;
|
||||
}
|
||||
|
||||
private:
|
||||
uint32_t range_end_() const { return this->r_.start_address + this->r_.register_count; }
|
||||
// The resolved offset must fit its uint8_t field or the sensor would parse the wrong slice.
|
||||
static bool place_offset(SensorItem *curr, uint32_t offset) {
|
||||
if (offset > std::numeric_limits<uint8_t>::max())
|
||||
return false;
|
||||
curr->offset = static_cast<uint8_t>(offset);
|
||||
return true;
|
||||
}
|
||||
static bool has_custom_size(const SensorItem *item) {
|
||||
return item->get_register_size() != static_cast<size_t>(item->entity_count()) * 2;
|
||||
}
|
||||
FixedVector<RegisterRange> &ranges_;
|
||||
RegisterRange r_ = {};
|
||||
bool have_range_ = false;
|
||||
bool range_forced_ = false; // a reuse: false member blocks the coverage join
|
||||
bool range_shared_ = false; // only a share-widened range absorbs by coverage
|
||||
size_t range_bytes_ = 0;
|
||||
bool range_custom_size_ = false;
|
||||
SensorItem *prev_ = nullptr;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
void ModbusController::create_polling_commands_() {
|
||||
if (this->sensorset_.empty()) {
|
||||
ESP_LOGW(TAG, "No sensors registered");
|
||||
return;
|
||||
}
|
||||
|
||||
// Sensors are walked in the sensor set's order (see SensorItemsComparator): register type, then
|
||||
// force_new_range ahead of the rest, then address - so the walk is not purely address-ordered.
|
||||
// Each keeps the address it was configured with; what is resolved here is its `offset`, the position
|
||||
// of its data within the response of whichever range it ends up in.
|
||||
// One range per sensor is a strict upper bound: each walk step closes at most one range, plus one
|
||||
// closed after the walk. Sized to that bound so no push is ever silently dropped, then handed on by move.
|
||||
// At most one range closes per sensor plus one final close, so sensorset_.size() bounds the pushes
|
||||
// (FixedVector silently drops past capacity).
|
||||
FixedVector<RegisterRange> ranges;
|
||||
ranges.init(this->sensorset_.size());
|
||||
RegisterRange r = {};
|
||||
bool have_range = false;
|
||||
// Set while the open range belongs to a force_new_range sensor: a range the user asked to keep
|
||||
// separate must not quietly absorb other sensors.
|
||||
bool range_forced = false;
|
||||
// Set once a sensor has joined by sharing the range's start address, which widens the read. Only a
|
||||
// widened range can absorb a later sensor by coverage: ranges that were kept apart before stay apart,
|
||||
// so their frames and polling rates are untouched.
|
||||
bool range_shared = false;
|
||||
// Bytes the range's registers have consumed so far. An extending sensor starts after them, so a
|
||||
// register that returns more bytes than its count implies pushes the sensors after it along.
|
||||
// range_custom_size records whether any of them returns something other than two bytes per register,
|
||||
// which is what makes a position inside the range impossible to work out from addresses alone. Coils
|
||||
// count as such: they carry one bit per address, so bit ranges never take the coverage join.
|
||||
size_t range_bytes = 0;
|
||||
bool range_custom_size = false;
|
||||
SensorItem *prev = nullptr;
|
||||
RangeBuilder builder(ranges);
|
||||
for (SensorItem *curr : this->sensorset_) {
|
||||
ESP_LOGV(TAG, "Register: 0x%X count=%d size=%zu offset=%u addr=%p", curr->start_address, curr->register_count,
|
||||
ESP_LOGV(TAG, "Register: 0x%X width=%u size=%zu offset=%u addr=%p", curr->start_address, curr->entity_count(),
|
||||
curr->get_register_size(), curr->offset, curr);
|
||||
|
||||
const bool custom_size = curr->get_register_size() != static_cast<size_t>(curr->register_count) * 2;
|
||||
|
||||
bool join = false;
|
||||
if (have_range && !curr->force_new_range && r.register_type == curr->register_type &&
|
||||
curr->register_type != modbus::EntityType::CUSTOM) {
|
||||
if (curr->start_address == (r.start_address + r.register_count - prev->register_count) &&
|
||||
prev->start_address + prev->register_count == r.start_address + r.register_count &&
|
||||
curr->register_count == prev->register_count && curr->get_register_size() == prev->get_register_size()) {
|
||||
// A second sensor on the register(s) the previous one covers: it reads those same bytes,
|
||||
// starting where that sensor's offset pointed, so a chain configured 0/2/4 resolves to 0/2/6.
|
||||
// Both address tests matter. The first identifies the previous sensor's register by working back
|
||||
// from the range's end, which only describes it while it actually sits there - hence the second.
|
||||
// A sensor that joined mid-range must never anchor this, or the next one inherits its offset.
|
||||
curr->offset = static_cast<uint8_t>(prev->offset + curr->offset_from_start_address);
|
||||
join = true;
|
||||
ESP_LOGV(TAG, "Re-use previous register 0x%X", curr->start_address);
|
||||
} else if (curr->start_address == (r.start_address + r.register_count)) {
|
||||
// The next contiguous register(s): the data begins after what the range has consumed so far -
|
||||
// the byte cursor for registers, the distance in bits for coils.
|
||||
curr->offset =
|
||||
static_cast<uint8_t>((curr->addresses_bits() ? curr->start_address - r.start_address : range_bytes) +
|
||||
curr->offset_from_start_address);
|
||||
range_bytes += curr->get_register_size();
|
||||
range_custom_size = range_custom_size || custom_size;
|
||||
r.register_count += curr->register_count;
|
||||
join = true;
|
||||
ESP_LOGV(TAG, "Extend range to include 0x%X", curr->start_address);
|
||||
} else if (range_shared && !range_forced && curr->start_address >= r.start_address &&
|
||||
curr->start_address + curr->register_count <= r.start_address + r.register_count &&
|
||||
!range_custom_size && !custom_size) {
|
||||
// The registers already fall inside a range that a shared-address join widened, so this sensor
|
||||
// reads its slice of that response instead of adding an overlapping second poll. The guards keep
|
||||
// it narrow: only a widened range, never a force-isolated one; only where every register in the
|
||||
// range returns two bytes, so interior positions follow from the addresses; only sensors genuinely
|
||||
// inside it, which is why the lower bound is needed given the walk is not address-ordered.
|
||||
const uint16_t addr_delta = curr->start_address - r.start_address;
|
||||
curr->offset = static_cast<uint8_t>((curr->addresses_bits() ? addr_delta : addr_delta * 2) +
|
||||
curr->offset_from_start_address);
|
||||
join = true;
|
||||
ESP_LOGV(TAG, "Register 0x%X already covered by range 0x%X", curr->start_address, r.start_address);
|
||||
}
|
||||
bool join = builder.can_join(curr) &&
|
||||
(builder.try_reuse_register(curr) || builder.try_extend(curr) || builder.try_cover(curr));
|
||||
if (!join && builder.always_declined(curr)) {
|
||||
ESP_LOGW(TAG, "reuse_previous_range on 0x%X cannot join the previous range; starting a new range",
|
||||
curr->start_address);
|
||||
}
|
||||
|
||||
// Sensors on the same start address have to share one range: a response is dispatched to a single
|
||||
// range per (start_address, register_type), so a second range with that key would never receive
|
||||
// data. This holds for force_new_range and custom entities too. The read widens to cover whichever
|
||||
// sensor needs the most registers, which also fixes a short read for coils that use offset.
|
||||
if (!join && have_range && r.register_type == curr->register_type && r.start_address == curr->start_address) {
|
||||
curr->offset = curr->offset_from_start_address; // shares the range start
|
||||
r.register_count = std::max(r.register_count, curr->register_count);
|
||||
range_bytes = std::max(range_bytes, curr->get_register_size());
|
||||
range_custom_size = range_custom_size || custom_size;
|
||||
range_shared = true;
|
||||
range_forced = range_forced || curr->force_new_range;
|
||||
join = true;
|
||||
ESP_LOGV(TAG, "Share range start 0x%X", curr->start_address);
|
||||
}
|
||||
|
||||
if (!join) {
|
||||
if (have_range) {
|
||||
ESP_LOGV(TAG, "Add range 0x%X %d", r.start_address, r.register_count);
|
||||
ranges.push_back(std::move(r));
|
||||
}
|
||||
r = {};
|
||||
range_bytes = curr->get_register_size();
|
||||
range_custom_size = custom_size;
|
||||
range_forced = curr->force_new_range;
|
||||
range_shared = false;
|
||||
curr->offset = curr->offset_from_start_address;
|
||||
r.start_address = curr->start_address;
|
||||
r.register_count = curr->register_count;
|
||||
r.register_type = curr->register_type;
|
||||
if (curr->register_type == modbus::EntityType::CUSTOM)
|
||||
r.custom_pdu = &curr->custom_pdu;
|
||||
have_range = true;
|
||||
}
|
||||
|
||||
// Every member records its range's first register. The resolved offset is relative to it, so the
|
||||
// two together give the sensor's real position, and the address a write entity targets.
|
||||
curr->range_start_address = r.start_address;
|
||||
r.sensors.insert(curr);
|
||||
prev = curr;
|
||||
join = join || builder.try_share(curr);
|
||||
if (!join)
|
||||
builder.open(curr);
|
||||
builder.record(curr);
|
||||
}
|
||||
if (have_range) {
|
||||
ESP_LOGV(TAG, "Add last range 0x%X %d", r.start_address, r.register_count);
|
||||
ranges.push_back(std::move(r));
|
||||
}
|
||||
// Staged in a setup-time vector so the device storage can be sized exactly (see polling_devices_).
|
||||
builder.close();
|
||||
|
||||
this->polling_devices_.init(ranges.size());
|
||||
for (auto &range : ranges) {
|
||||
this->polling_devices_.emplace_back(*this, std::move(range));
|
||||
@@ -490,8 +538,8 @@ void ModbusController::dump_config() {
|
||||
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
|
||||
ESP_LOGCONFIG(TAG, "sensormap");
|
||||
for (auto &it : this->sensorset_) {
|
||||
ESP_LOGCONFIG(TAG, " Sensor type=%u start=0x%X offset=0x%X count=%d size=%zu",
|
||||
static_cast<uint8_t>(it->register_type), it->start_address, it->offset, it->register_count,
|
||||
ESP_LOGCONFIG(TAG, " Sensor type=%u start=0x%X offset=0x%X width=%u size=%zu",
|
||||
static_cast<uint8_t>(it->register_type), it->start_address, it->offset, it->entity_count(),
|
||||
it->get_register_size());
|
||||
}
|
||||
ESP_LOGCONFIG(TAG, "ranges");
|
||||
|
||||
@@ -126,6 +126,16 @@ inline std::vector<uint16_t> float_to_payload(float value, SensorValueType value
|
||||
|
||||
class ModbusController;
|
||||
|
||||
/// How an item relates to the register range built just before it (same register type, address order).
|
||||
/// The numeric order doubles as the comparator tiebreak for items at the same address (see
|
||||
/// SensorItemsComparator): AUTO items form the shared range first, so a NEVER item comes last and
|
||||
/// shares a range it did not start (items on one address must share, see create_polling_commands_()).
|
||||
enum class RangeReuse : uint8_t {
|
||||
AUTO = 0, // join when adjacent and the position in the reply is exact (no non-standard response_size ahead)
|
||||
ALWAYS = 1, // join unconditionally, reading across any address gap
|
||||
NEVER = 2, // never join backward (later items may still extend this item's range)
|
||||
};
|
||||
|
||||
class SensorItem {
|
||||
public:
|
||||
/// Parse this sensor's slice out of its range's response and publish it. The span points into the
|
||||
@@ -159,11 +169,26 @@ class SensorItem {
|
||||
}
|
||||
|
||||
void set_custom_pdu(std::initializer_list<uint8_t> pdu) { this->custom_pdu.set(pdu.begin(), pdu.size()); }
|
||||
|
||||
/// Entities this item spans: one bit for bit-addressed types, ceil(bytes / 2) registers for RAW
|
||||
/// with a response_size, else the value type's register width.
|
||||
virtual uint16_t entity_count() const {
|
||||
if (modbus::helpers::is_entity_type_binary(this->register_type)) {
|
||||
return 1;
|
||||
}
|
||||
if (this->sensor_value_type == SensorValueType::RAW && this->response_bytes > 0) {
|
||||
return (this->response_bytes + 1) / 2;
|
||||
}
|
||||
return modbus::helpers::register_width_for(this->sensor_value_type);
|
||||
}
|
||||
|
||||
/// Bytes this item's registers occupy in a response: one per bit for bit-addressed types; response_size
|
||||
/// when set (devices that answer more bytes per register than the standard two); else two per register.
|
||||
size_t virtual get_register_size() const {
|
||||
if (this->addresses_bits()) {
|
||||
return 1;
|
||||
} else { // if CONF_RESPONSE_BYTES is used override the default
|
||||
return response_bytes > 0 ? response_bytes : register_count * 2;
|
||||
return response_bytes > 0 ? response_bytes : this->entity_count() * 2;
|
||||
}
|
||||
}
|
||||
// Override register size for modbus devices not using 1 register for one dword
|
||||
@@ -177,7 +202,6 @@ class SensorItem {
|
||||
/// for the registers ahead of it (including wide response_size ones) and for any offset inherited
|
||||
/// from an earlier sensor sharing the same register.
|
||||
uint8_t offset{0};
|
||||
uint8_t register_count{0};
|
||||
uint8_t response_bytes{0};
|
||||
/// The offset exactly as configured: measured from this sensor's own start_address, where `offset`
|
||||
/// is measured from the first register of the range it ends up polled in. Same units as `offset` -
|
||||
@@ -188,7 +212,7 @@ class SensorItem {
|
||||
/// First register of the range this sensor is polled in; equals start_address for an unpolled item.
|
||||
uint16_t range_start_address{0};
|
||||
SmallInlineBuffer<8> custom_pdu{};
|
||||
bool force_new_range{false};
|
||||
RangeReuse reuse_previous_range{RangeReuse::AUTO};
|
||||
};
|
||||
|
||||
// ModbusController::create_polling_commands_ tries to optimize register range
|
||||
@@ -201,16 +225,17 @@ class SensorItemsComparator {
|
||||
return lhs->register_type < rhs->register_type;
|
||||
}
|
||||
|
||||
// ensure that sensor with force_new_range set are before the others
|
||||
if (lhs->force_new_range != rhs->force_new_range) {
|
||||
return lhs->force_new_range > rhs->force_new_range;
|
||||
}
|
||||
|
||||
// sort by start address
|
||||
if (lhs->start_address != rhs->start_address) {
|
||||
return lhs->start_address < rhs->start_address;
|
||||
}
|
||||
|
||||
// at the same address: AUTO before ALWAYS before NEVER, so a NEVER item never starts the range
|
||||
// the others at that address are then forced to share (see RangeReuse)
|
||||
if (lhs->reuse_previous_range != rhs->reuse_previous_range) {
|
||||
return lhs->reuse_previous_range < rhs->reuse_previous_range;
|
||||
}
|
||||
|
||||
// sort by the offset as configured (ensures update of sensors in ascending order). The resolved
|
||||
// `offset` is deliberately not used: ranges are built while iterating this set and assign it, and
|
||||
// a sort key that changed under the iteration would corrupt the set's ordering.
|
||||
@@ -229,8 +254,8 @@ using SensorSet = std::set<SensorItem *, SensorItemsComparator>;
|
||||
struct RegisterRange {
|
||||
uint16_t start_address;
|
||||
modbus::EntityType register_type;
|
||||
uint8_t register_count;
|
||||
SensorSet sensors; // all sensors of this range
|
||||
uint16_t register_count; // registers (or bits) the poll command reads; joins across gaps can exceed 255
|
||||
SensorSet sensors; // all sensors of this range
|
||||
/// A custom range polls this PDU, referenced from the sensor that opened the range.
|
||||
const SmallInlineBuffer<8> *custom_pdu{nullptr};
|
||||
};
|
||||
|
||||
@@ -17,20 +17,22 @@ from esphome.const import (
|
||||
from esphome.types import ConfigType
|
||||
|
||||
from .. import (
|
||||
RANGE_REUSE,
|
||||
ModbusItemBaseSchema,
|
||||
SensorItem,
|
||||
add_modbus_base_properties,
|
||||
modbus_calc_properties,
|
||||
modbus_controller_ns,
|
||||
validate_custom_pdu_item,
|
||||
validate_range_reuse_migration,
|
||||
)
|
||||
from ..const import (
|
||||
CONF_BITMASK,
|
||||
CONF_CUSTOM_COMMAND,
|
||||
CONF_CUSTOM_PDU,
|
||||
CONF_FORCE_NEW_RANGE,
|
||||
CONF_MODBUS_CONTROLLER_ID,
|
||||
CONF_REGISTER_TYPE,
|
||||
CONF_REUSE_PREVIOUS_RANGE,
|
||||
CONF_USE_WRITE_MULTIPLE,
|
||||
CONF_VALUE_TYPE,
|
||||
CONF_WRITE_LAMBDA,
|
||||
@@ -86,13 +88,14 @@ CONFIG_SCHEMA = cv.All(
|
||||
),
|
||||
validate_min_max,
|
||||
validate_modbus_number,
|
||||
validate_range_reuse_migration,
|
||||
)
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = validate_custom_pdu_item
|
||||
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
byte_offset, reg_count = modbus_calc_properties(config)
|
||||
byte_offset = modbus_calc_properties(config)
|
||||
var = cg.new_Pvariable(
|
||||
config[CONF_ID],
|
||||
config[CONF_REGISTER_TYPE],
|
||||
@@ -100,8 +103,7 @@ async def to_code(config: ConfigType) -> None:
|
||||
byte_offset,
|
||||
config[CONF_BITMASK],
|
||||
config[CONF_VALUE_TYPE],
|
||||
reg_count,
|
||||
config[CONF_FORCE_NEW_RANGE],
|
||||
RANGE_REUSE[config[CONF_REUSE_PREVIOUS_RANGE]],
|
||||
)
|
||||
|
||||
await cg.register_component(var, config)
|
||||
|
||||
@@ -83,10 +83,10 @@ void ModbusNumber::control(float value) {
|
||||
|
||||
ESP_LOGD(TAG,
|
||||
"Updating register: connected Sensor=%s start address=0x%X register count=%d new value=%.02f (val=%.02f)",
|
||||
this->get_name().c_str(), this->start_address, this->register_count, value, write_value);
|
||||
this->get_name().c_str(), this->start_address, this->entity_count(), value, write_value);
|
||||
|
||||
bool queued;
|
||||
if (this->register_count == 1 && !this->use_write_multiple_) {
|
||||
if (this->entity_count() == 1 && !this->use_write_multiple_) {
|
||||
queued = this->write_single_register(this->write_address(), data[0]);
|
||||
} else {
|
||||
queued = this->write_multiple_registers(this->write_address(), data);
|
||||
|
||||
@@ -13,14 +13,13 @@ using value_to_data_t = std::function<float>(float);
|
||||
class ModbusNumber final : public number::Number, public Component, public SensorItem, public WriterEntity {
|
||||
public:
|
||||
ModbusNumber(modbus::EntityType register_type, uint16_t start_address, uint8_t offset, uint32_t bitmask,
|
||||
SensorValueType value_type, int register_count, bool force_new_range) {
|
||||
SensorValueType value_type, RangeReuse reuse_previous_range) {
|
||||
this->register_type = register_type;
|
||||
this->set_address(start_address);
|
||||
this->set_offset_from_start_address(offset);
|
||||
this->bitmask = bitmask;
|
||||
this->sensor_value_type = value_type;
|
||||
this->register_count = register_count;
|
||||
this->force_new_range = force_new_range;
|
||||
this->reuse_previous_range = reuse_previous_range;
|
||||
};
|
||||
|
||||
void dump_config() override;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import logging
|
||||
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import output
|
||||
from esphome.components.modbus.helpers import (
|
||||
@@ -12,6 +14,7 @@ from esphome.types import ConfigType
|
||||
from .. import (
|
||||
ModbusItemBaseSchema,
|
||||
SensorItem,
|
||||
entity_label,
|
||||
modbus_calc_properties,
|
||||
modbus_controller_ns,
|
||||
reject_odd_holding_write_offset,
|
||||
@@ -19,13 +22,18 @@ from .. import (
|
||||
from ..const import (
|
||||
CONF_CUSTOM_COMMAND,
|
||||
CONF_CUSTOM_PDU,
|
||||
CONF_FORCE_NEW_RANGE,
|
||||
CONF_MODBUS_CONTROLLER_ID,
|
||||
CONF_REGISTER_COUNT,
|
||||
CONF_REGISTER_TYPE,
|
||||
CONF_REUSE_PREVIOUS_RANGE,
|
||||
CONF_USE_WRITE_MULTIPLE,
|
||||
CONF_VALUE_TYPE,
|
||||
CONF_WRITE_LAMBDA,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DEPENDENCIES = ["modbus_controller"]
|
||||
CODEOWNERS = ["@martgras"]
|
||||
|
||||
@@ -38,26 +46,30 @@ ModbusBinaryOutput = modbus_controller_ns.class_(
|
||||
)
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.typed_schema(
|
||||
{
|
||||
"coil": output.BINARY_OUTPUT_SCHEMA.extend(ModbusItemBaseSchema).extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(ModbusBinaryOutput),
|
||||
cv.Required(CONF_ADDRESS): cv.positive_int,
|
||||
cv.Optional(CONF_CUSTOM_PDU): cv.invalid(
|
||||
"custom_pdu is not supported for outputs; use a write_lambda instead"
|
||||
),
|
||||
cv.Optional(CONF_CUSTOM_COMMAND): cv.invalid(
|
||||
"custom_command is not supported for outputs; use a write_lambda instead"
|
||||
),
|
||||
cv.Optional(CONF_WRITE_LAMBDA): cv.returning_lambda,
|
||||
cv.Optional(CONF_USE_WRITE_MULTIPLE, default=False): cv.boolean,
|
||||
}
|
||||
),
|
||||
"holding": cv.All(
|
||||
output.FLOAT_OUTPUT_SCHEMA.extend(ModbusItemBaseSchema).extend(
|
||||
def _warn_unused_range_options(config: ConfigType) -> ConfigType:
|
||||
# Outputs are write-only and never polled, so nothing here builds a range for them. The write
|
||||
# spans whatever the payload holds, so register_count no longer bounds it either.
|
||||
for key in (CONF_FORCE_NEW_RANGE, CONF_REGISTER_COUNT):
|
||||
if config.pop(key, None) is not None:
|
||||
_LOGGER.warning(
|
||||
"%s: '%s' has no effect on outputs; remove it. Removed in 2027.3.0",
|
||||
entity_label(config),
|
||||
key,
|
||||
)
|
||||
if config.pop(CONF_REUSE_PREVIOUS_RANGE, None) not in (None, "auto"):
|
||||
raise cv.Invalid(
|
||||
f"'{CONF_REUSE_PREVIOUS_RANGE}' has no effect on outputs: they are write-only and are "
|
||||
f"never part of a polled range. Remove it."
|
||||
)
|
||||
return config
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.typed_schema(
|
||||
{
|
||||
"coil": output.BINARY_OUTPUT_SCHEMA.extend(ModbusItemBaseSchema).extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(ModbusFloatOutput),
|
||||
cv.GenerateID(): cv.declare_id(ModbusBinaryOutput),
|
||||
cv.Required(CONF_ADDRESS): cv.positive_int,
|
||||
cv.Optional(CONF_CUSTOM_PDU): cv.invalid(
|
||||
"custom_pdu is not supported for outputs; use a write_lambda instead"
|
||||
@@ -65,25 +77,42 @@ CONFIG_SCHEMA = cv.typed_schema(
|
||||
cv.Optional(CONF_CUSTOM_COMMAND): cv.invalid(
|
||||
"custom_command is not supported for outputs; use a write_lambda instead"
|
||||
),
|
||||
cv.Optional(CONF_VALUE_TYPE, default="U_WORD"): cv.enum(
|
||||
SENSOR_VALUE_TYPE
|
||||
),
|
||||
cv.Optional(CONF_WRITE_LAMBDA): cv.returning_lambda,
|
||||
cv.Optional(CONF_MULTIPLY, default=1.0): cv.float_,
|
||||
cv.Optional(CONF_USE_WRITE_MULTIPLE, default=False): cv.boolean,
|
||||
}
|
||||
),
|
||||
reject_odd_holding_write_offset,
|
||||
),
|
||||
},
|
||||
lower=True,
|
||||
key=CONF_REGISTER_TYPE,
|
||||
default_type="holding",
|
||||
"holding": cv.All(
|
||||
output.FLOAT_OUTPUT_SCHEMA.extend(ModbusItemBaseSchema).extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(ModbusFloatOutput),
|
||||
cv.Required(CONF_ADDRESS): cv.positive_int,
|
||||
cv.Optional(CONF_CUSTOM_PDU): cv.invalid(
|
||||
"custom_pdu is not supported for outputs; use a write_lambda instead"
|
||||
),
|
||||
cv.Optional(CONF_CUSTOM_COMMAND): cv.invalid(
|
||||
"custom_command is not supported for outputs; use a write_lambda instead"
|
||||
),
|
||||
cv.Optional(CONF_VALUE_TYPE, default="U_WORD"): cv.enum(
|
||||
SENSOR_VALUE_TYPE
|
||||
),
|
||||
cv.Optional(CONF_WRITE_LAMBDA): cv.returning_lambda,
|
||||
cv.Optional(CONF_MULTIPLY, default=1.0): cv.float_,
|
||||
cv.Optional(CONF_USE_WRITE_MULTIPLE, default=False): cv.boolean,
|
||||
}
|
||||
),
|
||||
reject_odd_holding_write_offset,
|
||||
),
|
||||
},
|
||||
lower=True,
|
||||
key=CONF_REGISTER_TYPE,
|
||||
default_type="holding",
|
||||
),
|
||||
_warn_unused_range_options,
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
byte_offset, reg_count = modbus_calc_properties(config)
|
||||
byte_offset = modbus_calc_properties(config)
|
||||
# Binary Output
|
||||
write_template = None
|
||||
if config[CONF_REGISTER_TYPE] == "coil":
|
||||
@@ -109,7 +138,6 @@ async def to_code(config: ConfigType) -> None:
|
||||
config[CONF_ADDRESS],
|
||||
byte_offset,
|
||||
config[CONF_VALUE_TYPE],
|
||||
reg_count,
|
||||
)
|
||||
cg.add(var.set_write_multiply(config[CONF_MULTIPLY]))
|
||||
if CONF_WRITE_LAMBDA in config:
|
||||
|
||||
@@ -46,29 +46,24 @@ void ModbusFloatOutput::write_state(float value) {
|
||||
modbus::helpers::float_to_payload(data, value, this->sensor_value_type);
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "Updating register: start address=0x%X register count=%d new value=%.02f (val=%.02f)",
|
||||
this->start_address, this->register_count, value, original_value);
|
||||
ESP_LOGD(TAG, "Updating register: start address=0x%X register count=%u new value=%.02f (val=%.02f)",
|
||||
this->start_address, this->entity_count(), value, original_value);
|
||||
|
||||
// The command declares register_count registers, so the payload must be exactly that many words;
|
||||
// anything else would put a byte count on the wire that disagrees with the quantity field.
|
||||
// number_to_payload() appends nothing for RAW, so an empty payload must be caught before data[0].
|
||||
// float_to_payload() appends nothing for RAW, so an empty payload must be caught before data[0].
|
||||
if (data.empty()) {
|
||||
ESP_LOGW(TAG, "No payload was created for updating output");
|
||||
return;
|
||||
}
|
||||
|
||||
// register_count declares the READ range width - it may pull neighboring registers into one poll -
|
||||
// so a write covers exactly the registers the value occupies: the quantity comes from the payload,
|
||||
// never from register_count (padding to it would zero registers the user only declared for reading).
|
||||
// A payload wider than the declared range means the config and the lambda disagree - drop it.
|
||||
if (data.size() > this->register_count) {
|
||||
ESP_LOGE(TAG, "Payload has %zu registers but register_count is %u; dropping write", data.size(),
|
||||
this->register_count);
|
||||
// The value type sets the write width, so a wider payload means the config and the lambda disagree.
|
||||
if (data.size() > this->entity_count()) {
|
||||
ESP_LOGE(TAG, "Payload has %zu registers but the value type only spans %u; dropping write", data.size(),
|
||||
this->entity_count());
|
||||
return;
|
||||
}
|
||||
|
||||
bool queued;
|
||||
if (this->register_count == 1 && !this->use_write_multiple_) {
|
||||
if (this->entity_count() == 1 && !this->use_write_multiple_) {
|
||||
queued = this->write_single_register(this->write_address(), data[0]);
|
||||
} else {
|
||||
queued = this->write_multiple_registers(this->write_address(), data);
|
||||
@@ -85,7 +80,7 @@ void ModbusFloatOutput::dump_config() {
|
||||
" Device start address: 0x%X\n"
|
||||
" Register count: %d\n"
|
||||
" Value type: %d",
|
||||
this->start_address, this->register_count, static_cast<int>(this->sensor_value_type));
|
||||
this->start_address, this->entity_count(), static_cast<int>(this->sensor_value_type));
|
||||
}
|
||||
|
||||
// ModbusBinaryOutput
|
||||
@@ -145,7 +140,7 @@ void ModbusBinaryOutput::dump_config() {
|
||||
" Device start address: 0x%X\n"
|
||||
" Register count: %d\n"
|
||||
" Value type: %d",
|
||||
this->start_address, this->register_count, static_cast<int>(this->sensor_value_type));
|
||||
this->start_address, this->entity_count(), static_cast<int>(this->sensor_value_type));
|
||||
}
|
||||
|
||||
} // namespace esphome::modbus_controller
|
||||
|
||||
@@ -10,13 +10,12 @@ namespace esphome::modbus_controller {
|
||||
|
||||
class ModbusFloatOutput final : public output::FloatOutput, public Component, public SensorItem, public WriterEntity {
|
||||
public:
|
||||
ModbusFloatOutput(uint16_t start_address, uint8_t offset, SensorValueType value_type, int register_count) {
|
||||
ModbusFloatOutput(uint16_t start_address, uint8_t offset, SensorValueType value_type) {
|
||||
this->register_type = modbus::EntityType::HOLDING;
|
||||
// A byte offset folds into the address as whole registers; odd offsets are rejected at validation.
|
||||
this->set_address(start_address + offset / 2);
|
||||
this->set_offset_from_start_address(0);
|
||||
this->bitmask = 0xFFFFFFFF;
|
||||
this->register_count = register_count;
|
||||
this->sensor_value_type = value_type;
|
||||
}
|
||||
void dump_config() override;
|
||||
@@ -46,7 +45,6 @@ class ModbusBinaryOutput final : public output::BinaryOutput, public Component,
|
||||
this->set_address(start_address + offset);
|
||||
this->bitmask = 0xFFFFFFFF;
|
||||
this->sensor_value_type = SensorValueType::BIT;
|
||||
this->register_count = 1;
|
||||
this->set_offset_from_start_address(0);
|
||||
}
|
||||
void dump_config() override;
|
||||
|
||||
@@ -3,25 +3,24 @@ from typing import Any
|
||||
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import select
|
||||
from esphome.components.modbus.helpers import (
|
||||
SENSOR_VALUE_TYPE,
|
||||
TYPE_REGISTER_MAP,
|
||||
RegisterValues,
|
||||
)
|
||||
from esphome.components.modbus.helpers import SENSOR_VALUE_TYPE, RegisterValues
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ADDRESS, CONF_ID, CONF_LAMBDA, CONF_OPTIMISTIC
|
||||
from esphome.types import ConfigType
|
||||
|
||||
from .. import (
|
||||
RANGE_REUSE,
|
||||
ModbusController,
|
||||
SensorItem,
|
||||
modbus_controller_ns,
|
||||
validate_range_reuse_migration,
|
||||
validate_skip_updates_deprecated,
|
||||
)
|
||||
from ..const import (
|
||||
CONF_FORCE_NEW_RANGE,
|
||||
CONF_MODBUS_CONTROLLER_ID,
|
||||
CONF_REGISTER_COUNT,
|
||||
CONF_REUSE_PREVIOUS_RANGE,
|
||||
CONF_SKIP_UPDATES,
|
||||
CONF_USE_WRITE_MULTIPLE,
|
||||
CONF_VALUE_TYPE,
|
||||
@@ -55,18 +54,6 @@ def ensure_option_map() -> Callable[[Any], dict[str, int]]:
|
||||
return validator
|
||||
|
||||
|
||||
def register_count_value_type_min(value: ConfigType) -> ConfigType:
|
||||
reg_count = value.get(CONF_REGISTER_COUNT)
|
||||
if reg_count is not None:
|
||||
value_type = value[CONF_VALUE_TYPE]
|
||||
min_register_count = TYPE_REGISTER_MAP[value_type]
|
||||
if min_register_count > reg_count:
|
||||
raise cv.Invalid(
|
||||
f"Value type {value_type} needs at least {min_register_count} registers"
|
||||
)
|
||||
return value
|
||||
|
||||
|
||||
INTEGER_SENSOR_VALUE_TYPE = {
|
||||
key: value for key, value in SENSOR_VALUE_TYPE.items() if not key.startswith("FP")
|
||||
}
|
||||
@@ -81,9 +68,13 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.Optional(CONF_VALUE_TYPE, default="U_WORD"): cv.enum(
|
||||
INTEGER_SENSOR_VALUE_TYPE
|
||||
),
|
||||
cv.Optional(CONF_REGISTER_COUNT): cv.positive_int,
|
||||
cv.Optional(CONF_SKIP_UPDATES): validate_skip_updates_deprecated,
|
||||
cv.Optional(CONF_FORCE_NEW_RANGE, default=False): cv.boolean,
|
||||
cv.Optional(CONF_REUSE_PREVIOUS_RANGE, default="auto"): cv.Any(
|
||||
cv.boolean, cv.one_of("auto", lower=True)
|
||||
),
|
||||
# Deprecated options, migrated by validate_range_reuse_migration(). Remove before 2027.3.0
|
||||
cv.Optional(CONF_FORCE_NEW_RANGE): cv.boolean,
|
||||
cv.Optional(CONF_REGISTER_COUNT): cv.positive_int,
|
||||
cv.Required(CONF_OPTIONSMAP): ensure_option_map(),
|
||||
cv.Optional(CONF_USE_WRITE_MULTIPLE, default=False): cv.boolean,
|
||||
cv.Optional(CONF_OPTIMISTIC, default=False): cv.boolean,
|
||||
@@ -91,24 +82,18 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.Optional(CONF_WRITE_LAMBDA): cv.returning_lambda,
|
||||
},
|
||||
),
|
||||
register_count_value_type_min,
|
||||
validate_range_reuse_migration,
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
value_type = config[CONF_VALUE_TYPE]
|
||||
reg_count = config.get(CONF_REGISTER_COUNT)
|
||||
if reg_count is None:
|
||||
reg_count = TYPE_REGISTER_MAP[value_type]
|
||||
|
||||
options_map = config[CONF_OPTIONSMAP]
|
||||
|
||||
var = cg.new_Pvariable(
|
||||
config[CONF_ID],
|
||||
value_type,
|
||||
config[CONF_VALUE_TYPE],
|
||||
config[CONF_ADDRESS],
|
||||
reg_count,
|
||||
config[CONF_FORCE_NEW_RANGE],
|
||||
RANGE_REUSE[config[CONF_REUSE_PREVIOUS_RANGE]],
|
||||
list(options_map.values()),
|
||||
)
|
||||
|
||||
|
||||
@@ -83,19 +83,17 @@ void ModbusSelect::control(size_t index) {
|
||||
}
|
||||
}
|
||||
|
||||
// register_count declares the READ range width - it may pull neighboring registers into one poll -
|
||||
// so a write covers exactly the registers the value occupies: the quantity comes from the payload,
|
||||
// never from register_count (padding to it would zero registers the user only declared for reading).
|
||||
// A payload wider than the declared range means the config and the lambda disagree - drop it.
|
||||
if (data.size() > this->register_count) {
|
||||
ESP_LOGE(TAG, "Payload has %zu registers but register_count is %u; dropping write", data.size(),
|
||||
this->register_count);
|
||||
// A write covers exactly the registers the value occupies: the quantity comes from the payload. A
|
||||
// payload wider than the value type's register width means the config and the lambda disagree - drop it.
|
||||
if (data.size() > this->entity_count()) {
|
||||
ESP_LOGE(TAG, "Payload has %zu registers but the value type only spans %u; dropping write", data.size(),
|
||||
this->entity_count());
|
||||
return;
|
||||
}
|
||||
|
||||
const uint16_t write_address = this->write_address();
|
||||
bool queued;
|
||||
if ((this->register_count == 1) && (!this->use_write_multiple_)) {
|
||||
if ((this->entity_count() == 1) && (!this->use_write_multiple_)) {
|
||||
queued = this->write_single_register(write_address, data[0]);
|
||||
} else {
|
||||
queued = this->write_multiple_registers(write_address, data);
|
||||
|
||||
@@ -11,16 +11,15 @@ namespace esphome::modbus_controller {
|
||||
|
||||
class ModbusSelect final : public Component, public select::Select, public SensorItem, public WriterEntity {
|
||||
public:
|
||||
ModbusSelect(SensorValueType sensor_value_type, uint16_t start_address, uint8_t register_count, bool force_new_range,
|
||||
ModbusSelect(SensorValueType sensor_value_type, uint16_t start_address, RangeReuse reuse_previous_range,
|
||||
std::vector<int64_t> mapping) {
|
||||
this->register_type = modbus::EntityType::HOLDING; // not configurable
|
||||
this->sensor_value_type = sensor_value_type;
|
||||
this->set_address(start_address);
|
||||
this->set_offset_from_start_address(0); // not configurable
|
||||
this->bitmask = 0xFFFFFFFF; // not configurable
|
||||
this->register_count = register_count;
|
||||
this->response_bytes = 0; // not configurable
|
||||
this->force_new_range = force_new_range;
|
||||
this->response_bytes = 0; // not configurable
|
||||
this->reuse_previous_range = reuse_previous_range;
|
||||
this->mapping_ = std::move(mapping);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ADDRESS, CONF_ID
|
||||
|
||||
from .. import (
|
||||
RANGE_REUSE,
|
||||
ModbusItemBaseSchema,
|
||||
SensorItem,
|
||||
add_modbus_base_properties,
|
||||
@@ -12,13 +13,13 @@ from .. import (
|
||||
modbus_controller_ns,
|
||||
validate_custom_pdu_item,
|
||||
validate_modbus_register,
|
||||
validate_range_reuse_migration,
|
||||
)
|
||||
from ..const import (
|
||||
CONF_BITMASK,
|
||||
CONF_FORCE_NEW_RANGE,
|
||||
CONF_MODBUS_CONTROLLER_ID,
|
||||
CONF_REGISTER_COUNT,
|
||||
CONF_REGISTER_TYPE,
|
||||
CONF_REUSE_PREVIOUS_RANGE,
|
||||
CONF_VALUE_TYPE,
|
||||
)
|
||||
|
||||
@@ -38,27 +39,25 @@ CONFIG_SCHEMA = cv.All(
|
||||
{
|
||||
cv.Optional(CONF_REGISTER_TYPE): cv.enum(MODBUS_REGISTER_TYPE),
|
||||
cv.Optional(CONF_VALUE_TYPE, default="U_WORD"): cv.enum(SENSOR_VALUE_TYPE),
|
||||
cv.Optional(CONF_REGISTER_COUNT, default=0): cv.positive_int,
|
||||
}
|
||||
),
|
||||
validate_modbus_register,
|
||||
validate_range_reuse_migration,
|
||||
)
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = validate_custom_pdu_item
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
byte_offset, reg_count = modbus_calc_properties(config)
|
||||
value_type = config[CONF_VALUE_TYPE]
|
||||
byte_offset = modbus_calc_properties(config)
|
||||
var = cg.new_Pvariable(
|
||||
config[CONF_ID],
|
||||
config[CONF_REGISTER_TYPE],
|
||||
config[CONF_ADDRESS],
|
||||
byte_offset,
|
||||
config[CONF_BITMASK],
|
||||
value_type,
|
||||
reg_count,
|
||||
config[CONF_FORCE_NEW_RANGE],
|
||||
config[CONF_VALUE_TYPE],
|
||||
RANGE_REUSE[config[CONF_REUSE_PREVIOUS_RANGE]],
|
||||
)
|
||||
await cg.register_component(var, config)
|
||||
await sensor.register_sensor(var, config)
|
||||
|
||||
@@ -11,14 +11,13 @@ namespace esphome::modbus_controller {
|
||||
class ModbusSensor final : public Component, public sensor::Sensor, public SensorItem {
|
||||
public:
|
||||
ModbusSensor(modbus::EntityType register_type, uint16_t start_address, uint8_t offset, uint32_t bitmask,
|
||||
SensorValueType value_type, int register_count, bool force_new_range) {
|
||||
SensorValueType value_type, RangeReuse reuse_previous_range) {
|
||||
this->register_type = register_type;
|
||||
this->set_address(start_address);
|
||||
this->set_offset_from_start_address(offset);
|
||||
this->bitmask = bitmask;
|
||||
this->sensor_value_type = value_type;
|
||||
this->register_count = register_count;
|
||||
this->force_new_range = force_new_range;
|
||||
this->reuse_previous_range = reuse_previous_range;
|
||||
}
|
||||
|
||||
void parse_and_publish(std::span<const uint8_t> data) override;
|
||||
|
||||
@@ -6,6 +6,7 @@ from esphome.const import CONF_ADDRESS, CONF_ASSUMED_STATE, CONF_ID
|
||||
from esphome.types import ConfigType
|
||||
|
||||
from .. import (
|
||||
RANGE_REUSE,
|
||||
ModbusItemBaseSchema,
|
||||
SensorItem,
|
||||
add_modbus_base_properties,
|
||||
@@ -14,12 +15,13 @@ from .. import (
|
||||
reject_odd_holding_write_offset,
|
||||
validate_custom_pdu_item,
|
||||
validate_modbus_register,
|
||||
validate_range_reuse_migration,
|
||||
)
|
||||
from ..const import (
|
||||
CONF_BITMASK,
|
||||
CONF_FORCE_NEW_RANGE,
|
||||
CONF_MODBUS_CONTROLLER_ID,
|
||||
CONF_REGISTER_TYPE,
|
||||
CONF_REUSE_PREVIOUS_RANGE,
|
||||
CONF_USE_WRITE_MULTIPLE,
|
||||
CONF_WRITE_LAMBDA,
|
||||
)
|
||||
@@ -54,20 +56,21 @@ CONFIG_SCHEMA = cv.All(
|
||||
),
|
||||
validate_modbus_register,
|
||||
_validate_holding_offset,
|
||||
validate_range_reuse_migration,
|
||||
)
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = validate_custom_pdu_item
|
||||
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
byte_offset, _ = modbus_calc_properties(config)
|
||||
byte_offset = modbus_calc_properties(config)
|
||||
var = cg.new_Pvariable(
|
||||
config[CONF_ID],
|
||||
config[CONF_REGISTER_TYPE],
|
||||
config[CONF_ADDRESS],
|
||||
byte_offset,
|
||||
config[CONF_BITMASK],
|
||||
config[CONF_FORCE_NEW_RANGE],
|
||||
RANGE_REUSE[config[CONF_REUSE_PREVIOUS_RANGE]],
|
||||
)
|
||||
await cg.register_component(var, config)
|
||||
await switch.register_switch(var, config)
|
||||
|
||||
@@ -11,13 +11,12 @@ namespace esphome::modbus_controller {
|
||||
class ModbusSwitch final : public Component, public switch_::Switch, public SensorItem, public WriterEntity {
|
||||
public:
|
||||
ModbusSwitch(modbus::EntityType register_type, uint16_t start_address, uint8_t offset, uint32_t bitmask,
|
||||
bool force_new_range) {
|
||||
RangeReuse reuse_previous_range) {
|
||||
this->register_type = register_type;
|
||||
this->set_address(start_address);
|
||||
this->set_offset_from_start_address(offset);
|
||||
this->bitmask = bitmask;
|
||||
this->sensor_value_type = SensorValueType::BIT;
|
||||
this->register_count = 1;
|
||||
// A holding byte offset folds into the address as whole registers (odd offsets are rejected at
|
||||
// validation: a 16-bit register write cannot target half a register); a coil offset is a coil count.
|
||||
if (register_type == modbus::EntityType::HOLDING) {
|
||||
@@ -27,7 +26,7 @@ class ModbusSwitch final : public Component, public switch_::Switch, public Sens
|
||||
this->set_address(start_address + offset);
|
||||
this->set_offset_from_start_address(0);
|
||||
}
|
||||
this->force_new_range = force_new_range;
|
||||
this->reuse_previous_range = reuse_previous_range;
|
||||
};
|
||||
void setup() override;
|
||||
void write_state(bool state) override;
|
||||
|
||||
@@ -5,6 +5,7 @@ import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ADDRESS, CONF_ID
|
||||
|
||||
from .. import (
|
||||
RANGE_REUSE,
|
||||
ModbusItemBaseSchema,
|
||||
SensorItem,
|
||||
add_modbus_base_properties,
|
||||
@@ -12,14 +13,14 @@ from .. import (
|
||||
modbus_controller_ns,
|
||||
validate_custom_pdu_item,
|
||||
validate_modbus_register,
|
||||
validate_range_reuse_migration,
|
||||
)
|
||||
from ..const import (
|
||||
CONF_FORCE_NEW_RANGE,
|
||||
CONF_MODBUS_CONTROLLER_ID,
|
||||
CONF_RAW_ENCODE,
|
||||
CONF_REGISTER_COUNT,
|
||||
CONF_REGISTER_TYPE,
|
||||
CONF_RESPONSE_SIZE,
|
||||
CONF_REUSE_PREVIOUS_RANGE,
|
||||
)
|
||||
|
||||
DEPENDENCIES = ["modbus_controller"]
|
||||
@@ -47,32 +48,27 @@ CONFIG_SCHEMA = cv.All(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(ModbusTextSensor),
|
||||
cv.Optional(CONF_REGISTER_TYPE): cv.enum(MODBUS_REGISTER_TYPE),
|
||||
cv.Optional(CONF_REGISTER_COUNT, default=0): cv.positive_int,
|
||||
cv.Optional(CONF_RESPONSE_SIZE, default=2): cv.positive_int,
|
||||
cv.Optional(CONF_RESPONSE_SIZE, default=2): cv.int_range(min=1, max=250),
|
||||
cv.Optional(CONF_RAW_ENCODE, default="ANSI"): cv.enum(RAW_ENCODING),
|
||||
}
|
||||
),
|
||||
validate_modbus_register,
|
||||
validate_range_reuse_migration,
|
||||
)
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = validate_custom_pdu_item
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
byte_offset, reg_count = modbus_calc_properties(config)
|
||||
response_size = config[CONF_RESPONSE_SIZE]
|
||||
reg_count = config[CONF_REGISTER_COUNT]
|
||||
if reg_count == 0:
|
||||
reg_count = response_size // 2
|
||||
byte_offset = modbus_calc_properties(config)
|
||||
var = cg.new_Pvariable(
|
||||
config[CONF_ID],
|
||||
config[CONF_REGISTER_TYPE],
|
||||
config[CONF_ADDRESS],
|
||||
byte_offset,
|
||||
reg_count,
|
||||
config[CONF_RESPONSE_SIZE],
|
||||
config[CONF_RAW_ENCODE],
|
||||
config[CONF_FORCE_NEW_RANGE],
|
||||
RANGE_REUSE[config[CONF_REUSE_PREVIOUS_RANGE]],
|
||||
)
|
||||
|
||||
await cg.register_component(var, config)
|
||||
|
||||
@@ -12,17 +12,16 @@ enum class RawEncoding { NONE = 0, HEXBYTES = 1, COMMA = 2, ANSI = 3 };
|
||||
|
||||
class ModbusTextSensor final : public Component, public text_sensor::TextSensor, public SensorItem {
|
||||
public:
|
||||
ModbusTextSensor(modbus::EntityType register_type, uint16_t start_address, uint8_t offset, uint8_t register_count,
|
||||
uint16_t response_bytes, RawEncoding encode, bool force_new_range) {
|
||||
ModbusTextSensor(modbus::EntityType register_type, uint16_t start_address, uint8_t offset, uint16_t response_bytes,
|
||||
RawEncoding encode, RangeReuse reuse_previous_range) {
|
||||
this->register_type = register_type;
|
||||
this->set_address(start_address);
|
||||
this->set_offset_from_start_address(offset);
|
||||
this->response_bytes = response_bytes;
|
||||
this->register_count = register_count;
|
||||
this->encode_ = encode;
|
||||
this->bitmask = 0xFFFFFFFF;
|
||||
this->sensor_value_type = SensorValueType::RAW;
|
||||
this->force_new_range = force_new_range;
|
||||
this->reuse_previous_range = reuse_previous_range;
|
||||
}
|
||||
|
||||
void dump_config() override;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -24,7 +24,7 @@ dependencies:
|
||||
espressif/esp32-camera:
|
||||
version: 2.1.7
|
||||
espressif/mdns:
|
||||
version: 1.11.3
|
||||
version: 1.12.0
|
||||
espressif/esp_wifi_remote:
|
||||
version: 1.6.3
|
||||
rules:
|
||||
|
||||
Executable
+20
@@ -0,0 +1,20 @@
|
||||
#!/bin/sh
|
||||
# Prepare the dev environment for a new checkout or worktree.
|
||||
#
|
||||
# Installed into the git hooks directory by script/setup. Deliberately tiny and
|
||||
# self-contained: it stays valid on branches where script/setup does not exist,
|
||||
# and simply does nothing there.
|
||||
|
||||
# $3 is 1 for a branch checkout, 0 for a file checkout.
|
||||
[ "$3" = "1" ] || exit 0
|
||||
|
||||
top=$(git rev-parse --show-toplevel 2>/dev/null) || exit 0
|
||||
|
||||
# This also runs on ordinary branch switches, where there is nothing to do.
|
||||
[ -x "$top/venv/bin/python" ] && exit 0
|
||||
[ -x "$top/script/setup" ] || exit 0
|
||||
|
||||
# Clear VIRTUAL_ENV so a checkout made from a shell with an environment already
|
||||
# activated still gets its own, rather than having the active one repointed at
|
||||
# this working tree.
|
||||
exec env -u VIRTUAL_ENV "$top/script/setup"
|
||||
+43
-16
@@ -7,13 +7,19 @@ cd "$(dirname "$0")/.."
|
||||
if [ -n "$VIRTUAL_ENV" ]; then
|
||||
# A virtual environment is already active (e.g. the devcontainer's pre-provisioned
|
||||
# esphome-venv). Install into it rather than creating a ./venv in the workspace.
|
||||
created_venv=false
|
||||
venv_state=active
|
||||
elif [ -x venv/bin/python ]; then
|
||||
# Reuse the environment from an earlier run, so this script can be run again
|
||||
# at any time to pick up dependency changes.
|
||||
venv_state=reused
|
||||
source venv/bin/activate
|
||||
else
|
||||
created_venv=true
|
||||
venv_state=created
|
||||
# --clear replaces a partial environment left behind by an interrupted run.
|
||||
if [ -x "$(command -v uv)" ]; then
|
||||
uv venv --seed venv
|
||||
uv venv --clear --seed venv
|
||||
else
|
||||
python3 -m venv venv
|
||||
python3 -m venv --clear venv
|
||||
fi
|
||||
source venv/bin/activate
|
||||
fi
|
||||
@@ -25,20 +31,41 @@ fi
|
||||
uv pip install setuptools wheel
|
||||
uv pip install -e ".[dev,test]" --config-settings editable_mode=compat
|
||||
|
||||
# --overwrite replaces any hook already in place. Without it, prek finds a
|
||||
# previously installed pre-commit hook, moves it aside to
|
||||
# .git/hooks/pre-commit.legacy and keeps calling it, so every commit would
|
||||
# run both tools.
|
||||
prek install --overwrite
|
||||
# A worktree shares one git hooks directory with the main checkout it was
|
||||
# created from, so hooks are installed from the main checkout only. Installing
|
||||
# from a worktree would point the shared hook at that worktree's virtual
|
||||
# environment, breaking it for everyone once the worktree is removed.
|
||||
git_dir="$(git rev-parse --absolute-git-dir 2>/dev/null || true)"
|
||||
common_dir="$(git rev-parse --path-format=absolute --git-common-dir 2>/dev/null || true)"
|
||||
if [ -n "$common_dir" ] && [ "$git_dir" = "$common_dir" ]; then
|
||||
# --overwrite replaces any hook already in place. Without it, prek finds a
|
||||
# previously installed pre-commit hook, moves it aside to
|
||||
# .git/hooks/pre-commit.legacy and keeps calling it, so every commit would
|
||||
# run both tools.
|
||||
prek install --overwrite
|
||||
|
||||
# Prepares the virtual environment for new checkouts and worktrees. Installed
|
||||
# once here, it covers every worktree created from this checkout.
|
||||
if [ -d "$common_dir/hooks" ]; then
|
||||
cp script/git-hooks/post-checkout "$common_dir/hooks/post-checkout"
|
||||
chmod +x "$common_dir/hooks/post-checkout"
|
||||
fi
|
||||
fi
|
||||
|
||||
mkdir -p .temp
|
||||
|
||||
echo
|
||||
echo
|
||||
if [ "$created_venv" = true ]; then
|
||||
echo "Virtual environment created at ./venv. Run 'source venv/bin/activate' to use it."
|
||||
else
|
||||
echo "Dependencies installed into the active virtual environment:"
|
||||
echo " $VIRTUAL_ENV"
|
||||
echo "It is already active in this shell, so no 'source venv/bin/activate' is needed."
|
||||
fi
|
||||
case "$venv_state" in
|
||||
created)
|
||||
echo "Virtual environment created at ./venv. Run 'source venv/bin/activate' to use it."
|
||||
;;
|
||||
reused)
|
||||
echo "Dependencies updated in the existing ./venv. Run 'source venv/bin/activate' to use it."
|
||||
;;
|
||||
active)
|
||||
echo "Dependencies installed into the active virtual environment:"
|
||||
echo " $VIRTUAL_ENV"
|
||||
echo "It is already active in this shell, so no 'source venv/bin/activate' is needed."
|
||||
;;
|
||||
esac
|
||||
|
||||
@@ -21,6 +21,7 @@ binary_sensor:
|
||||
name: Test Binary Sensor with Lambda
|
||||
register_type: input
|
||||
address: 0x3201
|
||||
reuse_previous_range: false
|
||||
lambda: |-
|
||||
return x;
|
||||
|
||||
@@ -85,6 +86,7 @@ select:
|
||||
name: Test Select with Lambda
|
||||
address: 1001
|
||||
value_type: U_WORD
|
||||
reuse_previous_range: auto
|
||||
optionsmap:
|
||||
"Off": 0
|
||||
"On": 1
|
||||
@@ -140,9 +142,10 @@ sensor:
|
||||
register_type: holding
|
||||
address: 0x9002
|
||||
value_type: U_WORD
|
||||
reuse_previous_range: true
|
||||
lambda: |-
|
||||
return x / 10.0;
|
||||
# Non-mergeable sensor sharing the start address of modbus_sensor1 (different register_count):
|
||||
# Non-mergeable sensor sharing the start address of modbus_sensor1 (different value type width):
|
||||
# must join the same range, never open a second range keyed on the same (address, type).
|
||||
- platform: modbus_controller
|
||||
modbus_controller_id: modbus_controller1
|
||||
@@ -187,8 +190,8 @@ sensor:
|
||||
value_type: U_WORD
|
||||
lambda: |-
|
||||
return modbus_controller::get_data<uint16_t>(data, item->offset) * 0.1f;
|
||||
# force_new_range sensors sort before plain ones, so this high-address forced sensor is grouped
|
||||
# first and the lower-address plain sensors above must still get their own ranges.
|
||||
# The deprecated force_new_range migrates to reuse_previous_range: false, so this sensor never
|
||||
# joins a range built before it and the lower-address sensors above keep their own ranges.
|
||||
- platform: modbus_controller
|
||||
modbus_controller_id: modbus_controller1
|
||||
id: modbus_sensor_forced_high
|
||||
@@ -224,7 +227,6 @@ text_sensor:
|
||||
name: Test Text Sensor
|
||||
register_type: holding
|
||||
address: 0x9013
|
||||
register_count: 3
|
||||
raw_encode: HEXBYTES
|
||||
response_size: 6
|
||||
- platform: modbus_controller
|
||||
@@ -233,12 +235,13 @@ text_sensor:
|
||||
name: Test Text Sensor with Lambda
|
||||
register_type: holding
|
||||
address: 0x9014
|
||||
register_count: 2
|
||||
response_size: 4
|
||||
lambda: |-
|
||||
return "Modified: " + x;
|
||||
# A register reporting FEWER bytes than 2*register_count (response_size: 3 for 2 registers), followed
|
||||
# by a contiguous sensor: the follower's byte position must track the actual 3 bytes, not underflow.
|
||||
# A register reporting FEWER bytes than two per register (response_size: 3 over 2 registers), followed
|
||||
# by a contiguous reuse:true sensor (auto never joins past a response_size register): the follower's
|
||||
# byte position must track the actual 3 bytes, not underflow.
|
||||
# register_count matches the derived width, so it migrates with a deprecation warning.
|
||||
- platform: modbus_controller
|
||||
modbus_controller_id: modbus_controller1
|
||||
id: modbus_text_sensor_narrow
|
||||
@@ -255,4 +258,5 @@ text_sensor:
|
||||
register_type: holding
|
||||
address: 0x9032
|
||||
register_count: 1
|
||||
reuse_previous_range: true
|
||||
raw_encode: HEXBYTES
|
||||
|
||||
@@ -27,8 +27,8 @@ uart_mock:
|
||||
# so these also pin the grouping: an extra or differently shaped read fails the test.
|
||||
- expect_tx: [0x01, 0x01, 0x00, 0x10, 0x00, 0x02, 0xBC, 0x0E] # coils 0x10 count 2
|
||||
inject_rx: [0x01, 0x01, 0x01, 0x01, 0x90, 0x48] # bit0 set, bit1 clear
|
||||
- expect_tx: [0x01, 0x03, 0x01, 0x60, 0x00, 0x01, 0x85, 0xE8] # holding 0x160 count 1
|
||||
inject_rx: [0x01, 0x03, 0x02, 0x01, 0x60, 0xB9, 0xFC] # 352
|
||||
- expect_tx: [0x01, 0x03, 0x01, 0x60, 0x00, 0x02, 0xC5, 0xE9] # holding 0x160 count 2
|
||||
inject_rx: [0x01, 0x03, 0x04, 0x01, 0x60, 0x01, 0x61, 0x3B, 0xA9] # 352, 353
|
||||
- expect_tx: [0x01, 0x03, 0x01, 0x00, 0x00, 0x01, 0x85, 0xF6] # holding 0x100 count 1
|
||||
inject_rx: [0x01, 0x03, 0x04, 0x01, 0x11, 0x02, 0x22, 0x2A, 0xB3] # 4 bytes: 273 then 546
|
||||
- expect_tx: [0x01, 0x03, 0x01, 0x20, 0x00, 0x04, 0x44, 0x3F] # holding 0x120 count 4
|
||||
@@ -49,8 +49,6 @@ uart_mock:
|
||||
inject_rx: [0x01, 0x03, 0x02, 0x33, 0x33, 0xEC, 0xA1] # 13107
|
||||
- expect_tx: [0x01, 0x03, 0x01, 0x70, 0x00, 0x03, 0x05, 0xEC] # holding 0x170 count 3
|
||||
inject_rx: [0x01, 0x03, 0x06, 0x00, 0x2A, 0x1B, 0x2C, 0x03, 0x0D, 0x3E, 0xAB] # 6 bytes
|
||||
- expect_tx: [0x01, 0x03, 0x01, 0x61, 0x00, 0x01, 0xD4, 0x28] # holding 0x161 count 1
|
||||
inject_rx: [0x01, 0x03, 0x02, 0x01, 0x61, 0x78, 0x3C] # 353
|
||||
|
||||
modbus:
|
||||
uart_id: virtual_uart_dev
|
||||
@@ -104,8 +102,9 @@ sensor:
|
||||
value_type: U_DWORD
|
||||
modbus_controller_id: modbus_controller_ok
|
||||
|
||||
# D - a wide (response_size) register followed by a contiguous one: the follower must start after the
|
||||
# bytes the wide register actually returned, not after 2 * register_count.
|
||||
# D - a wide (response_size) register followed by a contiguous reuse:true one (auto never joins past
|
||||
# a response_size register): the follower must start after the bytes the wide register actually
|
||||
# returned, not after two per register.
|
||||
- platform: modbus_controller
|
||||
name: "wide_first"
|
||||
address: 0x130
|
||||
@@ -118,6 +117,7 @@ sensor:
|
||||
address: 0x131
|
||||
register_type: holding
|
||||
value_type: U_WORD
|
||||
reuse_previous_range: true
|
||||
modbus_controller_id: modbus_controller_ok
|
||||
|
||||
# E - a gap: these must never share a range.
|
||||
@@ -195,13 +195,14 @@ sensor:
|
||||
value_type: U_WORD
|
||||
modbus_controller_id: modbus_controller_ok
|
||||
|
||||
# H - a sensor pinned to its own range, followed by a contiguous one.
|
||||
# H - a sensor that never joins the range built before it (reuse_previous_range: false), followed
|
||||
# by a contiguous plain item that extends the new range it started.
|
||||
- platform: modbus_controller
|
||||
name: "forced_first"
|
||||
address: 0x160
|
||||
register_type: holding
|
||||
value_type: U_WORD
|
||||
force_new_range: true
|
||||
reuse_previous_range: false
|
||||
modbus_controller_id: modbus_controller_ok
|
||||
- platform: modbus_controller
|
||||
name: "forced_next"
|
||||
|
||||
@@ -0,0 +1,227 @@
|
||||
esphome:
|
||||
name: uart-mock-modbus-ranges-test
|
||||
|
||||
host:
|
||||
api:
|
||||
logger:
|
||||
level: VERBOSE
|
||||
|
||||
external_components:
|
||||
- source:
|
||||
type: local
|
||||
path: EXTERNAL_COMPONENT_PATH
|
||||
|
||||
# Dummy uart entry to satisfy modbus's DEPENDENCIES = ["uart"]
|
||||
# The actual UART bus used is the uart_mock component below
|
||||
uart:
|
||||
baud_rate: 115200
|
||||
port: /dev/null
|
||||
|
||||
# Each expect_tx below pins the exact read request the controller's range builder emits, so this
|
||||
# fixture is a wire-level test of reuse_previous_range (auto/yes/no), gap joins, same-register reuse,
|
||||
# response_size surplus accounting, and RAW/text block reads.
|
||||
uart_mock:
|
||||
- id: virtual_uart_dev
|
||||
baud_rate: 9600
|
||||
rx_full_threshold: 120
|
||||
rx_timeout: 2
|
||||
# auto_start must be false to avoid races: the test presses the
|
||||
# "Start Scenario" button only after subscribing to states.
|
||||
auto_start: false
|
||||
debug:
|
||||
responses:
|
||||
- expect_tx: [0x01, 0x03, 0x00, 0x00, 0x00, 0x03, 0x05, 0xCB] # auto adjacency: one read covers 0x00-0x02
|
||||
inject_rx: [0x01, 0x03, 0x06, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0xFD, 0x74]
|
||||
- expect_tx: [0x01, 0x03, 0x00, 0x10, 0x00, 0x01, 0x85, 0xCF] # auto gap: 0x10 alone
|
||||
inject_rx: [0x01, 0x03, 0x02, 0x00, 0x04, 0xB9, 0x87]
|
||||
- expect_tx: [0x01, 0x03, 0x00, 0x13, 0x00, 0x01, 0x75, 0xCF] # auto gap: 0x13 alone
|
||||
inject_rx: [0x01, 0x03, 0x02, 0x00, 0x05, 0x78, 0x47]
|
||||
- expect_tx: [0x01, 0x03, 0x00, 0x20, 0x00, 0x04, 0x45, 0xC3] # yes across gap: one read 0x20-0x23, gap registers ignored
|
||||
inject_rx: [0x01, 0x03, 0x08, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x33, 0xD1]
|
||||
- expect_tx: [0x01, 0x03, 0x00, 0x30, 0x00, 0x01, 0x84, 0x05] # no isolation: 0x30 alone despite adjacency
|
||||
inject_rx: [0x01, 0x03, 0x02, 0x00, 0x0A, 0x38, 0x43]
|
||||
- expect_tx: [0x01, 0x03, 0x00, 0x31, 0x00, 0x01, 0xD5, 0xC5] # no isolation: 0x31 alone
|
||||
inject_rx: [0x01, 0x03, 0x02, 0x00, 0x0B, 0xF9, 0x83]
|
||||
- expect_tx: [0x01, 0x03, 0x00, 0x3F, 0x00, 0x01, 0xB4, 0x06] # open NEVER: 0x3F alone (the reuse:false item split off)
|
||||
inject_rx: [0x01, 0x03, 0x02, 0x00, 0x0C, 0xB8, 0x41]
|
||||
- expect_tx: [0x01, 0x03, 0x00, 0x40, 0x00, 0x02, 0xC5, 0xDF] # open NEVER: 0x40 (reuse: false) still extended by the auto item at 0x41
|
||||
inject_rx: [0x01, 0x03, 0x04, 0x00, 0x0D, 0x00, 0x0E, 0xEA, 0x34]
|
||||
- expect_tx: [0x01, 0x03, 0x00, 0x50, 0x00, 0x01, 0x84, 0x1B] # same-address reuse: one read, two sensors on 0x50
|
||||
inject_rx: [0x01, 0x03, 0x02, 0x12, 0x34, 0xB5, 0x33]
|
||||
- expect_tx: [0x01, 0x03, 0x00, 0x60, 0x00, 0x04, 0x44, 0x17] # text block + adjacent word: one read 0x60-0x63
|
||||
inject_rx: [0x01, 0x03, 0x08, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x00, 0x0F, 0x78, 0x0E]
|
||||
- expect_tx: [0x01, 0x03, 0x00, 0x70, 0x00, 0x02, 0xC5, 0xD0] # response_size surplus: 0x70 answers 4 bytes, reuse:true word at 0x71 shifted along
|
||||
inject_rx: [0x01, 0x03, 0x06, 0x00, 0x10, 0xAA, 0xBB, 0x00, 0x11, 0x71, 0x47]
|
||||
- expect_tx: [0x01, 0x03, 0x00, 0x90, 0x00, 0x01, 0x84, 0x27] # auto after surplus: 0x90 alone (auto never joins past response_size)
|
||||
inject_rx: [0x01, 0x03, 0x04, 0x00, 0x18, 0xCC, 0xDD, 0xEF, 0x6D]
|
||||
- expect_tx: [0x01, 0x03, 0x00, 0x91, 0x00, 0x01, 0xD5, 0xE7] # auto after surplus: 0x91 alone
|
||||
inject_rx: [0x01, 0x03, 0x02, 0x00, 0x19, 0x79, 0x8E]
|
||||
- expect_tx: [0x01, 0x03, 0x00, 0x80, 0x00, 0x04, 0x45, 0xE1] # RAW block via response_size: 8 bytes = 4 registers in one read
|
||||
inject_rx: [0x01, 0x03, 0x08, 0x00, 0x14, 0x00, 0x15, 0x00, 0x16, 0x00, 0x17, 0x6D, 0xDF]
|
||||
|
||||
modbus:
|
||||
uart_id: virtual_uart_dev
|
||||
send_wait_time: 200ms
|
||||
turnaround_time: 10ms
|
||||
|
||||
modbus_controller:
|
||||
- address: 1
|
||||
id: ranges_controller
|
||||
max_cmd_retries: 0
|
||||
# The test triggers a single poll by pressing the "Start Scenario" button
|
||||
update_interval: never
|
||||
|
||||
sensor:
|
||||
# Case 1: three adjacent registers merge into one read (auto default)
|
||||
- platform: modbus_controller
|
||||
modbus_controller_id: ranges_controller
|
||||
name: "adjacent_a"
|
||||
register_type: holding
|
||||
address: 0x00
|
||||
- platform: modbus_controller
|
||||
modbus_controller_id: ranges_controller
|
||||
name: "adjacent_b"
|
||||
register_type: holding
|
||||
address: 0x01
|
||||
- platform: modbus_controller
|
||||
modbus_controller_id: ranges_controller
|
||||
name: "adjacent_c"
|
||||
register_type: holding
|
||||
address: 0x02
|
||||
|
||||
# Case 2: a gap keeps auto items apart
|
||||
- platform: modbus_controller
|
||||
modbus_controller_id: ranges_controller
|
||||
name: "gap_a"
|
||||
register_type: holding
|
||||
address: 0x10
|
||||
- platform: modbus_controller
|
||||
modbus_controller_id: ranges_controller
|
||||
name: "gap_b"
|
||||
register_type: holding
|
||||
address: 0x13
|
||||
|
||||
# Case 3: reuse_previous_range: true bridges the gap into one read
|
||||
- platform: modbus_controller
|
||||
modbus_controller_id: ranges_controller
|
||||
name: "bridge_a"
|
||||
register_type: holding
|
||||
address: 0x20
|
||||
- platform: modbus_controller
|
||||
modbus_controller_id: ranges_controller
|
||||
name: "bridge_b"
|
||||
register_type: holding
|
||||
address: 0x23
|
||||
reuse_previous_range: true
|
||||
|
||||
# Case 4: reuse_previous_range: false splits adjacent registers
|
||||
- platform: modbus_controller
|
||||
modbus_controller_id: ranges_controller
|
||||
name: "split_a"
|
||||
register_type: holding
|
||||
address: 0x30
|
||||
- platform: modbus_controller
|
||||
modbus_controller_id: ranges_controller
|
||||
name: "split_b"
|
||||
register_type: holding
|
||||
address: 0x31
|
||||
reuse_previous_range: false
|
||||
|
||||
# Case 5: a reuse:false item starts its own range but stays open for later auto items
|
||||
- platform: modbus_controller
|
||||
modbus_controller_id: ranges_controller
|
||||
name: "open_prev"
|
||||
register_type: holding
|
||||
address: 0x3F
|
||||
- platform: modbus_controller
|
||||
modbus_controller_id: ranges_controller
|
||||
name: "open_never"
|
||||
register_type: holding
|
||||
address: 0x40
|
||||
reuse_previous_range: false
|
||||
- platform: modbus_controller
|
||||
modbus_controller_id: ranges_controller
|
||||
name: "open_tagalong"
|
||||
register_type: holding
|
||||
address: 0x41
|
||||
|
||||
# Case 6: two sensors on the same register share one read
|
||||
- platform: modbus_controller
|
||||
modbus_controller_id: ranges_controller
|
||||
name: "shared_lo"
|
||||
register_type: holding
|
||||
address: 0x50
|
||||
bitmask: 0x00FF
|
||||
- platform: modbus_controller
|
||||
modbus_controller_id: ranges_controller
|
||||
name: "shared_hi"
|
||||
register_type: holding
|
||||
address: 0x50
|
||||
bitmask: 0xFF00
|
||||
|
||||
# Case 10 (text block, see text_sensor below) shares the range with this word at 0x63
|
||||
- platform: modbus_controller
|
||||
modbus_controller_id: ranges_controller
|
||||
name: "after_text"
|
||||
register_type: holding
|
||||
address: 0x63
|
||||
|
||||
# Case 11: response_size surplus — the device answers 4 bytes for this single register, so the
|
||||
# following sensor's data sits 2 bytes later than its address alone implies. Joining past a
|
||||
# non-standard response_size takes an explicit reuse_previous_range: true.
|
||||
- platform: modbus_controller
|
||||
modbus_controller_id: ranges_controller
|
||||
name: "surplus"
|
||||
register_type: holding
|
||||
address: 0x70
|
||||
response_size: 4
|
||||
- platform: modbus_controller
|
||||
modbus_controller_id: ranges_controller
|
||||
name: "after_surplus"
|
||||
register_type: holding
|
||||
address: 0x71
|
||||
reuse_previous_range: true
|
||||
|
||||
# Case 13: auto never joins past a response_size register — despite adjacency these poll separately
|
||||
- platform: modbus_controller
|
||||
modbus_controller_id: ranges_controller
|
||||
name: "surplus_split"
|
||||
register_type: holding
|
||||
address: 0x90
|
||||
response_size: 4
|
||||
- platform: modbus_controller
|
||||
modbus_controller_id: ranges_controller
|
||||
name: "after_surplus_split"
|
||||
register_type: holding
|
||||
address: 0x91
|
||||
|
||||
# Case 12: RAW + response_size reads a block of ceil(8/2) = 4 registers
|
||||
- platform: modbus_controller
|
||||
modbus_controller_id: ranges_controller
|
||||
name: "raw_block"
|
||||
register_type: holding
|
||||
address: 0x80
|
||||
value_type: RAW
|
||||
response_size: 8
|
||||
lambda: |-
|
||||
return (float) data.size();
|
||||
|
||||
text_sensor:
|
||||
# Case 10: text sensor reads 3 registers (response_size 6)
|
||||
- platform: modbus_controller
|
||||
modbus_controller_id: ranges_controller
|
||||
name: "text_block"
|
||||
register_type: holding
|
||||
address: 0x60
|
||||
response_size: 6
|
||||
raw_encode: NONE
|
||||
|
||||
button:
|
||||
- platform: template
|
||||
name: "Start Scenario"
|
||||
id: start_scenario_btn
|
||||
on_press:
|
||||
- lambda: |-
|
||||
id(virtual_uart_dev).start_scenario();
|
||||
id(ranges_controller).set_update_interval(1000);
|
||||
id(ranges_controller).start_poller();
|
||||
@@ -32,9 +32,9 @@ uart_mock:
|
||||
# duplicate or overlapping range would put an extra frame on the bus and fail to match.
|
||||
- expect_tx: [0x01, 0x03, 0x90, 0x01, 0x00, 0x02, 0xB8, 0xCB] # Read holding 0x9001 count 2 on device 1
|
||||
inject_rx: [0x01, 0x03, 0x04, 0x03, 0x97, 0x02, 0x91, 0x8B, 0x57] # 0x9001=0x0397, 0x9002=0x0291
|
||||
# A force_new_range sensor at a HIGH address (0x30) sorts before the plain sensor at a LOW address
|
||||
# (0x10). The two must poll as separate ranges: the covered branch's lower-bound check prevents the
|
||||
# 0x10 sensor from being absorbed into the forced 0x30 range with a wrapped byte offset.
|
||||
# A sensor at 0x30 with the deprecated force_new_range (migrates to reuse_previous_range: false)
|
||||
# and a plain sensor at 0x10. The two must poll as separate ranges: the 0x10 sensor must not be
|
||||
# absorbed into the isolated 0x30 range.
|
||||
- expect_tx: [0x01, 0x03, 0x00, 0x30, 0x00, 0x01, 0x84, 0x05] # Read holding 0x30 count 1 (forced range)
|
||||
inject_rx: [0x01, 0x03, 0x02, 0x01, 0x11, 0x79, 0xD8] # 0x30 = 0x0111 = 273
|
||||
- expect_tx: [0x01, 0x03, 0x00, 0x10, 0x00, 0x01, 0x85, 0xCF] # Read holding 0x10 count 1 (own range)
|
||||
@@ -87,7 +87,7 @@ sensor:
|
||||
register_type: holding
|
||||
value_type: U_WORD
|
||||
modbus_controller_id: modbus_controller_ok
|
||||
# Forced sensor at a high address: sorts first, opens its own isolated range
|
||||
# Isolated sensor (deprecated spelling, migrates to reuse_previous_range: false): own range
|
||||
- platform: modbus_controller
|
||||
name: "forced_high"
|
||||
address: 0x30
|
||||
@@ -95,7 +95,7 @@ sensor:
|
||||
value_type: U_WORD
|
||||
force_new_range: true
|
||||
modbus_controller_id: modbus_controller_ok
|
||||
# Plain sensor at a lower address: must get its own range, never absorbed into the forced one
|
||||
# Plain sensor at a lower address: must get its own range, never absorbed into the isolated one
|
||||
- platform: modbus_controller
|
||||
name: "plain_low"
|
||||
address: 0x10
|
||||
|
||||
@@ -21,7 +21,7 @@ import asyncio
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
|
||||
from aioesphomeapi import ButtonInfo, NumberInfo, SwitchInfo
|
||||
from aioesphomeapi import ButtonInfo, NumberInfo, SwitchInfo, TextSensorState
|
||||
import pytest
|
||||
|
||||
from .state_utils import SensorTracker, find_entity, wait_for_state
|
||||
@@ -1158,3 +1158,78 @@ async def test_uart_mock_modbus_deprecated_write_buffer(
|
||||
assert warn_count == 1, (
|
||||
f"deprecation warning should fire exactly once per entity, got {warn_count}"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_uart_mock_modbus_ranges(
|
||||
yaml_config: str,
|
||||
run_compiled: RunCompiledFunction,
|
||||
api_client_connected: APIClientConnectedFactory,
|
||||
) -> None:
|
||||
"""Wire-level test of the range builder's reuse_previous_range semantics.
|
||||
|
||||
Every expect_tx in the fixture pins the exact read request the controller emits, so a
|
||||
wrongly merged or split range fails on the mock before any value arrives. Covers: auto
|
||||
adjacency merging, auto gap splitting, reuse:true bridging a gap (with correct data
|
||||
offsets past the gap), reuse:false splitting adjacent registers while staying open for
|
||||
later auto items, two sensors sharing one register, a text block read sized by
|
||||
response_size with a following word, response_size surplus shifting a later reuse:true
|
||||
sensor's bytes while an auto sensor refuses to join past the surplus, and a RAW block
|
||||
read of ceil(response_size / 2) registers.
|
||||
"""
|
||||
|
||||
line_callback, error_log_lines, warning_log_lines = _make_modbus_line_callback()
|
||||
|
||||
expected_values = {
|
||||
"adjacent_a": 1,
|
||||
"adjacent_b": 2,
|
||||
"adjacent_c": 3,
|
||||
"gap_a": 4,
|
||||
"gap_b": 5,
|
||||
"bridge_a": 6,
|
||||
"bridge_b": 9,
|
||||
"split_a": 10,
|
||||
"split_b": 11,
|
||||
"open_prev": 12,
|
||||
"open_never": 13,
|
||||
"open_tagalong": 14,
|
||||
"shared_lo": 0x34,
|
||||
"shared_hi": 0x12,
|
||||
"after_text": 15,
|
||||
"surplus": 16,
|
||||
"after_surplus": 17,
|
||||
"surplus_split": 24,
|
||||
"after_surplus_split": 25,
|
||||
"raw_block": 8, # the RAW lambda publishes data.size(): 4 registers = 8 bytes
|
||||
}
|
||||
tracker = SensorTracker(list(expected_values.keys()))
|
||||
futures = tracker.expect_all(expected_values)
|
||||
|
||||
# The tracker only handles numeric sensors; capture the text block separately.
|
||||
text_future: asyncio.Future = asyncio.get_running_loop().create_future()
|
||||
tracker_on_state = tracker.on_state
|
||||
|
||||
def on_state(state) -> None:
|
||||
if (
|
||||
isinstance(state, TextSensorState)
|
||||
and not state.missing_state
|
||||
and state.state == "ABCDEF"
|
||||
and not text_future.done()
|
||||
):
|
||||
text_future.set_result(True)
|
||||
tracker_on_state(state)
|
||||
|
||||
tracker.on_state = on_state
|
||||
|
||||
async with (
|
||||
run_compiled(yaml_config, line_callback=line_callback),
|
||||
api_client_connected() as client,
|
||||
):
|
||||
await tracker.setup_and_start_scenario(client)
|
||||
await tracker.await_all(futures)
|
||||
# text_block is not tracker-registered (non-numeric), so time out explicitly.
|
||||
try:
|
||||
await asyncio.wait_for(text_future, timeout=5.0)
|
||||
except TimeoutError:
|
||||
pytest.fail("text_block never published 'ABCDEF'")
|
||||
_assert_no_modbus_errors(error_log_lines, warning_log_lines)
|
||||
|
||||
Reference in New Issue
Block a user