Compare commits
21 Commits
84d2c99edd
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1846fa7b36 | ||
|
|
46f9bada4f | ||
|
|
20afd3d9ef | ||
|
|
85206e1dca | ||
|
|
666750f710 | ||
|
|
ef1f3e4e85 | ||
|
|
f47a29205e | ||
|
|
9f4362b5fd | ||
|
|
3774cde506 | ||
|
|
a775999c87 | ||
|
|
b0b317a0fe | ||
|
|
837ec18fad | ||
|
|
9eb283420a | ||
|
|
77548e7e9f | ||
|
|
cdb3b11db1 | ||
|
|
ff1ea6615c | ||
|
|
59e7071023 | ||
|
|
18faa5b83d | ||
|
|
35b7074e81 | ||
|
|
fff1295862 | ||
|
|
123ddc2688 |
@@ -12,7 +12,13 @@
|
|||||||
"Bash(grep -r \"esp_bt\" C:/data/stockcropper-sw/SC-F001/build/esp-idf/bt/ --include=\"*.cmake\" -l 2>/dev/null | head -5)",
|
"Bash(grep -r \"esp_bt\" C:/data/stockcropper-sw/SC-F001/build/esp-idf/bt/ --include=\"*.cmake\" -l 2>/dev/null | head -5)",
|
||||||
"Bash(grep -r \"esp_bt.h\" C:/data/stockcropper-sw/SC-F001/build/ --include=\"*.cmake\" 2>/dev/null | head -5)",
|
"Bash(grep -r \"esp_bt.h\" C:/data/stockcropper-sw/SC-F001/build/ --include=\"*.cmake\" 2>/dev/null | head -5)",
|
||||||
"WebFetch(domain:docs.espressif.com)",
|
"WebFetch(domain:docs.espressif.com)",
|
||||||
"Bash(curl -s \"https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/peripherals/clk_tree.html\" | sed 's/<script[^>]*>.*<\\\\/script>//g; s/<style[^>]*>.*<\\\\/style>//g; s/<[^>]*>//g; s/&/\\\\&/g; s/</</g; s/>/>/g; s/ / /g; s/"/\"/g; s/'/'\"'\"'/g' | tr -s ' \\\\n' | grep -v '^[[:space:]]*$' 2>&1 | head -2000)"
|
"Bash(curl -s \"https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/peripherals/clk_tree.html\" | sed 's/<script[^>]*>.*<\\\\/script>//g; s/<style[^>]*>.*<\\\\/style>//g; s/<[^>]*>//g; s/&/\\\\&/g; s/</</g; s/>/>/g; s/ / /g; s/"/\"/g; s/'/'\"'\"'/g' | tr -s ' \\\\n' | grep -v '^[[:space:]]*$' 2>&1 | head -2000)",
|
||||||
|
"Bash(find /c/data/stockcropper-sw/SC-F001/main -name \"*.c\" -o -name \"*.h\" | xargs grep -l \"soft_idle_enter\\\\|soft_idle_exit\" 2>/dev/null)",
|
||||||
|
"Bash(xxd:*)",
|
||||||
|
"Bash(ls -la \"C:\\\\data\\\\stockcropper-sw\\\\SC-F001\"/*.bin)",
|
||||||
|
"Bash(ls -la \"C:\\\\data\\\\stockcropper-sw\\\\SC-F001\"/logs/*.bin)",
|
||||||
|
"Bash(python3:*)",
|
||||||
|
"Bash(ls:*)"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1 +1,3 @@
|
|||||||
/build/
|
/build/
|
||||||
|
/26c3058c23be21cf6f9cc812bd5d0a8907b2ecf2/
|
||||||
|
*.pyc
|
||||||
563
BRINGUP_10JUN2026_1644.txt
Normal file
563
BRINGUP_10JUN2026_1644.txt
Normal file
@@ -0,0 +1,563 @@
|
|||||||
|
16:44:48.752 Connecting to COM3 @ 115200 ...
|
||||||
|
16:44:48.758 -> BU.BEGIN
|
||||||
|
16:44:48.780 <- ets Jul 29 2019 12:21:46
|
||||||
|
|
||||||
|
16:44:48.780 <-
|
||||||
|
|
||||||
|
16:44:48.780 <- rst:0x1 (POWERON_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
|
||||||
|
|
||||||
|
16:44:48.783 <- configsip: 0, SPIWP:0xee
|
||||||
|
|
||||||
|
16:44:48.785 <- clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
|
||||||
|
|
||||||
|
16:44:48.786 <- mode:DIO, clock div:2
|
||||||
|
|
||||||
|
16:44:48.789 <- load:0x3fffeba4,len:4
|
||||||
|
|
||||||
|
16:44:48.790 <- load:0x4009f000,len:3248
|
||||||
|
|
||||||
|
16:44:49.120 <- entry 0x4009f574
|
||||||
|
|
||||||
|
16:44:49.120 <- <20>OHAI<41>ets Jul 29 2019 12:21:46
|
||||||
|
|
||||||
|
16:44:49.122 <-
|
||||||
|
|
||||||
|
16:44:49.123 <- rst:0x10 (RTCWDT_RTC_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
|
||||||
|
|
||||||
|
16:44:49.126 <- configsip: 0, SPIWP:0xee
|
||||||
|
|
||||||
|
16:44:49.138 <- clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
|
||||||
|
|
||||||
|
16:44:49.142 <- mode:DIO, clock div:2
|
||||||
|
|
||||||
|
16:44:49.462 <- load:0x3fffeba4,len:4
|
||||||
|
|
||||||
|
16:44:49.463 <- load:0x4009f000,len:3248
|
||||||
|
|
||||||
|
16:44:49.465 <- entry 0x4009f574
|
||||||
|
|
||||||
|
16:44:49.469 <- <20>OHAI<41>ets Jul 29 2019 12:21:46
|
||||||
|
|
||||||
|
16:44:49.470 <-
|
||||||
|
|
||||||
|
16:44:49.470 <- rst:0x10 (RTCWDT_RTC_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
|
||||||
|
|
||||||
|
16:44:49.471 <- configsip: 0, SPIWP:0xee
|
||||||
|
|
||||||
|
16:44:49.472 <- clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
|
||||||
|
|
||||||
|
16:44:49.800 <- mode:DIO, clock div:2
|
||||||
|
|
||||||
|
16:44:49.800 <- load:0x3fffeba4,len:4
|
||||||
|
|
||||||
|
16:44:49.802 <- load:0x4009f000,len:3248
|
||||||
|
|
||||||
|
16:44:49.803 <- entry 0x4009f574
|
||||||
|
|
||||||
|
16:44:49.807 <- <20>OHAI<41>ets Jul 29 2019 12:21:46
|
||||||
|
|
||||||
|
16:44:49.808 <-
|
||||||
|
|
||||||
|
16:44:49.808 <- rst:0x10 (RTCWDT_RTC_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
|
||||||
|
|
||||||
|
16:44:49.808 <- configsip: 0, SPIWP:0xee
|
||||||
|
|
||||||
|
16:44:50.144 <- clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
|
||||||
|
|
||||||
|
16:44:50.144 <- mode:DIO, clock div:2
|
||||||
|
|
||||||
|
16:44:50.146 <- load:0x3fffeba4,len:4
|
||||||
|
|
||||||
|
16:44:50.151 <- load:0x4009f000,len:3248
|
||||||
|
|
||||||
|
16:44:50.151 <- entry 0x4009f574
|
||||||
|
|
||||||
|
16:44:50.152 <- <20>OHAI<41>ets Jul 29 2019 12:21:46
|
||||||
|
|
||||||
|
16:44:50.153 <-
|
||||||
|
|
||||||
|
16:44:50.153 <- rst:0x10 (RTCWDT_RTC_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
|
||||||
|
|
||||||
|
16:44:50.153 <- configsip: 0, SPIWP:0xee
|
||||||
|
|
||||||
|
16:44:50.268 -> BU.BEGIN
|
||||||
|
16:44:50.268 <- clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
|
||||||
|
|
||||||
|
16:44:50.271 <- mode:DIO, clock div:2
|
||||||
|
|
||||||
|
16:44:50.275 <- load:0x3fffeba4,len:4
|
||||||
|
|
||||||
|
16:44:50.276 <- load:0x4009f000,len:3248
|
||||||
|
|
||||||
|
16:44:50.277 <- entry 0x4009f574
|
||||||
|
|
||||||
|
16:44:50.495 <- <20>OHAI<41>ets Jul 29 2019 12:21:46
|
||||||
|
|
||||||
|
16:44:50.495 <-
|
||||||
|
|
||||||
|
16:44:50.495 <- rst:0x10 (RTCWDT_RTC_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
|
||||||
|
|
||||||
|
16:44:50.497 <- configsip: 0, SPIWP:0xee
|
||||||
|
|
||||||
|
16:44:50.497 <- clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
|
||||||
|
|
||||||
|
16:44:50.502 <- mode:DIO, clock div:2
|
||||||
|
|
||||||
|
16:44:50.502 <- load:0x3fffeba4,len:4
|
||||||
|
|
||||||
|
16:44:50.835 <- load:0x4009f000,len:3248
|
||||||
|
|
||||||
|
16:44:50.835 <- entry 0x4009f574
|
||||||
|
|
||||||
|
16:44:50.838 <- <20>OHAI<41>ets Jul 29 2019 12:21:46
|
||||||
|
|
||||||
|
16:44:50.842 <-
|
||||||
|
|
||||||
|
16:44:50.842 <- rst:0x10 (RTCWDT_RTC_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
|
||||||
|
|
||||||
|
16:44:50.842 <- configsip: 0, SPIWP:0xee
|
||||||
|
|
||||||
|
16:44:50.843 <- clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
|
||||||
|
|
||||||
|
16:44:50.844 <- mode:DIO, clock div:2
|
||||||
|
|
||||||
|
16:44:51.178 <- load:0x3fffeba4,len:4
|
||||||
|
|
||||||
|
16:44:51.178 <- load:0x4009f000,len:3248
|
||||||
|
|
||||||
|
16:44:51.180 <- entry 0x4009f574
|
||||||
|
|
||||||
|
16:44:51.184 <- <20>OHAI<41>ets Jul 29 2019 12:21:46
|
||||||
|
|
||||||
|
16:44:51.184 <-
|
||||||
|
|
||||||
|
16:44:51.184 <- rst:0x10 (RTCWDT_RTC_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
|
||||||
|
|
||||||
|
16:44:51.185 <- configsip: 0, SPIWP:0xee
|
||||||
|
|
||||||
|
16:44:51.186 <- clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
|
||||||
|
|
||||||
|
16:44:51.515 <- mode:DIO, clock div:2
|
||||||
|
|
||||||
|
16:44:51.515 <- load:0x3fffeba4,len:4
|
||||||
|
|
||||||
|
16:44:51.521 <- load:0x4009f000,len:3248
|
||||||
|
|
||||||
|
16:44:51.521 <- entry 0x4009f574
|
||||||
|
|
||||||
|
16:44:51.522 <- <20>OHAI<41>ets Jul 29 2019 12:21:46
|
||||||
|
|
||||||
|
16:44:51.523 <-
|
||||||
|
|
||||||
|
16:44:51.523 <- rst:0x10 (RTCWDT_RTC_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
|
||||||
|
|
||||||
|
16:44:51.523 <- configsip: 0, SPIWP:0xee
|
||||||
|
|
||||||
|
16:44:51.770 -> BU.BEGIN
|
||||||
|
16:44:51.770 <- clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
|
||||||
|
|
||||||
|
16:44:51.772 <- mode:DIO, clock div:2
|
||||||
|
|
||||||
|
16:44:51.776 <- load:0x3fffeba4,len:4
|
||||||
|
|
||||||
|
16:44:51.777 <- load:0x4009f000,len:3248
|
||||||
|
|
||||||
|
16:44:51.777 <- entry 0x4009f574
|
||||||
|
|
||||||
|
16:44:51.867 <- <20>OHAI<41>ets Jul 29 2019 12:21:46
|
||||||
|
|
||||||
|
16:44:51.867 <-
|
||||||
|
|
||||||
|
16:44:51.867 <- rst:0x10 (RTCWDT_RTC_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
|
||||||
|
|
||||||
|
16:44:51.869 <- configsip: 0, SPIWP:0xee
|
||||||
|
|
||||||
|
16:44:51.873 <- clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
|
||||||
|
|
||||||
|
16:44:51.874 <- mode:DIO, clock div:2
|
||||||
|
|
||||||
|
16:44:51.874 <- load:0x3fffeba4,len:4
|
||||||
|
|
||||||
|
16:44:52.207 <- load:0x4009f000,len:3248
|
||||||
|
|
||||||
|
16:44:52.207 <- entry 0x4009f574
|
||||||
|
|
||||||
|
16:44:52.209 <- <20>OHAI<41>ets Jul 29 2019 12:21:46
|
||||||
|
|
||||||
|
16:44:52.210 <-
|
||||||
|
|
||||||
|
16:44:52.210 <- rst:0x10 (RTCWDT_RTC_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
|
||||||
|
|
||||||
|
16:44:52.214 <- configsip: 0, SPIWP:0xee
|
||||||
|
|
||||||
|
16:44:52.214 <- clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
|
||||||
|
|
||||||
|
16:44:52.216 <- mode:DIO, clock div:2
|
||||||
|
|
||||||
|
16:44:52.550 <- load:0x3fffeba4,len:4
|
||||||
|
|
||||||
|
16:44:52.550 <- load:0x4009f000,len:3248
|
||||||
|
|
||||||
|
16:44:52.552 <- entry 0x4009f574
|
||||||
|
|
||||||
|
16:44:52.557 <- <20>OHAI<41>ets Jul 29 2019 12:21:46
|
||||||
|
|
||||||
|
16:44:52.557 <-
|
||||||
|
|
||||||
|
16:44:52.557 <- rst:0x10 (RTCWDT_RTC_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
|
||||||
|
|
||||||
|
16:44:52.558 <- configsip: 0, SPIWP:0xee
|
||||||
|
|
||||||
|
16:44:52.558 <- clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
|
||||||
|
|
||||||
|
16:44:52.888 <- mode:DIO, clock div:2
|
||||||
|
|
||||||
|
16:44:52.888 <- load:0x3fffeba4,len:4
|
||||||
|
|
||||||
|
16:44:52.890 <- load:0x4009f000,len:3248
|
||||||
|
|
||||||
|
16:44:52.894 <- entry 0x4009f574
|
||||||
|
|
||||||
|
16:44:52.895 <- <20>OHAI<41>ets Jul 29 2019 12:21:46
|
||||||
|
|
||||||
|
16:44:52.895 <-
|
||||||
|
|
||||||
|
16:44:52.896 <- rst:0x10 (RTCWDT_RTC_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
|
||||||
|
|
||||||
|
16:44:52.896 <- configsip: 0, SPIWP:0xee
|
||||||
|
|
||||||
|
16:44:53.231 <- clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
|
||||||
|
|
||||||
|
16:44:53.232 <- mode:DIO, clock div:2
|
||||||
|
|
||||||
|
16:44:53.233 <- load:0x3fffeba4,len:4
|
||||||
|
|
||||||
|
16:44:53.238 <- load:0x4009f000,len:3248
|
||||||
|
|
||||||
|
16:44:53.238 <- entry 0x4009f574
|
||||||
|
|
||||||
|
16:44:53.239 <- <20>OHAI<41>ets Jul 29 2019 12:21:46
|
||||||
|
|
||||||
|
16:44:53.240 <-
|
||||||
|
|
||||||
|
16:44:53.240 <- rst:0x10 (RTCWDT_RTC_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
|
||||||
|
|
||||||
|
16:44:53.240 <- configsip: 0, SPIWP:0xee
|
||||||
|
|
||||||
|
16:44:53.260 -> BU.BEGIN
|
||||||
|
16:44:53.260 <- clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
|
||||||
|
|
||||||
|
16:44:53.262 <- mode:DIO, clock div:2
|
||||||
|
|
||||||
|
16:44:53.266 <- load:0x3fffeba4,len:4
|
||||||
|
|
||||||
|
16:44:53.268 <- load:0x4009f000,len:3248
|
||||||
|
|
||||||
|
16:44:53.269 <- entry 0x4009f574
|
||||||
|
|
||||||
|
16:44:53.583 <- <20>OHAI<41>ets Jul 29 2019 12:21:46
|
||||||
|
|
||||||
|
16:44:53.584 <-
|
||||||
|
|
||||||
|
16:44:53.584 <- rst:0x10 (RTCWDT_RTC_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
|
||||||
|
|
||||||
|
16:44:53.586 <- configsip: 0, SPIWP:0xee
|
||||||
|
|
||||||
|
16:44:53.590 <- clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
|
||||||
|
|
||||||
|
16:44:53.590 <- mode:DIO, clock div:2
|
||||||
|
|
||||||
|
16:44:53.591 <- load:0x3fffeba4,len:4
|
||||||
|
|
||||||
|
16:44:53.922 <- load:0x4009f000,len:3248
|
||||||
|
|
||||||
|
16:44:53.923 <- entry 0x4009f574
|
||||||
|
|
||||||
|
16:44:53.924 <- <20>OHAI<41>ets Jul 29 2019 12:21:46
|
||||||
|
|
||||||
|
16:44:53.925 <-
|
||||||
|
|
||||||
|
16:44:53.926 <- rst:0x10 (RTCWDT_RTC_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
|
||||||
|
|
||||||
|
16:44:53.930 <- configsip: 0, SPIWP:0xee
|
||||||
|
|
||||||
|
16:44:53.931 <- clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
|
||||||
|
|
||||||
|
16:44:53.932 <- mode:DIO, clock div:2
|
||||||
|
|
||||||
|
16:44:54.266 <- load:0x3fffeba4,len:4
|
||||||
|
|
||||||
|
16:44:54.266 <- load:0x4009f000,len:3248
|
||||||
|
|
||||||
|
16:44:54.271 <- entry 0x4009f574
|
||||||
|
|
||||||
|
16:44:54.272 <- <20>OHAI<41>ets Jul 29 2019 12:21:46
|
||||||
|
|
||||||
|
16:44:54.273 <-
|
||||||
|
|
||||||
|
16:44:54.273 <- rst:0x10 (RTCWDT_RTC_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
|
||||||
|
|
||||||
|
16:44:54.273 <- configsip: 0, SPIWP:0xee
|
||||||
|
|
||||||
|
16:44:54.274 <- clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
|
||||||
|
|
||||||
|
16:44:54.604 <- mode:DIO, clock div:2
|
||||||
|
|
||||||
|
16:44:54.604 <- load:0x3fffeba4,len:4
|
||||||
|
|
||||||
|
16:44:54.606 <- load:0x4009f000,len:3248
|
||||||
|
|
||||||
|
16:44:54.610 <- entry 0x4009f574
|
||||||
|
|
||||||
|
16:44:54.610 <- <20>OHAI<41>ets Jul 29 2019 12:21:46
|
||||||
|
|
||||||
|
16:44:54.611 <-
|
||||||
|
|
||||||
|
16:44:54.611 <- rst:0x10 (RTCWDT_RTC_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
|
||||||
|
|
||||||
|
16:44:54.612 <- configsip: 0, SPIWP:0xee
|
||||||
|
|
||||||
|
16:44:54.776 -> BU.BEGIN
|
||||||
|
16:44:54.776 <- clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
|
||||||
|
|
||||||
|
16:44:54.778 <- mode:DIO, clock div:2
|
||||||
|
|
||||||
|
16:44:54.782 <- load:0x3fffeba4,len:4
|
||||||
|
|
||||||
|
16:44:54.782 <- load:0x4009f000,len:3248
|
||||||
|
|
||||||
|
16:44:54.783 <- entry 0x4009f574
|
||||||
|
|
||||||
|
16:44:54.956 <- <20>OHAI<41>ets Jul 29 2019 12:21:46
|
||||||
|
|
||||||
|
16:44:54.956 <-
|
||||||
|
|
||||||
|
16:44:54.956 <- rst:0x10 (RTCWDT_RTC_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
|
||||||
|
|
||||||
|
16:44:54.958 <- configsip: 0, SPIWP:0xee
|
||||||
|
|
||||||
|
16:44:54.958 <- clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
|
||||||
|
|
||||||
|
16:44:54.963 <- mode:DIO, clock div:2
|
||||||
|
|
||||||
|
16:44:54.963 <- load:0x3fffeba4,len:4
|
||||||
|
|
||||||
|
16:44:55.296 <- load:0x4009f000,len:3248
|
||||||
|
|
||||||
|
16:44:55.307 <- entry 0x4009f574
|
||||||
|
|
||||||
|
16:44:55.309 <- <20>OHAI<41>ets Jul 29 2019 12:21:46
|
||||||
|
|
||||||
|
16:44:55.313 <-
|
||||||
|
|
||||||
|
16:44:55.313 <- rst:0x10 (RTCWDT_RTC_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
|
||||||
|
|
||||||
|
16:44:55.314 <- configsip: 0, SPIWP:0xee
|
||||||
|
|
||||||
|
16:44:55.315 <- clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
|
||||||
|
|
||||||
|
16:44:55.315 <- mode:DIO, clock div:2
|
||||||
|
|
||||||
|
16:44:55.638 <- load:0x3fffeba4,len:4
|
||||||
|
|
||||||
|
16:44:55.639 <- load:0x4009f000,len:3248
|
||||||
|
|
||||||
|
16:44:55.641 <- entry 0x4009f574
|
||||||
|
|
||||||
|
16:44:55.641 <- <20>OHAI<41>ets Jul 29 2019 12:21:46
|
||||||
|
|
||||||
|
16:44:55.646 <-
|
||||||
|
|
||||||
|
16:44:55.646 <- rst:0x10 (RTCWDT_RTC_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
|
||||||
|
|
||||||
|
16:44:55.647 <- configsip: 0, SPIWP:0xee
|
||||||
|
|
||||||
|
16:44:55.648 <- clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
|
||||||
|
|
||||||
|
16:44:55.977 <- mode:DIO, clock div:2
|
||||||
|
|
||||||
|
16:44:55.977 <- load:0x3fffeba4,len:4
|
||||||
|
|
||||||
|
16:44:55.978 <- load:0x4009f000,len:3248
|
||||||
|
|
||||||
|
16:44:55.979 <- entry 0x4009f574
|
||||||
|
|
||||||
|
16:44:55.983 <- <20>OHAI<41>ets Jul 29 2019 12:21:46
|
||||||
|
|
||||||
|
16:44:55.984 <-
|
||||||
|
|
||||||
|
16:44:55.984 <- rst:0x10 (RTCWDT_RTC_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
|
||||||
|
|
||||||
|
16:44:55.985 <- configsip: 0, SPIWP:0xee
|
||||||
|
|
||||||
|
16:44:56.291 -> BU.BEGIN
|
||||||
|
16:44:56.291 <- clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
|
||||||
|
|
||||||
|
16:44:56.293 <- mode:DIO, clock div:2
|
||||||
|
|
||||||
|
16:44:56.293 <- load:0x3fffeba4,len:4
|
||||||
|
|
||||||
|
16:44:56.293 <- load:0x4009f000,len:3248
|
||||||
|
|
||||||
|
16:44:56.297 <- entry 0x4009f574
|
||||||
|
|
||||||
|
16:44:56.328 <- <20>OHAI<41>ets Jul 29 2019 12:21:46
|
||||||
|
|
||||||
|
16:44:56.328 <-
|
||||||
|
|
||||||
|
16:44:56.328 <- rst:0x10 (RTCWDT_RTC_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
|
||||||
|
|
||||||
|
16:44:56.330 <- configsip: 0, SPIWP:0xee
|
||||||
|
|
||||||
|
16:44:56.334 <- clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
|
||||||
|
|
||||||
|
16:44:56.337 <- mode:DIO, clock div:2
|
||||||
|
|
||||||
|
16:44:56.339 <- load:0x3fffeba4,len:4
|
||||||
|
|
||||||
|
16:44:56.668 <- load:0x4009f000,len:3248
|
||||||
|
|
||||||
|
16:44:56.668 <- entry 0x4009f574
|
||||||
|
|
||||||
|
16:44:56.670 <- <20>OHAI<41>ets Jul 29 2019 12:21:46
|
||||||
|
|
||||||
|
16:44:56.681 <-
|
||||||
|
|
||||||
|
16:44:56.681 <- rst:0x10 (RTCWDT_RTC_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
|
||||||
|
|
||||||
|
16:44:56.681 <- configsip: 0, SPIWP:0xee
|
||||||
|
|
||||||
|
16:44:56.683 <- clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
|
||||||
|
|
||||||
|
16:44:56.683 <- mode:DIO, clock div:2
|
||||||
|
|
||||||
|
16:44:57.011 <- load:0x3fffeba4,len:4
|
||||||
|
|
||||||
|
16:44:57.011 <- load:0x4009f000,len:3248
|
||||||
|
|
||||||
|
16:44:57.014 <- entry 0x4009f574
|
||||||
|
|
||||||
|
16:44:57.014 <- <20>OHAI<41>ets Jul 29 2019 12:21:46
|
||||||
|
|
||||||
|
16:44:57.015 <-
|
||||||
|
|
||||||
|
16:44:57.015 <- rst:0x10 (RTCWDT_RTC_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
|
||||||
|
|
||||||
|
16:44:57.019 <- configsip: 0, SPIWP:0xee
|
||||||
|
|
||||||
|
16:44:57.020 <- clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
|
||||||
|
|
||||||
|
16:44:57.349 <- mode:DIO, clock div:2
|
||||||
|
|
||||||
|
16:44:57.350 <- load:0x3fffeba4,len:4
|
||||||
|
|
||||||
|
16:44:57.352 <- load:0x4009f000,len:3248
|
||||||
|
|
||||||
|
16:44:57.356 <- entry 0x4009f574
|
||||||
|
|
||||||
|
16:44:57.357 <- <20>OHAI<41>ets Jul 29 2019 12:21:46
|
||||||
|
|
||||||
|
16:44:57.357 <-
|
||||||
|
|
||||||
|
16:44:57.358 <- rst:0x10 (RTCWDT_RTC_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
|
||||||
|
|
||||||
|
16:44:57.358 <- configsip: 0, SPIWP:0xee
|
||||||
|
|
||||||
|
16:44:57.692 <- clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
|
||||||
|
|
||||||
|
16:44:57.692 <- mode:DIO, clock div:2
|
||||||
|
|
||||||
|
16:44:57.694 <- load:0x3fffeba4,len:4
|
||||||
|
|
||||||
|
16:44:57.699 <- load:0x4009f000,len:3248
|
||||||
|
|
||||||
|
16:44:57.700 <- entry 0x4009f574
|
||||||
|
|
||||||
|
16:44:57.700 <- <20>OHAI<41>ets Jul 29 2019 12:21:46
|
||||||
|
|
||||||
|
16:44:57.701 <-
|
||||||
|
|
||||||
|
16:44:57.701 <- rst:0x10 (RTCWDT_RTC_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
|
||||||
|
|
||||||
|
16:44:57.702 <- configsip: 0, SPIWP:0xee
|
||||||
|
|
||||||
|
16:44:57.809 -> BU.BEGIN
|
||||||
|
16:44:57.809 <- clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
|
||||||
|
|
||||||
|
16:44:57.811 <- mode:DIO, clock div:2
|
||||||
|
|
||||||
|
16:44:57.815 <- load:0x3fffeba4,len:4
|
||||||
|
|
||||||
|
16:44:57.816 <- load:0x4009f000,len:3248
|
||||||
|
|
||||||
|
16:44:57.816 <- entry 0x4009f574
|
||||||
|
|
||||||
|
16:44:58.045 <- <20>OHAI<41>ets Jul 29 2019 12:21:46
|
||||||
|
|
||||||
|
16:44:58.045 <-
|
||||||
|
|
||||||
|
16:44:58.045 <- rst:0x10 (RTCWDT_RTC_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
|
||||||
|
|
||||||
|
16:44:58.047 <- configsip: 0, SPIWP:0xee
|
||||||
|
|
||||||
|
16:44:58.051 <- clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
|
||||||
|
|
||||||
|
16:44:58.051 <- mode:DIO, clock div:2
|
||||||
|
|
||||||
|
16:44:58.052 <- load:0x3fffeba4,len:4
|
||||||
|
|
||||||
|
16:44:58.384 <- load:0x4009f000,len:3248
|
||||||
|
|
||||||
|
16:44:58.384 <- entry 0x4009f574
|
||||||
|
|
||||||
|
16:44:58.390 <- <20>OHAI<41>ets Jul 29 2019 12:21:46
|
||||||
|
|
||||||
|
16:44:58.391 <-
|
||||||
|
|
||||||
|
16:44:58.391 <- rst:0x10 (RTCWDT_RTC_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
|
||||||
|
|
||||||
|
16:44:58.392 <- configsip: 0, SPIWP:0xee
|
||||||
|
|
||||||
|
16:44:58.392 <- clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
|
||||||
|
|
||||||
|
16:44:58.393 <- mode:DIO, clock div:2
|
||||||
|
|
||||||
|
16:44:58.728 <- load:0x3fffeba4,len:4
|
||||||
|
|
||||||
|
16:44:58.728 <- load:0x4009f000,len:3248
|
||||||
|
|
||||||
|
16:44:58.730 <- entry 0x4009f574
|
||||||
|
|
||||||
|
16:44:58.733 <- <20>OHAI<41>ets Jul 29 2019 12:21:46
|
||||||
|
|
||||||
|
16:44:58.734 <-
|
||||||
|
|
||||||
|
16:44:58.734 <- rst:0x10 (RTCWDT_RTC_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
|
||||||
|
|
||||||
|
16:44:58.735 <- configsip: 0, SPIWP:0xee
|
||||||
|
|
||||||
|
16:44:58.735 <- clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
|
||||||
|
|
||||||
|
16:44:59.066 <- mode:DIO, clock div:2
|
||||||
|
|
||||||
|
16:45:03.464 -> BU.BEGIN
|
||||||
|
16:45:03.464 <- load:0x3fffeba4,len:4
|
||||||
|
|
||||||
|
16:45:03.465 <- load:0x4009f000,len:3248
|
||||||
|
|
||||||
|
16:45:03.466 <- entry 0x4009f574
|
||||||
|
|
||||||
|
16:45:03.466 <- <20>OHAI<41>ets Jul 29 2019 12:21:46
|
||||||
|
|
||||||
|
16:45:03.467 <-
|
||||||
|
|
||||||
|
16:45:03.467 <- rst:0x10 (RTCWDT_RTC_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
|
||||||
|
|
||||||
|
16:45:03.468 <- configsip: 0, SPIWP:0xee
|
||||||
|
|
||||||
|
16:45:03.469 <- clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
|
||||||
|
|
||||||
|
16:45:03.469 <- mode:DIO, clock div:2
|
||||||
|
|
||||||
|
16:45:03.470 <- load:0x3fffeba4,len:4
|
||||||
|
|
||||||
219
BRINGUP_10JUN2026_1645.txt
Normal file
219
BRINGUP_10JUN2026_1645.txt
Normal file
@@ -0,0 +1,219 @@
|
|||||||
|
16:45:30.920
|
||||||
|
------------------------------------------------------------
|
||||||
|
Flashing COM3
|
||||||
|
------------------------------------------------------------
|
||||||
|
16:45:30.924 flashing from D:\SC\SC-F001\build
|
||||||
|
16:45:30.925 files: [('0x1000', 'bootloader/bootloader.bin'), ('0x10000', 'SC-F001.bin'), ('0x8000', 'partition_table/partition-table.bin'), ('0xd000', 'ota_data_initial.bin')]
|
||||||
|
16:45:56.045 Flash complete
|
||||||
|
16:45:57.546 Connecting to COM3 @ 115200 ...
|
||||||
|
16:45:57.555 -> BU.BEGIN
|
||||||
|
16:45:57.820 <- BU.BEGIN
|
||||||
|
|
||||||
|
16:45:57.820 <-
|
||||||
|
|
||||||
|
16:45:57.820 <-
|
||||||
|
|
||||||
|
16:45:57.820 <- BU.OK begin fw=85206e1 board=V5 t=0.00
|
||||||
|
|
||||||
|
16:45:57.822 -> BU.INFO
|
||||||
|
16:45:57.823 <- E rmt: hw buffer too small, received symbols truncated
|
||||||
|
|
||||||
|
16:45:57.823 <- I (1870) BT_HID: Scanning for HID devices (3s)...
|
||||||
|
|
||||||
|
16:45:57.823 <- I (1870) BT_HID: BLE HID host initialised
|
||||||
|
|
||||||
|
16:45:57.839 <- I (1870) WEBSERVER: Initializing webserver...
|
||||||
|
|
||||||
|
16:45:57.839 <- I (1870) WEBSERVER: AP LAUNCHING
|
||||||
|
|
||||||
|
16:45:57.839 <- I (1890) wifi:wifi driver task: 3ffe28f8, prio:23, stack:6656, core=0
|
||||||
|
|
||||||
|
16:45:57.839 <- I (1890) wifi:wifi firmware version: ccaebfa
|
||||||
|
|
||||||
|
16:45:57.839 <- I (1890) wifi:wifi certification version: v7.0
|
||||||
|
|
||||||
|
16:45:57.866 <- I (1890) wifi:config NVS flash: enabled
|
||||||
|
|
||||||
|
16:45:57.866 <- I (1890) wifi:config nano formating: disabled
|
||||||
|
|
||||||
|
16:45:57.866 <- I (1900) wifi:Init data frame dynamic rx buffer num: 32
|
||||||
|
|
||||||
|
16:45:57.866 <- I (1900) wifi:Init static rx mgmt buffer num: 5
|
||||||
|
|
||||||
|
16:45:57.866 <- I (1910) wifi:Init management short buffer num: 32
|
||||||
|
|
||||||
|
16:45:57.866 <-
|
||||||
|
|
||||||
|
16:45:57.887 <- BU.OK info reset=POWERON heap=111556 min_heap=111556 fw=85206e1 build=2026-06-10 21:41:39
|
||||||
|
|
||||||
|
16:46:26.006 -> BU.FLASH
|
||||||
|
16:46:26.007 <- I (1910) wifi:Init dynamic tx buffer num: 32
|
||||||
|
|
||||||
|
16:46:26.007 <- I (1920) wifi:Init static rx buffer size: 1600
|
||||||
|
|
||||||
|
16:46:26.007 <- I (1930) wifi:Init static rx buffer num: 10
|
||||||
|
|
||||||
|
16:46:26.007 <- I (1930) wifi:Init dynamic rx buffer num: 32
|
||||||
|
|
||||||
|
16:46:26.007 <- I (1940) wifi_init: rx ba win: 6
|
||||||
|
|
||||||
|
16:46:26.008 <- I (1940) wifi_init: accept mbox: 6
|
||||||
|
|
||||||
|
16:46:26.008 <- I (1940) wifi_init: tcpip mbox: 32
|
||||||
|
|
||||||
|
16:46:26.008 <- I (1950) wifi_init: udp mbox: 6
|
||||||
|
|
||||||
|
16:46:26.008 <- I (1950) wifi_init: tcp mbox: 6
|
||||||
|
|
||||||
|
16:46:26.008 <- I (1950) wifi_init: tcp tx win: 5760
|
||||||
|
|
||||||
|
16:46:26.008 <- I (1960) wifi_init: tcp rx win: 5760
|
||||||
|
|
||||||
|
16:46:26.008 <- I (1960) wifi_init: tcp mss: 1440
|
||||||
|
|
||||||
|
16:46:26.008 <- I (2250) wifi:mode : softAP (80:f3:da:64:fa:19)
|
||||||
|
|
||||||
|
16:46:26.008 <- I (2260) wifi:Total power save buffer number: 16
|
||||||
|
|
||||||
|
16:46:26.009 <- I (2260) wifi:Init max length of beacon: 752/752
|
||||||
|
|
||||||
|
16:46:26.009 <- I (2260) wifi:Init max length of beacon: 752/752
|
||||||
|
|
||||||
|
16:46:26.009 <- I (2260) esp_netif_lwip: DHCP server started on interface WIFI_AP_DEF with IP: 192.168.4.1
|
||||||
|
|
||||||
|
16:46:26.009 <- I (2270) DNS_SERVER: DNS server started on port 53
|
||||||
|
|
||||||
|
16:46:26.009 <- I (2270) mdns_mem: mDNS task will be created from internal RAM
|
||||||
|
|
||||||
|
16:46:26.009 <- I (2280) WEBSERVER: SoftAP ready. SSID: sc.local, Channel: 6, Password: password
|
||||||
|
|
||||||
|
16:46:26.009 <- I (2290) WEBSERVER: Access at: http://sc.local.local or http://192.168.4.1
|
||||||
|
|
||||||
|
16:46:26.010 <- I (2300) WEBSERVER: STARTING HTTP
|
||||||
|
|
||||||
|
16:46:26.010 <- I (2300) WEBSERVER: HTTP server started successfully
|
||||||
|
|
||||||
|
16:46:26.010 <- I (2310) WEBSERVER: Registered URI handler: /
|
||||||
|
|
||||||
|
16:46:26.010 <- I (2310) WEBSERVER: Registered URI handler: /get
|
||||||
|
|
||||||
|
16:46:26.010 <- I (2320) WEBSERVER: Registered URI handler: /post
|
||||||
|
|
||||||
|
16:46:26.011 <- I (2320) WEBSERVER: Registered URI handler: /log
|
||||||
|
|
||||||
|
16:46:26.011 <- I (2330) WEBSERVER: Registered URI handler: /ota
|
||||||
|
|
||||||
|
16:46:26.011 <- I (2330) WEBSERVER: Registered URI handler: /*
|
||||||
|
|
||||||
|
16:46:26.011 <- I (2340) WEBSERVER: Webserver initialization complete
|
||||||
|
|
||||||
|
16:46:26.011 <- E rmt: hw buffer too small, received symbols truncated
|
||||||
|
|
||||||
|
16:46:26.011 <- I (4880) BT_HID: Found 0 HID device(s)
|
||||||
|
|
||||||
|
16:46:26.011 <- I (4880) BT_HID: No HID devices found, retrying in 2000ms...
|
||||||
|
|
||||||
|
16:46:26.011 <- E rmt: hw buffer too small, received symbols truncated
|
||||||
|
|
||||||
|
16:46:26.012 <- E rmt: hw buffer too small, received symbols truncated
|
||||||
|
|
||||||
|
16:46:26.012 <- E rmt: hw buffer too small, received symbols truncated
|
||||||
|
|
||||||
|
16:46:26.012 <- E rmt: hw buffer too small, received symbols truncated
|
||||||
|
|
||||||
|
16:46:26.012 <- I (6880) BT_HID: Scanning for HID devices (3s)...
|
||||||
|
|
||||||
|
16:46:26.012 <- E rmt: hw buffer too small, received symbols truncated
|
||||||
|
|
||||||
|
16:46:26.013 <- E rmt: hw buffer too small, received symbols truncated
|
||||||
|
|
||||||
|
16:46:26.013 <- E rmt: hw buffer too small, received symbols truncated
|
||||||
|
|
||||||
|
16:46:26.013 <- E rmt: hw buffer too small, received symbols truncated
|
||||||
|
|
||||||
|
16:46:26.013 <- E rmt: hw buffer too small, received symbols truncated
|
||||||
|
|
||||||
|
16:46:26.014 <- E rmt: hw buffer too small, received symbols truncated
|
||||||
|
|
||||||
|
16:46:26.014 <- E rmt: hw buffer too small, received symbols truncated
|
||||||
|
|
||||||
|
16:46:26.014 <- E rmt: hw buffer too small, received symbols truncated
|
||||||
|
|
||||||
|
16:46:26.014 <- I (9890) BT_HID: Found 0 HID device(s)
|
||||||
|
|
||||||
|
16:46:26.014 <- I (9890) BT_HID: No HID devices found, retrying in 2000ms...
|
||||||
|
|
||||||
|
16:46:26.014 <- E rmt: hw buffer too small, received symbols truncated
|
||||||
|
|
||||||
|
16:46:26.014 <- I (11890) BT_HID: Scanning for HID devices (3s)...
|
||||||
|
|
||||||
|
16:46:26.014 <- E rmt: hw buffer too small, received symbols truncated
|
||||||
|
|
||||||
|
16:46:26.014 <- I (14900) BT_HID: Found 0 HID device(s)
|
||||||
|
|
||||||
|
16:46:26.015 <- I (14900) BT_HID: No HID devices found, retrying in 2000ms...
|
||||||
|
|
||||||
|
16:46:26.015 <- E rmt: hw buffer too small, received symbols truncated
|
||||||
|
|
||||||
|
16:46:26.015 <- I (16900) BT_HID: Scanning for HID devices (3s)...
|
||||||
|
|
||||||
|
16:46:26.015 <- E rmt: hw buffer too small, received symbols truncated
|
||||||
|
|
||||||
|
16:46:26.016 <- I (19910) BT_HID: Found 0 HID device(s)
|
||||||
|
|
||||||
|
16:46:26.016 <- I (19910) BT_HID: No HID devices found, retrying in 2000ms...
|
||||||
|
|
||||||
|
16:46:26.016 <- E rmt: hw buffer too small, received symbols truncated
|
||||||
|
|
||||||
|
16:46:26.016 <- I (21910) BT_HID: Scanning for HID devices (3s)...
|
||||||
|
|
||||||
|
16:46:26.016 <- E rmt: hw buffer too small, received symbols truncated
|
||||||
|
|
||||||
|
16:46:26.017 <- E rmt: hw buffer too small, received symbols truncated
|
||||||
|
|
||||||
|
16:46:26.017 <- E rmt: hw buffer too small, received symbols truncated
|
||||||
|
|
||||||
|
16:46:26.017 <- I (24920) BT_HID: Found 0 HID device(s)
|
||||||
|
|
||||||
|
16:46:26.017 <- I (24920) BT_HID: No HID devices found, retrying in 2000ms...
|
||||||
|
|
||||||
|
16:46:26.017 <- E rmt: hw buffer too small, received symbols truncated
|
||||||
|
|
||||||
|
16:46:26.017 <- E rmt: hw buffer too small, received symbols truncated
|
||||||
|
|
||||||
|
16:46:26.017 <- E rmt: hw buffer too small, received symbols truncated
|
||||||
|
|
||||||
|
16:46:26.017 <- I (26920) BT_HID: Scanning for HID devices (3s)...
|
||||||
|
|
||||||
|
16:46:26.036 <- E rmt: hw buffer too small, received symbols truncated
|
||||||
|
|
||||||
|
16:46:26.036 <- I (29930) BT_HID: Found 0 HID device(s)
|
||||||
|
|
||||||
|
16:46:26.036 <- I (29930) BT_HID: No HID devices found, retrying in 2000ms...
|
||||||
|
|
||||||
|
16:46:26.036 <- E rmt: hw buffer too small, received symbols truncated
|
||||||
|
|
||||||
|
16:46:26.036 <-
|
||||||
|
|
||||||
|
16:46:26.545 <- BU.OK flash post_part=roundtrip log_head=11 log_tail=0 partitions_size=4128768
|
||||||
|
|
||||||
|
16:46:32.269 -> BU.I2C
|
||||||
|
16:46:32.269 <- E rmt: hw buffer too small, received symbols truncated
|
||||||
|
|
||||||
|
16:46:32.269 <- I (31930) BT_HID: Scanning for HID devices (3s)...
|
||||||
|
|
||||||
|
16:46:32.269 <- I (34940) BT_HID: Found 0 HID device(s)
|
||||||
|
|
||||||
|
16:46:32.269 <- I (34940) BT_HID: No HID devices found, retrying in 2000ms...
|
||||||
|
|
||||||
|
16:46:32.272 <- E rmt: hw buffer too small, received symbols truncated
|
||||||
|
|
||||||
|
16:46:32.272 <- E rmt: hw buffer too small, received symbols truncated
|
||||||
|
|
||||||
|
16:46:32.272 <- E rmt: hw buffer too small, received symbols truncated
|
||||||
|
|
||||||
|
16:46:32.272 <- E rmt: hw buffer too small, received symbols truncated
|
||||||
|
|
||||||
|
16:46:32.777 <- E rmt: hw buffer too small, received symbols truncated
|
||||||
|
|
||||||
|
16:46:32.777 <- I (36350) I2C: POST: TCA9555 OK (port0=0x0123)
|
||||||
BIN
BRINGUP_10JUN2026_1648.txt
Normal file
BIN
BRINGUP_10JUN2026_1648.txt
Normal file
Binary file not shown.
BIN
BRINGUP_13JUN2026_1652.txt
Normal file
BIN
BRINGUP_13JUN2026_1652.txt
Normal file
Binary file not shown.
486
BRINGUP_18MAY2026_1031.txt
Normal file
486
BRINGUP_18MAY2026_1031.txt
Normal file
@@ -0,0 +1,486 @@
|
|||||||
|
10:31:38.960
|
||||||
|
------------------------------------------------------------
|
||||||
|
Flashing COM3
|
||||||
|
------------------------------------------------------------
|
||||||
|
10:31:38.962 erase_flash @ COM3
|
||||||
|
10:31:43.318 flashing from D:\SC\SC-F001\build
|
||||||
|
10:31:43.318 files: [('0x1000', 'bootloader/bootloader.bin'), ('0x10000', 'SC-F001.bin'), ('0x8000', 'partition_table/partition-table.bin'), ('0xd000', 'ota_data_initial.bin')]
|
||||||
|
10:32:08.923 Flash complete
|
||||||
|
10:32:10.424 Connecting to COM3 @ 115200 ...
|
||||||
|
10:32:10.437 -> BU.BEGIN
|
||||||
|
10:32:10.673 <- BU.BEGIN
|
||||||
|
|
||||||
|
10:32:10.673 <-
|
||||||
|
|
||||||
|
10:32:10.673 <-
|
||||||
|
|
||||||
|
10:32:10.673 <- BU.OK begin fw=85206e1 board=V5 t=0.00
|
||||||
|
|
||||||
|
10:32:10.676 -> BU.INFO
|
||||||
|
10:32:10.676 <- E rmt: hw buffer too small, received symbols truncated
|
||||||
|
|
||||||
|
10:32:10.688 <- I (1860) BT_HID: Scanning for HID devices (3s)...
|
||||||
|
|
||||||
|
10:32:10.688 <- I (1860) BT_HID: BLE HID host initialised
|
||||||
|
|
||||||
|
10:32:10.693 <- I (1860) WEBSERVER: Initializing webserver...
|
||||||
|
|
||||||
|
10:32:10.693 <- I (1860) WEBSERVER: AP LAUNCHING
|
||||||
|
|
||||||
|
10:32:10.693 <- I (1880) wifi:wifi driver task: 3ffe28d8, prio:23, stack:6656, core=0
|
||||||
|
|
||||||
|
10:32:10.693 <- I (1880) wifi:wifi firmware version: ccaebfa
|
||||||
|
|
||||||
|
10:32:10.693 <- I (1880) wifi:wifi certification version: v7.0
|
||||||
|
|
||||||
|
10:32:10.720 <- I (1880) wifi:config NVS flash: enabled
|
||||||
|
|
||||||
|
10:32:10.720 <- I (1880) wifi:config nano formating: disabled
|
||||||
|
|
||||||
|
10:32:10.720 <- I (1890) wifi:Init data frame dynamic rx buffer num: 32
|
||||||
|
|
||||||
|
10:32:10.720 <- I (1890) wifi:Init static rx mgmt buffer num: 5
|
||||||
|
|
||||||
|
10:32:10.720 <- I (1900) wifi:Init management short buffer num: 32
|
||||||
|
|
||||||
|
10:32:10.720 <-
|
||||||
|
|
||||||
|
10:32:10.741 <- BU.OK info reset=POWERON heap=111588 min_heap=111588 fw=85206e1 build=2026-05-01 23:33:51
|
||||||
|
|
||||||
|
10:33:33.716 -> BU.FLASH
|
||||||
|
10:33:33.716 <- I (1900) wifi:Init dynamic tx buffer num: 32
|
||||||
|
|
||||||
|
10:33:33.716 <- I (1910) wifi:Init static rx buffer size: 1600
|
||||||
|
|
||||||
|
10:33:33.716 <- I (1920) wifi:Init static rx buffer num: 10
|
||||||
|
|
||||||
|
10:33:33.716 <- I (1920) wifi:Init dynamic rx buffer num: 32
|
||||||
|
|
||||||
|
10:33:33.717 <- I (1930) wifi_init: rx ba win: 6
|
||||||
|
|
||||||
|
10:33:33.717 <- I (1930) wifi_init: accept mbox: 6
|
||||||
|
|
||||||
|
10:33:33.717 <- I (1930) wifi_init: tcpip mbox: 32
|
||||||
|
|
||||||
|
10:33:33.717 <- I (1940) wifi_init: udp mbox: 6
|
||||||
|
|
||||||
|
10:33:33.717 <- I (1940) wifi_init: tcp mbox: 6
|
||||||
|
|
||||||
|
10:33:33.718 <- I (1940) wifi_init: tcp tx win: 5760
|
||||||
|
|
||||||
|
10:33:33.718 <- I (1950) wifi_init: tcp rx win: 5760
|
||||||
|
|
||||||
|
10:33:33.718 <- I (1950) wifi_init: tcp mss: 1440
|
||||||
|
|
||||||
|
10:33:33.718 <- I (2240) wifi:mode : softAP (80:f3:da:65:a9:15)
|
||||||
|
|
||||||
|
10:33:33.718 <- I (2250) wifi:Total power save buffer number: 16
|
||||||
|
|
||||||
|
10:33:33.718 <- I (2250) wifi:Init max length of beacon: 752/752
|
||||||
|
|
||||||
|
10:33:33.719 <- I (2250) wifi:Init max length of beacon: 752/752
|
||||||
|
|
||||||
|
10:33:33.719 <- I (2250) esp_netif_lwip: DHCP server started on interface WIFI_AP_DEF with IP: 192.168.4.1
|
||||||
|
|
||||||
|
10:33:33.719 <- I (2260) DNS_SERVER: DNS server started on port 53
|
||||||
|
|
||||||
|
10:33:33.719 <- I (2260) mdns_mem: mDNS task will be created from internal RAM
|
||||||
|
|
||||||
|
10:33:33.719 <- I (2270) WEBSERVER: SoftAP ready. SSID: sc.local, Channel: 6, Password: password
|
||||||
|
|
||||||
|
10:33:33.720 <- I (2280) WEBSERVER: Access at: http://sc.local.local or http://192.168.4.1
|
||||||
|
|
||||||
|
10:33:33.720 <- I (2280) WEBSERVER: STARTING HTTP
|
||||||
|
|
||||||
|
10:33:33.720 <- I (2290) WEBSERVER: HTTP server started successfully
|
||||||
|
|
||||||
|
10:33:33.720 <- I (2290) WEBSERVER: Registered URI handler: /
|
||||||
|
|
||||||
|
10:33:33.720 <- I (2300) WEBSERVER: Registered URI handler: /get
|
||||||
|
|
||||||
|
10:33:33.720 <- I (2300) WEBSERVER: Registered URI handler: /post
|
||||||
|
|
||||||
|
10:33:33.721 <- I (2310) WEBSERVER: Registered URI handler: /log
|
||||||
|
|
||||||
|
10:33:33.721 <- I (2310) WEBSERVER: Registered URI handler: /ota
|
||||||
|
|
||||||
|
10:33:33.721 <- I (2320) WEBSERVER: Registered URI handler: /*
|
||||||
|
|
||||||
|
10:33:33.721 <- I (2330) WEBSERVER: Webserver initialization complete
|
||||||
|
|
||||||
|
10:33:33.722 <- E rmt: hw buffer too small, received symbols truncated
|
||||||
|
|
||||||
|
10:33:33.722 <- E rmt: hw buffer too small, received symbols truncated
|
||||||
|
|
||||||
|
10:33:33.722 <- I (4870) BT_HID: Found 0 HID device(s)
|
||||||
|
|
||||||
|
10:33:33.722 <- I (4870) BT_HID: No HID devices found, retrying in 2000ms...
|
||||||
|
|
||||||
|
10:33:33.722 <- I (6870) BT_HID: Scanning for HID devices (3s)...
|
||||||
|
|
||||||
|
10:33:33.722 <- E rmt: hw buffer too small, received symbols truncated
|
||||||
|
|
||||||
|
10:33:33.722 <- I (9880) BT_HID: Found 0 HID device(s)
|
||||||
|
|
||||||
|
10:33:33.722 <- I (9880) BT_HID: No HID devices found, retrying in 2000ms...
|
||||||
|
|
||||||
|
10:33:33.723 <- E rmt: hw buffer too small, received symbols truncated
|
||||||
|
|
||||||
|
10:33:33.723 <- E rmt: hw buffer too small, received symbols truncated
|
||||||
|
|
||||||
|
10:33:33.723 <- E rmt: hw buffer too small, received symbols truncated
|
||||||
|
|
||||||
|
10:33:33.723 <- I (11880) BT_HID: Scanning for HID devices (3s)...
|
||||||
|
|
||||||
|
10:33:33.723 <- E rmt: hw buffer too small, received symbols truncated
|
||||||
|
|
||||||
|
10:33:33.724 <- E rmt: hw buffer too small, received symbols truncated
|
||||||
|
|
||||||
|
10:33:33.724 <- I (14890) BT_HID: Found 0 HID device(s)
|
||||||
|
|
||||||
|
10:33:33.724 <- I (14890) BT_HID: No HID devices found, retrying in 2000ms...
|
||||||
|
|
||||||
|
10:33:33.724 <- E rmt: hw buffer too small, received symbols truncated
|
||||||
|
|
||||||
|
10:33:33.725 <- E rmt: hw buffer too small, received symbols truncated
|
||||||
|
|
||||||
|
10:33:33.725 <- I (16890) BT_HID: Scanning for HID devices (3s)...
|
||||||
|
|
||||||
|
10:33:33.725 <- E rmt: hw buffer too small, received symbols truncated
|
||||||
|
|
||||||
|
10:33:33.725 <- E rmt: hw buffer too small, received symbols truncated
|
||||||
|
|
||||||
|
10:33:33.725 <- E rmt: hw buffer too small, received symbols truncated
|
||||||
|
|
||||||
|
10:33:33.725 <- E rmt: hw buffer too small, received symbols truncated
|
||||||
|
|
||||||
|
10:33:33.725 <- E rmt: hw buffer too small, received symbols truncated
|
||||||
|
|
||||||
|
10:33:33.725 <- E rmt: hw buffer too small, received symbols truncated
|
||||||
|
|
||||||
|
10:33:33.725 <- I (19900) BT_HID: Found 0 HID device(s)
|
||||||
|
|
||||||
|
10:33:33.726 <- I (19900) BT_HID: No HID devices found, retrying in 2000ms...
|
||||||
|
|
||||||
|
10:33:33.726 <- E rmt: hw buffer too small, received symbols truncated
|
||||||
|
|
||||||
|
10:33:33.726 <- I (21900) BT_HID: Scanning for HID devices (3s)...
|
||||||
|
|
||||||
|
10:33:33.726 <- E rmt: hw buffer too small, received symbols truncated
|
||||||
|
|
||||||
|
10:33:33.727 <- E rmt: hw buffer too small, received symbols truncated
|
||||||
|
|
||||||
|
10:33:33.727 <- E rmt: hw buffer too small, received symbols truncated
|
||||||
|
|
||||||
|
10:33:33.727 <- E rmt: hw buffer too small, received symbols truncated
|
||||||
|
|
||||||
|
10:33:33.727 <- E rmt: hw buffer too small, received symbols truncated
|
||||||
|
|
||||||
|
10:33:33.727 <- I (24910) BT_HID: Found 0 HID device(s)
|
||||||
|
|
||||||
|
10:33:33.728 <- I (24910) BT_HID: No HID devices found, retrying in 2000ms...
|
||||||
|
|
||||||
|
10:33:33.728 <- E rmt: hw buffer too small, received symbols truncated
|
||||||
|
|
||||||
|
10:33:33.728 <- I (26910) BT_HID: Scanning for HID devices (3s)...
|
||||||
|
|
||||||
|
10:33:33.728 <- E rmt: hw buffer too small, received symbols truncated
|
||||||
|
|
||||||
|
10:33:33.729 <- I (29920) BT_HID: Found 0 HID device(s)
|
||||||
|
|
||||||
|
10:33:33.729 <- I (29920) BT_HID: No HID devices found, retrying in 2000ms...
|
||||||
|
|
||||||
|
10:33:33.729 <- E rmt: hw buffer too small, received symbols truncated
|
||||||
|
|
||||||
|
10:33:33.729 <- I (31920) BT_HID: Scanning for HID devices (3s)...
|
||||||
|
|
||||||
|
10:33:33.730 <- I (34930) BT_HID: Found 0 HID device(s)
|
||||||
|
|
||||||
|
10:33:33.730 <- I ( small, received symbols truncated
|
||||||
|
|
||||||
|
10:33:33.730 <- E rmt: hw buffer too small, received symbols truncated
|
||||||
|
|
||||||
|
10:33:33.730 <- E rmt: hw buffer too small, rece, received symbols truncated
|
||||||
|
|
||||||
|
10:33:33.730 <- E rmt: hw buffer too small, rece, received symbols truncated
|
||||||
|
|
||||||
|
10:33:33.730 <- E rmt: hw buffer too small, received symbols truncated
|
||||||
|
|
||||||
|
10:33:33.730 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.730 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.730 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.730 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.730 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.730 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.730 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.730 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.730 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.731 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.731 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.731 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.731 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.731 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.731 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.731 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.731 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.731 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.731 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.732 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.732 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.732 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.732 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.732 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.732 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.732 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.732 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.732 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.732 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.732 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.732 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.732 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.732 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.732 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.732 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.732 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.732 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.732 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.732 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.732 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.732 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.733 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.733 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.733 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.733 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.733 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.733 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.733 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.733 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.733 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.733 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.734 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.734 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.734 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.734 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.734 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.734 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.734 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.734 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.734 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.734 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.734 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.735 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.735 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.735 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.735 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.735 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.735 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.735 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.735 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.735 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.735 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.735 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.736 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.736 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.736 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.736 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.736 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.736 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.736 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.736 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.736 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.736 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.736 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.736 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.736 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.736 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.736 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.736 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.736 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.736 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.736 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.736 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.736 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.737 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.737 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.737 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.737 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.737 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.737 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.737 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.737 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.737 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.737 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.737 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.738 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.738 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.738 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.738 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.738 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.738 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.738 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.738 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.738 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.738 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.739 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.739 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.739 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.739 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.739 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.739 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.739 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.739 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.739 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.739 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.739 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.740 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.740 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.740 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.740 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.740 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.740 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.740 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.740 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.740 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.740 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.740 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.741 <- ived symbols truncated
|
||||||
|
|
||||||
|
10:33:33.741 <- ived symbols truncated
|
||||||
|
|
||||||
137
BRINGUP_18MAY2026_1033.txt
Normal file
137
BRINGUP_18MAY2026_1033.txt
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
10:33:57.732
|
||||||
|
------------------------------------------------------------
|
||||||
|
Flashing COM3
|
||||||
|
------------------------------------------------------------
|
||||||
|
10:33:57.734 erase_flash @ COM3
|
||||||
|
10:34:01.505 flashing from D:\SC\SC-F001\build
|
||||||
|
10:34:01.507 files: [('0x1000', 'bootloader/bootloader.bin'), ('0x10000', 'SC-F001.bin'), ('0x8000', 'partition_table/partition-table.bin'), ('0xd000', 'ota_data_initial.bin')]
|
||||||
|
10:34:26.696 Flash complete
|
||||||
|
10:34:28.197 Connecting to COM3 @ 115200 ...
|
||||||
|
10:34:28.210 -> BU.BEGIN
|
||||||
|
10:34:28.470 <- BU.BEGIN
|
||||||
|
|
||||||
|
10:34:28.470 <-
|
||||||
|
|
||||||
|
10:34:28.470 <-
|
||||||
|
|
||||||
|
10:34:28.470 <- BU.OK begin fw=85206e1 board=V5 t=0.00
|
||||||
|
|
||||||
|
10:34:28.473 -> BU.INFO
|
||||||
|
10:34:28.473 <- E rmt: hw buffer too small, received symbols truncated
|
||||||
|
|
||||||
|
10:34:28.485 <- I (1880) BT_HID: Scanning for HID devices (3s)...
|
||||||
|
|
||||||
|
10:34:28.485 <- I (1880) BT_HID: BLE HID host initialised
|
||||||
|
|
||||||
|
10:34:28.490 <- I (1880) WEBSERVER: Initializing webserver...
|
||||||
|
|
||||||
|
10:34:28.490 <- I (1880) WEBSERVER: AP LAUNCHING
|
||||||
|
|
||||||
|
10:34:28.490 <- I (1900) wifi:wifi driver task: 3ffe28f8, prio:23, stack:6656, core=0
|
||||||
|
|
||||||
|
10:34:28.490 <- I (1900) wifi:wifi firmware version: ccaebfa
|
||||||
|
|
||||||
|
10:34:28.490 <- I (1900) wifi:wifi certification version: v7.0
|
||||||
|
|
||||||
|
10:34:28.516 <- I (1900) wifi:config NVS flash: enabled
|
||||||
|
|
||||||
|
10:34:28.516 <- I (1900) wifi:config nano formating: disabled
|
||||||
|
|
||||||
|
10:34:28.516 <- I (1910) wifi:Init data frame dynamic rx buffer num: 32
|
||||||
|
|
||||||
|
10:34:28.516 <- I (1910) wifi:Init static rx mgmt buffer num: 5
|
||||||
|
|
||||||
|
10:34:28.516 <- I (1920) wifi:Init management short buffer num: 32
|
||||||
|
|
||||||
|
10:34:28.516 <-
|
||||||
|
|
||||||
|
10:34:28.538 <- BU.OK info reset=POWERON heap=111556 min_heap=111556 fw=85206e1 build=2026-05-01 23:33:51
|
||||||
|
|
||||||
|
10:34:30.409 -> BU.FLASH
|
||||||
|
10:34:30.409 <- I (1920) wifi:Init dynamic tx buffer num: 32
|
||||||
|
|
||||||
|
10:34:30.410 <- I (1930) wifi:Init static rx buffer size: 1600
|
||||||
|
|
||||||
|
10:34:30.410 <- I (1940) wifi:Init static rx buffer num: 10
|
||||||
|
|
||||||
|
10:34:30.410 <- I (1940) wifi:Init dynamic rx buffer num: 32
|
||||||
|
|
||||||
|
10:34:30.410 <- I (1950) wifi_init: rx ba win: 6
|
||||||
|
|
||||||
|
10:34:30.410 <- I (1950) wifi_init: accept mbox: 6
|
||||||
|
|
||||||
|
10:34:30.410 <- I (1950) wifi_init: tcpip mbox: 32
|
||||||
|
|
||||||
|
10:34:30.410 <- I (1960) wifi_init: udp mbox: 6
|
||||||
|
|
||||||
|
10:34:30.410 <- I (1960) wifi_init: tcp mbox: 6
|
||||||
|
|
||||||
|
10:34:30.411 <- I (1960) wifi_init: tcp tx win: 5760
|
||||||
|
|
||||||
|
10:34:30.411 <- I (1970) wifi_init: tcp rx win: 5760
|
||||||
|
|
||||||
|
10:34:30.411 <- I (1970) wifi_init: tcp mss: 1440
|
||||||
|
|
||||||
|
10:34:30.411 <- I (2260) wifi:mode : softAP (80:f3:da:65:a9:15)
|
||||||
|
|
||||||
|
10:34:30.411 <- I (2260) wifi:Total power save buffer number: 16
|
||||||
|
|
||||||
|
10:34:30.411 <- I (2270) wifi:Init max length of beacon: 752/752
|
||||||
|
|
||||||
|
10:34:30.411 <- I (2270) wifi:Init max length of beacon: 752/752
|
||||||
|
|
||||||
|
10:34:30.411 <- I (2270) esp_netif_lwip: DHCP server started on interface WIFI_AP_DEF with IP: 192.168.4.1
|
||||||
|
|
||||||
|
10:34:30.411 <- I (2280) DNS_SERVER: DNS server started on port 53
|
||||||
|
|
||||||
|
10:34:30.412 <- I (2280) mdns_mem: mDNS task will be created from internal RAM
|
||||||
|
|
||||||
|
10:34:30.412 <- I (2290) WEBSERVER: SoftAP ready. SSID: sc.local, Channel: 6, Password: password
|
||||||
|
|
||||||
|
10:34:30.412 <- I (2300) WEBSERVER: Access at: http://sc.local.local or http://192.168.4.1
|
||||||
|
|
||||||
|
10:34:30.413 <- I (2300) WEBSERVER: STARTING HTTP
|
||||||
|
|
||||||
|
10:34:30.413 <- I (2310) WEBSERVER: HTTP server started successfully
|
||||||
|
|
||||||
|
10:34:30.413 <- I (2310) WEBSERVER: Registered URI handler: /
|
||||||
|
|
||||||
|
10:34:30.413 <- I (2320) WEBSERVER: Registered URI handler: /get
|
||||||
|
|
||||||
|
10:34:30.413 <- I (2320) WEBSERVER: Registered URI handler: /post
|
||||||
|
|
||||||
|
10:34:30.433 <- I (2330) WEBSERVER: Registered URI handler: /log
|
||||||
|
|
||||||
|
10:34:30.433 <- I (2330) WEBSERVER: Registered URI handler: /ota
|
||||||
|
|
||||||
|
10:34:30.434 <- I (2340) WEBSERVER: Registered URI handler: /*
|
||||||
|
|
||||||
|
10:34:30.434 <- I (2340) WEBSERVER: Webserver initialization complete
|
||||||
|
|
||||||
|
10:34:30.434 <-
|
||||||
|
|
||||||
|
10:34:30.940 <- BU.OK flash post_part=roundtrip log_head=11 log_tail=0 partitions_size=4128768
|
||||||
|
|
||||||
|
10:34:32.493 -> BU.I2C
|
||||||
|
10:34:32.522 <- I (4890) BT_HID: Found 0 HID device(s)
|
||||||
|
|
||||||
|
10:34:32.522 <- I (4890) BT_HID: No HID devices found, retrying in 2000ms...
|
||||||
|
|
||||||
|
10:34:32.522 <- E rmt: hw buffer too small, received symbols truncated
|
||||||
|
|
||||||
|
10:34:32.522 <- I (5940) I2C: POST: TCA9555 OK (port0=0x0121)
|
||||||
|
|
||||||
|
10:34:32.522 <-
|
||||||
|
|
||||||
|
10:34:33.038 <- BU.OK i2c tca9555=ack
|
||||||
|
|
||||||
|
10:34:33.042 -> BU.LED.WATCH
|
||||||
|
10:34:33.555 <-
|
||||||
|
|
||||||
|
10:34:33.555 <- BU.EVENT led t=4.84 pressed=0
|
||||||
|
|
||||||
|
10:34:33.555 <- I (6890) BT_HID: Scanning for HID devices (3s)...
|
||||||
|
|
||||||
|
10:34:34.564 <- E rmt: hw buffer too small, received symbols truncated
|
||||||
|
|
||||||
|
10:34:34.564 <-
|
||||||
BIN
BRINGUP_22JUN2026_1609.txt
Normal file
BIN
BRINGUP_22JUN2026_1609.txt
Normal file
Binary file not shown.
334
CLAUDE.md
334
CLAUDE.md
@@ -1,277 +1,63 @@
|
|||||||
# SC-F001 Firmware — CLAUDE.md
|
# SC-F001 Firmware — CLAUDE.md
|
||||||
|
|
||||||
## Overview
|
See `README.md` for full project documentation (hardware, architecture, protocols, algorithms).
|
||||||
|
|
||||||
The SC-F001 is a **solar-powered automated crop harvesting robot** built on the ESP32. It drives a carriage horizontally via a drive motor and lifts/lowers a cutting head via a jack motor, with an auxiliary "fluffer" motor always running during operation. The firmware handles motor sequencing, safety interlocks, remote control, data logging, and a WiFi web interface.
|
|
||||||
|
|
||||||
**Primary operational cycle:** Idle → Move Start Delay → Jack Up → Drive → Jack Down → Idle
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Hardware Platform
|
## Workflow
|
||||||
|
|
||||||
**MCU:** ESP32 (Xtensa dual-core), IDF framework
|
- **Minimize shell commands.** Every Bash call requires user approval. Prefer Read/Edit/Write/Glob/Grep tools. Only use Bash when a shell command is genuinely needed (e.g., `idf.py build`, git operations).
|
||||||
|
- **Webpage build step:** After editing `webpage.html`, run `webpage_compile.py` to regenerate `webpage_gzip.h` before building.
|
||||||
|
- **Don't touch git.**
|
||||||
|
---
|
||||||
|
|
||||||
**GPIO Map:**
|
## sdkconfig Management
|
||||||
| GPIO | Function |
|
|
||||||
|------|----------|
|
|
||||||
| 13 | Button interrupt (active low, pull-up) — also EXT0 wakeup |
|
|
||||||
| 14 | Jack position sensor / encoder |
|
|
||||||
| 16 | Drive encoder |
|
|
||||||
| 19 | Aux sensor 2 (reserved) |
|
|
||||||
| 21/22 | I2C SDA/SCL (400kHz) → TCA9555 I/O expander |
|
|
||||||
| 25 | 433MHz RF receiver (RMT input) |
|
|
||||||
| 26 | Solar charger bulk enable (RTC GPIO, holds across deep sleep) |
|
|
||||||
| 27 | Safety sensor (active low) |
|
|
||||||
| 32/33 | External 32.768 kHz RTC crystal (standard watch crystal, 2¹⁵ Hz) |
|
|
||||||
| 36 (VP) | ADC: drive current sense |
|
|
||||||
| 39 (VN) | ADC: battery voltage |
|
|
||||||
| 34 | ADC: jack current sense |
|
|
||||||
| 35 | ADC: aux current sense |
|
|
||||||
|
|
||||||
**TCA9555 (I2C at 0x20):**
|
**Two files, different roles:**
|
||||||
- Port 0 (input): 2 physical buttons + 2 additional inputs
|
- `sdkconfig.defaults` — checked into git. Contains only intentional project overrides with comments explaining why. Applied by `idf.py reconfigure` on top of IDF defaults.
|
||||||
- Port 1 (output): 3× H-bridge relay pairs (DRIVE, JACK, AUX) + LEDs
|
- `sdkconfig` — generated/modified by `idf.py menuconfig` or `reconfigure`. Contains every resolved setting. Also checked in for reproducibility, but treat `sdkconfig.defaults` as the source of truth for project-specific choices.
|
||||||
|
|
||||||
**Motor / Bridge Specs:**
|
**Rules:**
|
||||||
- `BRIDGE_DRIVE` — 100A max, ACS37220 sense chip (13.2 mV/A, inverted polarity)
|
- When changing a setting, add it to `sdkconfig.defaults` with a comment, then also apply it to `sdkconfig` so the next build picks it up without requiring `idf.py reconfigure`.
|
||||||
- `BRIDGE_JACK` — 30A max, ACS37042 sense chip (44 mV/A)
|
- Never hand-edit `sdkconfig` without also updating `sdkconfig.defaults` for the same setting — otherwise the change will be lost on the next `reconfigure`.
|
||||||
- `BRIDGE_AUX` — 30A max, ACS37042 sense chip (44 mV/A)
|
- Keep `sdkconfig.defaults` small and well-commented. Don't dump the full config into it.
|
||||||
|
|
||||||
|
**Current project-specific overrides (sdkconfig.defaults):**
|
||||||
|
| Setting | Value | Why |
|
||||||
|
|---------|-------|-----|
|
||||||
|
| `CONFIG_ESP_TASK_WDT_PANIC` | y | WDT timeout → panic → reboot (feeds OTA rollback counter) |
|
||||||
|
| `CONFIG_RTC_CLK_SRC_INT_RC` | y | Use internal 150kHz RC oscillator — no external 32kHz crystal. Avoids failed XTAL probe that corrupts RTC slow memory. |
|
||||||
|
| `CONFIG_HTTPD_WS_SUPPORT` | y | WebSocket support in esp_http_server — `/ws` real-time control + 1 Hz status push. |
|
||||||
|
|
||||||
|
**Already correct at IDF defaults (verified, no override needed):**
|
||||||
|
| Setting | Value | Status |
|
||||||
|
|---------|-------|--------|
|
||||||
|
| `CONFIG_FREERTOS_CHECK_STACKOVERFLOW_CANARY` | y | Stack overflow detection via canary (method 2) |
|
||||||
|
| `CONFIG_ESP_SYSTEM_PANIC_PRINT_REBOOT` | y | Print backtrace then reboot on panic |
|
||||||
|
| `CONFIG_BROWNOUT_DET_LVL_SEL_0` | y | ~2.43V brownout on ESP32 3.3V rail (appropriate — battery low-V is handled by `LOW_PROTECTION_V` in FSM) |
|
||||||
|
| `CONFIG_PARTITION_TABLE_CUSTOM` | y | Custom partitions.csv with ota_0 + ota_1 |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Software Architecture
|
## Managed Components
|
||||||
|
|
||||||
```
|
Only **mdns** is used. The TCA9555 is driven by a custom raw I2C driver in `i2c.c` (not the `esp-idf-lib/tca95x5` library). LittleFS is not used.
|
||||||
app_main()
|
|
||||||
├── rtc_xtal_init() RTC crystal + EXT0 wakeup + sleep wakeup check
|
|
||||||
├── i2c_init() TCA9555 init (relays off, LEDs off)
|
|
||||||
├── adc_init() ADC1 calibration (12dB attenuation, line-fit)
|
|
||||||
├── storage_init() Flash params + circular log buffer
|
|
||||||
├── solar_run_fsm() (called in main loop too)
|
|
||||||
├── uart_init() Serial JSON API task
|
|
||||||
├── rf_433_init() 433MHz RMT receiver task
|
|
||||||
├── bt_hid_init() BLE HID host scanner task
|
|
||||||
├── fsm_init() Control FSM task (priority 10, 20ms tick)
|
|
||||||
└── webserver_init() WiFi softAP + HTTP + mDNS + DNS
|
|
||||||
|
|
||||||
Main loop (50ms):
|
`idf_component.yml` pins mdns to `~1.9.1` (compatible patch updates only). If adding a new component, pin it with `~` (e.g. `"~1.2.0"`) to allow patches but not breaking changes.
|
||||||
i2c_poll_buttons()
|
|
||||||
fsm_request() based on button events
|
|
||||||
solar_run_fsm()
|
|
||||||
driveLEDs() status animation
|
|
||||||
rtc_check_shutdown_timer() → deep sleep on inactivity (180s)
|
|
||||||
```
|
|
||||||
|
|
||||||
**Task Priorities:**
|
After changing `idf_component.yml`, run `idf.py reconfigure` to update `managed_components/`.
|
||||||
- FSM control task: priority 10 (real-time)
|
|
||||||
- All others: default priority
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Key Files
|
## Conventions
|
||||||
|
|
||||||
| File | Purpose |
|
- **Naming:** `snake_case` functions with module prefix (`fsm_init`, `i2c_poll_buttons`); `UPPER_SNAKE_CASE` constants/enums
|
||||||
|------|---------|
|
- **Module pattern:** `.c` / `.h` pairs; headers expose only public API
|
||||||
| `main.c` | Entry point, 50ms main loop, factory reset, LED animation |
|
- **Concurrency:** FSM commands via `xQueueSend`; log writes via async queue; GPIO ISR → minimal work → sensor queue
|
||||||
| `control_fsm.c/h` | State machine, relay control, current monitoring, calibration |
|
- **State machine pattern:** transitions in one `switch`, relay outputs in a second `switch` (separated)
|
||||||
| `power_mgmt.c/h` | ADC reading, e-fuse thermal algorithm, battery voltage |
|
- **Watchdog:** `esp_task_wdt_add/reset` in each task, 10s timeout
|
||||||
| `sensors.c/h` | GPIO ISR-based sensor debouncing, encoder counters |
|
- **Logging:** `ESP_LOGI(TAG, ...)` per module; flash circular log for telemetry
|
||||||
| `i2c.c/h` | TCA9555 relay/LED/button control |
|
- **No dynamic allocation** in ISR or high-priority paths
|
||||||
| `storage.c/h` | 47-param NVM table + circular binary log buffer |
|
|
||||||
| `comms.c/h` | Unified GET/POST JSON API (shared by HTTP and UART) |
|
|
||||||
| `webserver.c/h` | WiFi softAP, HTTP server, embedded gzip webpage |
|
|
||||||
| `uart_comms.c/h` | Serial JSON interface (115200 8N1) |
|
|
||||||
| `rf_433.c/h` | 433MHz OOK receiver, keycode learn/match |
|
|
||||||
| `bt_hid.c/h` | BLE HID host, media remote button mapping |
|
|
||||||
| `rtc.c/h` | Unix time, harvest alarms, deep sleep scheduling |
|
|
||||||
| `solar.c/h` | Simple FLOAT/BULK solar charge state machine |
|
|
||||||
| `sc_err.h` | Error code definitions |
|
|
||||||
| `log_test.c/h` | Flash log unit tests |
|
|
||||||
| `hard_ui.c` | Legacy LCD code (unused/obsolete) |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Control FSM States
|
|
||||||
|
|
||||||
```
|
|
||||||
STATE_IDLE
|
|
||||||
STATE_MOVE_START_DELAY (1s)
|
|
||||||
STATE_JACK_UP_START (detect current spike → jack engaged)
|
|
||||||
STATE_JACK_UP (continue until timer/e-fuse)
|
|
||||||
STATE_DRIVE_START_DELAY (1s)
|
|
||||||
STATE_DRIVE (encoder-based distance control)
|
|
||||||
STATE_DRIVE_END_DELAY (1s)
|
|
||||||
STATE_JACK_DOWN (reverse until e-fuse/sensor)
|
|
||||||
→ back to STATE_IDLE
|
|
||||||
|
|
||||||
STATE_UNDO_JACK_START (emergency: reverse jack immediately)
|
|
||||||
STATE_UNDO_JACK (run until e-fuse/sensor)
|
|
||||||
→ back to STATE_IDLE
|
|
||||||
|
|
||||||
CAL_JACK_DELAY / CAL_JACK_MOVE (jack calibration sequence)
|
|
||||||
CAL_DRIVE_DELAY / CAL_DRIVE_MOVE (drive calibration sequence)
|
|
||||||
```
|
|
||||||
|
|
||||||
**Guards before START:**
|
|
||||||
- Remaining distance > 0 (leash protection)
|
|
||||||
- Battery V ≥ `LOW_PROTECTION_V` (default 10V)
|
|
||||||
- Safety sensor active (debounced stable)
|
|
||||||
- All e-fuses not tripped
|
|
||||||
|
|
||||||
**FSM Loop (20ms tick in `control_task()`):**
|
|
||||||
1. `process_bridge_current()` — ADC → EMA → auto-zero → e-fuse
|
|
||||||
2. `process_battery_voltage()` — ADC → EMA
|
|
||||||
3. `sensors_check()` — drain ISR queue, update counters/debounce
|
|
||||||
4. State machine transitions (timer + sensor + efuse checks)
|
|
||||||
5. `driveRelays()` — write relay output from current state
|
|
||||||
6. `send_fsm_log()` — 39-byte timestamped entry to flash
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## E-Fuse Algorithm (`power_mgmt.c`)
|
|
||||||
|
|
||||||
Per bridge, each 20ms tick:
|
|
||||||
1. Raw ADC → EMA filter (α = `ADC_ALPHA_ISENS`)
|
|
||||||
2. Auto-zero: learn zero offset when motor is off + grace period expired
|
|
||||||
3. Grace period: 250ms after relay closes (ignores startup inrush)
|
|
||||||
4. **Instant trip:** I ≥ `EFUSE_KINST` × I_nom (default 2×)
|
|
||||||
5. **Thermal trip:** heat accumulates as I²·Δt; dissipates at τ_cool rate
|
|
||||||
6. **Auto-reset:** after `EFUSE_TCOOL` seconds of cooling (default 5s)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Safety Sensor Debouncing (Asymmetric)
|
|
||||||
|
|
||||||
```
|
|
||||||
LOW (safe): 1000ms make time → slow to declare safe (SAFETY_MAKE_US)
|
|
||||||
HIGH (break): 300ms break time → fast to kill operation (SAFETY_BREAK_US)
|
|
||||||
```
|
|
||||||
|
|
||||||
Safety break → immediate `STATE_UNDO_JACK_START`.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Communication Interfaces
|
|
||||||
|
|
||||||
### WiFi (softAP)
|
|
||||||
- SSID/password/channel configurable via params (`WIFI_SSID`, `WIFI_PASS`, `WIFI_CHANNEL`)
|
|
||||||
- mDNS hostname: `sc.local`
|
|
||||||
- Captive portal DNS: all queries → 192.168.4.1
|
|
||||||
- HTTP port 80
|
|
||||||
|
|
||||||
### HTTP API (port 80)
|
|
||||||
| Endpoint | Method | Description |
|
|
||||||
|----------|--------|-------------|
|
|
||||||
| `/` | GET | Embedded gzip HTML webpage |
|
|
||||||
| `/get` | GET | JSON system status |
|
|
||||||
| `/set` | POST | JSON commands + parameter updates |
|
|
||||||
| `/log` | GET | Binary log download (4B JSON len + JSON + 8B offsets + log data) |
|
|
||||||
|
|
||||||
### UART (115200 8N1)
|
|
||||||
- `GET` → same as HTTP GET /get
|
|
||||||
- `POST: {json}` → same as HTTP POST /set
|
|
||||||
- `HELP` → command reference
|
|
||||||
- Shares `comms_handle_get()` / `comms_handle_post()` with HTTP
|
|
||||||
|
|
||||||
### 433MHz RF (GPIO25, RMT)
|
|
||||||
- 24-bit OOK codes (P_HIGH≈1040µs, P_LOW≈340µs, margin 70µs)
|
|
||||||
- 8 stored keycodes → FSM_OVERRIDE_* commands
|
|
||||||
- Learn mode: capture next RX → temp buffer → user commits via web
|
|
||||||
|
|
||||||
### Bluetooth HID Host
|
|
||||||
- Scans for BLE HID devices (service UUID 0x1812)
|
|
||||||
- Tries saved BDA first, then scans for best RSSI
|
|
||||||
- Button mapping:
|
|
||||||
- VOL_UP → Jack Up (override pulse)
|
|
||||||
- VOL_DOWN → Jack Down
|
|
||||||
- PREV → Drive Reverse
|
|
||||||
- NEXT → Drive Forward
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Storage Layout
|
|
||||||
|
|
||||||
**Flash partition "storage":**
|
|
||||||
```
|
|
||||||
0x0000 – 0x0FFF Parameters (4 sectors, CRC32-protected, 47 params)
|
|
||||||
0x1000 – end Circular log buffer (head/tail tracked)
|
|
||||||
```
|
|
||||||
|
|
||||||
**Log entry format (39 bytes typical):**
|
|
||||||
```
|
|
||||||
[0:8] Timestamp ms (u64 BE)
|
|
||||||
[8:12] Battery voltage (f32)
|
|
||||||
[12:16] Drive current (f32)
|
|
||||||
[16:20] Jack current (f32)
|
|
||||||
[20:24] Aux current (f32)
|
|
||||||
[24:26] Drive encoder count (i16)
|
|
||||||
[26] Sensor states (packed)
|
|
||||||
[27:31] Drive heat (f32)
|
|
||||||
[31:35] Jack heat (f32)
|
|
||||||
[35:39] Aux heat (f32)
|
|
||||||
```
|
|
||||||
|
|
||||||
**Key Parameters:**
|
|
||||||
- Motion: `DRIVE_DIST`, `JACK_DIST`, `DRIVE_KT`, `JACK_KT`, `DRIVE_KE`
|
|
||||||
- E-fuse: `EFUSE_INOM_1/2/3`, `EFUSE_HEAT_THRESH`, `EFUSE_KINST`, `EFUSE_TCOOL`
|
|
||||||
- Safety: `SAFETY_BREAK_US`, `SAFETY_MAKE_US`, `LOW_PROTECTION_V`
|
|
||||||
- RF: `KEYCODE_0` … `KEYCODE_7`
|
|
||||||
- WiFi: `WIFI_SSID`, `WIFI_PASS`, `WIFI_CHANNEL`
|
|
||||||
- Schedule: `NUM_MOVES`, `MOVE_START`, `MOVE_END` (seconds-since-midnight)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## RTC & 32.768 kHz Crystal
|
|
||||||
|
|
||||||
**Crystal:** Standard 32.768 kHz (32768 Hz = 2¹⁵ Hz) tuning-fork watch crystal on GPIO32/GPIO33. This frequency is universal for RTCs because it divides to exactly 1 Hz with a 15-bit binary counter.
|
|
||||||
|
|
||||||
**sdkconfig.defaults settings:**
|
|
||||||
- `CONFIG_RTC_CLK_SRC_EXT_CRYS=y` — selects the external crystal as the RTC slow clock source instead of the internal ~150 kHz RC oscillator
|
|
||||||
- `CONFIG_ESP32_RTC_EXT_CRYST_ADDIT_CURRENT_V2=y` — enables extra drive current during the crystal startup window; required for high-ESR tuning-fork crystals (e.g. CM315D32768DZFT ~70 kΩ ESR)
|
|
||||||
|
|
||||||
**Known startup failure mode:** On power-on, the ESP32 bootloader attempts to calibrate the crystal. If it fails to detect oscillation within its calibration window, it logs `W: 32 kHz XTAL not found, switching to internal 150 kHz oscillator` and falls back to the RC oscillator. The RC oscillator has ±5% accuracy, producing up to ~180 s/hr of RTC drift — this completely breaks harvest scheduling.
|
|
||||||
|
|
||||||
**Firmware mitigation (`rtc_xtal_init()` in `rtc.c`):** If `rtc_clk_slow_src_get()` does not return `SOC_RTC_SLOW_CLK_SRC_XTAL32K` at startup, the code applies a manual bootstrap: `rtc_clk_32k_bootstrap(20000)` (~600 ms of extra drive current at 32 kHz cycles), waits 500 ms for oscillation to stabilise, then calls `rtc_clk_slow_src_set(SOC_RTC_SLOW_CLK_SRC_XTAL32K)` to switch explicitly. Success or failure is logged via `ESP_LOGI/LOGE`.
|
|
||||||
|
|
||||||
**Diagnosing crystal issues:** Run `RTCDEBUG` over UART and check `slow_clk_src`. It reports either `XTAL32K (OK)` or `NOT XTAL32K — check crystal!`. The `logtool/rtc_test.py` script automates this and runs multi-cycle drift tests.
|
|
||||||
|
|
||||||
**Time persistence across deep sleep:** `rtc_backup_s` and `rtc_sleep_entry_s` are `RTC_DATA_ATTR` (survive deep sleep). On wakeup, `rtc_restore_time()` adds exactly `DEEP_SLEEP_US / 1e6` seconds to `rtc_sleep_entry_s` to reconstruct the correct time without an NTP sync.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Power Management
|
|
||||||
|
|
||||||
- **Battery voltage:** GPIO39, divider → `V = raw × 0.00767 + 0.4`
|
|
||||||
- **Solar charger:** GPIO26 (RTC hold) — FLOAT/BULK FSM, bulk for 20s when V < 5V for 5s
|
|
||||||
- **Inactivity shutdown:** 180s → deep sleep
|
|
||||||
- **Deep sleep wakeup:** RTC timer (120s), RTC alarm (next harvest), EXT0 GPIO13 (button)
|
|
||||||
- **RTC_DATA_ATTR:** FSM state, errors, alarm times, charge state — survive deep sleep
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Error Codes (`sc_err.h`)
|
|
||||||
|
|
||||||
```c
|
|
||||||
SC_ERR_EFUSE_TRIP_1 = 0x201 // Drive overcurrent/overheat
|
|
||||||
SC_ERR_EFUSE_TRIP_2 = 0x202 // Jack
|
|
||||||
SC_ERR_EFUSE_TRIP_3 = 0x203 // Aux
|
|
||||||
SC_ERR_SAFETY_TRIP = 0x210 // Safety sensor break
|
|
||||||
SC_ERR_LEASH_HIT = 0x211 // Distance limit reached
|
|
||||||
SC_ERR_RTC_NOT_SET = 0x220 // Clock not synchronized
|
|
||||||
SC_ERR_LOW_BATTERY = 0x230 // Voltage below threshold
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Build System
|
|
||||||
|
|
||||||
- **Framework:** ESP-IDF (>=4.1.0)
|
|
||||||
- **Component deps** (`idf_component.yml`): `espressif/mdns`, `joltwallet/littlefs`, `esp-idf-lib/tca95x5`
|
|
||||||
- **IDF requires:** `driver`, `esp_http_server`, `esp_netif`, `lwip`, `json`, `esp_timer`, `esp_adc`, `app_update`, `esp_wifi`, `nvs_flash`, `mdns`, `bt`, `esp_hid`
|
|
||||||
- **Webpage:** `webpage.html` → `webpage_compile.py` → `webpage_gzip.h` (embedded gzip binary). **Must re-run `webpage_compile.py` after any HTML edit before building.**
|
|
||||||
- **Version:** `version.h.in` filled by CMake from git tags → `FIRMWARE_VERSION`, `BUILD_DATE`
|
|
||||||
- **Factory reset:** Hold GPIO13 button on cold boot → full parameter + log erase
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -281,13 +67,17 @@ Single-file SPA. Compiled to a gzip binary embedded in firmware. All JS is inlin
|
|||||||
|
|
||||||
**Key globals:**
|
**Key globals:**
|
||||||
- `const ge = (id) => document.getElementById(id)` — shorthand used everywhere
|
- `const ge = (id) => document.getElementById(id)` — shorthand used everywhere
|
||||||
- `let data = {}` — full `/get` JSON response, updated every poll cycle
|
- `let data = {}` — full status JSON; refreshed by the WebSocket push (or the poll fallback)
|
||||||
- `let paramTableCreated = false` — tracks whether the DANGER ZONE param table has been built yet
|
- `let paramTableCreated = false` — tracks whether the DANGER ZONE param table has been built yet
|
||||||
- `let pollInterval` — handle for the 2-second `fetchStatus()` interval
|
- `let ws` — the `/ws` WebSocket; `wsConnected()` is the live check. `connectWS()` opens it, auto-reconnects (3 s backoff), and `stopPolling()` once open
|
||||||
|
- `let pollInterval` — handle for the `fetchStatus()` poll **fallback** (only fetches while the WS is down)
|
||||||
|
|
||||||
|
**Real-time channel (`./ws` WebSocket):** primary transport. Server pushes status JSON ~1 Hz (drives `updateUI()` exactly like a poll); client sends remote-control commands via `sendCmd()` (`ws.send()`, falling back to `./post`). Tab-hidden closes the WS so the device can soft-idle; tab-visible reconnects. See README "WebSocket" section for the server side + stop-on-disconnect safety.
|
||||||
|
|
||||||
**Endpoints used by JS (all relative):**
|
**Endpoints used by JS (all relative):**
|
||||||
- `./get` — GET, returns full system status JSON; polled every 2 s by `fetchStatus()`
|
- `./ws` — WebSocket, real-time status push + remote-control commands (primary)
|
||||||
- `./post` — POST `application/json`, handles commands + parameter updates
|
- `./get` — GET, full system status JSON; polled by `fetchStatus()` only as a fallback when the WS is down
|
||||||
|
- `./post` — POST `application/json`, handles commands + parameter updates (also the remote-control fallback)
|
||||||
- `./log` — GET/POST, binary log download
|
- `./log` — GET/POST, binary log download
|
||||||
- `./ota` — POST, firmware upload
|
- `./ota` — POST, firmware upload
|
||||||
|
|
||||||
@@ -304,14 +94,14 @@ All fields optional. `parameters` is a flat object of param key → value.
|
|||||||
- `cancel_btn` calls `location.reload()`
|
- `cancel_btn` calls `location.reload()`
|
||||||
|
|
||||||
**Sections (top to bottom):**
|
**Sections (top to bottom):**
|
||||||
1. Status display (voltage, state, distance, etc.) — auto-updated from `data`
|
1. Status display (voltage, state, distance, error flags) — auto-updated from `data`
|
||||||
2. Schedule settings (`<details>`) — MOVE_START / MOVE_END / NUM_MOVES
|
2. Schedule settings (`<details>`) — daily `MOVE_TIME_NN` slots (HH:MM); `startRemote`/`stopRemote` jog via `sendCmd()`, releasing sends `stop_override`
|
||||||
3. Remote Control (`<details>`) — jog buttons + RF programming
|
3. Remote Control (`<details open>`) — jog buttons + RF programming
|
||||||
4. **WiFi Settings** (`<details open>`) — NET_SSID, NET_PASS, WIFI_SSID, WIFI_PASS with dedicated `applyWifiSettings()` button
|
4. **WiFi Settings** (`<details>`) — WIFI_SSID, WIFI_PASS (STA mode disabled: NET_SSID/NET_PASS inputs commented out)
|
||||||
5. **DANGER ZONE** (`<details>`) — calibration, version, OTA upload, log download, auto-generated parameter table, REBOOT/SLEEP
|
5. **DANGER ZONE** (`<details>`) — calibration, version, OTA upload, log download, auto-generated parameter table, jack-position + heap (free / min) readouts, REBOOT / SLEEP / RESTART WIFI / FACTORY RESET
|
||||||
|
|
||||||
**`updateParamTable()`:**
|
**`updateParamTable()`:**
|
||||||
- On first call: builds a `<table id="table">` row per parameter, sorted alphabetically, skipping `WIFI_PARAM_KEYS = {NET_SSID, NET_PASS, WIFI_SSID, WIFI_PASS}` (those live in the dedicated WiFi section)
|
- On first call: builds a `<table id="table">` row per parameter, sorted alphabetically, skipping keys for which `paramSkipped(key)` is true — i.e. members of `PARAM_TABLE_SKIP = {NET_SSID, NET_PASS, WIFI_SSID, WIFI_PASS, MOVE_START, MOVE_END, NUM_MOVES}` (WiFi keys live in the dedicated WiFi section; the MOVE_* trio is deprecated/superseded by the `MOVE_TIME_NN` schedule)
|
||||||
- On subsequent calls: updates existing input values (skips changed/focused inputs); if a new key appears, rebuilds
|
- On subsequent calls: updates existing input values (skips changed/focused inputs); if a new key appears, rebuilds
|
||||||
|
|
||||||
**Modal helpers** (all return Promises):
|
**Modal helpers** (all return Promises):
|
||||||
@@ -321,17 +111,5 @@ All fields optional. `parameters` is a flat object of param key → value.
|
|||||||
|
|
||||||
**Adding a new dedicated UI section:**
|
**Adding a new dedicated UI section:**
|
||||||
1. Add `<input id="PARAM_<KEY>" onchange="markChanged(this)"/>` in HTML
|
1. Add `<input id="PARAM_<KEY>" onchange="markChanged(this)"/>` in HTML
|
||||||
2. Add key to `WIFI_PARAM_KEYS` (or equivalent filter set) in `updateParamTable()` so it isn't duplicated in the raw table
|
2. Add the key to `PARAM_TABLE_SKIP` so it isn't duplicated in the auto-generated raw table. (Note: keep dedicated inputs out of the raw table — a stray `PARAM_<KEY>` input that's *also* in the table makes `updateParamTable()` rebuild every poll.)
|
||||||
3. Optionally add a dedicated apply function following `applyWifiSettings()` pattern
|
3. Optionally add a dedicated apply function following `applyWifiSettings()` pattern
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Conventions
|
|
||||||
|
|
||||||
- **Naming:** `snake_case` functions with module prefix (`fsm_init`, `i2c_poll_buttons`); `UPPER_SNAKE_CASE` constants/enums
|
|
||||||
- **Module pattern:** `.c` / `.h` pairs; headers expose only public API
|
|
||||||
- **Concurrency:** FSM commands via `xQueueSend`; log writes via async queue; GPIO ISR → minimal work → sensor queue
|
|
||||||
- **State machine pattern:** transitions in one `switch`, relay outputs in a second `switch` (separated)
|
|
||||||
- **Watchdog:** `esp_task_wdt_add/reset` in each task, 10s timeout
|
|
||||||
- **Logging:** `ESP_LOGI(TAG, ...)` per module; flash circular log for telemetry
|
|
||||||
- **No dynamic allocation** in ISR or high-priority paths
|
|
||||||
|
|||||||
5
LICENSE
5
LICENSE
@@ -1,5 +0,0 @@
|
|||||||
Code in this repository is in the Public Domain (or CC0 licensed, at your option.)
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, this
|
|
||||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
|
||||||
CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
194
README-ISSUES.md
Normal file
194
README-ISSUES.md
Normal file
@@ -0,0 +1,194 @@
|
|||||||
|
# README.md Issues
|
||||||
|
|
||||||
|
Audit of `README.md` against the actual firmware code. The active board is **V5** (`#define BOARD_V5` in `board_config.h`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## GPIO Map (lines 13–28) — Wrong for active board (V5)
|
||||||
|
|
||||||
|
The table documents the **V4 pinout**. On V5 the sensor and ADC GPIOs are completely different:
|
||||||
|
|
||||||
|
| README says | V4 pin | Actual V5 | Source |
|
||||||
|
|---|---|---|---|
|
||||||
|
| GPIO14 = Drive encoder | ISENS2 | **GPIO14 = JACK position sensor** | `sensors.c:26` |
|
||||||
|
| GPIO16 = Jack position sensor | ISENS3 | **GPIO16 = not used** (V5 uses GPIO14 for JACK) | `sensors.c:26` |
|
||||||
|
| GPIO19 = Aux sensor 2 (reserved) | BATTERY | **GPIO19 = DRIVE encoder** | `sensors.c:26` |
|
||||||
|
| GPIO23 = *missing* | — | **GPIO23 = AUX2** (unused, J3 unreliable) | `sensors.c:26` |
|
||||||
|
| GPIO34 = ADC: jack current sense | ISENS2 | **GPIO34 = V_ISENS_MAIN** (shared ACS) | `power_mgmt.c:49` |
|
||||||
|
| GPIO35 = ADC: aux current sense | ISENS3 | **GPIO35 = battery voltage** (ADC1_CH7) | `power_mgmt.c:51` |
|
||||||
|
| GPIO36/VP = ADC: drive current sense | ISENS1 | **GPIO36 = V_VOC** (OC threshold diag) | `power_mgmt.c:50` |
|
||||||
|
| GPIO39/VN = ADC: battery voltage | BATTERY | **GPIO39 = FAULT** (digital input, ACS37220) | `power_mgmt.c:52` |
|
||||||
|
|
||||||
|
The `sensors.h` enum comments (lines 19–22) are also stale — they list V4 pins but the V5 pins are assigned at runtime in `sensors.c:26`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## TCA9555 description (line 32) — Wrong
|
||||||
|
|
||||||
|
> Port 1 (output): 3× H-bridge relay pairs (DRIVE, JACK, AUX) + LEDs
|
||||||
|
|
||||||
|
LEDs are on **Port 0** (P05–P07), not Port 1. See `i2c_set_led1()` at `i2c.c:98` writing to `TCA_REG_OUTPUT0`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ACS chip statement (line 47) — Unqualified
|
||||||
|
|
||||||
|
> All power goes through a ACS37220 sense chip (13.2 mV/A)
|
||||||
|
|
||||||
|
True for V5 (single shared ACS37220). On V4, JACK and AUX use **ACS37042 (44 mV/A)** — `power_mgmt.c:457-458`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Software Architecture diagram (lines 53–66) — Missing steps
|
||||||
|
|
||||||
|
The init sequence omits:
|
||||||
|
- Factory-reset detection loop (`main.c:174-227`)
|
||||||
|
- `rtc_restore_time()` after `storage_init()` (`main.c:232`)
|
||||||
|
- `adc_post()` after `log_init()` (`main.c:236`)
|
||||||
|
- `storage_post()` (`main.c:237`)
|
||||||
|
- OTA rollback counter check (`main.c:241-278`)
|
||||||
|
- `send_bat_log()` at boot (`main.c:282`)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Main loop description (lines 68–73) — Major omissions
|
||||||
|
|
||||||
|
Missing from the described main loop:
|
||||||
|
- **Soft-idle polling path** (`main.c:340-363`) — handles wake-on-button, wake-on-alarm, solar FSM, and shutdown timer during sleep. This is ~25% of the main loop body.
|
||||||
|
- Button hold-to-reboot logic (`main.c:388-397`)
|
||||||
|
- Triple-tap detection (`main.c:411-431`)
|
||||||
|
- Alarm-triggered `FSM_CMD_START` (`main.c:496-499`)
|
||||||
|
- Periodic `send_bat_log()` every 120s in IDLE (`main.c:450-452`)
|
||||||
|
- `esp_task_wdt_reset()` at end of each tick (`main.c:503`)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## FreeRTOS Tasks table (lines 76–84) — Wrong priorities, missing task
|
||||||
|
|
||||||
|
| Task | README says | Actual priority | Source |
|
||||||
|
|---|---|---|---|
|
||||||
|
| UART task | "default" | **12** | `uart_comms.c:294` |
|
||||||
|
| RF 433 task | "default" | **5** | `rf_433.c:223` |
|
||||||
|
| BT HID task | "default" | **4** | `bt_hid.c:583` |
|
||||||
|
|
||||||
|
Missing task: **log_writer** (priority 5, created by `log_init()` at `storage.c:1069-1076`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Key Files — `hard_ui.c` doesn't exist
|
||||||
|
|
||||||
|
`hard_ui.c` listed at line 107 — no such file in `main/`. Either stale or was removed.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## FSM state diagram (lines 113–129) — Missing state
|
||||||
|
|
||||||
|
`STATE_DRIVE_FLUFF_START` is omitted. Actual sequence:
|
||||||
|
|
||||||
|
```
|
||||||
|
START_DELAY → JACK_UP_START → JACK_UP → DRIVE_START_DELAY → DRIVE_FLUFF_START → DRIVE → DRIVE_END_DELAY → JACK_DOWN
|
||||||
|
```
|
||||||
|
|
||||||
|
Cal states listed as `CAL_JACK_DELAY` but actual enum names are `STATE_CALIBRATE_JACK_DELAY`, `STATE_CALIBRATE_JACK_MOVE`, `STATE_CALIBRATE_DRIVE_DELAY`, `STATE_CALIBRATE_DRIVE_MOVE`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## FSM Loop — wrong log size (lines 137–143)
|
||||||
|
|
||||||
|
> send_fsm_log() — 39-byte timestamped entry
|
||||||
|
|
||||||
|
`LOGSIZE = 25` at `control_fsm.c:179`. The actual 25-byte layout is:
|
||||||
|
|
||||||
|
```
|
||||||
|
[0:8] ts_ms (u64)
|
||||||
|
[8:12] bat_V (f32)
|
||||||
|
[12:16] current_A (f32) — combined, not per-bridge
|
||||||
|
[16:18] counter (i16)
|
||||||
|
[18:19] sensors (u8)
|
||||||
|
[19:23] heat (f32) — max across bridges
|
||||||
|
[23:25] i2c_out (u16)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## `/log` HTTP endpoint (line 184) — Wrong method
|
||||||
|
|
||||||
|
> `/log` — GET
|
||||||
|
|
||||||
|
Registered as `HTTP_ANY` (`webserver.c:870`). Handles both GET and POST (`webserver.c:150-153`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## UART `POST:` example (line 189) — Wrong syntax
|
||||||
|
|
||||||
|
> POST: {json}
|
||||||
|
|
||||||
|
The UART handler (`uart_comms.c`) receives raw JSON strings, not HTTP-style method prefixes.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Flash partitions (lines 212–220) — Wrong offsets and sizes
|
||||||
|
|
||||||
|
| Partition | README | Actual (`partitions.csv`) |
|
||||||
|
|---|---|---|
|
||||||
|
| post_test | offset 0x310000, 4K | offset **0x3F0000**, 4K |
|
||||||
|
| params | offset 0x311000, **16K** | offset **0x3F1000**, **32K** |
|
||||||
|
| log | offset 0x315000, **~4.9MB** | offset **0x400000**, **4096K** |
|
||||||
|
|
||||||
|
Missing from table: NVS (0x9000, 16K), otadata (0xD000), phy_init (0xF000), **ota_0 (0x10000, 1984K)**, **ota_1 (0x200000, 1984K)**.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Log entry format (lines 222–234) — Describes old 39-byte format
|
||||||
|
|
||||||
|
The 39-byte layout with separate per-bridge currents and heats doesn't match `send_fsm_log()` which uses 25 bytes (combined current, single max heat, adds `i2c_out`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Parameter count — Off by one
|
||||||
|
|
||||||
|
> 48-param NVM table (`storage.c/h` description, line 97)
|
||||||
|
|
||||||
|
`storage.h:72-121` defines **49** parameters (check `NUM_PARAMS` from the enum).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Key Parameters list (lines 236–242) — Missing 30+ params
|
||||||
|
|
||||||
|
Missing: `ADC_ALPHA_BATTERY`, `ADC_ALPHA_ISENS`, `ADC_ALPHA_IAZ`, `ADC_DB_IAZ`, `JACK_I_UP`, `JACK_I_DOWN`, `V_SENS_K`, `V_SENS_OFFSET`, `EFUSE_INRUSH_US`, `EFUSE_TAUCOOL`, `FLUFF_PREDRIVE_MS`, `CHG_LOW_V`, `CHG_LOW_S`, `CHG_BULK_S`, `RF_PULSE_LENGTH`, `LOW_PROTECTION_S`, `BUILD_VERSION`, `BOOT_TIME`, `JACK_IS_DOWN`, `WIFI_CHANNEL`, `NET_SSID`, `NET_PASS`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Deep sleep claim (line 248) — Incorrect
|
||||||
|
|
||||||
|
> deep sleep is disabled (soft idle instead)
|
||||||
|
|
||||||
|
`hibernate_enter()` at `rtc.c:184` calls `esp_deep_sleep_start()`. Deep sleep is used for hibernate mode (triggered by explicit web/UART command). Soft idle is the inactivity-triggered default, but deep sleep is not disabled.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Factory reset GPIO reference (lines 270, 280, 354) — Wrong pin
|
||||||
|
|
||||||
|
> Hold GPIO13 button / power cycle with GPIO13 held
|
||||||
|
|
||||||
|
GPIO13 is the **NCA9535 INT line**, not a button. The actual button state is read via I2C (`i2c_button_held_raw(0)` at `main.c:176`). The user holds the physical button on the I2C expander, not GPIO13.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Battery voltage GPIO (line 326) — Wrong for V5
|
||||||
|
|
||||||
|
> Battery voltage: GPIO39
|
||||||
|
|
||||||
|
On V5, battery voltage is on **GPIO35** (ADC1_CH7). GPIO39 is the **FAULT digital input** (`power_mgmt.c:51-52`). This correctly describes V4 only.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## "fluffer motor always running" (line 3) — Overstated
|
||||||
|
|
||||||
|
> auxiliary "fluffer" motor always running during operation
|
||||||
|
|
||||||
|
The fluffer (AUX) only runs during `STATE_DRIVE_FLUFF_START` and `STATE_DRIVE`. It does not run during jack-up, jack-down, or delay states (`control_fsm.c:716-776`).
|
||||||
365
README.md
365
README.md
@@ -1,4 +1,363 @@
|
|||||||
SC-F001
|
# SC-F001 Firmware
|
||||||
=======
|
|
||||||
|
|
||||||
Firmware for SC-B001
|
**Solar-powered autonomous livestock shelter mover** built on the ESP32. Drives horizontally via a motor, lifts/lowers a the structure via a jack motor, with an auxiliary "fluffer" motor always running while driving. The firmware handles motor sequencing, safety interlocks, remote control, data logging, and a WiFi web interface.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Hardware Platform
|
||||||
|
|
||||||
|
**MCU:** ESP32 (Xtensa dual-core), ESP-IDF framework
|
||||||
|
|
||||||
|
**GPIO Map:**
|
||||||
|
| GPIO | Function |
|
||||||
|
|---------|-------------------------------------------------------------------------|
|
||||||
|
| 13 | Button interrupt (active low, pull-up) |
|
||||||
|
| 14 | Jack position sensor |
|
||||||
|
| 16 | Not Used |
|
||||||
|
| 19 | Drive encoder |
|
||||||
|
| 21/22 | I2C SDA/SCL (400kHz) → TCA9555 I/O expander |
|
||||||
|
| 25 | 433MHz RF receiver (RMT input) |
|
||||||
|
| 26 | Solar charger bulk enable (RTC GPIO) |
|
||||||
|
| 27 | Safety sensor (active low) |
|
||||||
|
| 32/33 | External 32.768 kHz RTC crystal (on PCB, not used — see RTC section) |
|
||||||
|
| 34 | ADC: Current Sensor |
|
||||||
|
| 35 | ADC: Battery Voltage |
|
||||||
|
| 36 (VP) | ADC: Current Sensor VOC |
|
||||||
|
| 39 (VN) | ADC: Current Sensor FAULT |
|
||||||
|
|
||||||
|
**TCA9555 (I2C at 0x21):**
|
||||||
|
- Port 0 (input): 2 physical buttons + 2 additional inputs + LEDs
|
||||||
|
- Port 1 (output): 3× H-bridge relay pairs (DRIVE, JACK, AUX)
|
||||||
|
|
||||||
|
- P00: SW1 (has external 4.7kOhm pullup)
|
||||||
|
- P01: SW2 (not populated on SC-B001-V5)
|
||||||
|
- P02-P04: N/C
|
||||||
|
- P05-P07: LEDs (through 100ohm resistors)
|
||||||
|
- P10: Sensor enable (1=ENABLE, 0=DISABLE)
|
||||||
|
- P11: KC3 (not connected)
|
||||||
|
- P12: KB3 (not connected)
|
||||||
|
- P13: KA3 (aux relay)
|
||||||
|
- P14: KB2 (jack B)
|
||||||
|
- P15: KA2 (jack A)
|
||||||
|
- P16: KB1 (drive B)
|
||||||
|
- P17: KA1 (drive A)
|
||||||
|
|
||||||
|
All power goes through a ACS37220LEZATR-100B3 sense chip (13.2 mV/A)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Software Architecture
|
||||||
|
|
||||||
|
```
|
||||||
|
app_main()
|
||||||
|
├── i2c_init() TCA9555 init (relays off, LEDs off)
|
||||||
|
├── rtc_xtal_init() Button GPIO setup
|
||||||
|
├── boot_reset_reason Check boot reason for factory reset
|
||||||
|
├── adc_init() ADC1 calibration (12dB attenuation, line-fit)
|
||||||
|
├── storage_init() Flash params
|
||||||
|
├── log_init() Circular log buffer
|
||||||
|
├── adc_post()
|
||||||
|
├── storage_post()
|
||||||
|
├── solar_run_fsm() (called in main loop too)
|
||||||
|
├── uart_init() Serial JSON API task
|
||||||
|
├── sensors_init() GPIO ISR setup for sensors/encoders
|
||||||
|
├── fsm_init() Control FSM task (priority 10, 20ms tick)
|
||||||
|
├── rf_433_init() 433MHz RMT receiver task
|
||||||
|
├── bt_hid_init() BLE HID host scanner task
|
||||||
|
└── webserver_init() WiFi softAP + HTTP + WebSocket + mDNS + DNS
|
||||||
|
|
||||||
|
Main loop (50ms):
|
||||||
|
soft-idle check
|
||||||
|
button hold-to-reboot
|
||||||
|
triple-tap detection
|
||||||
|
alarm detection
|
||||||
|
periodic send_bat_log
|
||||||
|
i2c_poll_buttons()
|
||||||
|
fsm_request() based on button events
|
||||||
|
solar_run_fsm()
|
||||||
|
drive_leds() status animation
|
||||||
|
rtc_check_shutdown_timer() → soft idle after INACTIVITY_TIMEOUT_S (default 300s)
|
||||||
|
esp_task_wdt_reset()
|
||||||
|
```
|
||||||
|
|
||||||
|
**FreeRTOS Tasks:**
|
||||||
|
| Task | Created by | Priority | Tick | Purpose |
|
||||||
|
|---------------------------|--------------------|---------------|----------------|----------------------------------------------------------------------|
|
||||||
|
| `app_main` (main loop) | system | 1 (default) | 50ms | Button polling, LED animation, solar FSM, shutdown timer |
|
||||||
|
| `control_task` | `fsm_init()` | 10 | 20ms | FSM state machine, relay control, ADC current monitoring, e-fuse |
|
||||||
|
| UART task | `uart_init()` | default | event-driven | Serial JSON command processing |
|
||||||
|
| RF 433 task | `rf_433_init()` | default | event-driven | RMT receive + keycode matching |
|
||||||
|
| BT HID task | `bt_hid_init()` | default | event-driven | BLE HID host scanning + button mapping |
|
||||||
|
| httpd workers | `webserver_init()` | default | event-driven | HTTP request handling (multiple workers spawned by esp_http_server) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Key Files
|
||||||
|
|
||||||
|
| File | Purpose |
|
||||||
|
|----------------------|----------------------------------------------------------------------|
|
||||||
|
| `main.c` | Entry point, 50ms main loop, factory reset, LED animation |
|
||||||
|
| `control_fsm.c/h` | State machine, relay control, current monitoring, calibration |
|
||||||
|
| `power_mgmt.c/h` | ADC reading, e-fuse thermal algorithm, battery voltage |
|
||||||
|
| `sensors.c/h` | GPIO ISR-based sensor debouncing, encoder counters |
|
||||||
|
| `i2c.c/h` | TCA9555 relay/LED/button control |
|
||||||
|
| `storage.c/h` | NVM table + circular binary log buffer |
|
||||||
|
| `comms.c/h` | Unified GET/POST JSON API (shared by HTTP and UART) |
|
||||||
|
| `webserver.c/h` | WiFi softAP, HTTP server, embedded gzip webpage |
|
||||||
|
| `uart_comms.c/h` | Serial JSON interface (115200 8N1) |
|
||||||
|
| `rf_433.c/h` | 433MHz OOK receiver, keycode learn/match |
|
||||||
|
| `bt_hid.c/h` | BLE HID host, media remote button mapping |
|
||||||
|
| `rtc.c/h` | Unix time, harvest alarms, soft idle, inactivity timer |
|
||||||
|
| `solar.c/h` | Simple FLOAT/BULK solar charge state machine |
|
||||||
|
| `sc_err.h` | Error code definitions |
|
||||||
|
| `log_test.c/h` | Flash log unit tests |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Control FSM States
|
||||||
|
|
||||||
|
```
|
||||||
|
STATE_IDLE
|
||||||
|
STATE_MOVE_START_DELAY (1s)
|
||||||
|
STATE_JACK_UP_START (detect current spike → jack engaged)
|
||||||
|
STATE_JACK_UP (continue until timer/e-fuse)
|
||||||
|
STATE_DRIVE_START_DELAY (1s)
|
||||||
|
STATE_DRIVE_FLUFF_START
|
||||||
|
STATE_DRIVE (encoder-based distance control)
|
||||||
|
STATE_DRIVE_END_DELAY (1s)
|
||||||
|
STATE_JACK_DOWN (reverse until e-fuse/sensor)
|
||||||
|
→ back to STATE_IDLE
|
||||||
|
|
||||||
|
STATE_UNDO_JACK_START (emergency: reverse jack, run until e-fuse/sensor)
|
||||||
|
→ back to STATE_IDLE
|
||||||
|
|
||||||
|
STATE_CALIBRATE_JACK_DELAY / STATE_CALIBRATE_JACK_MOVE (jack calibration sequence)
|
||||||
|
STATE_CALIBRATE_DRIVE_DELAY / STATE_CALIBRATE_DRIVE_MOVE (drive calibration sequence)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Guards before START:**
|
||||||
|
- Remaining distance > 0 (leash protection)
|
||||||
|
- Battery V ≥ `LOW_PROTECTION_V` (default 10V)
|
||||||
|
- Safety sensor active (debounced stable)
|
||||||
|
- All e-fuses not tripped
|
||||||
|
|
||||||
|
**FSM Loop (20ms tick in `control_task()`):**
|
||||||
|
1. `process_bridge_current()` — ADC → EMA → auto-zero → e-fuse
|
||||||
|
2. `process_battery_voltage()` — ADC → EMA
|
||||||
|
3. `sensors_check()` — drain ISR queue, update counters/debounce
|
||||||
|
4. State machine transitions (timer + sensor + efuse checks)
|
||||||
|
5. `drive_relays()` — write relay output from current state
|
||||||
|
6. `send_fsm_log()` — timestamped entry to flash
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## E-Fuse Algorithm (`power_mgmt.c`)
|
||||||
|
|
||||||
|
Per bridge, each 20ms tick:
|
||||||
|
1. Raw ADC → EMA filter (α = `ADC_ALPHA_ISENS`)
|
||||||
|
2. Auto-zero: learn zero offset when motor is off + grace period expired
|
||||||
|
3. Grace period: 250ms after relay closes (ignores startup inrush)
|
||||||
|
4. **Instant trip:** I ≥ `EFUSE_KINST` × I_nom (default 2×)
|
||||||
|
5. **Thermal trip:** heat accumulates as I²·Δt; dissipates at τ_cool rate
|
||||||
|
6. **Auto-reset:** after `EFUSE_TCOOL` seconds of cooling (default 5s)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Safety Sensor Debouncing (Asymmetric)
|
||||||
|
|
||||||
|
```
|
||||||
|
LOW (safe): 1000ms make time → slow to declare safe (SAFETY_MAKE_US)
|
||||||
|
HIGH (break): 300ms break time → fast to kill operation (SAFETY_BREAK_US)
|
||||||
|
```
|
||||||
|
|
||||||
|
Safety break → immediate `STATE_UNDO_JACK_START`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Communication Interfaces
|
||||||
|
|
||||||
|
### WiFi (softAP)
|
||||||
|
- SSID/password/channel configurable via params (`WIFI_SSID`, `WIFI_PASS`, `WIFI_CHANNEL`)
|
||||||
|
- mDNS hostname: `sc.local`
|
||||||
|
- Captive portal DNS: all queries → 192.168.4.1
|
||||||
|
- HTTP port 80
|
||||||
|
- The softAP and HTTP server stay up during soft idle so a client can always associate and revive the device (see Power Management).
|
||||||
|
|
||||||
|
### HTTP API (port 80)
|
||||||
|
| Endpoint | Method | Description |
|
||||||
|
|------------|--------|----------------------------------------------------------------------|
|
||||||
|
| `/` | GET | Embedded gzip HTML webpage |
|
||||||
|
| `/get` | GET | JSON system status (polling fallback when the WebSocket is down) |
|
||||||
|
| `/post` | POST | JSON commands + parameter updates |
|
||||||
|
| `/ws` | GET | WebSocket: real-time control channel (see below) |
|
||||||
|
| `/log` | ANY | Binary log download (4B JSON len + JSON + 8B offsets + log data) |
|
||||||
|
| `/ota` | POST | Firmware update upload |
|
||||||
|
|
||||||
|
### WebSocket (`/ws`) — real-time channel
|
||||||
|
Requires `CONFIG_HTTPD_WS_SUPPORT=y` (set in `sdkconfig.defaults`). The web UI opens a WebSocket on load and uses it for:
|
||||||
|
- **client → server:** low-latency remote-control commands (`fwd`/`rev`/`extend`/`retract`/`aux`/`stop_override`) as small JSON text frames, routed through `comms_handle_post()` so they share the POST command vocabulary.
|
||||||
|
- **server → client:** a 1 Hz status push (same JSON as `/get`), replacing the old 2 s HTTP poll. Built only when ≥1 client is connected (no heap churn when idle).
|
||||||
|
|
||||||
|
**Safety:** any WS socket close (tab closed, WiFi dropped, crash) fires `stop_override()` via the httpd `close_fn`, halting jogged motion without relying on the `RF_PULSE_LENGTH` timeout. Held jog also re-sends every 150 ms, re-arming that timeout as a backstop.
|
||||||
|
|
||||||
|
**Robustness:** a vanished client leaves a stale TCP socket (no FIN). The broadcast pre-checks writability with a zero-timeout `select()` and sets a 2 s `SO_SNDTIMEO` on WS sockets, so a dead client is reclaimed (`httpd_sess_trigger_close`) instead of blocking the shared httpd task — which previously wedged the server and broke reconnects. The client falls back to `/get` polling + `/post` if the WS won't connect.
|
||||||
|
|
||||||
|
### UART (115200 8N1)
|
||||||
|
- `GET` → same as HTTP GET /get
|
||||||
|
- `POST: {json}` → same as HTTP POST /post
|
||||||
|
- `RTCDEBUG` → dump RTC timekeeping state (time, backup, sleep entry, clock source)
|
||||||
|
- `HELP` → command reference
|
||||||
|
- Shares `comms_handle_get()` / `comms_handle_post()` with HTTP
|
||||||
|
|
||||||
|
### 433MHz RF (GPIO25, RMT)
|
||||||
|
- 24-bit OOK codes (P_HIGH≈1040µs, P_LOW≈340µs, margin 70µs)
|
||||||
|
- 8 stored keycodes → FSM_OVERRIDE_* commands
|
||||||
|
- Learn mode: capture next RX → temp buffer → user commits via web
|
||||||
|
|
||||||
|
### Bluetooth HID Host
|
||||||
|
- Scans for BLE HID devices (service UUID 0x1812)
|
||||||
|
- Tries saved BDA first, then scans for best RSSI
|
||||||
|
- Button mapping:
|
||||||
|
- VOL_UP → Jack Up (override pulse)
|
||||||
|
- VOL_DOWN → Jack Down
|
||||||
|
- PREV → Drive Reverse
|
||||||
|
- NEXT → Drive Forward
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Storage Layout
|
||||||
|
|
||||||
|
**Flash partitions (8MB flash):**
|
||||||
|
|
||||||
|
| Partition | Offset | Size | Purpose |
|
||||||
|
|-------------|-----------|-----------|----------------------------------------------------------------|
|
||||||
|
| nvs | 0x9000 | 16K | WiFi/BT config, board revision, RTC time backup |
|
||||||
|
| otadata | 0xD000 | 8K | OTA boot selection |
|
||||||
|
| phy_init | 0xF000 | 4K | RF calibration data |
|
||||||
|
| ota_0 | 0x10000 | 1984K | Factory / primary app slot |
|
||||||
|
| ota_1 | 0x200000 | 1984K | OTA update slot |
|
||||||
|
| post_test | 0x3F0000 | 4K | Power-on self-test scratch sector |
|
||||||
|
| params | 0x3F1000 | 32K | CRC32-protected parameter storage (49 params) |
|
||||||
|
| log | 0x400000 | 4096K | Circular binary log buffer (head/tail tracked) |
|
||||||
|
|
||||||
|
**Log entry format (25 bytes typical):**
|
||||||
|
```
|
||||||
|
[0:8] ts_ms (u64)
|
||||||
|
[8:12] bat_V (f32)
|
||||||
|
[12:16] current_A (f32) — combined, not per-bridge
|
||||||
|
[16:18] counter (i16)
|
||||||
|
[18:19] sensors (u8)
|
||||||
|
[19:23] heat (f32) — max across bridges
|
||||||
|
[23:25] i2c_out (u16)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## RTC & Timekeeping
|
||||||
|
|
||||||
|
**Time source:** `esp_timer` (40 MHz APB crystal, ~20 ppm accuracy). The external 32.768 kHz crystal on GPIO32/33 is present on the PCB but **not used** — deep sleep is not used normally (soft idle instead), so RTC slow clock accuracy is irrelevant. The RTC slow clock uses the default internal RC oscillator.
|
||||||
|
|
||||||
|
**`rtc_xtal_init()` in `rtc.c`:** Configures the button GPIO (GPIO13); no crystal bootstrap or sleep wakeup sources.
|
||||||
|
|
||||||
|
**Time persistence across resets:** `rtc_save_time()` writes the current unix timestamp to NVS (namespace `"hw"`, key `"rtc_time"`). On boot, `rtc_restore_time()` tries `RTC_DATA_ATTR` first, then falls back to NVS. This ensures time survives software resets even when the bootloader reloads RTC slow memory. The saved time will be stale by the reboot duration (~2s), which is acceptable.
|
||||||
|
|
||||||
|
**Diagnosing time issues:** Run `RTCDEBUG` over UART. Reports current time, sync time, elapsed since sync, next alarm, uptime, and soft idle state.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Button & LED Behavior
|
||||||
|
|
||||||
|
Single physical button (button 0 via TCA9555 I2C expander) controls all interactions. All logic lives in the main loop (50ms tick) in `main.c`.
|
||||||
|
|
||||||
|
### Button Actions by State
|
||||||
|
|
||||||
|
**IDLE — Triple-tap to start:**
|
||||||
|
- 3 taps within a 2-second window triggers `FSM_CMD_START`
|
||||||
|
- Start fires immediately on the 3rd tap
|
||||||
|
- LED feedback: 1 tap → LED 1, 2 taps → LED 1+2, 3 taps → LED 1+2+3 (then start)
|
||||||
|
- LEDs persist until next tap or window expiry; counter resets on expiry
|
||||||
|
|
||||||
|
**IDLE / CALIBRATE — 3-second hold to reboot:**
|
||||||
|
- Saves RTC time to NVS, then calls `esp_restart()`
|
||||||
|
- LED progression: off (0–750ms) → LED 1 (750–1500ms) → LED 1+2 (1500–2250ms) → LED 1+2+3 (2250–3000ms) → flash all (6× at 150ms) → reboot
|
||||||
|
|
||||||
|
**Moving states** — any tap sends `FSM_CMD_UNDO`
|
||||||
|
|
||||||
|
**UNDO state (UNDO_JACK_START)** — any tap sends `FSM_CMD_STOP` (emergency stop)
|
||||||
|
|
||||||
|
**Calibration states** — tap advances through calibration steps (unchanged)
|
||||||
|
|
||||||
|
**Factory reset** — two ways, both run `factory_reset()`: (1) power cycle with GPIO13 held for 10 seconds (only triggers on `ESP_RST_POWERON` or `ESP_RST_EXT`), or (2) the **FACTORY RESET** button in the DANGER ZONE (web UI → `cmd: "factory_reset"` → reset + reboot). Resets all params and erases log/post_test partitions. Preserves NVS (board_rev, BT pairing, RTC time).
|
||||||
|
|
||||||
|
### LED Status Indicators
|
||||||
|
|
||||||
|
**Physical LED layout** — the three LEDs are wired to TCA9555 port-0 pins
|
||||||
|
P05, P06, P07. Read bottom → top when checking error codes:
|
||||||
|
|
||||||
|
| TCA pin | Bit | Physical position | Called |
|
||||||
|
|---------|-----|-------------------|--------|
|
||||||
|
| P05 | 0 | bottom | LED1 |
|
||||||
|
| P06 | 1 | middle | LED2 |
|
||||||
|
| P07 | 2 | top | LED3 |
|
||||||
|
|
||||||
|
A pattern written as `001` (LSB first) means **only the bottom LED is lit**,
|
||||||
|
`100` means **only the top LED is lit**, and `111` means all three.
|
||||||
|
|
||||||
|
| State | Pattern | Timing |
|
||||||
|
|-------------------|----------------------------------------------|-------------------------------------------|
|
||||||
|
| Idle | LED1 blink | 0.5Hz (1s on / 1s off) |
|
||||||
|
| Error | Rapid all-blink → error code hold | 5Hz for 1s, then code for 2s (3s cycle) |
|
||||||
|
| Moving / delays | Waterfall 001→011→111→110→100→000 | ~1 cycle/s (167ms per step) |
|
||||||
|
| Calibrating | All LEDs flash | 1Hz (500ms on / 500ms off) |
|
||||||
|
| Undo | All LEDs solid on | Continuous |
|
||||||
|
| Booting | LED1 solid | Until init complete |
|
||||||
|
|
||||||
|
**Error code bits (during 2s hold phase):**
|
||||||
|
|
||||||
|
| LED Pattern (bottom→top) | Meaning |
|
||||||
|
|--------------------------|--------------------------------------------------------|
|
||||||
|
| 001 — only bottom (P05) lit | Efuse tripped (any bridge) or low battery |
|
||||||
|
| 010 — only middle (P06) lit | RTC/clock not set |
|
||||||
|
| 100 — only top (P07) lit | Safety sensor break or leash limit hit |
|
||||||
|
| 111 — all three lit | Unknown FSM error (fallback) |
|
||||||
|
|
||||||
|
Error codes are also shown on the web interface status field with individual flag names.
|
||||||
|
|
||||||
|
### Implementation Details
|
||||||
|
|
||||||
|
- Tap detection uses **release edge** (`i2c_get_button_released()`) with `btn_held < 1000ms` guard (long presses don't count as taps)
|
||||||
|
- 2-second tap window starts on first tap, fixed duration (not reset by subsequent taps)
|
||||||
|
- All button state sampled once per tick: `btn_pressed`, `btn_tripped`, `btn_released`, `btn_held`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Power Management
|
||||||
|
|
||||||
|
- **Battery voltage:** GPIO35, thru divider → `V = raw × V_SENS_K + V_SENS_OFFSET` (defaults: K=0.00766̄, offset=0.4)
|
||||||
|
- **Solar charger:** GPIO26 (RTC hold) — FLOAT/BULK FSM, bulk for 20s when V < 5V for 5s
|
||||||
|
- **Inactivity shutdown:** after `INACTIVITY_TIMEOUT_S` (default 300s) → **soft idle** (BT off, LEDs off, sensor rail off — not deep sleep). **WiFi softAP + HTTP server stay up.** Any incoming request (page load, `/get`, WS connect, command) or a button press calls `rtc_reset_shutdown_timer()`, which wakes from soft idle — so an already-associated client can revive the device just by reconnecting, without re-associating.
|
||||||
|
- **RTC_DATA_ATTR:** Sync timestamps, alarm times, charge state — survive software resets (panics, WDT)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Error Codes (`sc_err.h`)
|
||||||
|
|
||||||
|
```c
|
||||||
|
SC_ERR_EFUSE_TRIP_1 = 0x201 // Drive overcurrent/overheat
|
||||||
|
SC_ERR_EFUSE_TRIP_2 = 0x202 // Jack
|
||||||
|
SC_ERR_EFUSE_TRIP_3 = 0x203 // Aux
|
||||||
|
SC_ERR_SAFETY_TRIP = 0x210 // Safety sensor break
|
||||||
|
SC_ERR_LEASH_HIT = 0x211 // Distance limit reached
|
||||||
|
SC_ERR_RTC_NOT_SET = 0x220 // Clock not synchronized
|
||||||
|
SC_ERR_LOW_BATTERY = 0x230 // Voltage below threshold
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Build System
|
||||||
|
|
||||||
|
- **Framework:** ESP-IDF (>=5.0)
|
||||||
|
- **Component deps** (`main/idf_component.yml`): `espressif/mdns ~1.9.1`
|
||||||
|
- **IDF requires:** `driver`, `esp_http_server`, `esp_netif`, `lwip`, `json`, `esp_timer`, `esp_adc`, `app_update`, `esp_wifi`, `nvs_flash`, `mdns`, `bt`, `esp_hid`
|
||||||
|
- **Webpage:** `webpage.html` → `webpage_compile.py` → `webpage_gzip.h` (embedded gzip binary). **Must re-run `webpage_compile.py` after any HTML edit before building.**
|
||||||
|
- **Version:** `version.h.in` filled by CMake from git tags → `FIRMWARE_VERSION`, `BUILD_DATE`
|
||||||
|
|||||||
BIN
SC-F001-released.bin
Normal file
BIN
SC-F001-released.bin
Normal file
Binary file not shown.
20
TODO.md
20
TODO.md
@@ -1,20 +0,0 @@
|
|||||||
# TODO
|
|
||||||
- [test] Seamless crashing
|
|
||||||
- crashes need to not cause RTC to lose time
|
|
||||||
- the remaining_distance needs to be unaffected
|
|
||||||
- the equivalent of a try-catch block on the whole program
|
|
||||||
- this should also make a log
|
|
||||||
- [x] Logtool: python tool that shows logs
|
|
||||||
-[x] needs to support both opening a log.bin and streaming from http://ip-address-or-hostname/log
|
|
||||||
-[x] needs to have a CLI table output
|
|
||||||
-[test] needs to have a GUI output (matplotlib)
|
|
||||||
- [test] Refactor; make sure everything adheres to naming conventions
|
|
||||||
- [test] Renaming wifi (should reboot the wifi/web comms to take effect)
|
|
||||||
- [x] Make sure external RTC crystal is actually in use
|
|
||||||
- [x] Warn if time is de-synced from client by more than 5 minutes
|
|
||||||
- [x] Bluetooth pairing
|
|
||||||
- [ ] WiFi Network Connection
|
|
||||||
- add entries for wifi network ssid/password
|
|
||||||
- try to connect to the wifi network first
|
|
||||||
- if that fails then broadcast ad-hoc network like currently
|
|
||||||
- [ ] Hard Reset
|
|
||||||
BIN
bringup/__pycache__/calibrate.cpython-311.pyc
Normal file
BIN
bringup/__pycache__/calibrate.cpython-311.pyc
Normal file
Binary file not shown.
BIN
bringup/__pycache__/flash.cpython-311.pyc
Normal file
BIN
bringup/__pycache__/flash.cpython-311.pyc
Normal file
Binary file not shown.
BIN
bringup/__pycache__/flash.cpython-313.pyc
Normal file
BIN
bringup/__pycache__/flash.cpython-313.pyc
Normal file
Binary file not shown.
BIN
bringup/__pycache__/protocol.cpython-311.pyc
Normal file
BIN
bringup/__pycache__/protocol.cpython-311.pyc
Normal file
Binary file not shown.
BIN
bringup/__pycache__/protocol.cpython-313.pyc
Normal file
BIN
bringup/__pycache__/protocol.cpython-313.pyc
Normal file
Binary file not shown.
BIN
bringup/__pycache__/stages.cpython-311.pyc
Normal file
BIN
bringup/__pycache__/stages.cpython-311.pyc
Normal file
Binary file not shown.
BIN
bringup/__pycache__/stages.cpython-313.pyc
Normal file
BIN
bringup/__pycache__/stages.cpython-313.pyc
Normal file
Binary file not shown.
182
bringup/bringup.py
Normal file
182
bringup/bringup.py
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
SC-F001 Bring-up Tool
|
||||||
|
|
||||||
|
Flashes the firmware (optional) and then walks the operator through the
|
||||||
|
bring-up procedure documented in docs/SC-F001/BRINGUP.md.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
bringup.py --port <COMx | /dev/ttyUSB0> [options]
|
||||||
|
|
||||||
|
Options:
|
||||||
|
--port <p> Serial port (required)
|
||||||
|
--baud <n> Baud rate for UART protocol (default: 115200)
|
||||||
|
--out <basename> Transcript basename (default: dated)
|
||||||
|
|
||||||
|
Flashing (optional):
|
||||||
|
--flash Flash the firmware before running tests
|
||||||
|
--build-dir <p> Build directory containing flasher_args.json
|
||||||
|
(default: ../build/ relative to this script)
|
||||||
|
--flash-baud <n> Baud for esptool (default: 460800)
|
||||||
|
--erase `esptool erase_flash` before writing (slow)
|
||||||
|
--flash-only Flash and exit (no bring-up tests)
|
||||||
|
|
||||||
|
Testing:
|
||||||
|
--skip-relays Skip the live relay pulse stage
|
||||||
|
--no-calibrate Skip battery-voltage calibration prompt
|
||||||
|
--no-transcript Do not write a .txt transcript file
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
from datetime import datetime
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
sys.path.insert(0, str(Path(__file__).parent))
|
||||||
|
|
||||||
|
import fmt # noqa: E402
|
||||||
|
from protocol import Link # noqa: E402
|
||||||
|
from stages import all_stages, Tally # noqa: E402
|
||||||
|
import flash as flasher # noqa: E402
|
||||||
|
|
||||||
|
|
||||||
|
def _default_basename() -> str:
|
||||||
|
return "BRINGUP_" + datetime.now().strftime("%d%b%Y_%H%M").upper()
|
||||||
|
|
||||||
|
|
||||||
|
def _do_flash(args, log_fn) -> None:
|
||||||
|
build_dir = Path(args.build_dir) if args.build_dir else None
|
||||||
|
try:
|
||||||
|
flasher.flash(
|
||||||
|
port=args.port,
|
||||||
|
build_dir=build_dir,
|
||||||
|
baud=args.flash_baud,
|
||||||
|
erase_all=args.erase,
|
||||||
|
log=log_fn,
|
||||||
|
)
|
||||||
|
except flasher.FlashError as e:
|
||||||
|
log_fn(f"FLASH FAILED: {e}")
|
||||||
|
raise SystemExit(2)
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
ap = argparse.ArgumentParser(description="SC-F001 bring-up tool (flash + test)")
|
||||||
|
ap.add_argument("--port", required=True, help="serial port (COM5, /dev/ttyUSB0, ...)")
|
||||||
|
ap.add_argument("--baud", type=int, default=115200)
|
||||||
|
ap.add_argument("--out", default=None, help="transcript basename")
|
||||||
|
|
||||||
|
ap.add_argument("--flash", action="store_true",
|
||||||
|
help="flash firmware before tests")
|
||||||
|
ap.add_argument("--build-dir", default=None,
|
||||||
|
help="build dir with flasher_args.json (default: ../build)")
|
||||||
|
ap.add_argument("--flash-baud", type=int, default=460800)
|
||||||
|
ap.add_argument("--erase", action="store_true",
|
||||||
|
help="erase_flash before writing")
|
||||||
|
ap.add_argument("--flash-only", action="store_true",
|
||||||
|
help="flash and exit; skip tests")
|
||||||
|
|
||||||
|
ap.add_argument("--skip-relays", action="store_true")
|
||||||
|
ap.add_argument("--no-calibrate", action="store_true")
|
||||||
|
ap.add_argument("--no-transcript", action="store_true")
|
||||||
|
args = ap.parse_args()
|
||||||
|
|
||||||
|
basename = args.out or _default_basename()
|
||||||
|
transcript_path = None if args.no_transcript else Path(basename + ".txt")
|
||||||
|
transcript_file = None
|
||||||
|
transcript_cb = None
|
||||||
|
|
||||||
|
if transcript_path:
|
||||||
|
transcript_file = transcript_path.open("w", encoding="utf-8")
|
||||||
|
def _tx(line: str) -> None:
|
||||||
|
ts = datetime.now().strftime("%H:%M:%S.%f")[:-3]
|
||||||
|
transcript_file.write(f"{ts} {fmt.strip(line)}\n")
|
||||||
|
transcript_file.flush()
|
||||||
|
transcript_cb = _tx
|
||||||
|
print(f"Transcript → {transcript_path}")
|
||||||
|
|
||||||
|
def _log(msg: str) -> None:
|
||||||
|
print(msg)
|
||||||
|
if transcript_cb:
|
||||||
|
transcript_cb(msg)
|
||||||
|
|
||||||
|
# Phase 1: optional flash
|
||||||
|
if args.flash or args.flash_only:
|
||||||
|
_log(fmt.stage(f"Flashing {args.port}"))
|
||||||
|
_do_flash(args, _log)
|
||||||
|
_log(fmt.pass_("Flash complete"))
|
||||||
|
if args.flash_only:
|
||||||
|
if transcript_file:
|
||||||
|
transcript_file.close()
|
||||||
|
return 0
|
||||||
|
# Give the chip a moment to finish hard_reset before we open the port
|
||||||
|
time.sleep(1.5)
|
||||||
|
|
||||||
|
# Phase 2: connect and walk bring-up stages
|
||||||
|
_log(f"Connecting to {args.port} @ {args.baud} ...")
|
||||||
|
link = Link(args.port, baud=args.baud, transcript=transcript_cb)
|
||||||
|
link.ser.reset_input_buffer()
|
||||||
|
|
||||||
|
tally = Tally()
|
||||||
|
stages = all_stages(skip_relays=args.skip_relays,
|
||||||
|
no_calibrate=args.no_calibrate)
|
||||||
|
|
||||||
|
def _snapshot(t: Tally) -> tuple[int, int, int, int]:
|
||||||
|
return (t.passed, t.failed, t.warnings, t.skipped)
|
||||||
|
|
||||||
|
def _restore(t: Tally, snap: tuple[int, int, int, int]) -> None:
|
||||||
|
t.passed, t.failed, t.warnings, t.skipped = snap
|
||||||
|
|
||||||
|
try:
|
||||||
|
try:
|
||||||
|
for stage in stages:
|
||||||
|
while True:
|
||||||
|
snap = _snapshot(tally)
|
||||||
|
try:
|
||||||
|
stage(link, tally)
|
||||||
|
except TimeoutError as e:
|
||||||
|
print(f" TIMEOUT: {e}")
|
||||||
|
tally.note_fail()
|
||||||
|
except Exception as e:
|
||||||
|
print(f" EXCEPTION in stage: {e!r}")
|
||||||
|
tally.note_fail()
|
||||||
|
if tally.failed > snap[1]:
|
||||||
|
ans = input(fmt.prompt(" Stage had FAILs — retry? [y/n]") + ": ").strip().lower()
|
||||||
|
if ans.startswith("y"):
|
||||||
|
_restore(tally, snap)
|
||||||
|
continue
|
||||||
|
break
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print(fmt.warn("\nAborted by operator"))
|
||||||
|
try:
|
||||||
|
link.send("BU.END")
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
print(fmt.stage("Bring-up summary"))
|
||||||
|
print(fmt.summary_line(tally.passed, tally.failed, tally.warnings, tally.skipped))
|
||||||
|
if tally.failed == 0:
|
||||||
|
print(f" {fmt.pass_('ALL PASS')}")
|
||||||
|
else:
|
||||||
|
print(f" {fmt.fail('FAILURES PRESENT — review above')}")
|
||||||
|
finally:
|
||||||
|
# Close link + transcript deterministically — Python would clean up
|
||||||
|
# on interpreter exit, but on KeyboardInterrupt or other unexpected
|
||||||
|
# exits the file handle should be released as soon as we leave main.
|
||||||
|
try:
|
||||||
|
link.close()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
if transcript_file:
|
||||||
|
try:
|
||||||
|
transcript_file.close()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return 0 if tally.failed == 0 else 1
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
39
bringup/calibrate.py
Normal file
39
bringup/calibrate.py
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
"""Battery voltage calibration math.
|
||||||
|
|
||||||
|
Firmware model (power_mgmt.c:278):
|
||||||
|
V_bat = mV * V_SENS_K + V_SENS_OFFSET
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class CalResult:
|
||||||
|
k: float
|
||||||
|
offset: float
|
||||||
|
note: str
|
||||||
|
|
||||||
|
|
||||||
|
def single_point_offset(bat_mv: float, v_true: float, k: float) -> CalResult:
|
||||||
|
"""Fix K, solve for OFFSET so that V_bat(bat_mv) == v_true."""
|
||||||
|
offset = v_true - bat_mv * k
|
||||||
|
return CalResult(k=k, offset=offset, note="single-point offset")
|
||||||
|
|
||||||
|
|
||||||
|
def two_point(
|
||||||
|
bat_mv_1: float, v_true_1: float,
|
||||||
|
bat_mv_2: float, v_true_2: float,
|
||||||
|
) -> CalResult:
|
||||||
|
"""Solve for K and OFFSET from two (mV, V) pairs."""
|
||||||
|
dmv = bat_mv_2 - bat_mv_1
|
||||||
|
if abs(dmv) < 1e-3:
|
||||||
|
raise ValueError("Two calibration points are too close — pick wider V")
|
||||||
|
k = (v_true_2 - v_true_1) / dmv
|
||||||
|
offset = v_true_1 - bat_mv_1 * k
|
||||||
|
return CalResult(k=k, offset=offset, note="two-point")
|
||||||
|
|
||||||
|
|
||||||
|
def verify(bat_mv: float, cal: CalResult) -> float:
|
||||||
|
return bat_mv * cal.k + cal.offset
|
||||||
108
bringup/flash.py
Normal file
108
bringup/flash.py
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
"""Wrap esptool to flash the SC-F001 firmware from a build directory.
|
||||||
|
|
||||||
|
Reads `build/flasher_args.json` (produced by `idf.py build`) to get the
|
||||||
|
authoritative list of offsets + binaries, then invokes esptool once with all
|
||||||
|
of them — no hardcoded offsets here.
|
||||||
|
|
||||||
|
Requires esptool reachable either as a Python module (`pip install esptool`)
|
||||||
|
or as `esptool.py` on PATH (e.g., from an ESP-IDF activation).
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import importlib.util
|
||||||
|
import json
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
class FlashError(RuntimeError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def _default_build_dir() -> Path:
|
||||||
|
# bringup dir sits at SC-F001/bringup; build sits at SC-F001/build
|
||||||
|
return Path(__file__).resolve().parent.parent / "build"
|
||||||
|
|
||||||
|
|
||||||
|
def _resolve_esptool_invocation() -> list[str]:
|
||||||
|
"""Return the command prefix to run esptool, preferring the installed
|
||||||
|
module in the current interpreter, falling back to esptool.py on PATH.
|
||||||
|
|
||||||
|
Raises FlashError with an actionable message if neither is available.
|
||||||
|
"""
|
||||||
|
if importlib.util.find_spec("esptool") is not None:
|
||||||
|
return [sys.executable, "-m", "esptool"]
|
||||||
|
fallback = shutil.which("esptool.py") or shutil.which("esptool")
|
||||||
|
if fallback:
|
||||||
|
return [fallback]
|
||||||
|
raise FlashError(
|
||||||
|
"esptool is not installed in this Python and not on PATH.\n"
|
||||||
|
" Install with: "
|
||||||
|
f"{sys.executable} -m pip install -r "
|
||||||
|
f"{Path(__file__).parent / 'requirements.txt'}\n"
|
||||||
|
" Or activate an ESP-IDF shell that provides esptool.py."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _load_manifest(build_dir: Path) -> dict:
|
||||||
|
manifest = build_dir / "flasher_args.json"
|
||||||
|
if not manifest.exists():
|
||||||
|
raise FlashError(
|
||||||
|
f"Build manifest not found: {manifest}\n"
|
||||||
|
f"Run `idf.py build` from SC-F001/ first, or pass --build-dir."
|
||||||
|
)
|
||||||
|
return json.loads(manifest.read_text())
|
||||||
|
|
||||||
|
|
||||||
|
def _resolve_flash_args(build_dir: Path, manifest: dict) -> list[str]:
|
||||||
|
"""Expand manifest into a (offset, abs-path) list suitable for esptool."""
|
||||||
|
args: list[str] = []
|
||||||
|
# flasher_args.json's flash_files is {offset_hex: relpath}.
|
||||||
|
for offset_hex, rel in manifest["flash_files"].items():
|
||||||
|
p = (build_dir / rel).resolve()
|
||||||
|
if not p.exists():
|
||||||
|
raise FlashError(f"Missing firmware binary: {p}")
|
||||||
|
args.append(offset_hex)
|
||||||
|
args.append(str(p))
|
||||||
|
return args
|
||||||
|
|
||||||
|
|
||||||
|
def flash(
|
||||||
|
port: str,
|
||||||
|
build_dir: Path | None = None,
|
||||||
|
baud: int = 460800,
|
||||||
|
erase_all: bool = False,
|
||||||
|
log: callable = print,
|
||||||
|
) -> None:
|
||||||
|
build_dir = (build_dir or _default_build_dir()).resolve()
|
||||||
|
manifest = _load_manifest(build_dir)
|
||||||
|
|
||||||
|
chip = manifest.get("extra_esptool_args", {}).get("chip", "esp32")
|
||||||
|
before = manifest.get("extra_esptool_args", {}).get("before", "default_reset")
|
||||||
|
after = manifest.get("extra_esptool_args", {}).get("after", "hard_reset")
|
||||||
|
|
||||||
|
esptool_cmd = _resolve_esptool_invocation()
|
||||||
|
base_cmd = esptool_cmd + [
|
||||||
|
"--chip", chip,
|
||||||
|
"--port", port,
|
||||||
|
"--baud", str(baud),
|
||||||
|
"--before", before,
|
||||||
|
"--after", after,
|
||||||
|
]
|
||||||
|
|
||||||
|
if erase_all:
|
||||||
|
log(f" erase_flash @ {port}")
|
||||||
|
subprocess.check_call(base_cmd + ["erase_flash"])
|
||||||
|
|
||||||
|
write_args = manifest.get("write_flash_args", [])
|
||||||
|
cmd = base_cmd + ["write_flash"] + write_args + _resolve_flash_args(build_dir, manifest)
|
||||||
|
|
||||||
|
log(f" flashing from {build_dir}")
|
||||||
|
log(f" files: {list(manifest['flash_files'].items())}")
|
||||||
|
try:
|
||||||
|
subprocess.check_call(cmd)
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
raise FlashError(f"esptool failed (exit {e.returncode})") from e
|
||||||
129
bringup/fmt.py
Normal file
129
bringup/fmt.py
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
"""Terminal formatting helpers for the bring-up tool.
|
||||||
|
|
||||||
|
Thin wrapper around ANSI escapes so stages.py / bringup.py can emit
|
||||||
|
consistently-styled headings, prompts, status tags, and result
|
||||||
|
summaries without sprinkling raw escape codes everywhere.
|
||||||
|
|
||||||
|
Colors auto-disable when stdout is not a TTY or when the `NO_COLOR`
|
||||||
|
environment variable is set (see no-color.org).
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
def _color_supported() -> bool:
|
||||||
|
if os.environ.get("NO_COLOR") is not None:
|
||||||
|
return False
|
||||||
|
if not sys.stdout.isatty():
|
||||||
|
return False
|
||||||
|
# Modern Windows 10+ terminals support VT100 once any ANSI sequence has
|
||||||
|
# been emitted — a no-op system("") call flips the flag on cmd.exe.
|
||||||
|
if os.name == "nt":
|
||||||
|
try:
|
||||||
|
os.system("")
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
_USE = _color_supported()
|
||||||
|
|
||||||
|
|
||||||
|
def _c(code: str) -> str:
|
||||||
|
return f"\x1b[{code}m" if _USE else ""
|
||||||
|
|
||||||
|
|
||||||
|
RESET = _c("0")
|
||||||
|
BOLD = _c("1")
|
||||||
|
DIM = _c("2")
|
||||||
|
|
||||||
|
RED = _c("31")
|
||||||
|
GREEN = _c("32")
|
||||||
|
YELLOW = _c("33")
|
||||||
|
BLUE = _c("34")
|
||||||
|
MAGENTA = _c("35")
|
||||||
|
CYAN = _c("36")
|
||||||
|
|
||||||
|
|
||||||
|
def stage(title: str) -> str:
|
||||||
|
"""Big block heading that opens a stage."""
|
||||||
|
bar = "-" * 60
|
||||||
|
return (
|
||||||
|
f"\n{CYAN}{bar}{RESET}\n"
|
||||||
|
f"{BOLD}{CYAN} {title}{RESET}\n"
|
||||||
|
f"{CYAN}{bar}{RESET}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def section(title: str) -> str:
|
||||||
|
"""Smaller sub-heading inside a stage."""
|
||||||
|
return f"\n{BOLD}{MAGENTA}-- {title} --{RESET}"
|
||||||
|
|
||||||
|
|
||||||
|
def prompt(text: str) -> str:
|
||||||
|
return f"{YELLOW}{text}{RESET}"
|
||||||
|
|
||||||
|
|
||||||
|
def tag(label: str, color: str) -> str:
|
||||||
|
return f"[{color}{BOLD}{label}{RESET}]"
|
||||||
|
|
||||||
|
|
||||||
|
OK_TAG = tag("OK", GREEN)
|
||||||
|
ERR_TAG = tag("ERR", RED)
|
||||||
|
SKIP_TAG = tag("SKIP", YELLOW)
|
||||||
|
WARN_TAG = tag("WARN", YELLOW)
|
||||||
|
INFO_TAG = tag("INFO", BLUE)
|
||||||
|
EVT_TAG = tag("EVT", CYAN)
|
||||||
|
|
||||||
|
|
||||||
|
def status_tag(status: str) -> str:
|
||||||
|
s = (status or "").upper()
|
||||||
|
return {
|
||||||
|
"OK": OK_TAG,
|
||||||
|
"ERR": ERR_TAG,
|
||||||
|
"SKIP": SKIP_TAG,
|
||||||
|
"WARN": WARN_TAG,
|
||||||
|
}.get(s, tag(s or "?", DIM))
|
||||||
|
|
||||||
|
|
||||||
|
def fail(text: str) -> str:
|
||||||
|
return f"{RED}{BOLD}{text}{RESET}"
|
||||||
|
|
||||||
|
|
||||||
|
def pass_(text: str) -> str:
|
||||||
|
return f"{GREEN}{BOLD}{text}{RESET}"
|
||||||
|
|
||||||
|
|
||||||
|
def warn(text: str) -> str:
|
||||||
|
return f"{YELLOW}{text}{RESET}"
|
||||||
|
|
||||||
|
|
||||||
|
def dim(text: str) -> str:
|
||||||
|
return f"{DIM}{text}{RESET}"
|
||||||
|
|
||||||
|
|
||||||
|
_ANSI_RE = None
|
||||||
|
|
||||||
|
|
||||||
|
def strip(text: str) -> str:
|
||||||
|
"""Return `text` with all ANSI escape sequences removed.
|
||||||
|
|
||||||
|
Used by the transcript writer so log files don't contain `\x1b[...m`
|
||||||
|
garbage when stdout is colored.
|
||||||
|
"""
|
||||||
|
global _ANSI_RE
|
||||||
|
if _ANSI_RE is None:
|
||||||
|
import re
|
||||||
|
_ANSI_RE = re.compile(r"\x1b\[[0-9;]*m")
|
||||||
|
return _ANSI_RE.sub("", text)
|
||||||
|
|
||||||
|
|
||||||
|
def summary_line(passed: int, failed: int, warnings: int, skipped: int) -> str:
|
||||||
|
color = GREEN if failed == 0 else RED
|
||||||
|
return (f" {color}{BOLD}pass={passed}{RESET} "
|
||||||
|
f"{RED if failed else DIM}{BOLD}fail={failed}{RESET} "
|
||||||
|
f"{YELLOW if warnings else DIM}warn={warnings}{RESET} "
|
||||||
|
f"{DIM}skip={skipped}{RESET}")
|
||||||
209
bringup/protocol.py
Normal file
209
bringup/protocol.py
Normal file
@@ -0,0 +1,209 @@
|
|||||||
|
"""Line-oriented protocol over UART for the SC-F001 bring-up procedure.
|
||||||
|
|
||||||
|
The firmware side is specified in docs/SC-F001/BRINGUP.md §3.
|
||||||
|
Commands are prefixed `BU.`; responses are `BU.OK`, `BU.ERR`, `BU.SKIP`,
|
||||||
|
or streamed `BU.EVENT` lines.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import re
|
||||||
|
import time
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
from typing import Callable, Iterator
|
||||||
|
|
||||||
|
import serial
|
||||||
|
|
||||||
|
|
||||||
|
LINE_TIMEOUT_S = 2.0
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Response:
|
||||||
|
status: str # "OK" | "ERR" | "SKIP"
|
||||||
|
cmd: str
|
||||||
|
fields: dict[str, str] = field(default_factory=dict)
|
||||||
|
raw: str = ""
|
||||||
|
|
||||||
|
def get(self, key: str, default: str | None = None) -> str | None:
|
||||||
|
return self.fields.get(key, default)
|
||||||
|
|
||||||
|
def getf(self, key: str, default: float = float("nan")) -> float:
|
||||||
|
v = self.fields.get(key)
|
||||||
|
if v is None:
|
||||||
|
return default
|
||||||
|
try:
|
||||||
|
return float(v)
|
||||||
|
except ValueError:
|
||||||
|
return default
|
||||||
|
|
||||||
|
def geti(self, key: str, default: int = 0) -> int:
|
||||||
|
v = self.fields.get(key)
|
||||||
|
if v is None:
|
||||||
|
return default
|
||||||
|
try:
|
||||||
|
return int(v, 0)
|
||||||
|
except ValueError:
|
||||||
|
return default
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Event:
|
||||||
|
cmd: str
|
||||||
|
fields: dict[str, str] = field(default_factory=dict)
|
||||||
|
raw: str = ""
|
||||||
|
|
||||||
|
|
||||||
|
_KV_RE = re.compile(r'(\w[\w.]*)=("[^"]*"|\S+)')
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_kv(rest: str) -> dict[str, str]:
|
||||||
|
out: dict[str, str] = {}
|
||||||
|
for m in _KV_RE.finditer(rest):
|
||||||
|
k = m.group(1)
|
||||||
|
v = m.group(2)
|
||||||
|
if v.startswith('"') and v.endswith('"'):
|
||||||
|
v = v[1:-1]
|
||||||
|
out[k] = v
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def parse_line(line: str) -> Response | Event | None:
|
||||||
|
"""Returns None for lines that aren't bring-up protocol (boot chatter etc.)."""
|
||||||
|
line = line.rstrip("\r\n")
|
||||||
|
if not line.startswith("BU."):
|
||||||
|
return None
|
||||||
|
tokens = line.split(None, 2)
|
||||||
|
tag = tokens[0] # BU.OK | BU.ERR | BU.EVENT | BU.SKIP
|
||||||
|
if len(tokens) < 2:
|
||||||
|
return None
|
||||||
|
cmd = tokens[1]
|
||||||
|
rest = tokens[2] if len(tokens) >= 3 else ""
|
||||||
|
fields = _parse_kv(rest)
|
||||||
|
if tag == "BU.EVENT":
|
||||||
|
return Event(cmd=cmd, fields=fields, raw=line)
|
||||||
|
status = tag.removeprefix("BU.")
|
||||||
|
if status in ("OK", "ERR", "SKIP"):
|
||||||
|
return Response(status=status, cmd=cmd, fields=fields, raw=line)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
class Link:
|
||||||
|
"""Wraps a serial.Serial with line I/O + protocol parsing."""
|
||||||
|
|
||||||
|
def __init__(self, port: str, baud: int = 115200, transcript: Callable[[str], None] | None = None):
|
||||||
|
# Don't let pyserial auto-assert DTR/RTS on open. ESP32 dev boards
|
||||||
|
# tie those into the BOOT/EN transistor pair — default-asserted lines
|
||||||
|
# hold the chip in reset for as long as the port is open, which
|
||||||
|
# silently blocks every command we send.
|
||||||
|
self.ser = serial.Serial()
|
||||||
|
self.ser.port = port
|
||||||
|
self.ser.baudrate = baud
|
||||||
|
self.ser.timeout = LINE_TIMEOUT_S
|
||||||
|
self.ser.dtr = False
|
||||||
|
self.ser.rts = False
|
||||||
|
self.ser.open()
|
||||||
|
# After open, re-assert False (some platforms override on open).
|
||||||
|
self.ser.dtr = False
|
||||||
|
self.ser.rts = False
|
||||||
|
self.transcript = transcript or (lambda _s: None)
|
||||||
|
self._buf = b""
|
||||||
|
|
||||||
|
def close(self) -> None:
|
||||||
|
try:
|
||||||
|
self.ser.close()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _readline(self, deadline: float) -> str | None:
|
||||||
|
while True:
|
||||||
|
remaining = deadline - time.monotonic()
|
||||||
|
if remaining <= 0:
|
||||||
|
return None
|
||||||
|
if b"\n" in self._buf:
|
||||||
|
line, _, self._buf = self._buf.partition(b"\n")
|
||||||
|
s = line.decode("utf-8", errors="replace")
|
||||||
|
self.transcript(f"<- {s}")
|
||||||
|
return s
|
||||||
|
self.ser.timeout = min(remaining, 0.5)
|
||||||
|
chunk = self.ser.read(256)
|
||||||
|
if chunk:
|
||||||
|
self._buf += chunk
|
||||||
|
|
||||||
|
def send(self, cmd: str) -> None:
|
||||||
|
if not cmd.endswith("\n"):
|
||||||
|
cmd = cmd + "\n"
|
||||||
|
self.transcript(f"-> {cmd.rstrip()}")
|
||||||
|
self.ser.write(cmd.encode("utf-8"))
|
||||||
|
self.ser.flush()
|
||||||
|
|
||||||
|
def request(self, cmd: str, overall_timeout_s: float = 5.0) -> Response:
|
||||||
|
"""Send a command and collect lines until a terminating OK/ERR/SKIP."""
|
||||||
|
self.send(cmd)
|
||||||
|
deadline = time.monotonic() + overall_timeout_s
|
||||||
|
while True:
|
||||||
|
line = self._readline(deadline)
|
||||||
|
if line is None:
|
||||||
|
raise TimeoutError(f"No terminating response to {cmd!r}")
|
||||||
|
parsed = parse_line(line)
|
||||||
|
if isinstance(parsed, Response):
|
||||||
|
return parsed
|
||||||
|
# Events during a non-streaming command are unexpected but not fatal
|
||||||
|
# — swallow them and keep reading.
|
||||||
|
|
||||||
|
def wait_ready(self, cmd: str = "BU.BEGIN",
|
||||||
|
per_attempt_s: float = 1.5,
|
||||||
|
overall_timeout_s: float = 30.0,
|
||||||
|
show_boot_chatter: bool = True) -> "Response":
|
||||||
|
"""Send `cmd` repeatedly until we get a Response back.
|
||||||
|
|
||||||
|
Used once at the start of a session to ride out the boot/init time
|
||||||
|
before uart_comms installs the UART driver — bytes sent earlier are
|
||||||
|
dropped by the hardware FIFO and never reach the firmware.
|
||||||
|
|
||||||
|
When `show_boot_chatter` is True (default), non-protocol lines
|
||||||
|
(ESP_LOG output, boot banner) are printed to stdout so the operator
|
||||||
|
can see what the device is actually doing while we wait.
|
||||||
|
"""
|
||||||
|
deadline = time.monotonic() + overall_timeout_s
|
||||||
|
last_err: Exception | None = None
|
||||||
|
attempt = 0
|
||||||
|
while time.monotonic() < deadline:
|
||||||
|
attempt += 1
|
||||||
|
remaining = deadline - time.monotonic()
|
||||||
|
print(f" [wait_ready] attempt {attempt}, {remaining:.0f}s left")
|
||||||
|
self.send(cmd)
|
||||||
|
per_deadline = time.monotonic() + per_attempt_s
|
||||||
|
while True:
|
||||||
|
line = self._readline(per_deadline)
|
||||||
|
if line is None:
|
||||||
|
last_err = TimeoutError(f"no response to {cmd!r}")
|
||||||
|
break
|
||||||
|
parsed = parse_line(line)
|
||||||
|
if isinstance(parsed, Response):
|
||||||
|
return parsed
|
||||||
|
if parsed is None and show_boot_chatter:
|
||||||
|
stripped = line.rstrip("\r\n")
|
||||||
|
if stripped:
|
||||||
|
print(f" [uart] {stripped}")
|
||||||
|
raise TimeoutError(
|
||||||
|
f"Device never answered {cmd!r} within {overall_timeout_s:.0f}s "
|
||||||
|
f"(last: {last_err})"
|
||||||
|
)
|
||||||
|
|
||||||
|
def request_stream(
|
||||||
|
self, cmd: str, overall_timeout_s: float
|
||||||
|
) -> Iterator[Event | Response]:
|
||||||
|
"""Yield each Event, then the terminating Response."""
|
||||||
|
self.send(cmd)
|
||||||
|
deadline = time.monotonic() + overall_timeout_s
|
||||||
|
while True:
|
||||||
|
line = self._readline(deadline)
|
||||||
|
if line is None:
|
||||||
|
raise TimeoutError(f"Timed out during streaming {cmd!r}")
|
||||||
|
parsed = parse_line(line)
|
||||||
|
if parsed is None:
|
||||||
|
continue
|
||||||
|
yield parsed
|
||||||
|
if isinstance(parsed, Response):
|
||||||
|
return
|
||||||
2
bringup/requirements.txt
Normal file
2
bringup/requirements.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
pyserial>=3.5
|
||||||
|
esptool>=4.6
|
||||||
470
bringup/stages.py
Normal file
470
bringup/stages.py
Normal file
@@ -0,0 +1,470 @@
|
|||||||
|
"""One function per bring-up stage. Each is explicit and independently
|
||||||
|
runnable from the operator prompt — no implicit sequencing between them."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import time
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import Callable
|
||||||
|
|
||||||
|
import fmt
|
||||||
|
from protocol import Link, Response, Event, parse_line
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Tally:
|
||||||
|
passed: int = 0
|
||||||
|
failed: int = 0
|
||||||
|
skipped: int = 0
|
||||||
|
warnings: int = 0
|
||||||
|
|
||||||
|
def note_pass(self) -> None: self.passed += 1
|
||||||
|
def note_fail(self) -> None: self.failed += 1
|
||||||
|
def note_skip(self) -> None: self.skipped += 1
|
||||||
|
def note_warn(self) -> None: self.warnings += 1
|
||||||
|
|
||||||
|
|
||||||
|
def _prompt(msg: str) -> None:
|
||||||
|
"""Block until operator presses Enter (Ctrl-C still aborts)."""
|
||||||
|
input(fmt.prompt(msg) + fmt.dim(" [Enter to continue]") + ": ")
|
||||||
|
|
||||||
|
|
||||||
|
def _show_response(label: str, r: Response) -> None:
|
||||||
|
bag = " ".join(f"{k}={v}" for k, v in r.fields.items())
|
||||||
|
print(f" {fmt.status_tag(r.status)} {label} {fmt.dim(bag)}")
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Stage 0 — Begin + identify
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def stage_begin(link: Link, t: Tally) -> None:
|
||||||
|
print(fmt.stage("Stage 0 — Begin"))
|
||||||
|
print(" Waiting for device to finish booting ...")
|
||||||
|
r = link.wait_ready("BU.BEGIN", per_attempt_s=1.5, overall_timeout_s=30.0)
|
||||||
|
_show_response("begin", r)
|
||||||
|
if r.status != "OK":
|
||||||
|
t.note_fail(); raise SystemExit("Device did not enter bring-up mode")
|
||||||
|
r = link.request("BU.INFO")
|
||||||
|
_show_response("info", r)
|
||||||
|
(t.note_pass if r.status == "OK" else t.note_fail)()
|
||||||
|
print(f" -> fw={r.get('fw')} board={r.get('board', '?')} reset={r.get('reset')} "
|
||||||
|
f"heap={r.get('heap')}")
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Stage 1 — Flash & persistence
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def stage_flash(link: Link, t: Tally) -> None:
|
||||||
|
print(fmt.stage("Stage 1 — Flash & storage"))
|
||||||
|
_prompt(" Run flash roundtrip + log head/tail check")
|
||||||
|
r = link.request("BU.FLASH", overall_timeout_s=10)
|
||||||
|
_show_response("flash", r)
|
||||||
|
(t.note_pass if r.status == "OK" else t.note_fail)()
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Stage 2 — I2C + LEDs
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def stage_i2c_led(link: Link, t: Tally) -> None:
|
||||||
|
import threading
|
||||||
|
|
||||||
|
print(fmt.stage("Stage 2 — I2C / TCA9555 / LEDs"))
|
||||||
|
_prompt(" Probe TCA9555 and run LED check")
|
||||||
|
|
||||||
|
r = link.request("BU.I2C")
|
||||||
|
_show_response("i2c", r)
|
||||||
|
if r.status != "OK":
|
||||||
|
t.note_fail(); return
|
||||||
|
t.note_pass()
|
||||||
|
|
||||||
|
# Firmware-driven LED watch: all LEDs solid when the button is released,
|
||||||
|
# fast waterfall while held. Operator actuates the button and confirms.
|
||||||
|
def reader() -> None:
|
||||||
|
try:
|
||||||
|
for item in link.request_stream("BU.LED.WATCH",
|
||||||
|
overall_timeout_s=3600):
|
||||||
|
if isinstance(item, Event) and item.cmd == "led":
|
||||||
|
state = "PRESSED" if item.fields.get("pressed") == "1" else "released"
|
||||||
|
print(f" [led] button {state}")
|
||||||
|
elif isinstance(item, Response):
|
||||||
|
return
|
||||||
|
except Exception as e: # pragma: no cover — defensive
|
||||||
|
print(f" [led-reader] {e!r}")
|
||||||
|
|
||||||
|
th = threading.Thread(target=reader, daemon=True)
|
||||||
|
th.start()
|
||||||
|
|
||||||
|
print(" LEDs should be SOLID (all on) when button is released.")
|
||||||
|
print(" Press-and-hold the button: LEDs should WATERFALL at ~3× speed.")
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
ans = input(" LEDs behaved correctly? [y/n]: ").strip().lower()
|
||||||
|
if ans.startswith("y"):
|
||||||
|
verdict = "pass"; break
|
||||||
|
if ans.startswith("n"):
|
||||||
|
verdict = "fail"; break
|
||||||
|
finally:
|
||||||
|
link.send("") # any byte aborts BU.LED.WATCH
|
||||||
|
th.join(timeout=3)
|
||||||
|
|
||||||
|
if verdict == "pass":
|
||||||
|
t.note_pass()
|
||||||
|
else:
|
||||||
|
print(f" {fmt.fail('LED visual check FAILED')}")
|
||||||
|
t.note_fail()
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Stage 3 — ADC + battery calibration
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def stage_adc(link: Link, t: Tally, calibrate: bool = True) -> None:
|
||||||
|
print(fmt.stage("Stage 3 — Analog front-end"))
|
||||||
|
_prompt(" Read ADC snapshot (battery / motor current)")
|
||||||
|
|
||||||
|
r = link.request("BU.ADC")
|
||||||
|
_show_response("adc", r)
|
||||||
|
if r.status != "OK":
|
||||||
|
t.note_fail(); return
|
||||||
|
t.note_pass()
|
||||||
|
|
||||||
|
bat_V = r.getf("bat_V", 0.0)
|
||||||
|
print(f" -> battery reports {bat_V:.3f} V")
|
||||||
|
|
||||||
|
# VOC and FAULT pins on V5 are unusable (wired direct to input-only
|
||||||
|
# ESP32 GPIOs, no external resistors — see README "V5 hardware caveats").
|
||||||
|
# They're intentionally ignored here.
|
||||||
|
|
||||||
|
if not calibrate:
|
||||||
|
return
|
||||||
|
_run_battery_cal(link, t)
|
||||||
|
|
||||||
|
|
||||||
|
def _run_battery_cal(link: Link, t: Tally) -> None:
|
||||||
|
from calibrate import single_point_offset, verify
|
||||||
|
|
||||||
|
print(fmt.section("Battery voltage calibration"))
|
||||||
|
|
||||||
|
# Read current K and raw mV.
|
||||||
|
k_r = link.request("BU.PARAM GET V_SENS_K")
|
||||||
|
if k_r.status != "OK":
|
||||||
|
print(" Could not read V_SENS_K"); t.note_fail(); return
|
||||||
|
k = k_r.getf("value")
|
||||||
|
|
||||||
|
adc_r = link.request("BU.ADC")
|
||||||
|
bat_mv = adc_r.getf("bat_mv")
|
||||||
|
# ADC noise rarely lands on exactly 0; check against a small range so a
|
||||||
|
# near-floor reading still flags as bogus.
|
||||||
|
if bat_mv < 50:
|
||||||
|
print(f" ADC read looks bogus (mv={bat_mv:.0f})"); t.note_fail(); return
|
||||||
|
|
||||||
|
raw_ans = input(" Measure the battery at the board terminals with a DMM.\n"
|
||||||
|
" Enter true voltage (V): ").strip()
|
||||||
|
try:
|
||||||
|
v_true = float(raw_ans)
|
||||||
|
except ValueError:
|
||||||
|
print(" Not a number — skipping cal"); t.note_skip(); return
|
||||||
|
|
||||||
|
# Sanity-check the operator-supplied true voltage. The system runs on a
|
||||||
|
# nominal 12-24 V battery; values outside 5..30 V are almost certainly a
|
||||||
|
# typo or DMM unit mistake (e.g. mV instead of V).
|
||||||
|
if not (5.0 <= v_true <= 30.0):
|
||||||
|
print(f" v_true={v_true:.3f} V is outside plausible 5..30 V range")
|
||||||
|
t.note_fail(); return
|
||||||
|
|
||||||
|
cal = single_point_offset(bat_mv, v_true, k)
|
||||||
|
predicted = verify(bat_mv, cal)
|
||||||
|
print(f" bat_mv={bat_mv:.0f} K={k:.10f} new OFFSET={cal.offset:+.6f} V")
|
||||||
|
print(f" predicted V_bat after cal = {predicted:.3f} (true = {v_true:.3f})")
|
||||||
|
|
||||||
|
# Sanity-check the computed offset. Default is 0.4 V; |offset| > 2 V means
|
||||||
|
# something else is wrong (broken divider, wrong K, ADC ref off).
|
||||||
|
if abs(cal.offset) > 2.0:
|
||||||
|
print(f" {fmt.fail('FAIL')}: |offset|={abs(cal.offset):.3f} V exceeds 2 V — "
|
||||||
|
f"check divider / K / DMM units")
|
||||||
|
t.note_fail(); return
|
||||||
|
|
||||||
|
wr = link.request(f"BU.PARAM SET V_SENS_OFFSET {cal.offset:.6f}")
|
||||||
|
_show_response("param.set", wr)
|
||||||
|
if wr.status != "OK":
|
||||||
|
t.note_fail(); return
|
||||||
|
|
||||||
|
# Read it back to confirm storage actually persisted what we sent.
|
||||||
|
rb = link.request("BU.PARAM GET V_SENS_OFFSET")
|
||||||
|
if rb.status != "OK":
|
||||||
|
print(" Could not read back V_SENS_OFFSET"); t.note_fail(); return
|
||||||
|
stored = rb.getf("value")
|
||||||
|
if abs(stored - cal.offset) > 1e-4:
|
||||||
|
print(f" {fmt.fail('FAIL')}: readback {stored:+.6f} != written {cal.offset:+.6f}")
|
||||||
|
t.note_fail(); return
|
||||||
|
|
||||||
|
# Verify by re-reading the ADC. Firmware's cmd_adc_once now bypasses the
|
||||||
|
# EMA, so bat_V here reflects the new offset immediately.
|
||||||
|
check = link.request("BU.ADC")
|
||||||
|
new_V = check.getf("bat_V")
|
||||||
|
err = new_V - v_true
|
||||||
|
print(f" Post-cal bat_V = {new_V:.3f} (err {err*1000:+.1f} mV)")
|
||||||
|
abs_err = abs(err)
|
||||||
|
if abs_err < 0.040:
|
||||||
|
print(f" {fmt.pass_('PASS')}: cal residual within ±40 mV")
|
||||||
|
t.note_pass()
|
||||||
|
elif abs_err < 0.100:
|
||||||
|
print(f" {fmt.warn('WARN')}: residual {err*1000:+.1f} mV (>40, <100 mV)")
|
||||||
|
t.note_warn()
|
||||||
|
else:
|
||||||
|
print(f" {fmt.fail('FAIL')}: residual {err*1000:+.1f} mV exceeds 100 mV")
|
||||||
|
t.note_fail()
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Stage 4 — Discrete sensors (mandatory edges)
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
SENSOR_NAMES = ["SAFETY"] # JACK and DRIVE are checked via the relay pulse stage.
|
||||||
|
|
||||||
|
|
||||||
|
def stage_sensors(link: Link, t: Tally) -> None:
|
||||||
|
"""Live-print safety-sensor edges until operator presses Enter.
|
||||||
|
|
||||||
|
Drive and jack sensors are encoder-style and only trip while the motor
|
||||||
|
runs — they're verified as a side effect of Stage 5 relay pulses.
|
||||||
|
"""
|
||||||
|
import threading
|
||||||
|
|
||||||
|
print(fmt.stage("Stage 4 — Sensor live view"))
|
||||||
|
print(" Live state of all 4 sensor pins is printed below when any one")
|
||||||
|
print(" changes. Per-edge events also print as they arrive.")
|
||||||
|
print(" Poke each sensor by hand / magnet / jumper to verify it responds.")
|
||||||
|
print(" SAFETY must show both break and make to pass; the others are")
|
||||||
|
print(" diagnostic only (drive/jack are properly tested in Stage 5).")
|
||||||
|
print(" Press Enter when you're satisfied.")
|
||||||
|
|
||||||
|
state = {"make": False, "break": False}
|
||||||
|
|
||||||
|
last_state_line = {"v": ""}
|
||||||
|
|
||||||
|
def reader() -> None:
|
||||||
|
try:
|
||||||
|
for item in link.request_stream("BU.SENSORS.WATCH 0",
|
||||||
|
overall_timeout_s=3600):
|
||||||
|
if isinstance(item, Event):
|
||||||
|
if item.cmd == "sensor":
|
||||||
|
name = item.fields.get("name")
|
||||||
|
edge = item.fields.get("edge")
|
||||||
|
if name == "SAFETY" and edge in state:
|
||||||
|
state[edge] = True
|
||||||
|
print(f" [{name}] {edge}")
|
||||||
|
elif item.cmd == "state":
|
||||||
|
# Live snapshot of all four sensors. Only print when
|
||||||
|
# the level line changes, so steady state doesn't spam.
|
||||||
|
f = item.fields
|
||||||
|
line = (f"SAFETY={f.get('SAFETY','?')} "
|
||||||
|
f"DRIVE={f.get('DRIVE','?')} "
|
||||||
|
f"JACK={f.get('JACK','?')} "
|
||||||
|
f"AUX={f.get('AUX','?')} "
|
||||||
|
f"isr=(s={f.get('isr_s','?')} "
|
||||||
|
f"d={f.get('isr_d','?')} "
|
||||||
|
f"j={f.get('isr_j','?')} "
|
||||||
|
f"a={f.get('isr_a','?')})")
|
||||||
|
if line != last_state_line["v"]:
|
||||||
|
print(f" [state] {line}")
|
||||||
|
last_state_line["v"] = line
|
||||||
|
elif isinstance(item, Response):
|
||||||
|
# terminating OK after we aborted
|
||||||
|
return
|
||||||
|
except Exception as e: # pragma: no cover — defensive
|
||||||
|
print(f" [reader] {e!r}")
|
||||||
|
|
||||||
|
th = threading.Thread(target=reader, daemon=True)
|
||||||
|
th.start()
|
||||||
|
|
||||||
|
input(" Press Enter when SAFETY has been actuated: ")
|
||||||
|
|
||||||
|
# Kick the firmware out of its watch loop: any byte aborts.
|
||||||
|
link.send("") # just the \n
|
||||||
|
th.join(timeout=3)
|
||||||
|
|
||||||
|
if state["make"] and state["break"]:
|
||||||
|
print(f" SAFETY: {fmt.pass_('PASS')} (saw break and make)")
|
||||||
|
t.note_pass()
|
||||||
|
else:
|
||||||
|
missing = [k for k, v in state.items() if not v]
|
||||||
|
print(f" SAFETY: {fmt.fail('FAIL')} — missed {missing}")
|
||||||
|
t.note_fail()
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Stage 5 — Relay bridges
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# (bridge, dir, ms, (dI_min, dI_max), check_edges)
|
||||||
|
# check_edges → bridge has an encoder-style sensor; pulse must produce
|
||||||
|
# at least one edge on it.
|
||||||
|
RELAY_TESTS = [
|
||||||
|
("SENSORS", "ON", 500, (0.0, 0.0), False),
|
||||||
|
("DRIVE", "FWD", 3000, (0.5, 25.0), True),
|
||||||
|
("DRIVE", "REV", 3000, (0.5, 25.0), True),
|
||||||
|
("JACK", "UP", 1200, (0.2, 25.0), True),
|
||||||
|
("JACK", "DOWN", 1200, (0.2, 25.0), True),
|
||||||
|
("AUX", "FWD", 150, (0.1, 25.0), False),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def stage_relays(link: Link, t: Tally) -> None:
|
||||||
|
print(fmt.stage("Stage 5 — Relay bridges"))
|
||||||
|
print(" PRECONDITIONS:")
|
||||||
|
print(" - Battery connected, fuse in place")
|
||||||
|
print(" - Drive wheels off ground / disengaged")
|
||||||
|
print(" - Safety interlock asserted (SAFETY sensor HIGH)")
|
||||||
|
|
||||||
|
for bridge, direction, ms, (lo, hi), check_edges in RELAY_TESTS:
|
||||||
|
_prompt(f" Pulse {bridge} {direction} for {ms} ms")
|
||||||
|
r = link.request(f"BU.RELAY {bridge} {direction} {ms}",
|
||||||
|
overall_timeout_s=ms / 1000.0 + 5.0)
|
||||||
|
_show_response(f"{bridge}/{direction}", r)
|
||||||
|
if r.status == "SKIP":
|
||||||
|
print(" Device refused (safety?)"); t.note_skip(); continue
|
||||||
|
if r.status != "OK":
|
||||||
|
t.note_fail(); continue
|
||||||
|
if bridge == "SENSORS":
|
||||||
|
t.note_pass(); continue
|
||||||
|
|
||||||
|
i_before = r.getf("I_before")
|
||||||
|
i_mid = r.getf("I_mid")
|
||||||
|
delta = abs(i_mid - i_before)
|
||||||
|
tripped = r.geti("tripped") == 1
|
||||||
|
edges = r.geti("edges")
|
||||||
|
stop = r.get("stop", "time")
|
||||||
|
actual_ms = r.geti("actual_ms", ms)
|
||||||
|
edge_str = f" edges={edges}" if check_edges else ""
|
||||||
|
stop_str = f" stop={stop} ({actual_ms}/{ms} ms)" if stop != "time" else ""
|
||||||
|
print(f" |ΔI| = {delta:.2f} A (expected {lo}-{hi}) "
|
||||||
|
f"tripped={tripped}{edge_str}{stop_str}")
|
||||||
|
|
||||||
|
if tripped:
|
||||||
|
print(f" {fmt.fail('FAIL')}: efuse tripped"); t.note_fail(); continue
|
||||||
|
if check_edges and edges <= 0:
|
||||||
|
print(f" {fmt.fail('FAIL')}: {bridge} sensor saw no edges — motor not turning?")
|
||||||
|
t.note_fail(); continue
|
||||||
|
if lo <= delta <= hi:
|
||||||
|
t.note_pass()
|
||||||
|
else:
|
||||||
|
print(f" {fmt.warn('WARN')}: ΔI outside nominal")
|
||||||
|
t.note_warn()
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Stage 6 — Radio & connectivity
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def stage_rf(link: Link, t: Tally) -> None:
|
||||||
|
import threading
|
||||||
|
|
||||||
|
print(fmt.stage("Stage 6a — RF 433 MHz"))
|
||||||
|
_prompt(" Watch for RF remote codes")
|
||||||
|
|
||||||
|
print(" Press buttons on the RF remote. Codes will print live.")
|
||||||
|
print(" Press Enter to stop.")
|
||||||
|
|
||||||
|
count = {"seen": 0}
|
||||||
|
|
||||||
|
def reader() -> None:
|
||||||
|
try:
|
||||||
|
for item in link.request_stream("BU.RF.WATCH 0",
|
||||||
|
overall_timeout_s=3600):
|
||||||
|
if isinstance(item, Event) and item.cmd == "rf":
|
||||||
|
code = item.fields.get("code", "?")
|
||||||
|
print(f" rf code={code}")
|
||||||
|
count["seen"] += 1
|
||||||
|
elif isinstance(item, Response):
|
||||||
|
return
|
||||||
|
except Exception as e: # pragma: no cover
|
||||||
|
print(f" [reader] {e!r}")
|
||||||
|
|
||||||
|
th = threading.Thread(target=reader, daemon=True)
|
||||||
|
th.start()
|
||||||
|
|
||||||
|
input(" Press Enter when done: ")
|
||||||
|
link.send("") # abort the watch
|
||||||
|
th.join(timeout=3)
|
||||||
|
|
||||||
|
print(f" -> {count['seen']} code(s) captured")
|
||||||
|
(t.note_pass if count["seen"] > 0 else t.note_warn)()
|
||||||
|
|
||||||
|
|
||||||
|
def stage_wifi(link: Link, t: Tally) -> None:
|
||||||
|
print(fmt.stage("Stage 6b — WiFi + web UI"))
|
||||||
|
_prompt(" Start SoftAP and wait for a client to load the web UI")
|
||||||
|
r = link.request("BU.WIFI.START", overall_timeout_s=20)
|
||||||
|
_show_response("wifi.start", r)
|
||||||
|
if r.status != "OK":
|
||||||
|
t.note_fail(); return
|
||||||
|
ssid = r.get("ssid", "?")
|
||||||
|
print(f"\n Connect a device to WiFi SSID `{ssid}` and open http://192.168.4.1/")
|
||||||
|
print(" Waiting for a client to associate and load the page... (Ctrl+C to abort)")
|
||||||
|
try:
|
||||||
|
for item in link.request_stream("BU.WIFI.WAIT", overall_timeout_s=3600):
|
||||||
|
if isinstance(item, Event):
|
||||||
|
print(f" {item.cmd} {' '.join(f'{k}={v}' for k,v in item.fields.items())}")
|
||||||
|
elif isinstance(item, Response):
|
||||||
|
_show_response("wifi.wait", item)
|
||||||
|
(t.note_pass if item.status == "OK" else t.note_fail)()
|
||||||
|
break
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print(" WiFi wait aborted by operator"); t.note_skip()
|
||||||
|
# Push a byte so the firmware's cmd_wifi_wait breaks out of its
|
||||||
|
# loop and unblocks the bring-up dispatcher; otherwise BU.END
|
||||||
|
# never reaches the device and the reboot doesn't happen.
|
||||||
|
# Then drain the resulting wifi.wait OK so it doesn't get
|
||||||
|
# mistaken for the response to a later command.
|
||||||
|
try:
|
||||||
|
link.send("")
|
||||||
|
deadline = time.monotonic() + 2.0
|
||||||
|
while time.monotonic() < deadline:
|
||||||
|
line = link._readline(deadline)
|
||||||
|
if line is None:
|
||||||
|
break
|
||||||
|
if isinstance(parse_line(line), Response):
|
||||||
|
break
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# End
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def stage_end(link: Link, t: Tally) -> None:
|
||||||
|
print(fmt.stage("Stage — End"))
|
||||||
|
_prompt(" Exit bring-up mode (device will reboot)")
|
||||||
|
r = link.request("BU.END", overall_timeout_s=5)
|
||||||
|
_show_response("end", r)
|
||||||
|
# Device reboots; no further response expected.
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Top-level driver
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
Stage = Callable[[Link, Tally], None]
|
||||||
|
|
||||||
|
|
||||||
|
def all_stages(skip_relays: bool = False, no_calibrate: bool = False) -> list[Stage]:
|
||||||
|
stages: list[Stage] = [
|
||||||
|
stage_begin,
|
||||||
|
stage_flash,
|
||||||
|
stage_i2c_led,
|
||||||
|
lambda link, t: stage_adc(link, t, calibrate=not no_calibrate),
|
||||||
|
stage_sensors,
|
||||||
|
]
|
||||||
|
if not skip_relays:
|
||||||
|
stages.append(stage_relays)
|
||||||
|
stages.extend([
|
||||||
|
stage_rf,
|
||||||
|
stage_wifi,
|
||||||
|
stage_end,
|
||||||
|
])
|
||||||
|
return stages
|
||||||
@@ -1,70 +1,4 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
esp-idf-lib/esp_idf_lib_helpers:
|
|
||||||
component_hash: 689853bb8993434f9556af0f2816e808bf77b5d22100144b21f3519993daf237
|
|
||||||
dependencies: []
|
|
||||||
source:
|
|
||||||
registry_url: https://components.espressif.com
|
|
||||||
type: service
|
|
||||||
targets:
|
|
||||||
- esp32
|
|
||||||
- esp32c2
|
|
||||||
- esp32c3
|
|
||||||
- esp32c5
|
|
||||||
- esp32c6
|
|
||||||
- esp32c61
|
|
||||||
- esp32h2
|
|
||||||
- esp32p4
|
|
||||||
- esp32s2
|
|
||||||
- esp32s3
|
|
||||||
version: 1.4.0
|
|
||||||
esp-idf-lib/i2cdev:
|
|
||||||
component_hash: 4f3838b2e68ab2b77fd43737139fa97dd0243b46af7b4a04588c67ff6b275ba1
|
|
||||||
dependencies:
|
|
||||||
- name: esp-idf-lib/esp_idf_lib_helpers
|
|
||||||
registry_url: https://components.espressif.com
|
|
||||||
require: private
|
|
||||||
version: '*'
|
|
||||||
source:
|
|
||||||
registry_url: https://components.espressif.com
|
|
||||||
type: service
|
|
||||||
targets:
|
|
||||||
- esp32
|
|
||||||
- esp32c2
|
|
||||||
- esp32c3
|
|
||||||
- esp32c5
|
|
||||||
- esp32c6
|
|
||||||
- esp32c61
|
|
||||||
- esp32h2
|
|
||||||
- esp32p4
|
|
||||||
- esp32s2
|
|
||||||
- esp32s3
|
|
||||||
version: 2.1.0
|
|
||||||
esp-idf-lib/tca95x5:
|
|
||||||
component_hash: 4bbdbd82828cf1fd5c03fd07e3ea2cb0f36daf16cb3ac7219d1e5decb9ec04ee
|
|
||||||
dependencies:
|
|
||||||
- name: esp-idf-lib/esp_idf_lib_helpers
|
|
||||||
registry_url: https://components.espressif.com
|
|
||||||
require: private
|
|
||||||
version: '*'
|
|
||||||
- name: esp-idf-lib/i2cdev
|
|
||||||
registry_url: https://components.espressif.com
|
|
||||||
require: private
|
|
||||||
version: '*'
|
|
||||||
source:
|
|
||||||
registry_url: https://components.espressif.com/
|
|
||||||
type: service
|
|
||||||
targets:
|
|
||||||
- esp32
|
|
||||||
- esp32c2
|
|
||||||
- esp32c3
|
|
||||||
- esp32c5
|
|
||||||
- esp32c6
|
|
||||||
- esp32c61
|
|
||||||
- esp32h2
|
|
||||||
- esp32p4
|
|
||||||
- esp32s2
|
|
||||||
- esp32s3
|
|
||||||
version: 1.0.7
|
|
||||||
espressif/mdns:
|
espressif/mdns:
|
||||||
component_hash: 29e47564b1a7ee778135e17fbbf2a2773f71c97ebabfe626c8eda7c958a7ad16
|
component_hash: 29e47564b1a7ee778135e17fbbf2a2773f71c97ebabfe626c8eda7c958a7ad16
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -79,21 +13,9 @@ dependencies:
|
|||||||
source:
|
source:
|
||||||
type: idf
|
type: idf
|
||||||
version: 5.3.1
|
version: 5.3.1
|
||||||
joltwallet/littlefs:
|
|
||||||
component_hash: 1808d73e99168f6f3c26dd31799a248484762b3a320ec4962dec11a145f4277f
|
|
||||||
dependencies:
|
|
||||||
- name: idf
|
|
||||||
require: private
|
|
||||||
version: '>=5.0'
|
|
||||||
source:
|
|
||||||
registry_url: https://components.espressif.com/
|
|
||||||
type: service
|
|
||||||
version: 1.20.3
|
|
||||||
direct_dependencies:
|
direct_dependencies:
|
||||||
- esp-idf-lib/tca95x5
|
|
||||||
- espressif/mdns
|
- espressif/mdns
|
||||||
- idf
|
- idf
|
||||||
- joltwallet/littlefs
|
manifest_hash: 7677ef9427111d5bfe7e9d00453defd2f35330f3a0aefe9690b0a5f577f93b06
|
||||||
manifest_hash: c3a20310a8ecc5e8e0221a7589abf8d2e372eb48f06d6b6fbb3fbf5f48a61aaf
|
|
||||||
target: esp32
|
target: esp32
|
||||||
version: 2.0.0
|
version: 2.0.0
|
||||||
|
|||||||
BIN
logtool/08APR2026_1630.bin
Normal file
BIN
logtool/08APR2026_1630.bin
Normal file
Binary file not shown.
1244
logtool/08APR2026_1630.txt
Normal file
1244
logtool/08APR2026_1630.txt
Normal file
File diff suppressed because it is too large
Load Diff
BIN
logtool/08APR2026_1631.bin
Normal file
BIN
logtool/08APR2026_1631.bin
Normal file
Binary file not shown.
3
logtool/08APR2026_1631.txt
Normal file
3
logtool/08APR2026_1631.txt
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
Fetching http://192.168.10.126/log ...
|
||||||
|
Log offsets: tail=0 head=50984 entries=1234
|
||||||
|
Device: version=? time=1775665887
|
||||||
1
logtool/08APR2026_1636.txt
Normal file
1
logtool/08APR2026_1636.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Fetching http://192.168.10.126/log ...
|
||||||
1
logtool/08APR2026_1637.txt
Normal file
1
logtool/08APR2026_1637.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Fetching http://sc.local/log ...
|
||||||
1
logtool/08APR2026_1638.txt
Normal file
1
logtool/08APR2026_1638.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Fetching http://192.168.4.1/log ...
|
||||||
1
logtool/08APR2026_1639.txt
Normal file
1
logtool/08APR2026_1639.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Fetching http://192.168.4.1/log ...
|
||||||
1
logtool/08APR2026_1643.txt
Normal file
1
logtool/08APR2026_1643.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Fetching http://192.168.4.1/log ...
|
||||||
1
logtool/08APR2026_1659.txt
Normal file
1
logtool/08APR2026_1659.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Fetching http://sc.local ...
|
||||||
1
logtool/08APR2026_1700.txt
Normal file
1
logtool/08APR2026_1700.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Fetching http://sc.local ...
|
||||||
1
logtool/08APR2026_1707.txt
Normal file
1
logtool/08APR2026_1707.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Fetching http://192.168.10.126/log ...
|
||||||
BIN
logtool/08APR2026_1708.bin
Normal file
BIN
logtool/08APR2026_1708.bin
Normal file
Binary file not shown.
212
logtool/08APR2026_1708.txt
Normal file
212
logtool/08APR2026_1708.txt
Normal file
@@ -0,0 +1,212 @@
|
|||||||
|
Fetching http://192.168.10.126/log ...
|
||||||
|
Log offsets: tail=0 head=7828 entries=195
|
||||||
|
Device: version=? time=1775668119
|
||||||
|
Time State Bat(V) Drive(A) Jack(A) Aux(A) Counter Stable Raw DrHeat JkHeat AxHeat
|
||||||
|
----------------------- ------------------------------ ------ -------- ------- ------ ------- ------ ------ ------ ------ ------
|
||||||
|
1970-01-01 00:00:00.000 BOOT rst=POWERON wake=NORMAL — — — — — — — — — —
|
||||||
|
1970-01-01 00:00:00.000 BOOT rst=POWERON wake=NORMAL — — — — — — — — — —
|
||||||
|
2026-04-08 16:58:48.000 TIME_SET — — — — — — — — — —
|
||||||
|
2026-04-08 16:59:36.565 IDLE 13.326 0.03 -0.15 -0.08 0 SAFETY SAFETY 0.0 0.0 0.0
|
||||||
|
2026-04-08 16:59:36.585 IDLE 12.521 3.20 33.46 0.17 0 SAFETY SAFETY 0.0 0.1 0.0
|
||||||
|
2026-04-08 16:59:36.605 IDLE 12.042 1.76 33.46 -0.01 0 SAFETY SAFETY 0.0 0.2 0.0
|
||||||
|
2026-04-08 16:59:36.625 IDLE 12.090 0.69 33.46 0.01 0 SAFETY SAFETY 0.0 0.3 0.0
|
||||||
|
2026-04-08 16:59:36.649 IDLE 12.236 1.59 26.17 0.10 0 SAFETY SAFETY 0.0 0.4 0.0
|
||||||
|
2026-04-08 16:59:36.665 IDLE 12.429 -0.00 17.46 0.04 0 SAFETY SAFETY 0.0 0.4 0.0
|
||||||
|
2026-04-08 16:59:36.685 IDLE 12.632 -0.00 10.87 -0.10 0 SAFETY SAFETY 0.0 0.5 0.0
|
||||||
|
2026-04-08 16:59:36.705 IDLE 12.795 0.53 7.21 0.04 0 SAFETY SAFETY 0.0 0.5 0.0
|
||||||
|
2026-04-08 16:59:36.725 IDLE 12.899 -0.01 4.53 -0.19 0 SAFETY SAFETY 0.0 0.5 0.0
|
||||||
|
2026-04-08 16:59:36.747 IDLE 12.986 0.45 3.51 -0.06 0 SAFETY SAFETY 0.0 0.5 0.0
|
||||||
|
2026-04-08 16:59:36.766 IDLE 13.037 -0.76 3.26 -0.10 0 SAFETY SAFETY 0.0 0.5 0.0
|
||||||
|
2026-04-08 16:59:36.786 IDLE 13.067 0.60 2.90 0.15 0 SAFETY SAFETY 0.0 0.5 0.0
|
||||||
|
2026-04-08 16:59:36.805 IDLE 13.066 -0.31 2.35 -0.35 0 SAFETY SAFETY 0.0 0.5 0.0
|
||||||
|
2026-04-08 16:59:36.825 IDLE 13.089 -0.01 2.13 -0.53 0 SAFETY SAFETY 0.0 0.5 0.0
|
||||||
|
2026-04-08 16:59:36.846 IDLE 13.104 -0.01 2.10 -0.03 0 SAFETY SAFETY 0.0 0.5 0.0
|
||||||
|
2026-04-08 16:59:36.865 IDLE 13.111 0.45 1.90 0.09 0 SAFETY SAFETY 0.0 0.5 0.0
|
||||||
|
2026-04-08 16:59:36.885 IDLE 13.134 0.07 1.33 0.18 0 SAFETY SAFETY 0.0 0.5 0.0
|
||||||
|
2026-04-08 16:59:36.906 IDLE 13.161 -0.24 1.43 0.15 0 SAFETY SAFETY 0.0 0.5 0.0
|
||||||
|
2026-04-08 16:59:36.925 IDLE 13.140 0.82 1.11 -0.19 0 SAFETY SAFETY 0.0 0.5 0.0
|
||||||
|
2026-04-08 16:59:36.946 IDLE 13.153 -0.01 1.83 0.08 0 SAFETY SAFETY 0.0 0.4 0.0
|
||||||
|
2026-04-08 16:59:36.965 IDLE 13.174 0.21 1.77 -0.10 0 SAFETY SAFETY 0.0 0.4 0.0
|
||||||
|
2026-04-08 16:59:36.985 IDLE 13.143 -0.01 1.49 -0.12 0 SAFETY SAFETY 0.0 0.4 0.0
|
||||||
|
2026-04-08 16:59:37.005 IDLE 13.139 0.52 2.64 0.15 0 SAFETY SAFETY 0.0 0.4 0.0
|
||||||
|
2026-04-08 16:59:37.025 IDLE 13.140 0.29 1.77 -0.07 0 SAFETY SAFETY 0.0 0.4 0.0
|
||||||
|
2026-04-08 16:59:37.047 IDLE 13.130 -1.53 2.08 0.06 0 SAFETY SAFETY 0.0 0.4 0.0
|
||||||
|
2026-04-08 16:59:37.066 IDLE 13.124 -0.01 2.77 0.08 0 SAFETY SAFETY 0.0 0.4 0.0
|
||||||
|
2026-04-08 16:59:37.086 IDLE 13.106 0.22 3.64 0.02 0 SAFETY SAFETY 0.0 0.4 0.0
|
||||||
|
2026-04-08 16:59:37.106 IDLE 13.097 -0.54 3.55 -0.03 0 SAFETY SAFETY 0.0 0.4 0.0
|
||||||
|
2026-04-08 16:59:37.125 IDLE 13.093 -0.16 3.27 0.02 0 SAFETY SAFETY 0.0 0.4 0.0
|
||||||
|
2026-04-08 16:59:37.145 IDLE 13.094 0.67 3.59 -0.10 0 SAFETY SAFETY 0.0 0.4 0.0
|
||||||
|
2026-04-08 16:59:37.166 IDLE 13.080 0.37 3.91 0.13 0 SAFETY SAFETY 0.0 0.4 0.0
|
||||||
|
2026-04-08 16:59:37.187 IDLE 13.069 1.80 3.01 0.02 0 SAFETY SAFETY 0.0 0.4 0.0
|
||||||
|
2026-04-08 16:59:37.205 IDLE 13.079 -0.40 3.85 0.06 0 SAFETY SAFETY 0.0 0.4 0.0
|
||||||
|
2026-04-08 16:59:37.225 IDLE 13.072 0.36 4.20 -0.07 0 SAFETY SAFETY 0.0 0.4 0.0
|
||||||
|
2026-04-08 16:59:37.246 IDLE 13.134 -0.02 4.00 -0.19 0 SAFETY SAFETY 0.0 0.4 0.0
|
||||||
|
2026-04-08 16:59:37.265 IDLE 13.096 0.36 3.57 0.15 0 SAFETY SAFETY 0.0 0.4 0.0
|
||||||
|
2026-04-08 16:59:37.285 IDLE 13.081 0.66 3.91 0.11 0 SAFETY SAFETY 0.0 0.4 0.0
|
||||||
|
2026-04-08 16:59:37.306 IDLE 13.061 0.28 4.00 -0.12 0 SAFETY SAFETY 0.0 0.4 0.0
|
||||||
|
2026-04-08 16:59:37.325 IDLE 13.060 -0.03 4.25 -0.17 0 SAFETY SAFETY 0.0 0.4 0.0
|
||||||
|
2026-04-08 16:59:37.345 IDLE 13.039 0.65 4.82 0.06 0 SAFETY SAFETY 0.0 0.4 0.0
|
||||||
|
2026-04-08 16:59:37.365 IDLE 13.095 0.65 4.19 -0.05 0 SAFETY SAFETY 0.0 0.4 0.0
|
||||||
|
2026-04-08 16:59:37.385 IDLE 13.057 0.19 4.10 -0.19 0 SAFETY SAFETY 0.0 0.4 0.0
|
||||||
|
2026-04-08 16:59:37.406 IDLE 13.046 0.27 4.91 0.11 0 SAFETY SAFETY 0.0 0.4 0.0
|
||||||
|
2026-04-08 16:59:37.425 IDLE 13.036 -0.04 3.51 -0.10 0 SAFETY SAFETY 0.0 0.4 0.0
|
||||||
|
2026-04-08 16:59:37.446 IDLE 13.028 1.70 4.62 0.15 0 SAFETY SAFETY 0.0 0.4 0.0
|
||||||
|
2026-04-08 16:59:37.466 IDLE 13.020 0.26 4.74 -0.12 0 SAFETY SAFETY 0.0 0.4 0.0
|
||||||
|
2026-04-08 16:59:37.485 IDLE 13.039 -0.04 4.44 0.09 0 SAFETY SAFETY 0.0 0.4 0.0
|
||||||
|
2026-04-08 16:59:37.505 IDLE 13.018 -1.33 5.14 0.02 0 SAFETY SAFETY 0.0 0.4 0.0
|
||||||
|
2026-04-08 16:59:37.525 IDLE 13.011 0.49 4.62 0.02 0 SAFETY SAFETY 0.0 0.4 0.0
|
||||||
|
2026-04-08 16:59:37.545 IDLE 13.004 0.03 5.37 -0.10 0 SAFETY SAFETY 0.0 0.4 0.0
|
||||||
|
2026-04-08 16:59:37.565 IDLE 12.996 0.34 5.03 -0.10 0 SAFETY SAFETY 0.0 0.4 0.0
|
||||||
|
2026-04-08 16:59:37.585 IDLE 12.977 0.26 4.99 0.04 0 SAFETY SAFETY 0.0 0.4 0.0
|
||||||
|
2026-04-08 16:59:37.606 IDLE 12.987 0.48 5.19 -0.03 0 SAFETY SAFETY 0.0 0.4 0.0
|
||||||
|
2026-04-08 16:59:37.627 IDLE 12.992 -0.05 4.76 0.09 0 SAFETY SAFETY 0.0 0.4 0.0
|
||||||
|
2026-04-08 16:59:37.645 IDLE 12.994 0.41 4.99 0.02 0 SAFETY SAFETY 0.0 0.4 0.0
|
||||||
|
2026-04-08 16:59:37.665 IDLE 12.987 0.41 4.99 -0.05 0 SAFETY SAFETY 0.0 0.4 0.0
|
||||||
|
2026-04-08 16:59:37.685 IDLE 12.996 0.63 4.51 0.02 0 SAFETY SAFETY 0.0 0.4 0.0
|
||||||
|
2026-04-08 16:59:37.709 IDLE 13.019 -0.66 5.87 0.18 0 SAFETY SAFETY 0.0 0.4 0.0
|
||||||
|
2026-04-08 16:59:37.725 IDLE 13.008 -0.43 5.19 0.02 0 SAFETY SAFETY 0.0 0.5 0.0
|
||||||
|
2026-04-08 16:59:37.749 IDLE 12.983 0.33 5.92 0.04 0 SAFETY SAFETY 0.0 0.5 0.0
|
||||||
|
2026-04-08 16:59:37.766 IDLE 12.974 0.18 5.71 0.02 0 SAFETY SAFETY 0.0 0.5 0.0
|
||||||
|
2026-04-08 16:59:37.785 IDLE 12.966 0.63 6.37 0.02 0 SAFETY SAFETY 0.0 0.5 0.0
|
||||||
|
2026-04-08 16:59:37.805 IDLE 12.966 0.02 5.44 -0.10 0 SAFETY SAFETY 0.0 0.5 0.0
|
||||||
|
2026-04-08 16:59:37.825 IDLE 12.950 -0.51 5.64 -0.01 0 SAFETY SAFETY 0.0 0.5 0.0
|
||||||
|
2026-04-08 16:59:37.846 IDLE 12.958 0.55 5.44 -0.01 0 SAFETY SAFETY 0.0 0.5 0.0
|
||||||
|
2026-04-08 16:59:37.866 IDLE 12.962 -0.05 5.33 -0.07 0 SAFETY SAFETY 0.0 0.5 0.0
|
||||||
|
2026-04-08 16:59:37.885 IDLE 12.964 0.17 5.55 -0.07 0 SAFETY SAFETY 0.0 0.5 0.0
|
||||||
|
2026-04-08 16:59:37.906 IDLE 12.781 0.47 5.58 0.06 0 SAFETY SAFETY 0.0 0.5 0.0
|
||||||
|
2026-04-08 16:59:37.925 IDLE 12.969 0.55 6.03 -0.07 0 SAFETY SAFETY 0.0 0.5 0.0
|
||||||
|
2026-04-08 16:59:37.945 IDLE 12.952 0.39 6.78 0.13 0 SAFETY SAFETY 0.0 0.5 0.0
|
||||||
|
2026-04-08 16:59:37.965 IDLE 12.928 -0.67 6.42 0.11 0 SAFETY SAFETY 0.0 0.5 0.0
|
||||||
|
2026-04-08 16:59:37.987 IDLE 12.924 0.62 6.85 0.31 0 SAFETY SAFETY 0.0 0.5 0.0
|
||||||
|
2026-04-08 16:59:39.066 IDLE 13.225 -3.19 -0.32 -0.08 0 SAFETY SAFETY 0.0 0.4 0.0
|
||||||
|
2026-04-08 16:59:39.085 IDLE 12.409 0.75 33.14 -0.13 0 SAFETY SAFETY 0.0 0.5 0.0
|
||||||
|
2026-04-08 16:59:39.105 IDLE 12.346 0.59 27.43 0.06 0 SAFETY SAFETY 0.0 0.6 0.0
|
||||||
|
2026-04-08 16:59:39.128 IDLE 12.507 -1.98 15.14 0.15 0 SAFETY SAFETY 0.0 0.6 0.0
|
||||||
|
2026-04-08 16:59:39.145 IDLE 12.652 0.68 9.34 0.10 0 SAFETY SAFETY 0.0 0.6 0.0
|
||||||
|
2026-04-08 16:59:39.166 IDLE 12.774 0.30 6.64 0.05 0 SAFETY SAFETY 0.0 0.6 0.0
|
||||||
|
2026-04-08 16:59:39.185 IDLE 12.855 0.07 5.41 -0.15 0 SAFETY SAFETY 0.0 0.6 0.0
|
||||||
|
2026-04-08 16:59:39.206 IDLE 12.906 0.22 4.82 -0.15 0 SAFETY SAFETY 0.0 0.6 0.0
|
||||||
|
2026-04-08 16:59:39.226 IDLE 12.947 0.44 3.73 0.10 0 SAFETY SAFETY 0.0 0.6 0.0
|
||||||
|
2026-04-08 16:59:39.245 IDLE 12.972 -0.77 3.57 -0.17 0 SAFETY SAFETY 0.0 0.6 0.0
|
||||||
|
2026-04-08 16:59:39.266 IDLE 13.022 0.60 2.65 0.01 0 SAFETY SAFETY 0.0 0.6 0.0
|
||||||
|
2026-04-08 16:59:39.285 IDLE 13.025 -4.62 3.20 0.30 0 SAFETY SAFETY 0.0 0.6 0.0
|
||||||
|
2026-04-08 16:59:39.305 IDLE 13.053 -0.44 2.60 -0.06 0 SAFETY SAFETY 0.0 0.6 0.0
|
||||||
|
2026-04-08 16:59:39.325 IDLE 13.071 -0.06 2.76 -0.01 0 SAFETY SAFETY 0.0 0.6 0.0
|
||||||
|
2026-04-08 16:59:39.345 IDLE 13.041 -0.29 2.59 0.01 0 SAFETY SAFETY 0.0 0.6 0.0
|
||||||
|
2026-04-08 16:59:39.366 IDLE 13.053 -0.29 1.88 0.12 0 SAFETY SAFETY 0.0 0.6 0.0
|
||||||
|
2026-04-08 16:59:39.386 IDLE 13.055 0.09 2.02 0.01 0 SAFETY SAFETY 0.0 0.6 0.0
|
||||||
|
2026-04-08 16:59:39.406 IDLE 13.060 0.09 2.19 -0.15 0 SAFETY SAFETY 0.0 0.6 0.0
|
||||||
|
2026-04-08 16:59:39.425 IDLE 13.044 0.47 2.48 0.14 0 SAFETY SAFETY 0.0 0.6 0.0
|
||||||
|
2026-04-08 16:59:39.445 IDLE 13.081 -0.59 2.38 0.44 0 SAFETY SAFETY 0.0 0.6 0.0
|
||||||
|
2026-04-08 16:59:39.465 IDLE 13.054 0.24 2.41 0.01 0 SAFETY SAFETY 0.0 0.6 0.0
|
||||||
|
2026-04-08 16:59:39.485 IDLE 13.075 -0.06 1.81 0.17 0 SAFETY SAFETY 0.0 0.6 0.0
|
||||||
|
2026-04-08 16:59:39.505 IDLE 13.066 -0.29 1.69 -0.11 0 SAFETY SAFETY 0.0 0.6 0.0
|
||||||
|
2026-04-08 16:59:39.525 IDLE 13.074 0.09 1.84 -0.13 0 SAFETY SAFETY 0.0 0.6 0.0
|
||||||
|
2026-04-08 16:59:39.545 IDLE 13.054 -0.13 1.80 0.26 0 SAFETY SAFETY 0.0 0.6 0.0
|
||||||
|
2026-04-08 16:59:39.565 IDLE 13.060 -0.28 1.89 0.01 0 SAFETY SAFETY 0.0 0.6 0.0
|
||||||
|
2026-04-08 16:59:39.588 IDLE 13.074 0.02 1.29 -0.06 0 SAFETY SAFETY 0.0 0.6 0.0
|
||||||
|
2026-04-08 16:59:39.606 IDLE 13.058 0.10 1.51 0.10 0 SAFETY SAFETY 0.0 0.6 0.0
|
||||||
|
2026-04-08 16:59:39.625 IDLE 13.058 0.02 1.91 0.14 0 SAFETY SAFETY 0.0 0.6 0.0
|
||||||
|
2026-04-08 16:59:39.645 IDLE 13.065 0.93 1.83 0.12 0 SAFETY SAFETY 0.0 0.6 0.0
|
||||||
|
2026-04-08 16:59:39.665 IDLE 13.077 0.01 1.25 0.00 0 SAFETY SAFETY 0.0 0.6 0.0
|
||||||
|
2026-04-08 16:59:39.686 IDLE 13.071 0.39 1.68 0.03 0 SAFETY SAFETY 0.0 0.6 0.0
|
||||||
|
2026-04-08 16:59:39.705 IDLE 13.084 0.09 1.42 0.00 0 SAFETY SAFETY 0.0 0.6 0.0
|
||||||
|
2026-04-08 16:59:39.725 IDLE 13.082 0.01 1.12 0.05 0 SAFETY SAFETY 0.0 0.6 0.0
|
||||||
|
2026-04-08 16:59:39.745 IDLE 13.085 0.39 1.61 -0.07 0 SAFETY SAFETY 0.0 0.6 0.0
|
||||||
|
2026-04-08 16:59:40.485 IDLE 13.202 1.28 -0.51 -0.18 0 SAFETY SAFETY 0.0 0.5 0.0
|
||||||
|
2026-04-08 16:59:40.507 IDLE 12.386 1.58 32.95 -0.02 0 SAFETY SAFETY 0.0 0.6 0.0
|
||||||
|
2026-04-08 16:59:40.525 IDLE 12.350 1.05 25.06 -0.09 0 SAFETY SAFETY 0.0 0.7 0.0
|
||||||
|
2026-04-08 16:59:40.545 IDLE 12.501 0.66 13.51 0.03 0 SAFETY SAFETY 0.0 0.7 0.0
|
||||||
|
2026-04-08 16:59:40.566 IDLE 12.649 0.58 8.36 0.01 0 SAFETY SAFETY 0.0 0.7 0.0
|
||||||
|
2026-04-08 16:59:40.585 IDLE 12.803 -0.17 6.54 0.21 0 SAFETY SAFETY 0.0 0.7 0.0
|
||||||
|
2026-04-08 16:59:40.606 IDLE 12.865 -0.17 4.86 -0.09 0 SAFETY SAFETY 0.0 0.7 0.0
|
||||||
|
2026-04-08 16:59:40.625 IDLE 12.904 0.20 3.72 -0.06 0 SAFETY SAFETY 0.0 0.7 0.0
|
||||||
|
2026-04-08 16:59:40.645 IDLE 12.912 0.66 4.01 -0.11 0 SAFETY SAFETY 0.0 0.7 0.0
|
||||||
|
2026-04-08 16:59:40.668 IDLE 12.939 -4.33 4.13 -0.11 0 SAFETY SAFETY 0.0 0.7 0.0
|
||||||
|
2026-04-08 16:59:40.685 IDLE 13.025 0.67 3.54 0.07 0 SAFETY SAFETY 0.0 0.7 0.0
|
||||||
|
2026-04-08 16:59:40.706 IDLE 13.011 0.37 3.75 -0.06 0 SAFETY SAFETY 0.0 0.7 0.0
|
||||||
|
2026-04-08 16:59:40.726 IDLE 13.000 0.29 4.38 0.10 0 SAFETY SAFETY 0.0 0.7 0.0
|
||||||
|
2026-04-08 16:59:40.745 IDLE 12.971 0.06 4.16 0.01 0 SAFETY SAFETY 0.0 0.7 0.0
|
||||||
|
2026-04-08 16:59:40.766 IDLE 12.968 0.52 4.32 -0.20 0 SAFETY SAFETY 0.0 0.7 0.0
|
||||||
|
2026-04-08 16:59:40.785 IDLE 12.948 0.67 4.70 -0.13 0 SAFETY SAFETY 0.0 0.7 0.0
|
||||||
|
2026-04-08 16:59:40.807 IDLE 12.941 -0.02 4.29 0.10 0 SAFETY SAFETY 0.0 0.7 0.0
|
||||||
|
2026-04-08 16:59:40.825 IDLE 12.942 0.28 4.20 0.03 0 SAFETY SAFETY 0.0 0.7 0.0
|
||||||
|
2026-04-08 16:59:40.845 IDLE 12.946 -0.32 4.70 0.10 0 SAFETY SAFETY 0.0 0.7 0.0
|
||||||
|
2026-04-08 16:59:40.866 IDLE 12.944 0.21 4.38 -0.06 0 SAFETY SAFETY 0.0 0.7 0.0
|
||||||
|
2026-04-08 16:59:40.885 IDLE 12.951 0.28 4.43 -0.51 0 SAFETY SAFETY 0.0 0.7 0.0
|
||||||
|
2026-04-08 16:59:40.906 IDLE 12.947 -0.10 5.00 -0.13 0 SAFETY SAFETY 0.0 0.7 0.0
|
||||||
|
2026-04-08 16:59:40.927 IDLE 12.937 0.21 4.84 0.06 0 SAFETY SAFETY 0.0 0.7 0.0
|
||||||
|
2026-04-08 16:59:40.945 IDLE 12.932 -0.32 4.88 -0.10 0 SAFETY SAFETY 0.0 0.7 0.0
|
||||||
|
2026-04-08 16:59:40.965 IDLE 12.911 -0.70 5.50 0.01 0 SAFETY SAFETY 0.0 0.7 0.0
|
||||||
|
2026-04-08 16:59:40.985 IDLE 12.946 -0.70 6.27 0.40 0 SAFETY SAFETY 0.0 0.7 0.0
|
||||||
|
2026-04-08 16:59:41.006 IDLE 12.917 0.44 6.07 -0.06 0 SAFETY SAFETY 0.0 0.7 0.0
|
||||||
|
2026-04-08 16:59:41.025 IDLE 12.915 -0.01 4.95 -0.10 0 SAFETY SAFETY 0.0 0.7 0.0
|
||||||
|
2026-04-08 16:59:41.045 IDLE 12.910 -0.01 5.18 0.17 0 SAFETY SAFETY 0.0 0.7 0.0
|
||||||
|
2026-04-08 16:59:41.065 IDLE 12.915 -3.34 4.73 -0.01 0 SAFETY SAFETY 0.0 0.7 0.0
|
||||||
|
2026-04-08 16:59:41.085 IDLE 12.917 0.68 4.70 0.05 0 SAFETY SAFETY 0.0 0.7 0.0
|
||||||
|
2026-04-08 16:59:41.106 IDLE 12.926 0.23 5.32 -0.01 0 SAFETY SAFETY 0.0 0.7 0.0
|
||||||
|
2026-04-08 16:59:41.125 IDLE 12.911 0.53 5.18 0.10 0 SAFETY SAFETY 0.0 0.7 0.0
|
||||||
|
2026-04-08 16:59:41.145 IDLE 12.896 -0.01 6.00 -0.01 0 SAFETY SAFETY 0.0 0.7 0.0
|
||||||
|
2026-04-08 16:59:41.165 IDLE 12.870 0.45 6.20 0.14 0 SAFETY SAFETY 0.0 0.7 0.0
|
||||||
|
2026-04-08 16:59:41.185 IDLE 12.914 -1.29 7.00 0.39 0 SAFETY SAFETY 0.0 0.7 0.0
|
||||||
|
2026-04-08 16:59:41.206 IDLE 12.882 0.38 6.20 -0.13 0 SAFETY SAFETY 0.0 0.7 0.0
|
||||||
|
2026-04-08 16:59:41.525 IDLE 13.179 -0.52 -0.55 0.08 0 SAFETY SAFETY 0.0 0.6 0.0
|
||||||
|
2026-04-08 16:59:41.546 IDLE 13.187 -0.29 -0.53 -0.19 0 SAFETY SAFETY 0.0 0.6 0.0
|
||||||
|
2026-04-08 16:59:41.565 IDLE 13.176 -0.29 -0.23 -0.03 0 SAFETY SAFETY 0.0 0.6 0.0
|
||||||
|
2026-04-08 16:59:41.585 IDLE 13.167 0.39 -0.46 0.10 0 SAFETY SAFETY 0.0 0.6 0.0
|
||||||
|
2026-04-08 16:59:41.606 IDLE 13.162 0.01 -0.32 -0.03 0 SAFETY SAFETY 0.0 0.6 0.0
|
||||||
|
2026-04-08 16:59:41.625 IDLE 13.144 -0.29 -0.52 0.10 0 SAFETY SAFETY 0.0 0.6 0.0
|
||||||
|
2026-04-08 16:59:41.645 IDLE 13.136 -0.36 -0.54 -0.01 0 SAFETY SAFETY 0.0 0.6 0.0
|
||||||
|
2026-04-08 16:59:41.666 IDLE 13.154 0.32 -0.52 0.19 0 SAFETY SAFETY 0.0 0.6 0.0
|
||||||
|
2026-04-08 16:59:41.685 IDLE 13.140 0.09 -0.47 -0.06 0 SAFETY SAFETY 0.0 0.6 0.0
|
||||||
|
2026-04-08 16:59:41.706 IDLE 13.141 0.01 -0.60 -0.10 0 SAFETY SAFETY 0.0 0.6 0.0
|
||||||
|
2026-04-08 16:59:41.726 IDLE 13.149 -0.14 -0.60 -0.03 0 SAFETY SAFETY 0.0 0.6 0.0
|
||||||
|
2026-04-08 16:59:41.746 IDLE 13.153 0.01 -0.41 -1.21 0 SAFETY SAFETY 0.0 0.6 0.0
|
||||||
|
2026-04-08 16:59:41.767 IDLE 12.362 0.69 32.95 0.06 0 SAFETY SAFETY 0.0 0.7 0.0
|
||||||
|
2026-04-08 16:59:41.785 IDLE 12.330 0.69 26.22 0.02 0 SAFETY SAFETY 0.0 0.8 0.0
|
||||||
|
2026-04-08 16:59:41.806 IDLE 12.468 0.39 14.18 -0.07 0 SAFETY SAFETY 0.0 0.8 0.0
|
||||||
|
2026-04-08 16:59:41.826 IDLE 12.629 0.38 8.36 0.18 0 SAFETY SAFETY 0.0 0.8 0.0
|
||||||
|
2026-04-08 16:59:41.846 IDLE 12.724 0.08 6.63 0.11 0 SAFETY SAFETY 0.0 0.8 0.0
|
||||||
|
2026-04-08 16:59:41.868 IDLE 12.757 -0.22 4.63 -0.19 0 SAFETY SAFETY 0.0 0.8 0.0
|
||||||
|
2026-04-08 16:59:41.885 IDLE 12.834 0.01 3.90 -0.12 0 SAFETY SAFETY 0.0 0.8 0.0
|
||||||
|
2026-04-08 16:59:41.906 IDLE 12.869 0.01 3.52 0.09 0 SAFETY SAFETY 0.0 0.8 0.0
|
||||||
|
2026-04-08 16:59:41.926 IDLE 12.914 0.31 2.59 0.11 0 SAFETY SAFETY 0.0 0.8 0.0
|
||||||
|
2026-04-08 16:59:41.945 IDLE 12.951 0.00 2.40 0.09 0 SAFETY SAFETY 0.0 0.8 0.0
|
||||||
|
2026-04-08 16:59:41.965 IDLE 12.970 0.61 2.57 0.18 0 SAFETY SAFETY 0.0 0.8 0.0
|
||||||
|
2026-04-08 16:59:41.985 IDLE 12.968 0.53 2.28 0.11 0 SAFETY SAFETY 0.0 0.8 0.0
|
||||||
|
2026-04-08 16:59:42.006 IDLE 12.982 -0.23 2.04 -0.07 0 SAFETY SAFETY 0.0 0.8 0.0
|
||||||
|
2026-04-08 16:59:42.025 IDLE 12.978 -1.13 2.28 0.02 0 SAFETY SAFETY 0.0 0.8 0.0
|
||||||
|
2026-04-08 16:59:42.045 IDLE 12.983 0.38 1.93 0.17 0 SAFETY SAFETY 0.0 0.8 0.0
|
||||||
|
2026-04-08 16:59:42.065 IDLE 12.986 0.00 2.76 -0.17 0 SAFETY SAFETY 0.0 0.8 0.0
|
||||||
|
2026-04-08 16:59:42.126 IDLE 13.070 1.06 -0.62 -0.12 0 SAFETY SAFETY 0.0 0.8 0.0
|
||||||
|
2026-04-08 16:59:42.145 IDLE 13.091 0.45 -0.59 0.17 0 SAFETY SAFETY 0.0 0.8 0.0
|
||||||
|
2026-04-08 16:59:42.166 IDLE 13.101 0.14 -0.57 -0.12 0 SAFETY SAFETY 0.0 0.8 0.0
|
||||||
|
2026-04-08 16:59:42.187 IDLE 13.114 0.07 -0.61 0.02 0 SAFETY SAFETY 0.0 0.8 0.0
|
||||||
|
2026-04-08 16:59:42.206 IDLE 13.109 -0.01 -0.31 0.04 0 SAFETY SAFETY 0.0 0.8 0.0
|
||||||
|
2026-04-08 16:59:42.225 IDLE 13.114 0.52 -0.69 0.15 0 SAFETY SAFETY 0.0 0.8 0.0
|
||||||
|
2026-04-08 16:59:42.245 IDLE 13.300 0.59 -0.58 0.06 0 SAFETY SAFETY 0.0 0.8 0.0
|
||||||
|
2026-04-08 16:59:42.265 IDLE 13.214 -0.32 -0.60 0.04 0 SAFETY SAFETY 0.0 0.8 0.0
|
||||||
|
2026-04-08 16:59:42.286 IDLE 13.205 -0.77 -0.37 0.24 0 SAFETY SAFETY 0.0 0.8 0.0
|
||||||
|
2026-04-08 16:59:42.306 IDLE 13.166 -4.24 -0.62 0.10 0 SAFETY SAFETY 0.0 0.8 0.0
|
||||||
|
2026-04-08 16:59:42.326 IDLE 13.150 0.01 -0.77 -0.15 0 SAFETY SAFETY 0.0 0.8 0.0
|
||||||
|
2026-04-08 16:59:42.345 IDLE 13.146 0.08 -0.45 -0.17 0 SAFETY SAFETY 0.0 0.7 0.0
|
||||||
|
2026-04-08 16:59:42.365 IDLE 13.125 0.39 -0.56 0.01 0 SAFETY SAFETY 0.0 0.7 0.0
|
||||||
|
2026-04-08 16:59:42.385 IDLE 13.126 0.23 -0.65 -0.05 0 SAFETY SAFETY 0.0 0.7 0.0
|
||||||
|
2026-04-08 16:59:42.409 IDLE 13.126 0.31 -0.76 -0.12 0 SAFETY SAFETY 0.0 0.7 0.0
|
||||||
|
2026-04-08 16:59:42.426 IDLE 13.123 0.23 -0.57 0.17 0 SAFETY SAFETY 0.0 0.7 0.0
|
||||||
|
2026-04-08 16:59:42.445 IDLE 13.148 0.00 -0.57 0.01 0 SAFETY SAFETY 0.0 0.7 0.0
|
||||||
|
2026-04-08 17:00:17.516 BAT 13.292 — — — — — — — — —
|
||||||
|
2026-04-08 17:02:17.516 BAT 13.263 — — — — — — — — —
|
||||||
|
2026-04-08 17:07:51.726 BAT 13.334 — — — — — — — — —
|
||||||
|
|
||||||
|
Entries : 195 total (189 FSM, 3 BAT, 0 CRASH, 2 BOOT, 1 TIME_SET)
|
||||||
|
Time : 2026-04-08 16:58:48.000 → 2026-04-08 17:07:51.726
|
||||||
|
Duration: 543.7 s (9.1 min)
|
||||||
|
Battery : 12.042 V – 13.334 V
|
||||||
|
|
||||||
|
BOOT events:
|
||||||
|
1970-01-01 00:00:00.000 rst=POWERON wake=NORMAL
|
||||||
|
1970-01-01 00:00:00.000 rst=POWERON wake=NORMAL
|
||||||
|
|
||||||
|
TIME_SET events:
|
||||||
|
2026-04-08 16:58:48.000
|
||||||
1
logtool/08APR2026_1709.txt
Normal file
1
logtool/08APR2026_1709.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Fetching http://sc.local ...
|
||||||
1
logtool/08APR2026_1718.txt
Normal file
1
logtool/08APR2026_1718.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Fetching http://sc.local ...
|
||||||
BIN
logtool/08APR2026_1719.bin
Normal file
BIN
logtool/08APR2026_1719.bin
Normal file
Binary file not shown.
223
logtool/08APR2026_1719.txt
Normal file
223
logtool/08APR2026_1719.txt
Normal file
@@ -0,0 +1,223 @@
|
|||||||
|
Fetching http://sc.local/log ...
|
||||||
|
Log offsets: tail=0 head=7912 entries=202
|
||||||
|
Device: version=? time=1775668769
|
||||||
|
Time State Bat(V) Drive(A) Jack(A) Aux(A) Counter Stable Raw DrHeat JkHeat AxHeat
|
||||||
|
----------------------- ------------------------------ ------ -------- ------- ------ ------- ------ ------ ------ ------ ------
|
||||||
|
1970-01-01 00:00:00.000 BOOT rst=POWERON wake=NORMAL — — — — — — — — — —
|
||||||
|
1970-01-01 00:00:00.000 BOOT rst=POWERON wake=NORMAL — — — — — — — — — —
|
||||||
|
2026-04-08 16:58:48.000 TIME_SET — — — — — — — — — —
|
||||||
|
2026-04-08 16:59:36.565 IDLE 13.326 0.03 -0.15 -0.08 0 SAFETY SAFETY 0.0 0.0 0.0
|
||||||
|
2026-04-08 16:59:36.585 IDLE 12.521 3.20 33.46 0.17 0 SAFETY SAFETY 0.0 0.1 0.0
|
||||||
|
2026-04-08 16:59:36.605 IDLE 12.042 1.76 33.46 -0.01 0 SAFETY SAFETY 0.0 0.2 0.0
|
||||||
|
2026-04-08 16:59:36.625 IDLE 12.090 0.69 33.46 0.01 0 SAFETY SAFETY 0.0 0.3 0.0
|
||||||
|
2026-04-08 16:59:36.649 IDLE 12.236 1.59 26.17 0.10 0 SAFETY SAFETY 0.0 0.4 0.0
|
||||||
|
2026-04-08 16:59:36.665 IDLE 12.429 -0.00 17.46 0.04 0 SAFETY SAFETY 0.0 0.4 0.0
|
||||||
|
2026-04-08 16:59:36.685 IDLE 12.632 -0.00 10.87 -0.10 0 SAFETY SAFETY 0.0 0.5 0.0
|
||||||
|
2026-04-08 16:59:36.705 IDLE 12.795 0.53 7.21 0.04 0 SAFETY SAFETY 0.0 0.5 0.0
|
||||||
|
2026-04-08 16:59:36.725 IDLE 12.899 -0.01 4.53 -0.19 0 SAFETY SAFETY 0.0 0.5 0.0
|
||||||
|
2026-04-08 16:59:36.747 IDLE 12.986 0.45 3.51 -0.06 0 SAFETY SAFETY 0.0 0.5 0.0
|
||||||
|
2026-04-08 16:59:36.766 IDLE 13.037 -0.76 3.26 -0.10 0 SAFETY SAFETY 0.0 0.5 0.0
|
||||||
|
2026-04-08 16:59:36.786 IDLE 13.067 0.60 2.90 0.15 0 SAFETY SAFETY 0.0 0.5 0.0
|
||||||
|
2026-04-08 16:59:36.805 IDLE 13.066 -0.31 2.35 -0.35 0 SAFETY SAFETY 0.0 0.5 0.0
|
||||||
|
2026-04-08 16:59:36.825 IDLE 13.089 -0.01 2.13 -0.53 0 SAFETY SAFETY 0.0 0.5 0.0
|
||||||
|
2026-04-08 16:59:36.846 IDLE 13.104 -0.01 2.10 -0.03 0 SAFETY SAFETY 0.0 0.5 0.0
|
||||||
|
2026-04-08 16:59:36.865 IDLE 13.111 0.45 1.90 0.09 0 SAFETY SAFETY 0.0 0.5 0.0
|
||||||
|
2026-04-08 16:59:36.885 IDLE 13.134 0.07 1.33 0.18 0 SAFETY SAFETY 0.0 0.5 0.0
|
||||||
|
2026-04-08 16:59:36.906 IDLE 13.161 -0.24 1.43 0.15 0 SAFETY SAFETY 0.0 0.5 0.0
|
||||||
|
2026-04-08 16:59:36.925 IDLE 13.140 0.82 1.11 -0.19 0 SAFETY SAFETY 0.0 0.5 0.0
|
||||||
|
2026-04-08 16:59:36.946 IDLE 13.153 -0.01 1.83 0.08 0 SAFETY SAFETY 0.0 0.4 0.0
|
||||||
|
2026-04-08 16:59:36.965 IDLE 13.174 0.21 1.77 -0.10 0 SAFETY SAFETY 0.0 0.4 0.0
|
||||||
|
2026-04-08 16:59:36.985 IDLE 13.143 -0.01 1.49 -0.12 0 SAFETY SAFETY 0.0 0.4 0.0
|
||||||
|
2026-04-08 16:59:37.005 IDLE 13.139 0.52 2.64 0.15 0 SAFETY SAFETY 0.0 0.4 0.0
|
||||||
|
2026-04-08 16:59:37.025 IDLE 13.140 0.29 1.77 -0.07 0 SAFETY SAFETY 0.0 0.4 0.0
|
||||||
|
2026-04-08 16:59:37.047 IDLE 13.130 -1.53 2.08 0.06 0 SAFETY SAFETY 0.0 0.4 0.0
|
||||||
|
2026-04-08 16:59:37.066 IDLE 13.124 -0.01 2.77 0.08 0 SAFETY SAFETY 0.0 0.4 0.0
|
||||||
|
2026-04-08 16:59:37.086 IDLE 13.106 0.22 3.64 0.02 0 SAFETY SAFETY 0.0 0.4 0.0
|
||||||
|
2026-04-08 16:59:37.106 IDLE 13.097 -0.54 3.55 -0.03 0 SAFETY SAFETY 0.0 0.4 0.0
|
||||||
|
2026-04-08 16:59:37.125 IDLE 13.093 -0.16 3.27 0.02 0 SAFETY SAFETY 0.0 0.4 0.0
|
||||||
|
2026-04-08 16:59:37.145 IDLE 13.094 0.67 3.59 -0.10 0 SAFETY SAFETY 0.0 0.4 0.0
|
||||||
|
2026-04-08 16:59:37.166 IDLE 13.080 0.37 3.91 0.13 0 SAFETY SAFETY 0.0 0.4 0.0
|
||||||
|
2026-04-08 16:59:37.187 IDLE 13.069 1.80 3.01 0.02 0 SAFETY SAFETY 0.0 0.4 0.0
|
||||||
|
2026-04-08 16:59:37.205 IDLE 13.079 -0.40 3.85 0.06 0 SAFETY SAFETY 0.0 0.4 0.0
|
||||||
|
2026-04-08 16:59:37.225 IDLE 13.072 0.36 4.20 -0.07 0 SAFETY SAFETY 0.0 0.4 0.0
|
||||||
|
2026-04-08 16:59:37.246 IDLE 13.134 -0.02 4.00 -0.19 0 SAFETY SAFETY 0.0 0.4 0.0
|
||||||
|
2026-04-08 16:59:37.265 IDLE 13.096 0.36 3.57 0.15 0 SAFETY SAFETY 0.0 0.4 0.0
|
||||||
|
2026-04-08 16:59:37.285 IDLE 13.081 0.66 3.91 0.11 0 SAFETY SAFETY 0.0 0.4 0.0
|
||||||
|
2026-04-08 16:59:37.306 IDLE 13.061 0.28 4.00 -0.12 0 SAFETY SAFETY 0.0 0.4 0.0
|
||||||
|
2026-04-08 16:59:37.325 IDLE 13.060 -0.03 4.25 -0.17 0 SAFETY SAFETY 0.0 0.4 0.0
|
||||||
|
2026-04-08 16:59:37.345 IDLE 13.039 0.65 4.82 0.06 0 SAFETY SAFETY 0.0 0.4 0.0
|
||||||
|
2026-04-08 16:59:37.365 IDLE 13.095 0.65 4.19 -0.05 0 SAFETY SAFETY 0.0 0.4 0.0
|
||||||
|
2026-04-08 16:59:37.385 IDLE 13.057 0.19 4.10 -0.19 0 SAFETY SAFETY 0.0 0.4 0.0
|
||||||
|
2026-04-08 16:59:37.406 IDLE 13.046 0.27 4.91 0.11 0 SAFETY SAFETY 0.0 0.4 0.0
|
||||||
|
2026-04-08 16:59:37.425 IDLE 13.036 -0.04 3.51 -0.10 0 SAFETY SAFETY 0.0 0.4 0.0
|
||||||
|
2026-04-08 16:59:37.446 IDLE 13.028 1.70 4.62 0.15 0 SAFETY SAFETY 0.0 0.4 0.0
|
||||||
|
2026-04-08 16:59:37.466 IDLE 13.020 0.26 4.74 -0.12 0 SAFETY SAFETY 0.0 0.4 0.0
|
||||||
|
2026-04-08 16:59:37.485 IDLE 13.039 -0.04 4.44 0.09 0 SAFETY SAFETY 0.0 0.4 0.0
|
||||||
|
2026-04-08 16:59:37.505 IDLE 13.018 -1.33 5.14 0.02 0 SAFETY SAFETY 0.0 0.4 0.0
|
||||||
|
2026-04-08 16:59:37.525 IDLE 13.011 0.49 4.62 0.02 0 SAFETY SAFETY 0.0 0.4 0.0
|
||||||
|
2026-04-08 16:59:37.545 IDLE 13.004 0.03 5.37 -0.10 0 SAFETY SAFETY 0.0 0.4 0.0
|
||||||
|
2026-04-08 16:59:37.565 IDLE 12.996 0.34 5.03 -0.10 0 SAFETY SAFETY 0.0 0.4 0.0
|
||||||
|
2026-04-08 16:59:37.585 IDLE 12.977 0.26 4.99 0.04 0 SAFETY SAFETY 0.0 0.4 0.0
|
||||||
|
2026-04-08 16:59:37.606 IDLE 12.987 0.48 5.19 -0.03 0 SAFETY SAFETY 0.0 0.4 0.0
|
||||||
|
2026-04-08 16:59:37.627 IDLE 12.992 -0.05 4.76 0.09 0 SAFETY SAFETY 0.0 0.4 0.0
|
||||||
|
2026-04-08 16:59:37.645 IDLE 12.994 0.41 4.99 0.02 0 SAFETY SAFETY 0.0 0.4 0.0
|
||||||
|
2026-04-08 16:59:37.665 IDLE 12.987 0.41 4.99 -0.05 0 SAFETY SAFETY 0.0 0.4 0.0
|
||||||
|
2026-04-08 16:59:37.685 IDLE 12.996 0.63 4.51 0.02 0 SAFETY SAFETY 0.0 0.4 0.0
|
||||||
|
2026-04-08 16:59:37.709 IDLE 13.019 -0.66 5.87 0.18 0 SAFETY SAFETY 0.0 0.4 0.0
|
||||||
|
2026-04-08 16:59:37.725 IDLE 13.008 -0.43 5.19 0.02 0 SAFETY SAFETY 0.0 0.5 0.0
|
||||||
|
2026-04-08 16:59:37.749 IDLE 12.983 0.33 5.92 0.04 0 SAFETY SAFETY 0.0 0.5 0.0
|
||||||
|
2026-04-08 16:59:37.766 IDLE 12.974 0.18 5.71 0.02 0 SAFETY SAFETY 0.0 0.5 0.0
|
||||||
|
2026-04-08 16:59:37.785 IDLE 12.966 0.63 6.37 0.02 0 SAFETY SAFETY 0.0 0.5 0.0
|
||||||
|
2026-04-08 16:59:37.805 IDLE 12.966 0.02 5.44 -0.10 0 SAFETY SAFETY 0.0 0.5 0.0
|
||||||
|
2026-04-08 16:59:37.825 IDLE 12.950 -0.51 5.64 -0.01 0 SAFETY SAFETY 0.0 0.5 0.0
|
||||||
|
2026-04-08 16:59:37.846 IDLE 12.958 0.55 5.44 -0.01 0 SAFETY SAFETY 0.0 0.5 0.0
|
||||||
|
2026-04-08 16:59:37.866 IDLE 12.962 -0.05 5.33 -0.07 0 SAFETY SAFETY 0.0 0.5 0.0
|
||||||
|
2026-04-08 16:59:37.885 IDLE 12.964 0.17 5.55 -0.07 0 SAFETY SAFETY 0.0 0.5 0.0
|
||||||
|
2026-04-08 16:59:37.906 IDLE 12.781 0.47 5.58 0.06 0 SAFETY SAFETY 0.0 0.5 0.0
|
||||||
|
2026-04-08 16:59:37.925 IDLE 12.969 0.55 6.03 -0.07 0 SAFETY SAFETY 0.0 0.5 0.0
|
||||||
|
2026-04-08 16:59:37.945 IDLE 12.952 0.39 6.78 0.13 0 SAFETY SAFETY 0.0 0.5 0.0
|
||||||
|
2026-04-08 16:59:37.965 IDLE 12.928 -0.67 6.42 0.11 0 SAFETY SAFETY 0.0 0.5 0.0
|
||||||
|
2026-04-08 16:59:37.987 IDLE 12.924 0.62 6.85 0.31 0 SAFETY SAFETY 0.0 0.5 0.0
|
||||||
|
2026-04-08 16:59:39.066 IDLE 13.225 -3.19 -0.32 -0.08 0 SAFETY SAFETY 0.0 0.4 0.0
|
||||||
|
2026-04-08 16:59:39.085 IDLE 12.409 0.75 33.14 -0.13 0 SAFETY SAFETY 0.0 0.5 0.0
|
||||||
|
2026-04-08 16:59:39.105 IDLE 12.346 0.59 27.43 0.06 0 SAFETY SAFETY 0.0 0.6 0.0
|
||||||
|
2026-04-08 16:59:39.128 IDLE 12.507 -1.98 15.14 0.15 0 SAFETY SAFETY 0.0 0.6 0.0
|
||||||
|
2026-04-08 16:59:39.145 IDLE 12.652 0.68 9.34 0.10 0 SAFETY SAFETY 0.0 0.6 0.0
|
||||||
|
2026-04-08 16:59:39.166 IDLE 12.774 0.30 6.64 0.05 0 SAFETY SAFETY 0.0 0.6 0.0
|
||||||
|
2026-04-08 16:59:39.185 IDLE 12.855 0.07 5.41 -0.15 0 SAFETY SAFETY 0.0 0.6 0.0
|
||||||
|
2026-04-08 16:59:39.206 IDLE 12.906 0.22 4.82 -0.15 0 SAFETY SAFETY 0.0 0.6 0.0
|
||||||
|
2026-04-08 16:59:39.226 IDLE 12.947 0.44 3.73 0.10 0 SAFETY SAFETY 0.0 0.6 0.0
|
||||||
|
2026-04-08 16:59:39.245 IDLE 12.972 -0.77 3.57 -0.17 0 SAFETY SAFETY 0.0 0.6 0.0
|
||||||
|
2026-04-08 16:59:39.266 IDLE 13.022 0.60 2.65 0.01 0 SAFETY SAFETY 0.0 0.6 0.0
|
||||||
|
2026-04-08 16:59:39.285 IDLE 13.025 -4.62 3.20 0.30 0 SAFETY SAFETY 0.0 0.6 0.0
|
||||||
|
2026-04-08 16:59:39.305 IDLE 13.053 -0.44 2.60 -0.06 0 SAFETY SAFETY 0.0 0.6 0.0
|
||||||
|
2026-04-08 16:59:39.325 IDLE 13.071 -0.06 2.76 -0.01 0 SAFETY SAFETY 0.0 0.6 0.0
|
||||||
|
2026-04-08 16:59:39.345 IDLE 13.041 -0.29 2.59 0.01 0 SAFETY SAFETY 0.0 0.6 0.0
|
||||||
|
2026-04-08 16:59:39.366 IDLE 13.053 -0.29 1.88 0.12 0 SAFETY SAFETY 0.0 0.6 0.0
|
||||||
|
2026-04-08 16:59:39.386 IDLE 13.055 0.09 2.02 0.01 0 SAFETY SAFETY 0.0 0.6 0.0
|
||||||
|
2026-04-08 16:59:39.406 IDLE 13.060 0.09 2.19 -0.15 0 SAFETY SAFETY 0.0 0.6 0.0
|
||||||
|
2026-04-08 16:59:39.425 IDLE 13.044 0.47 2.48 0.14 0 SAFETY SAFETY 0.0 0.6 0.0
|
||||||
|
2026-04-08 16:59:39.445 IDLE 13.081 -0.59 2.38 0.44 0 SAFETY SAFETY 0.0 0.6 0.0
|
||||||
|
2026-04-08 16:59:39.465 IDLE 13.054 0.24 2.41 0.01 0 SAFETY SAFETY 0.0 0.6 0.0
|
||||||
|
2026-04-08 16:59:39.485 IDLE 13.075 -0.06 1.81 0.17 0 SAFETY SAFETY 0.0 0.6 0.0
|
||||||
|
2026-04-08 16:59:39.505 IDLE 13.066 -0.29 1.69 -0.11 0 SAFETY SAFETY 0.0 0.6 0.0
|
||||||
|
2026-04-08 16:59:39.525 IDLE 13.074 0.09 1.84 -0.13 0 SAFETY SAFETY 0.0 0.6 0.0
|
||||||
|
2026-04-08 16:59:39.545 IDLE 13.054 -0.13 1.80 0.26 0 SAFETY SAFETY 0.0 0.6 0.0
|
||||||
|
2026-04-08 16:59:39.565 IDLE 13.060 -0.28 1.89 0.01 0 SAFETY SAFETY 0.0 0.6 0.0
|
||||||
|
2026-04-08 16:59:39.588 IDLE 13.074 0.02 1.29 -0.06 0 SAFETY SAFETY 0.0 0.6 0.0
|
||||||
|
2026-04-08 16:59:39.606 IDLE 13.058 0.10 1.51 0.10 0 SAFETY SAFETY 0.0 0.6 0.0
|
||||||
|
2026-04-08 16:59:39.625 IDLE 13.058 0.02 1.91 0.14 0 SAFETY SAFETY 0.0 0.6 0.0
|
||||||
|
2026-04-08 16:59:39.645 IDLE 13.065 0.93 1.83 0.12 0 SAFETY SAFETY 0.0 0.6 0.0
|
||||||
|
2026-04-08 16:59:39.665 IDLE 13.077 0.01 1.25 0.00 0 SAFETY SAFETY 0.0 0.6 0.0
|
||||||
|
2026-04-08 16:59:39.686 IDLE 13.071 0.39 1.68 0.03 0 SAFETY SAFETY 0.0 0.6 0.0
|
||||||
|
2026-04-08 16:59:39.705 IDLE 13.084 0.09 1.42 0.00 0 SAFETY SAFETY 0.0 0.6 0.0
|
||||||
|
2026-04-08 16:59:39.725 IDLE 13.082 0.01 1.12 0.05 0 SAFETY SAFETY 0.0 0.6 0.0
|
||||||
|
2026-04-08 16:59:39.745 IDLE 13.085 0.39 1.61 -0.07 0 SAFETY SAFETY 0.0 0.6 0.0
|
||||||
|
2026-04-08 16:59:40.485 IDLE 13.202 1.28 -0.51 -0.18 0 SAFETY SAFETY 0.0 0.5 0.0
|
||||||
|
2026-04-08 16:59:40.507 IDLE 12.386 1.58 32.95 -0.02 0 SAFETY SAFETY 0.0 0.6 0.0
|
||||||
|
2026-04-08 16:59:40.525 IDLE 12.350 1.05 25.06 -0.09 0 SAFETY SAFETY 0.0 0.7 0.0
|
||||||
|
2026-04-08 16:59:40.545 IDLE 12.501 0.66 13.51 0.03 0 SAFETY SAFETY 0.0 0.7 0.0
|
||||||
|
2026-04-08 16:59:40.566 IDLE 12.649 0.58 8.36 0.01 0 SAFETY SAFETY 0.0 0.7 0.0
|
||||||
|
2026-04-08 16:59:40.585 IDLE 12.803 -0.17 6.54 0.21 0 SAFETY SAFETY 0.0 0.7 0.0
|
||||||
|
2026-04-08 16:59:40.606 IDLE 12.865 -0.17 4.86 -0.09 0 SAFETY SAFETY 0.0 0.7 0.0
|
||||||
|
2026-04-08 16:59:40.625 IDLE 12.904 0.20 3.72 -0.06 0 SAFETY SAFETY 0.0 0.7 0.0
|
||||||
|
2026-04-08 16:59:40.645 IDLE 12.912 0.66 4.01 -0.11 0 SAFETY SAFETY 0.0 0.7 0.0
|
||||||
|
2026-04-08 16:59:40.668 IDLE 12.939 -4.33 4.13 -0.11 0 SAFETY SAFETY 0.0 0.7 0.0
|
||||||
|
2026-04-08 16:59:40.685 IDLE 13.025 0.67 3.54 0.07 0 SAFETY SAFETY 0.0 0.7 0.0
|
||||||
|
2026-04-08 16:59:40.706 IDLE 13.011 0.37 3.75 -0.06 0 SAFETY SAFETY 0.0 0.7 0.0
|
||||||
|
2026-04-08 16:59:40.726 IDLE 13.000 0.29 4.38 0.10 0 SAFETY SAFETY 0.0 0.7 0.0
|
||||||
|
2026-04-08 16:59:40.745 IDLE 12.971 0.06 4.16 0.01 0 SAFETY SAFETY 0.0 0.7 0.0
|
||||||
|
2026-04-08 16:59:40.766 IDLE 12.968 0.52 4.32 -0.20 0 SAFETY SAFETY 0.0 0.7 0.0
|
||||||
|
2026-04-08 16:59:40.785 IDLE 12.948 0.67 4.70 -0.13 0 SAFETY SAFETY 0.0 0.7 0.0
|
||||||
|
2026-04-08 16:59:40.807 IDLE 12.941 -0.02 4.29 0.10 0 SAFETY SAFETY 0.0 0.7 0.0
|
||||||
|
2026-04-08 16:59:40.825 IDLE 12.942 0.28 4.20 0.03 0 SAFETY SAFETY 0.0 0.7 0.0
|
||||||
|
2026-04-08 16:59:40.845 IDLE 12.946 -0.32 4.70 0.10 0 SAFETY SAFETY 0.0 0.7 0.0
|
||||||
|
2026-04-08 16:59:40.866 IDLE 12.944 0.21 4.38 -0.06 0 SAFETY SAFETY 0.0 0.7 0.0
|
||||||
|
2026-04-08 16:59:40.885 IDLE 12.951 0.28 4.43 -0.51 0 SAFETY SAFETY 0.0 0.7 0.0
|
||||||
|
2026-04-08 16:59:40.906 IDLE 12.947 -0.10 5.00 -0.13 0 SAFETY SAFETY 0.0 0.7 0.0
|
||||||
|
2026-04-08 16:59:40.927 IDLE 12.937 0.21 4.84 0.06 0 SAFETY SAFETY 0.0 0.7 0.0
|
||||||
|
2026-04-08 16:59:40.945 IDLE 12.932 -0.32 4.88 -0.10 0 SAFETY SAFETY 0.0 0.7 0.0
|
||||||
|
2026-04-08 16:59:40.965 IDLE 12.911 -0.70 5.50 0.01 0 SAFETY SAFETY 0.0 0.7 0.0
|
||||||
|
2026-04-08 16:59:40.985 IDLE 12.946 -0.70 6.27 0.40 0 SAFETY SAFETY 0.0 0.7 0.0
|
||||||
|
2026-04-08 16:59:41.006 IDLE 12.917 0.44 6.07 -0.06 0 SAFETY SAFETY 0.0 0.7 0.0
|
||||||
|
2026-04-08 16:59:41.025 IDLE 12.915 -0.01 4.95 -0.10 0 SAFETY SAFETY 0.0 0.7 0.0
|
||||||
|
2026-04-08 16:59:41.045 IDLE 12.910 -0.01 5.18 0.17 0 SAFETY SAFETY 0.0 0.7 0.0
|
||||||
|
2026-04-08 16:59:41.065 IDLE 12.915 -3.34 4.73 -0.01 0 SAFETY SAFETY 0.0 0.7 0.0
|
||||||
|
2026-04-08 16:59:41.085 IDLE 12.917 0.68 4.70 0.05 0 SAFETY SAFETY 0.0 0.7 0.0
|
||||||
|
2026-04-08 16:59:41.106 IDLE 12.926 0.23 5.32 -0.01 0 SAFETY SAFETY 0.0 0.7 0.0
|
||||||
|
2026-04-08 16:59:41.125 IDLE 12.911 0.53 5.18 0.10 0 SAFETY SAFETY 0.0 0.7 0.0
|
||||||
|
2026-04-08 16:59:41.145 IDLE 12.896 -0.01 6.00 -0.01 0 SAFETY SAFETY 0.0 0.7 0.0
|
||||||
|
2026-04-08 16:59:41.165 IDLE 12.870 0.45 6.20 0.14 0 SAFETY SAFETY 0.0 0.7 0.0
|
||||||
|
2026-04-08 16:59:41.185 IDLE 12.914 -1.29 7.00 0.39 0 SAFETY SAFETY 0.0 0.7 0.0
|
||||||
|
2026-04-08 16:59:41.206 IDLE 12.882 0.38 6.20 -0.13 0 SAFETY SAFETY 0.0 0.7 0.0
|
||||||
|
2026-04-08 16:59:41.525 IDLE 13.179 -0.52 -0.55 0.08 0 SAFETY SAFETY 0.0 0.6 0.0
|
||||||
|
2026-04-08 16:59:41.546 IDLE 13.187 -0.29 -0.53 -0.19 0 SAFETY SAFETY 0.0 0.6 0.0
|
||||||
|
2026-04-08 16:59:41.565 IDLE 13.176 -0.29 -0.23 -0.03 0 SAFETY SAFETY 0.0 0.6 0.0
|
||||||
|
2026-04-08 16:59:41.585 IDLE 13.167 0.39 -0.46 0.10 0 SAFETY SAFETY 0.0 0.6 0.0
|
||||||
|
2026-04-08 16:59:41.606 IDLE 13.162 0.01 -0.32 -0.03 0 SAFETY SAFETY 0.0 0.6 0.0
|
||||||
|
2026-04-08 16:59:41.625 IDLE 13.144 -0.29 -0.52 0.10 0 SAFETY SAFETY 0.0 0.6 0.0
|
||||||
|
2026-04-08 16:59:41.645 IDLE 13.136 -0.36 -0.54 -0.01 0 SAFETY SAFETY 0.0 0.6 0.0
|
||||||
|
2026-04-08 16:59:41.666 IDLE 13.154 0.32 -0.52 0.19 0 SAFETY SAFETY 0.0 0.6 0.0
|
||||||
|
2026-04-08 16:59:41.685 IDLE 13.140 0.09 -0.47 -0.06 0 SAFETY SAFETY 0.0 0.6 0.0
|
||||||
|
2026-04-08 16:59:41.706 IDLE 13.141 0.01 -0.60 -0.10 0 SAFETY SAFETY 0.0 0.6 0.0
|
||||||
|
2026-04-08 16:59:41.726 IDLE 13.149 -0.14 -0.60 -0.03 0 SAFETY SAFETY 0.0 0.6 0.0
|
||||||
|
2026-04-08 16:59:41.746 IDLE 13.153 0.01 -0.41 -1.21 0 SAFETY SAFETY 0.0 0.6 0.0
|
||||||
|
2026-04-08 16:59:41.767 IDLE 12.362 0.69 32.95 0.06 0 SAFETY SAFETY 0.0 0.7 0.0
|
||||||
|
2026-04-08 16:59:41.785 IDLE 12.330 0.69 26.22 0.02 0 SAFETY SAFETY 0.0 0.8 0.0
|
||||||
|
2026-04-08 16:59:41.806 IDLE 12.468 0.39 14.18 -0.07 0 SAFETY SAFETY 0.0 0.8 0.0
|
||||||
|
2026-04-08 16:59:41.826 IDLE 12.629 0.38 8.36 0.18 0 SAFETY SAFETY 0.0 0.8 0.0
|
||||||
|
2026-04-08 16:59:41.846 IDLE 12.724 0.08 6.63 0.11 0 SAFETY SAFETY 0.0 0.8 0.0
|
||||||
|
2026-04-08 16:59:41.868 IDLE 12.757 -0.22 4.63 -0.19 0 SAFETY SAFETY 0.0 0.8 0.0
|
||||||
|
2026-04-08 16:59:41.885 IDLE 12.834 0.01 3.90 -0.12 0 SAFETY SAFETY 0.0 0.8 0.0
|
||||||
|
2026-04-08 16:59:41.906 IDLE 12.869 0.01 3.52 0.09 0 SAFETY SAFETY 0.0 0.8 0.0
|
||||||
|
2026-04-08 16:59:41.926 IDLE 12.914 0.31 2.59 0.11 0 SAFETY SAFETY 0.0 0.8 0.0
|
||||||
|
2026-04-08 16:59:41.945 IDLE 12.951 0.00 2.40 0.09 0 SAFETY SAFETY 0.0 0.8 0.0
|
||||||
|
2026-04-08 16:59:41.965 IDLE 12.970 0.61 2.57 0.18 0 SAFETY SAFETY 0.0 0.8 0.0
|
||||||
|
2026-04-08 16:59:41.985 IDLE 12.968 0.53 2.28 0.11 0 SAFETY SAFETY 0.0 0.8 0.0
|
||||||
|
2026-04-08 16:59:42.006 IDLE 12.982 -0.23 2.04 -0.07 0 SAFETY SAFETY 0.0 0.8 0.0
|
||||||
|
2026-04-08 16:59:42.025 IDLE 12.978 -1.13 2.28 0.02 0 SAFETY SAFETY 0.0 0.8 0.0
|
||||||
|
2026-04-08 16:59:42.045 IDLE 12.983 0.38 1.93 0.17 0 SAFETY SAFETY 0.0 0.8 0.0
|
||||||
|
2026-04-08 16:59:42.065 IDLE 12.986 0.00 2.76 -0.17 0 SAFETY SAFETY 0.0 0.8 0.0
|
||||||
|
2026-04-08 16:59:42.126 IDLE 13.070 1.06 -0.62 -0.12 0 SAFETY SAFETY 0.0 0.8 0.0
|
||||||
|
2026-04-08 16:59:42.145 IDLE 13.091 0.45 -0.59 0.17 0 SAFETY SAFETY 0.0 0.8 0.0
|
||||||
|
2026-04-08 16:59:42.166 IDLE 13.101 0.14 -0.57 -0.12 0 SAFETY SAFETY 0.0 0.8 0.0
|
||||||
|
2026-04-08 16:59:42.187 IDLE 13.114 0.07 -0.61 0.02 0 SAFETY SAFETY 0.0 0.8 0.0
|
||||||
|
2026-04-08 16:59:42.206 IDLE 13.109 -0.01 -0.31 0.04 0 SAFETY SAFETY 0.0 0.8 0.0
|
||||||
|
2026-04-08 16:59:42.225 IDLE 13.114 0.52 -0.69 0.15 0 SAFETY SAFETY 0.0 0.8 0.0
|
||||||
|
2026-04-08 16:59:42.245 IDLE 13.300 0.59 -0.58 0.06 0 SAFETY SAFETY 0.0 0.8 0.0
|
||||||
|
2026-04-08 16:59:42.265 IDLE 13.214 -0.32 -0.60 0.04 0 SAFETY SAFETY 0.0 0.8 0.0
|
||||||
|
2026-04-08 16:59:42.286 IDLE 13.205 -0.77 -0.37 0.24 0 SAFETY SAFETY 0.0 0.8 0.0
|
||||||
|
2026-04-08 16:59:42.306 IDLE 13.166 -4.24 -0.62 0.10 0 SAFETY SAFETY 0.0 0.8 0.0
|
||||||
|
2026-04-08 16:59:42.326 IDLE 13.150 0.01 -0.77 -0.15 0 SAFETY SAFETY 0.0 0.8 0.0
|
||||||
|
2026-04-08 16:59:42.345 IDLE 13.146 0.08 -0.45 -0.17 0 SAFETY SAFETY 0.0 0.7 0.0
|
||||||
|
2026-04-08 16:59:42.365 IDLE 13.125 0.39 -0.56 0.01 0 SAFETY SAFETY 0.0 0.7 0.0
|
||||||
|
2026-04-08 16:59:42.385 IDLE 13.126 0.23 -0.65 -0.05 0 SAFETY SAFETY 0.0 0.7 0.0
|
||||||
|
2026-04-08 16:59:42.409 IDLE 13.126 0.31 -0.76 -0.12 0 SAFETY SAFETY 0.0 0.7 0.0
|
||||||
|
2026-04-08 16:59:42.426 IDLE 13.123 0.23 -0.57 0.17 0 SAFETY SAFETY 0.0 0.7 0.0
|
||||||
|
2026-04-08 16:59:42.445 IDLE 13.148 0.00 -0.57 0.01 0 SAFETY SAFETY 0.0 0.7 0.0
|
||||||
|
2026-04-08 17:00:17.516 BAT 13.292 — — — — — — — — —
|
||||||
|
2026-04-08 17:02:17.516 BAT 13.263 — — — — — — — — —
|
||||||
|
2026-04-08 17:07:51.726 BAT 13.334 — — — — — — — — —
|
||||||
|
1970-01-01 00:00:00.000 BOOT rst=SW wake=NORMAL — — — — — — — — — —
|
||||||
|
2026-04-08 17:09:42.000 TIME_SET — — — — — — — — — —
|
||||||
|
2026-04-08 17:11:10.209 BAT 13.282 — — — — — — — — —
|
||||||
|
1970-01-01 00:00:00.000 BOOT rst=SW wake=NORMAL — — — — — — — — — —
|
||||||
|
2026-04-08 17:15:58.000 TIME_SET — — — — — — — — — —
|
||||||
|
2026-04-08 17:15:58.025 BAT 13.306 — — — — — — — — —
|
||||||
|
2026-04-08 17:17:58.074 BAT 13.304 — — — — — — — — —
|
||||||
|
|
||||||
|
Entries : 202 total (189 FSM, 6 BAT, 0 CRASH, 4 BOOT, 3 TIME_SET)
|
||||||
|
Time : 2026-04-08 16:58:48.000 → 2026-04-08 17:17:58.074
|
||||||
|
Duration: 1150.1 s (19.2 min)
|
||||||
|
Battery : 12.042 V – 13.334 V
|
||||||
|
|
||||||
|
BOOT events:
|
||||||
|
1970-01-01 00:00:00.000 rst=POWERON wake=NORMAL
|
||||||
|
1970-01-01 00:00:00.000 rst=POWERON wake=NORMAL
|
||||||
|
1970-01-01 00:00:00.000 rst=SW wake=NORMAL
|
||||||
|
1970-01-01 00:00:00.000 rst=SW wake=NORMAL
|
||||||
|
|
||||||
|
TIME_SET events:
|
||||||
|
2026-04-08 16:58:48.000
|
||||||
|
2026-04-08 17:09:42.000
|
||||||
|
2026-04-08 17:15:58.000
|
||||||
BIN
logtool/16MAR2026_2223.bin
Normal file
BIN
logtool/16MAR2026_2223.bin
Normal file
Binary file not shown.
2
logtool/16MAR2026_2223.txt
Normal file
2
logtool/16MAR2026_2223.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
Reading storage-16MAR2026-1728.bin ...
|
||||||
|
Parsed 52 entries
|
||||||
BIN
logtool/16MAR2026_2225.bin
Normal file
BIN
logtool/16MAR2026_2225.bin
Normal file
Binary file not shown.
58
logtool/16MAR2026_2225.txt
Normal file
58
logtool/16MAR2026_2225.txt
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
Reading storage-16MAR2026-1728.bin ...
|
||||||
|
Parsed 52 entries
|
||||||
|
Time State Bat(V) Drive(A) Jack(A) Aux(A) Counter Stable Raw DrHeat JkHeat AxHeat
|
||||||
|
----------------------- -------------------- ------------------- --------------------------------------- ----------------------------------- ------------------------- ------- ----------------- ---------------- ------------------------------------ -------------------------------------- ------
|
||||||
|
UNK(0xd4) — — — — — — — — — —
|
||||||
|
1970-01-01 00:00:15.894 IDLE -34926674051072.000 0.00 18141941858304.00 -0.00 15947 DRIVE+AUX2 - 0.0 0.0 0.0
|
||||||
|
UNK(0xc0) — — — — — — — — — —
|
||||||
|
UNK(0x3d) — — — — — — — — — —
|
||||||
|
UNK(0x3c) — — — — — — — — — —
|
||||||
|
UNK(0x40) — — — — — — — — — —
|
||||||
|
2458965396544290816 IDLE -0.000 0.00 -0.00 0.00 -13883 SAFETY+DRIVE+AUX2 SAFETY+JACK+AUX2 3.8 0.0 0.0
|
||||||
|
UNK(0x40) — — — — — — — — — —
|
||||||
|
UNK(0x9c) — — — — — — — — — —
|
||||||
|
PARSE_ERR 0.000 0.00 0.00 0.00 0 - - 0.0 0.0 0.0
|
||||||
|
UNK(0x30) — — — — — — — — — —
|
||||||
|
72339069014654230 IDLE -0.000 0.00 -0.00 0.00 0 - - 0.0 0.0 0.0
|
||||||
|
UNK(0x20) — — — — — — — — — —
|
||||||
|
15564440319108055738 IDLE 0.000 0.00 0.00 0.00 0 - - 0.0 0.0 0.0
|
||||||
|
UNK(0x91) — — — — — — — — — —
|
||||||
|
UNK(0x20) — — — — — — — — — —
|
||||||
|
UNK(0xcd) — — — — — — — — — —
|
||||||
|
UNK(0x40) — — — — — — — — — —
|
||||||
|
72339069014687196 IDLE 3.486 0.00 0.00 0.00 16147 SAFETY+AUX2 DRIVE+AUX2 0.0 0.0 0.0
|
||||||
|
PARSE_ERR 0.000 0.00 0.00 0.00 0 - - 0.0 0.0 0.0
|
||||||
|
UNK(0xea) — — — — — — — — — —
|
||||||
|
UNK(0x11) — — — — — — — — — —
|
||||||
|
UNK(0x41) — — — — — — — — — —
|
||||||
|
UNK(0xae) — — — — — — — — — —
|
||||||
|
UNK(0x40) — — — — — — — — — —
|
||||||
|
PARSE_ERR 0.000 0.00 0.00 0.00 0 - - 0.0 0.0 0.0
|
||||||
|
UNK(0x20) — — — — — — — — — —
|
||||||
|
11096869488759107671 CALIBRATE_JACK_DELAY 0.000 55944611772684566528.00 -0.00 0.00 0 - - 0.0 67650244627660800.0 0.0
|
||||||
|
UNK(0x80) — — — — — — — — — —
|
||||||
|
281474977709058 JACK_UP_START 0.000 0.00 -0.00 -295735836036957208576.00 15488 SAFETY+DRIVE SAFETY -0.0 0.0 0.0
|
||||||
|
UNK(0xd3) — — — — — — — — — —
|
||||||
|
6151020166072509758 MOVE_START_DELAY 0.000 0.00 11751184508542699927613527293952.00 0.00 -27648 AUX2 AUX2 184761406165897707520.0 912500379009562710927105661457137664.0 96.1
|
||||||
|
UNK(0xb8) — — — — — — — — — —
|
||||||
|
1972-01-28 13:05:11.952 IDLE 0.000 68671368807317504.00 0.00 12.83 -26752 JACK+AUX2 SAFETY+DRIVE -0.0 124029976523403327445139456.0 0.0
|
||||||
|
4430992851352526 IDLE 0.000 -76302429880697741276410658934489088.00 0.00 0.00 16718 - DRIVE+AUX2 1923670617821681863848652481495040.0 7180627444374621246193664.0 0.0
|
||||||
|
UNK(0x40) — — — — — — — — — —
|
||||||
|
PARSE_ERR 0.000 0.00 0.00 0.00 0 - - 0.0 0.0 0.0
|
||||||
|
UNK(0x11) — — — — — — — — — —
|
||||||
|
UNK(0x18) — — — — — — — — — —
|
||||||
|
UNK(0x40) — — — — — — — — — —
|
||||||
|
PARSE_ERR 0.000 0.00 0.00 0.00 0 - - 0.0 0.0 0.0
|
||||||
|
UNK(0xd4) — — — — — — — — — —
|
||||||
|
UNK(0x40) — — — — — — — — — —
|
||||||
|
UNK(0x35) — — — — — — — — — —
|
||||||
|
UNK(0x60) — — — — — — — — — —
|
||||||
|
UNK(0x5b) — — — — — — — — — —
|
||||||
|
UNK(0x7f) — — — — — — — — — —
|
||||||
|
UNK(0x0f) — — — — — — — — — —
|
||||||
|
UNK(0x40) — — — — — — — — — —
|
||||||
|
UNK(0x3e) — — — — — — — — — —
|
||||||
|
UNK(0x40) — — — — — — — — — —
|
||||||
|
UNK(0xff) — — — — — — — — — —
|
||||||
|
|
||||||
|
Entries : 52 total (15 FSM, 0 BAT, 0 CRASH, 0 BOOT, 0 TIME_SET)
|
||||||
BIN
logtool/17MAR2026_0815.bin
Normal file
BIN
logtool/17MAR2026_0815.bin
Normal file
Binary file not shown.
3
logtool/17MAR2026_0815.txt
Normal file
3
logtool/17MAR2026_0815.txt
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
Reading storage-16MAR2026-1728.bin ...
|
||||||
|
Log offsets: tail=16384 head=4329920
|
||||||
|
Parsed 54831 entries
|
||||||
BIN
logtool/17MAR2026_0816.bin
Normal file
BIN
logtool/17MAR2026_0816.bin
Normal file
Binary file not shown.
54841
logtool/17MAR2026_0816.txt
Normal file
54841
logtool/17MAR2026_0816.txt
Normal file
File diff suppressed because it is too large
Load Diff
BIN
logtool/17MAR2026_0819.bin
Normal file
BIN
logtool/17MAR2026_0819.bin
Normal file
Binary file not shown.
3
logtool/17MAR2026_0819.txt
Normal file
3
logtool/17MAR2026_0819.txt
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
Reading storage-16MAR2026-1728.bin ...
|
||||||
|
Log offsets: tail=16384 head=4329920
|
||||||
|
Parsed 54831 entries
|
||||||
BIN
logtool/17MAR2026_0821.bin
Normal file
BIN
logtool/17MAR2026_0821.bin
Normal file
Binary file not shown.
3
logtool/17MAR2026_0821.txt
Normal file
3
logtool/17MAR2026_0821.txt
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
Reading storage-16MAR2026-1728.bin ...
|
||||||
|
Log offsets: tail=16384 head=4329920
|
||||||
|
Parsed 54831 entries
|
||||||
BIN
logtool/17MAR2026_0859.bin
Normal file
BIN
logtool/17MAR2026_0859.bin
Normal file
Binary file not shown.
3
logtool/17MAR2026_0859.txt
Normal file
3
logtool/17MAR2026_0859.txt
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
Reading storage-16MAR2026-1728.bin ...
|
||||||
|
Log offsets: tail=16384 head=4329920
|
||||||
|
Parsed 70748 entries
|
||||||
Binary file not shown.
Binary file not shown.
@@ -28,51 +28,48 @@ def _row(e: dict) -> list:
|
|||||||
e.get('time_str', ''),
|
e.get('time_str', ''),
|
||||||
name,
|
name,
|
||||||
f"{e.get('bat_V', 0):.3f}",
|
f"{e.get('bat_V', 0):.3f}",
|
||||||
f"{e.get('drive_A', 0):.2f}",
|
f"{e.get('current_A', 0):.2f}",
|
||||||
f"{e.get('jack_A', 0):.2f}",
|
|
||||||
f"{e.get('aux_A', 0):.2f}",
|
|
||||||
str(e.get('counter', 0)),
|
str(e.get('counter', 0)),
|
||||||
_sensor_str(e.get('sensors_stable', 0)),
|
_sensor_str(e.get('sensors_stable', 0)),
|
||||||
_sensor_str(e.get('sensors_raw', 0)),
|
_sensor_str(e.get('sensors_raw', 0)),
|
||||||
f"{e.get('drive_heat', 0):.1f}",
|
f"{e.get('heat', 0):.1f}",
|
||||||
f"{e.get('jack_heat', 0):.1f}",
|
f"0x{e.get('i2c_out', 0):04X}",
|
||||||
f"{e.get('aux_heat', 0):.1f}",
|
|
||||||
]
|
]
|
||||||
elif t == LOG_TYPE_BAT:
|
elif t == LOG_TYPE_BAT:
|
||||||
return [
|
return [
|
||||||
e.get('time_str', ''),
|
e.get('time_str', ''),
|
||||||
'BAT',
|
'BAT',
|
||||||
f"{e.get('bat_V', 0):.3f}",
|
f"{e.get('bat_V', 0):.3f}",
|
||||||
'—', '—', '—', '—', '—', '—', '—', '—', '—',
|
'—', '—', '—', '—', '—', '—',
|
||||||
]
|
]
|
||||||
elif t == LOG_TYPE_CRASH:
|
elif t == LOG_TYPE_CRASH:
|
||||||
return [
|
return [
|
||||||
e.get('time_str', ''),
|
e.get('time_str', ''),
|
||||||
f"*** CRASH: {e.get('reason_str', '?')}",
|
f"*** CRASH: {e.get('reason_str', '?')}",
|
||||||
'—', '—', '—', '—', '—', '—', '—', '—', '—', '—',
|
'—', '—', '—', '—', '—', '—', '—',
|
||||||
]
|
]
|
||||||
elif t == LOG_TYPE_BOOT:
|
elif t == LOG_TYPE_BOOT:
|
||||||
return [
|
return [
|
||||||
e.get('time_str', ''),
|
e.get('time_str', ''),
|
||||||
f"BOOT rst={e.get('reason_str', '?')} wake={e.get('wake_str', '?')}",
|
f"BOOT rst={e.get('reason_str', '?')} wake={e.get('wake_str', '?')}",
|
||||||
'—', '—', '—', '—', '—', '—', '—', '—', '—', '—',
|
'—', '—', '—', '—', '—', '—', '—',
|
||||||
]
|
]
|
||||||
elif t == LOG_TYPE_TIME_SET:
|
elif t == LOG_TYPE_TIME_SET:
|
||||||
return [
|
return [
|
||||||
e.get('time_str', ''),
|
e.get('time_str', ''),
|
||||||
'TIME_SET',
|
'TIME_SET',
|
||||||
'—', '—', '—', '—', '—', '—', '—', '—', '—', '—',
|
'—', '—', '—', '—', '—', '—', '—',
|
||||||
]
|
]
|
||||||
else:
|
else:
|
||||||
return [
|
return [
|
||||||
e.get('time_str', ''),
|
e.get('time_str', ''),
|
||||||
name,
|
name,
|
||||||
'—', '—', '—', '—', '—', '—', '—', '—', '—', '—',
|
'—', '—', '—', '—', '—', '—', '—',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
_HEADERS = ['Time', 'State', 'Bat(V)', 'Drive(A)', 'Jack(A)', 'Aux(A)',
|
_HEADERS = ['Time', 'State', 'Bat(V)', 'Cur(A)',
|
||||||
'Counter', 'Stable', 'Raw', 'DrHeat', 'JkHeat', 'AxHeat']
|
'Counter', 'Stable', 'Raw', 'Heat', 'I2COut']
|
||||||
|
|
||||||
|
|
||||||
def print_table(entries: list, type_filter: str = None):
|
def print_table(entries: list, type_filter: str = None):
|
||||||
|
|||||||
29
logtool/debug-notes.md
Normal file
29
logtool/debug-notes.md
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
# Logtool Debug Notes — 16 MAR 2026
|
||||||
|
|
||||||
|
## Problem
|
||||||
|
`storage-16MAR2026-1728.bin` not parsing correctly.
|
||||||
|
|
||||||
|
## Root Cause (SOLVED)
|
||||||
|
Two issues in `parser.py`:
|
||||||
|
|
||||||
|
### 1. Unrecognized file format
|
||||||
|
The file format is `[4B tail BE][4B head BE][raw log data]` — no JSON header.
|
||||||
|
- `head - tail == file_size - 8` (the raw log data is exactly the flash region from tail to head)
|
||||||
|
- The old HTTP format was: `[4B json_len BE][json][4B tail BE][4B head BE][raw log data]`
|
||||||
|
- The parser's autodetect only recognized the old HTTP format (and required json_len < 8192)
|
||||||
|
- **Fix:** Added detection for bare tail+head format in `autodetect_and_parse()`
|
||||||
|
|
||||||
|
### 2. Type-first vs type-last detection failure
|
||||||
|
The log entries use **type-first** format: `[len][type][payload]`
|
||||||
|
But `_try_detect_type_first()` returned False because:
|
||||||
|
- First entry had type=0x00 at both positions (ambiguous)
|
||||||
|
- Timestamp was near-zero (RTC not yet set), so timestamp sanity check failed
|
||||||
|
- Function gave up after only 1 entry (`break` at end of loop)
|
||||||
|
- **Fix:** Loop over multiple entries (up to 200), added voltage sanity check (0.5-60V)
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
- 54,831 entries parsed successfully
|
||||||
|
- FSM states: IDLE (51169), JACK_UP (1078), DRIVE (994), etc.
|
||||||
|
- Voltages: 3.3V–13.3V (reasonable for 3S LiPo system)
|
||||||
|
- Timestamps: Jan 13 – Feb 6, 2026 (after RTC set)
|
||||||
|
- Old HTTP-format .bin files still parse correctly
|
||||||
@@ -73,15 +73,12 @@ def show_plots(entries: list, title: str = "SC-F001 Log"):
|
|||||||
add_crash_lines(ax0)
|
add_crash_lines(ax0)
|
||||||
ax0.grid(True, alpha=0.3)
|
ax0.grid(True, alpha=0.3)
|
||||||
|
|
||||||
# 2. Currents
|
# 2. Current (single channel — V5 has one shared sensor)
|
||||||
ax1 = axes[1]
|
ax1 = axes[1]
|
||||||
ax1.set_ylabel('Current (A)')
|
ax1.set_ylabel('Current (A)')
|
||||||
if fsm:
|
if fsm:
|
||||||
ts = to_dt(_ts_arr(fsm))
|
ts = to_dt(_ts_arr(fsm))
|
||||||
ax1.plot(ts, _val_arr(fsm, 'drive_A'), label='Drive', linewidth=1)
|
ax1.plot(ts, _val_arr(fsm, 'current_A'), color='orange', linewidth=1)
|
||||||
ax1.plot(ts, _val_arr(fsm, 'jack_A'), label='Jack', linewidth=1)
|
|
||||||
ax1.plot(ts, _val_arr(fsm, 'aux_A'), label='Aux', linewidth=1)
|
|
||||||
ax1.legend(fontsize=8, loc='upper right')
|
|
||||||
add_crash_lines(ax1)
|
add_crash_lines(ax1)
|
||||||
ax1.grid(True, alpha=0.3)
|
ax1.grid(True, alpha=0.3)
|
||||||
|
|
||||||
@@ -102,19 +99,16 @@ def show_plots(entries: list, title: str = "SC-F001 Log"):
|
|||||||
add_crash_lines(ax2)
|
add_crash_lines(ax2)
|
||||||
ax2.grid(True, alpha=0.3)
|
ax2.grid(True, alpha=0.3)
|
||||||
|
|
||||||
# 4. Thermal accumulators
|
# 4. Thermal accumulator (single — max of bridge heats)
|
||||||
ax3 = axes[3]
|
ax3 = axes[3]
|
||||||
ax3.set_ylabel('Heat (I²t)')
|
ax3.set_ylabel('Heat (I²t)')
|
||||||
if fsm:
|
if fsm:
|
||||||
ts = to_dt(_ts_arr(fsm))
|
ts = to_dt(_ts_arr(fsm))
|
||||||
ax3.plot(ts, _val_arr(fsm, 'drive_heat'), label='Drive', linewidth=1)
|
ax3.plot(ts, _val_arr(fsm, 'heat'), color='red', linewidth=1)
|
||||||
ax3.plot(ts, _val_arr(fsm, 'jack_heat'), label='Jack', linewidth=1)
|
|
||||||
ax3.plot(ts, _val_arr(fsm, 'aux_heat'), label='Aux', linewidth=1)
|
|
||||||
ax3.legend(fontsize=8, loc='upper right')
|
|
||||||
add_crash_lines(ax3)
|
add_crash_lines(ax3)
|
||||||
ax3.grid(True, alpha=0.3)
|
ax3.grid(True, alpha=0.3)
|
||||||
|
|
||||||
ax3.xaxis.set_major_formatter(mdates.DateFormatter('%H:%M:%S'))
|
ax3.xaxis.set_major_formatter(mdates.AutoDateFormatter(ax3.xaxis.get_major_locator()))
|
||||||
fig.autofmt_xdate()
|
fig.autofmt_xdate()
|
||||||
plt.tight_layout()
|
plt.tight_layout()
|
||||||
plt.show()
|
plt.show()
|
||||||
@@ -140,18 +134,12 @@ def live_plot(url: str, interval_s: float = 2.0):
|
|||||||
ax.grid(True, alpha=0.3)
|
ax.grid(True, alpha=0.3)
|
||||||
|
|
||||||
lines = {
|
lines = {
|
||||||
'bat': axes[0].plot([], [], color='green', linewidth=1)[0],
|
'bat': axes[0].plot([], [], color='green', linewidth=1)[0],
|
||||||
'drive': axes[1].plot([], [], label='Drive', linewidth=1)[0],
|
'current': axes[1].plot([], [], color='orange', linewidth=1)[0],
|
||||||
'jack': axes[1].plot([], [], label='Jack', linewidth=1)[0],
|
'state': axes[2].step([], [], where='post', linewidth=1, color='navy')[0],
|
||||||
'aux': axes[1].plot([], [], label='Aux', linewidth=1)[0],
|
'heat': axes[3].plot([], [], color='red', linewidth=1)[0],
|
||||||
'state': axes[2].step([], [], where='post', linewidth=1, color='navy')[0],
|
|
||||||
'drheat': axes[3].plot([], [], label='Drive', linewidth=1)[0],
|
|
||||||
'jkheat': axes[3].plot([], [], label='Jack', linewidth=1)[0],
|
|
||||||
'axheat': axes[3].plot([], [], label='Aux', linewidth=1)[0],
|
|
||||||
}
|
}
|
||||||
axes[1].legend(fontsize=8, loc='upper right')
|
axes[3].xaxis.set_major_formatter(mdates.AutoDateFormatter(axes[3].xaxis.get_major_locator()))
|
||||||
axes[3].legend(fontsize=8, loc='upper right')
|
|
||||||
axes[3].xaxis.set_major_formatter(mdates.DateFormatter('%H:%M:%S'))
|
|
||||||
|
|
||||||
state = {'current_tail': 0, 'first': True}
|
state = {'current_tail': 0, 'first': True}
|
||||||
|
|
||||||
@@ -181,13 +169,9 @@ def live_plot(url: str, interval_s: float = 2.0):
|
|||||||
|
|
||||||
if fsm:
|
if fsm:
|
||||||
ts = to_dt([e['ts_ms'] for e in fsm])
|
ts = to_dt([e['ts_ms'] for e in fsm])
|
||||||
lines['drive'].set_data(ts, [e.get('drive_A', 0) for e in fsm])
|
lines['current'].set_data(ts, [e.get('current_A', 0) for e in fsm])
|
||||||
lines['jack'].set_data( ts, [e.get('jack_A', 0) for e in fsm])
|
|
||||||
lines['aux'].set_data( ts, [e.get('aux_A', 0) for e in fsm])
|
|
||||||
lines['state'].set_data(ts, [e.get('entry_type', 0) for e in fsm])
|
lines['state'].set_data(ts, [e.get('entry_type', 0) for e in fsm])
|
||||||
lines['drheat'].set_data(ts, [e.get('drive_heat', 0) for e in fsm])
|
lines['heat'].set_data(ts, [e.get('heat', 0) for e in fsm])
|
||||||
lines['jkheat'].set_data(ts, [e.get('jack_heat', 0) for e in fsm])
|
|
||||||
lines['axheat'].set_data(ts, [e.get('aux_heat', 0) for e in fsm])
|
|
||||||
|
|
||||||
all_bat = sorted(
|
all_bat = sorted(
|
||||||
[e for e in all_entries if 'bat_V' in e],
|
[e for e in all_entries if 'bat_V' in e],
|
||||||
|
|||||||
@@ -58,6 +58,11 @@ def _normalize_source(raw: str) -> tuple:
|
|||||||
if raw.endswith('.bin'):
|
if raw.endswith('.bin'):
|
||||||
return False, raw
|
return False, raw
|
||||||
if raw.startswith('http://') or raw.startswith('https://'):
|
if raw.startswith('http://') or raw.startswith('https://'):
|
||||||
|
# Append /log if URL has no path (or just /)
|
||||||
|
from urllib.parse import urlparse
|
||||||
|
p = urlparse(raw)
|
||||||
|
if p.path in ('', '/'):
|
||||||
|
raw = raw.rstrip('/') + '/log'
|
||||||
return True, raw
|
return True, raw
|
||||||
return True, f'http://{raw}/log'
|
return True, f'http://{raw}/log'
|
||||||
|
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ _FALLBACK_FSM_STATES = {
|
|||||||
10: "CALIBRATE_JACK_MOVE",
|
10: "CALIBRATE_JACK_MOVE",
|
||||||
11: "CALIBRATE_DRIVE_DELAY",
|
11: "CALIBRATE_DRIVE_DELAY",
|
||||||
12: "CALIBRATE_DRIVE_MOVE",
|
12: "CALIBRATE_DRIVE_MOVE",
|
||||||
|
13: "DRIVE_FLUFF_START",
|
||||||
}
|
}
|
||||||
|
|
||||||
ESP_RESET_REASONS = {
|
ESP_RESET_REASONS = {
|
||||||
@@ -108,23 +109,32 @@ def _ts_to_str(ts_ms: int) -> str:
|
|||||||
|
|
||||||
|
|
||||||
def _unpack_fsm(payload: bytes, fsm_states: dict) -> dict:
|
def _unpack_fsm(payload: bytes, fsm_states: dict) -> dict:
|
||||||
if len(payload) < 39:
|
"""Single-current FSM payload (25 bytes):
|
||||||
raise ValueError(f"FSM payload too short: {len(payload)} < 39")
|
ts(8) bat(4) current(4) counter(2) sensors(1) heat(4) i2c_out(2).
|
||||||
ts_ms, bat_V, drive_A, jack_A, aux_A, counter, sensors, \
|
V5 hardware has one shared current sensor; V4 had three but only one
|
||||||
drive_heat, jack_heat, aux_heat = struct.unpack_from('<QffffhBfff', payload, 0)
|
bridge is active at a time, so the single channel suffices.
|
||||||
|
i2c_out is the last-written 16-bit TCA9555 output state
|
||||||
|
(high byte = OUTPUT0 / LEDs, low byte = OUTPUT1 / relays)."""
|
||||||
|
if len(payload) < 19:
|
||||||
|
raise ValueError(f"FSM payload too short: {len(payload)} < 19")
|
||||||
|
ts_ms, bat_V, current_A, counter, sensors = \
|
||||||
|
struct.unpack_from('<QffhB', payload, 0)
|
||||||
|
heat = 0.0
|
||||||
|
i2c_out = 0
|
||||||
|
if len(payload) >= 23:
|
||||||
|
heat, = struct.unpack_from('<f', payload, 19)
|
||||||
|
if len(payload) >= 25:
|
||||||
|
i2c_out, = struct.unpack_from('<H', payload, 23)
|
||||||
return {
|
return {
|
||||||
'ts_ms': ts_ms,
|
'ts_ms': ts_ms,
|
||||||
'time_str': _ts_to_str(ts_ms),
|
'time_str': _ts_to_str(ts_ms),
|
||||||
'bat_V': round(bat_V, 3),
|
'bat_V': round(bat_V, 3),
|
||||||
'drive_A': round(drive_A, 3),
|
'current_A': round(current_A, 3),
|
||||||
'jack_A': round(jack_A, 3),
|
|
||||||
'aux_A': round(aux_A, 3),
|
|
||||||
'counter': counter,
|
'counter': counter,
|
||||||
'sensors_stable': sensors & 0x0F,
|
'sensors_stable': sensors & 0x0F,
|
||||||
'sensors_raw': (sensors >> 4) & 0x0F,
|
'sensors_raw': (sensors >> 4) & 0x0F,
|
||||||
'drive_heat': round(drive_heat, 2),
|
'heat': round(heat, 2),
|
||||||
'jack_heat': round(jack_heat, 2),
|
'i2c_out': i2c_out,
|
||||||
'aux_heat': round(aux_heat, 2),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -187,10 +197,19 @@ def _unpack_time_set(payload: bytes) -> dict:
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def parse_entries(data: bytes, fsm_states: dict = None) -> list:
|
def _is_valid_entry_type(t: int) -> bool:
|
||||||
|
return (0 <= t <= 13) or t in (LOG_TYPE_BAT, LOG_TYPE_CRASH, LOG_TYPE_BOOT, LOG_TYPE_TIME_SET)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_entries(data: bytes, fsm_states: dict = None, type_first: bool = False) -> list:
|
||||||
"""
|
"""
|
||||||
Parse a stream of raw binary log entries.
|
Parse a stream of raw binary log entries.
|
||||||
Returns list of dicts, each with 'entry_type' and type-specific fields.
|
Returns list of dicts, each with 'entry_type' and type-specific fields.
|
||||||
|
|
||||||
|
Entry format depends on type_first:
|
||||||
|
False (current FW): [len u8][payload (len-1 bytes)][type u8]
|
||||||
|
True (old FW): [len u8][type u8][payload (len-1 bytes)]
|
||||||
|
In both cases total bytes consumed per entry = len + 1.
|
||||||
"""
|
"""
|
||||||
if fsm_states is None:
|
if fsm_states is None:
|
||||||
fsm_states = _FALLBACK_FSM_STATES
|
fsm_states = _FALLBACK_FSM_STATES
|
||||||
@@ -202,28 +221,73 @@ def parse_entries(data: bytes, fsm_states: dict = None) -> list:
|
|||||||
while i < n:
|
while i < n:
|
||||||
b = data[i]
|
b = data[i]
|
||||||
|
|
||||||
# Erased flash or sector padding → done or skip sector
|
# Erased flash or sector padding → skip to next sector
|
||||||
if b == 0xFF:
|
if b == 0xFF or b == 0x00:
|
||||||
break
|
|
||||||
if b == 0x00:
|
|
||||||
# Sector padding: skip to next 4096-byte boundary
|
|
||||||
sector_size = 4096
|
sector_size = 4096
|
||||||
next_sector = ((i // sector_size) + 1) * sector_size
|
next_sector = ((i // sector_size) + 1) * sector_size
|
||||||
i = next_sector
|
i = next_sector
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
# In type_first (old FW) format, sectors have a small zero-pad header
|
||||||
|
# that isn't full-sector padding. Only skip individual zero bytes.
|
||||||
|
if type_first and b == 0x00:
|
||||||
|
i += 1
|
||||||
|
continue
|
||||||
|
|
||||||
entry_len = b # stored len = payload_size + 1
|
entry_len = b # stored len = payload_size + 1
|
||||||
payload_size = entry_len - 1
|
payload_size = entry_len - 1
|
||||||
type_offset = i + 1 + payload_size # = i + entry_len
|
end_offset = i + entry_len # last byte of this entry's content
|
||||||
|
|
||||||
if type_offset >= n:
|
if end_offset >= n:
|
||||||
break # truncated
|
break # truncated
|
||||||
|
|
||||||
payload = data[i + 1 : i + 1 + payload_size]
|
# Detect entry format: with type byte (total = len+1) or without (total = len).
|
||||||
entry_type = data[type_offset]
|
# Check if data[end_offset] is the start of the next entry (no type byte)
|
||||||
|
# vs a type byte followed by the next entry at end_offset+1.
|
||||||
|
has_type_byte = True
|
||||||
|
if end_offset + 1 < n:
|
||||||
|
next_at_len = data[end_offset] # byte right after payload
|
||||||
|
next_at_len1 = data[end_offset + 1] # byte one further
|
||||||
|
# If the byte at end_offset looks like a valid next-entry len byte
|
||||||
|
# (matches current entry len or is another plausible len), and the
|
||||||
|
# byte at end_offset+1 does NOT, then there's no type byte.
|
||||||
|
next_ok = next_at_len not in (0x00, 0xFF) and next_at_len < 250
|
||||||
|
next1_ok = next_at_len1 not in (0x00, 0xFF) and next_at_len1 < 250
|
||||||
|
if next_ok and not _is_valid_entry_type(next_at_len):
|
||||||
|
# end_offset byte isn't a valid type, treat as next entry (no type)
|
||||||
|
has_type_byte = False
|
||||||
|
elif next_ok and next_at_len == entry_len and not next1_ok:
|
||||||
|
# Same len repeating at stride=len (not len+1) → no type byte
|
||||||
|
has_type_byte = False
|
||||||
|
|
||||||
|
if not has_type_byte:
|
||||||
|
# No type byte: [len][payload], total = len bytes, FSM type implied
|
||||||
|
payload = data[i + 1 : i + entry_len]
|
||||||
|
entry_type = 0 # default to IDLE / FSM
|
||||||
|
i = end_offset # advance by len (not len+1)
|
||||||
|
elif type_first:
|
||||||
|
entry_type = data[i + 1]
|
||||||
|
payload = data[i + 2 : i + 1 + entry_len]
|
||||||
|
# Fallback: if type-first gives invalid type, try type-last
|
||||||
|
if not _is_valid_entry_type(entry_type):
|
||||||
|
alt_type = data[end_offset]
|
||||||
|
if _is_valid_entry_type(alt_type):
|
||||||
|
entry_type = alt_type
|
||||||
|
payload = data[i + 1 : i + 1 + payload_size]
|
||||||
|
i = end_offset + 1
|
||||||
|
else:
|
||||||
|
payload = data[i + 1 : i + 1 + payload_size]
|
||||||
|
entry_type = data[end_offset]
|
||||||
|
# Fallback: if type-last gives invalid type, try type-first
|
||||||
|
if not _is_valid_entry_type(entry_type):
|
||||||
|
alt_type = data[i + 1]
|
||||||
|
if _is_valid_entry_type(alt_type):
|
||||||
|
entry_type = alt_type
|
||||||
|
payload = data[i + 2 : i + 1 + entry_len]
|
||||||
|
i = end_offset + 1
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if 0 <= entry_type <= 12:
|
if 0 <= entry_type <= 13:
|
||||||
e = _unpack_fsm(payload, fsm_states)
|
e = _unpack_fsm(payload, fsm_states)
|
||||||
e['entry_type'] = entry_type
|
e['entry_type'] = entry_type
|
||||||
e['state_name'] = fsm_states.get(entry_type, f"STATE_{entry_type}")
|
e['state_name'] = fsm_states.get(entry_type, f"STATE_{entry_type}")
|
||||||
@@ -258,7 +322,7 @@ def parse_entries(data: bytes, fsm_states: dict = None) -> list:
|
|||||||
}
|
}
|
||||||
|
|
||||||
entries.append(e)
|
entries.append(e)
|
||||||
i = type_offset + 1 # advance past type byte
|
# i was already advanced in the format-detection block above
|
||||||
|
|
||||||
return entries
|
return entries
|
||||||
|
|
||||||
@@ -271,9 +335,13 @@ def parse_response(blob: bytes, fsm_states: dict = None) -> tuple:
|
|||||||
if len(blob) < 8:
|
if len(blob) < 8:
|
||||||
raise ValueError("Response too short")
|
raise ValueError("Response too short")
|
||||||
|
|
||||||
|
# Detect HTML response (device served webpage instead of binary log)
|
||||||
|
if blob[:5] in (b'<!DOC', b'<!doc', b'<html', b'<HTML'):
|
||||||
|
raise ValueError("Got HTML instead of binary log — check URL resolves to /log endpoint")
|
||||||
|
|
||||||
json_len = struct.unpack_from('>I', blob, 0)[0]
|
json_len = struct.unpack_from('>I', blob, 0)[0]
|
||||||
if json_len > 65536 or len(blob) < 4 + json_len + 8:
|
if json_len > 65536 or len(blob) < 4 + json_len + 8:
|
||||||
raise ValueError(f"Invalid json_len {json_len}")
|
raise ValueError(f"Invalid json_len {json_len} (expected binary log format, got {blob[:20]})")
|
||||||
|
|
||||||
json_bytes = blob[4 : 4 + json_len]
|
json_bytes = blob[4 : 4 + json_len]
|
||||||
meta = json.loads(json_bytes.decode('utf-8'))
|
meta = json.loads(json_bytes.decode('utf-8'))
|
||||||
@@ -285,18 +353,118 @@ def parse_response(blob: bytes, fsm_states: dict = None) -> tuple:
|
|||||||
return meta, tail, head, entries
|
return meta, tail, head, entries
|
||||||
|
|
||||||
|
|
||||||
|
def _detect_old_partition_dump(blob: bytes) -> int:
|
||||||
|
"""
|
||||||
|
Detect old firmware partition dump format.
|
||||||
|
Old format: 8-byte file header + 0x4000 bytes params + log entries
|
||||||
|
with type byte at the start of each entry's content region.
|
||||||
|
Returns the log data start offset, or 0 if not detected.
|
||||||
|
"""
|
||||||
|
if len(blob) < 0x4100:
|
||||||
|
return 0
|
||||||
|
# Check if offset 0x4000 looks like a log sector: leading zero-pad
|
||||||
|
# followed by a valid entry with a valid type byte at +1 (type-first format)
|
||||||
|
base = 0x4000
|
||||||
|
# Find first non-zero byte in the sector
|
||||||
|
first_nz = 0
|
||||||
|
while first_nz < 4096 and blob[base + first_nz] == 0x00:
|
||||||
|
first_nz += 1
|
||||||
|
if first_nz >= 4096:
|
||||||
|
return 0
|
||||||
|
entry_len = blob[base + first_nz]
|
||||||
|
if entry_len < 2 or base + first_nz + 1 + entry_len > len(blob):
|
||||||
|
return 0
|
||||||
|
# In old format, the type byte is the first byte after the len byte
|
||||||
|
entry_type = blob[base + first_nz + 1]
|
||||||
|
if _is_valid_entry_type(entry_type):
|
||||||
|
return base
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def _try_detect_type_first(data: bytes) -> bool:
|
||||||
|
"""
|
||||||
|
Given raw log entry data, try to determine if entries use
|
||||||
|
type-first format (old FW) vs type-last format (current FW).
|
||||||
|
Samples multiple entries and checks which placement yields
|
||||||
|
valid entry types, plausible timestamps, or reasonable voltages.
|
||||||
|
"""
|
||||||
|
i = 0
|
||||||
|
n = len(data)
|
||||||
|
attempts = 0
|
||||||
|
max_attempts = 200
|
||||||
|
while i < n and attempts < max_attempts:
|
||||||
|
b = data[i]
|
||||||
|
if b == 0xFF:
|
||||||
|
break
|
||||||
|
if b == 0x00:
|
||||||
|
i = ((i // 4096) + 1) * 4096
|
||||||
|
continue
|
||||||
|
entry_len = b
|
||||||
|
end_offset = i + entry_len
|
||||||
|
if end_offset >= n:
|
||||||
|
break
|
||||||
|
# type-last (current): type is at end_offset
|
||||||
|
type_last = data[end_offset]
|
||||||
|
# type-first (old): type is at i+1
|
||||||
|
type_first_val = data[i + 1]
|
||||||
|
last_valid = _is_valid_entry_type(type_last)
|
||||||
|
first_valid = _is_valid_entry_type(type_first_val)
|
||||||
|
if first_valid and not last_valid:
|
||||||
|
return True
|
||||||
|
if last_valid and not first_valid:
|
||||||
|
return False
|
||||||
|
# Both valid or neither — try parsing the payload to disambiguate
|
||||||
|
if first_valid and last_valid:
|
||||||
|
payload_first = data[i + 2 : i + 1 + entry_len]
|
||||||
|
payload_last = data[i + 1 : i + 1 + entry_len - 1]
|
||||||
|
for payload, is_first in [(payload_first, True), (payload_last, False)]:
|
||||||
|
if len(payload) >= 12:
|
||||||
|
ts = struct.unpack_from('<Q', payload, 0)[0]
|
||||||
|
# Plausible if timestamp is 2020-2030 in ms
|
||||||
|
if 1577836800000 < ts < 1893456000000:
|
||||||
|
return is_first
|
||||||
|
# Also check if the float at offset 8 is a reasonable voltage (0-60V)
|
||||||
|
v = struct.unpack_from('<f', payload, 8)[0]
|
||||||
|
if 0.5 < v < 60.0:
|
||||||
|
return is_first
|
||||||
|
# Advance to next entry and keep trying
|
||||||
|
i = end_offset + 1
|
||||||
|
attempts += 1
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def autodetect_and_parse(blob: bytes, fsm_states: dict = None) -> tuple:
|
def autodetect_and_parse(blob: bytes, fsm_states: dict = None) -> tuple:
|
||||||
"""
|
"""
|
||||||
Auto-detect whether blob is HTTP response format or raw flash binary.
|
Auto-detect whether blob is HTTP response format, old partition dump,
|
||||||
|
or raw flash binary.
|
||||||
Returns (json_meta_or_None, tail_or_None, head_or_None, entries).
|
Returns (json_meta_or_None, tail_or_None, head_or_None, entries).
|
||||||
"""
|
"""
|
||||||
# HTTP format: first 4 bytes = BE uint32 json_len, byte 4 should be '{'
|
# HTTP format: first 4 bytes = BE uint32 json_len, byte 4 should be '{'
|
||||||
if len(blob) >= 5:
|
if len(blob) >= 5:
|
||||||
candidate_len = struct.unpack_from('>I', blob, 0)[0]
|
candidate_len = struct.unpack_from('>I', blob, 0)[0]
|
||||||
if candidate_len < 8192 and blob[4:5] == b'{':
|
if candidate_len < len(blob) and blob[4:5] == b'{':
|
||||||
meta, tail, head, entries = parse_response(blob, fsm_states)
|
meta, tail, head, entries = parse_response(blob, fsm_states)
|
||||||
return meta, tail, head, entries
|
return meta, tail, head, entries
|
||||||
|
|
||||||
# Raw binary
|
# Bare tail+head format: [4B tail BE][4B head BE][raw log data]
|
||||||
entries = parse_entries(blob, fsm_states)
|
# Detect by checking if head - tail == len(blob) - 8
|
||||||
|
if len(blob) >= 16:
|
||||||
|
tail_val, head_val = struct.unpack_from('>II', blob, 0)
|
||||||
|
if head_val > tail_val and (head_val - tail_val) == len(blob) - 8:
|
||||||
|
log_data = blob[8:]
|
||||||
|
type_first = _try_detect_type_first(log_data)
|
||||||
|
entries = parse_entries(log_data, fsm_states, type_first=type_first)
|
||||||
|
return None, tail_val, head_val, entries
|
||||||
|
|
||||||
|
# Old partition dump: 8-byte header + 0x4000 params + log entries (type-first)
|
||||||
|
log_offset = _detect_old_partition_dump(blob)
|
||||||
|
if log_offset > 0:
|
||||||
|
log_data = blob[log_offset:]
|
||||||
|
type_first = _try_detect_type_first(log_data)
|
||||||
|
entries = parse_entries(log_data, fsm_states, type_first=type_first)
|
||||||
|
return None, None, None, entries
|
||||||
|
|
||||||
|
# Raw binary — auto-detect type placement
|
||||||
|
type_first = _try_detect_type_first(blob)
|
||||||
|
entries = parse_entries(blob, fsm_states, type_first=type_first)
|
||||||
return None, None, None, entries
|
return None, None, None, entries
|
||||||
|
|||||||
BIN
logtool/storage-16MAR2026-1728.bin
Normal file
BIN
logtool/storage-16MAR2026-1728.bin
Normal file
Binary file not shown.
16
main/.cproject
Normal file
16
main/.cproject
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<?fileVersion 4.0.0?><cproject storage_type_id="org.eclipse.cdt.core.XmlProjectDescriptionStorage">
|
||||||
|
<storageModule moduleId="org.eclipse.cdt.core.settings">
|
||||||
|
<cconfiguration id="org.eclipse.cdt.core.default.config.2078965387">
|
||||||
|
<storageModule buildSystemId="org.eclipse.cdt.core.defaultConfigDataProvider" id="org.eclipse.cdt.core.default.config.2078965387" moduleId="org.eclipse.cdt.core.settings" name="Configuration">
|
||||||
|
<externalSettings/>
|
||||||
|
<extensions/>
|
||||||
|
</storageModule>
|
||||||
|
<storageModule moduleId="org.eclipse.cdt.core.externalSettings"/>
|
||||||
|
</cconfiguration>
|
||||||
|
</storageModule>
|
||||||
|
<storageModule moduleId="org.eclipse.cdt.core.pathentry">
|
||||||
|
<pathentry excluding="**/CMakeFiles/**" kind="out" path="build"/>
|
||||||
|
</storageModule>
|
||||||
|
<storageModule moduleId="org.eclipse.cdt.core.LanguageSettingsProviders"/>
|
||||||
|
</cproject>
|
||||||
1
main/.gitignore
vendored
Normal file
1
main/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
/build/
|
||||||
20
main/.project
Normal file
20
main/.project
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<projectDescription>
|
||||||
|
<name>main</name>
|
||||||
|
<comment></comment>
|
||||||
|
<projects>
|
||||||
|
</projects>
|
||||||
|
<buildSpec>
|
||||||
|
<buildCommand>
|
||||||
|
<name>org.eclipse.cdt.core.cBuilder</name>
|
||||||
|
<triggers>clean,full,incremental,</triggers>
|
||||||
|
<arguments>
|
||||||
|
</arguments>
|
||||||
|
</buildCommand>
|
||||||
|
</buildSpec>
|
||||||
|
<natures>
|
||||||
|
<nature>org.eclipse.cdt.core.cnature</nature>
|
||||||
|
<nature>org.eclipse.cdt.core.ccnature</nature>
|
||||||
|
<nature>org.eclipse.cdt.cmake.core.cmakeNature</nature>
|
||||||
|
</natures>
|
||||||
|
</projectDescription>
|
||||||
@@ -1,10 +1,11 @@
|
|||||||
# See the build system documentation in IDF programming guide
|
# See the build system documentation in IDF programming guide
|
||||||
# for more information about component CMakeLists.txt files.
|
# for more information about component CMakeLists.txt files.
|
||||||
|
|
||||||
|
# Seed version.h at configure time so it exists for the first build.
|
||||||
include(${CMAKE_CURRENT_LIST_DIR}/version.cmake)
|
include(${CMAKE_CURRENT_LIST_DIR}/version.cmake)
|
||||||
|
|
||||||
idf_component_register(
|
idf_component_register(
|
||||||
SRCS main.c log_test.c i2c.c rtc.c storage.c uart_comms.c control_fsm.c power_mgmt.c rf_433.c rtc.c sensors.c solar.c webserver.c simple_dns_server.c comms.c bt_hid.c # list the source files of this component
|
SRCS main.c log_test.c partition_test.c i2c.c rtc.c storage.c uart_comms.c control_fsm.c power_mgmt.c bringup.c rf_433.c rtc.c sensors.c solar.c webserver.c simple_dns_server.c comms.c bt_hid.c # list the source files of this component
|
||||||
INCLUDE_DIRS "." "${CMAKE_BINARY_DIR}"
|
INCLUDE_DIRS "." "${CMAKE_BINARY_DIR}"
|
||||||
PRIV_INCLUDE_DIRS # optional, add here private include directories
|
PRIV_INCLUDE_DIRS # optional, add here private include directories
|
||||||
|
|
||||||
@@ -27,4 +28,18 @@ if(NOT CMAKE_BUILD_EARLY_EXPANSION)
|
|||||||
|
|
||||||
add_custom_target(generate_webpage_h DEPENDS ${COMPONENT_DIR}/webpage.h)
|
add_custom_target(generate_webpage_h DEPENDS ${COMPONENT_DIR}/webpage.h)
|
||||||
add_dependencies(${COMPONENT_LIB} generate_webpage_h)
|
add_dependencies(${COMPONENT_LIB} generate_webpage_h)
|
||||||
|
|
||||||
|
# Regenerate version.h on every build so git SHA / dirty flag stay
|
||||||
|
# current as commits land between builds. configure_file inside
|
||||||
|
# version.cmake is a no-op when the content matches, so rebuilds only
|
||||||
|
# cascade when something actually changed.
|
||||||
|
add_custom_target(update_version ALL
|
||||||
|
COMMAND ${CMAKE_COMMAND}
|
||||||
|
-DSRC_DIR=${COMPONENT_DIR}
|
||||||
|
-DBIN_DIR=${CMAKE_BINARY_DIR}
|
||||||
|
-P ${COMPONENT_DIR}/version.cmake
|
||||||
|
BYPRODUCTS ${CMAKE_BINARY_DIR}/version.h
|
||||||
|
COMMENT "Refreshing version.h from git"
|
||||||
|
)
|
||||||
|
add_dependencies(${COMPONENT_LIB} update_version)
|
||||||
endif()
|
endif()
|
||||||
18
main/board_config.h
Normal file
18
main/board_config.h
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
/*
|
||||||
|
* board_config.h — hardware revision selection
|
||||||
|
*
|
||||||
|
* V5 is the active board. V4 builds are kept working as an afterthought;
|
||||||
|
* comment BOARD_V5 and uncomment BOARD_V4 to build V4 firmware.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef MAIN_BOARD_CONFIG_H_
|
||||||
|
#define MAIN_BOARD_CONFIG_H_
|
||||||
|
|
||||||
|
#define BOARD_V5
|
||||||
|
// #define BOARD_V4
|
||||||
|
|
||||||
|
#if defined(BOARD_V5) == defined(BOARD_V4)
|
||||||
|
#error "Define exactly one of BOARD_V5 or BOARD_V4 in board_config.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* MAIN_BOARD_CONFIG_H_ */
|
||||||
864
main/bringup.c
Normal file
864
main/bringup.c
Normal file
@@ -0,0 +1,864 @@
|
|||||||
|
/*
|
||||||
|
* bringup.c — manufacturing / bench bring-up procedure over UART.
|
||||||
|
*
|
||||||
|
* Line protocol specified in docs/SC-F001/BRINGUP.md §3.
|
||||||
|
* All responses are written with printf() so they share the UART stream
|
||||||
|
* with uart_comms; keep every line short and grep-friendly.
|
||||||
|
*
|
||||||
|
* Distinct from the per-module POST routines (adc_post, i2c_post, ...) that
|
||||||
|
* run on every boot — those are the actual power-on self-tests.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "bringup.h"
|
||||||
|
#include "board_config.h"
|
||||||
|
#include "control_fsm.h"
|
||||||
|
#include "i2c.h"
|
||||||
|
#include "power_mgmt.h"
|
||||||
|
#include "rf_433.h"
|
||||||
|
#include "sensors.h"
|
||||||
|
#include "solar.h"
|
||||||
|
#include "storage.h"
|
||||||
|
#include "version.h"
|
||||||
|
#include "webserver.h"
|
||||||
|
|
||||||
|
#include "esp_err.h"
|
||||||
|
#include "esp_log.h"
|
||||||
|
#include "esp_partition.h"
|
||||||
|
#include "esp_system.h"
|
||||||
|
#include "esp_task_wdt.h"
|
||||||
|
#include "esp_timer.h"
|
||||||
|
#include "esp_wifi.h"
|
||||||
|
#include "driver/gpio.h"
|
||||||
|
#include "driver/uart.h"
|
||||||
|
#include "freertos/FreeRTOS.h"
|
||||||
|
#include "freertos/task.h"
|
||||||
|
|
||||||
|
#include <math.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <strings.h>
|
||||||
|
|
||||||
|
#define TAG "BRINGUP"
|
||||||
|
|
||||||
|
static bool s_active = false;
|
||||||
|
static int64_t s_start_us = 0;
|
||||||
|
static volatile int s_http_reqs = 0;
|
||||||
|
|
||||||
|
/* -------- state helpers -------- */
|
||||||
|
|
||||||
|
bool bringup_mode_is_active(void) { return s_active; }
|
||||||
|
|
||||||
|
void bringup_notify_http_request(void) { s_http_reqs++; }
|
||||||
|
|
||||||
|
void bringup_mode_enter(void)
|
||||||
|
{
|
||||||
|
extern relay_port_t last_relay_state;
|
||||||
|
s_active = true;
|
||||||
|
s_start_us = esp_timer_get_time();
|
||||||
|
s_http_reqs = 0;
|
||||||
|
/* All bridges off, sensor rail (P10) up — system is still on. */
|
||||||
|
relay_port_t idle = {.bridges = {.SENSORS = 1}};
|
||||||
|
last_relay_state = idle;
|
||||||
|
i2c_relays_idle();
|
||||||
|
}
|
||||||
|
|
||||||
|
void bringup_mode_exit(void)
|
||||||
|
{
|
||||||
|
extern relay_port_t last_relay_state;
|
||||||
|
s_active = false;
|
||||||
|
relay_port_t idle = {.bridges = {.SENSORS = 1}};
|
||||||
|
last_relay_state = idle;
|
||||||
|
i2c_relays_idle();
|
||||||
|
}
|
||||||
|
|
||||||
|
static float elapsed_s(void)
|
||||||
|
{
|
||||||
|
return (esp_timer_get_time() - s_start_us) / 1e6f;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------- output helpers -------- */
|
||||||
|
|
||||||
|
/* Build the line in a stack buffer and emit with a single write so concurrent
|
||||||
|
* ESP_LOGx output (notably the wifi driver during BU.WIFI.START) cannot slice
|
||||||
|
* into the middle of it. Leading '\n' protects against partial lines that
|
||||||
|
* another task may have written without a terminator. */
|
||||||
|
__attribute__((format(printf, 2, 3)))
|
||||||
|
static void emit(const char *kind, const char *fmt, ...)
|
||||||
|
{
|
||||||
|
char buf[256];
|
||||||
|
/* Reserve one byte at the end for the trailing '\n' so a long line is
|
||||||
|
* truncated within the body rather than dropping the newline. Without
|
||||||
|
* this, a body that filled the buffer would produce a line glued to
|
||||||
|
* whatever came next on the wire. */
|
||||||
|
const int cap = (int)sizeof(buf) - 1; // room for '\n'
|
||||||
|
int n = snprintf(buf, cap, "\nBU.%s ", kind);
|
||||||
|
if (n < 0) n = 0;
|
||||||
|
if (n > cap) n = cap;
|
||||||
|
va_list ap;
|
||||||
|
va_start(ap, fmt);
|
||||||
|
int m = vsnprintf(buf + n, cap - n, fmt, ap);
|
||||||
|
va_end(ap);
|
||||||
|
if (m > 0) n += (m < cap - n) ? m : cap - n;
|
||||||
|
buf[n++] = '\n';
|
||||||
|
fwrite(buf, 1, n, stdout);
|
||||||
|
fflush(stdout);
|
||||||
|
}
|
||||||
|
|
||||||
|
#define OK(fmt, ...) emit("OK", fmt, ##__VA_ARGS__)
|
||||||
|
#define ERR(fmt, ...) emit("ERR", fmt, ##__VA_ARGS__)
|
||||||
|
#define SKIP(fmt, ...) emit("SKIP", fmt, ##__VA_ARGS__)
|
||||||
|
#define EVT(fmt, ...) emit("EVENT", fmt, ##__VA_ARGS__)
|
||||||
|
|
||||||
|
/* -------- tokenizer -------- */
|
||||||
|
|
||||||
|
/* strsep-style: mutate s in place, return next token (no quotes handled). */
|
||||||
|
static char *next_tok(char **s)
|
||||||
|
{
|
||||||
|
if (!s || !*s) return NULL;
|
||||||
|
while (**s == ' ' || **s == '\t') (*s)++;
|
||||||
|
if (**s == '\0') return NULL;
|
||||||
|
char *start = *s;
|
||||||
|
while (**s && **s != ' ' && **s != '\t') (*s)++;
|
||||||
|
if (**s) { *(*s)++ = '\0'; }
|
||||||
|
return start;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void str_upper(char *s)
|
||||||
|
{
|
||||||
|
for (; *s; s++) if (*s >= 'a' && *s <= 'z') *s -= 32;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------- parameter lookup by name -------- */
|
||||||
|
|
||||||
|
static int param_find(const char *name)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < NUM_PARAMS; i++) {
|
||||||
|
if (strcmp(name, get_param_name((param_idx_t)i)) == 0) return i;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------- command handlers -------- */
|
||||||
|
|
||||||
|
static void cmd_begin(char *args)
|
||||||
|
{
|
||||||
|
(void)args;
|
||||||
|
bringup_mode_enter();
|
||||||
|
OK("begin fw=%s board=%s t=%.2f",
|
||||||
|
FIRMWARE_VERSION,
|
||||||
|
#ifdef BOARD_V5
|
||||||
|
"V5",
|
||||||
|
#else
|
||||||
|
"V4",
|
||||||
|
#endif
|
||||||
|
elapsed_s());
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cmd_end(char *args)
|
||||||
|
{
|
||||||
|
(void)args;
|
||||||
|
OK("end reboot t=%.2f", elapsed_s());
|
||||||
|
fflush(stdout);
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(200));
|
||||||
|
bringup_mode_exit();
|
||||||
|
esp_restart();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cmd_info(char *args)
|
||||||
|
{
|
||||||
|
(void)args;
|
||||||
|
esp_reset_reason_t r = esp_reset_reason();
|
||||||
|
const char *rname = "?";
|
||||||
|
switch (r) {
|
||||||
|
case ESP_RST_POWERON: rname = "POWERON"; break;
|
||||||
|
case ESP_RST_EXT: rname = "EXT"; break;
|
||||||
|
case ESP_RST_SW: rname = "SW"; break;
|
||||||
|
case ESP_RST_PANIC: rname = "PANIC"; break;
|
||||||
|
case ESP_RST_INT_WDT: rname = "INT_WDT"; break;
|
||||||
|
case ESP_RST_TASK_WDT: rname = "TASK_WDT";break;
|
||||||
|
case ESP_RST_WDT: rname = "WDT"; break;
|
||||||
|
case ESP_RST_DEEPSLEEP:rname = "DEEPSLEEP";break;
|
||||||
|
case ESP_RST_BROWNOUT: rname = "BROWNOUT";break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
OK("info reset=%s heap=%u min_heap=%u fw=%s build=%s",
|
||||||
|
rname,
|
||||||
|
(unsigned)esp_get_free_heap_size(),
|
||||||
|
(unsigned)esp_get_minimum_free_heap_size(),
|
||||||
|
FIRMWARE_VERSION, BUILD_DATE);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cmd_flash(char *args)
|
||||||
|
{
|
||||||
|
(void)args;
|
||||||
|
const esp_partition_t *p = esp_partition_find_first(
|
||||||
|
ESP_PARTITION_TYPE_DATA, 0x42, "post_test");
|
||||||
|
if (!p) { ERR("flash reason=\"no post_test partition\""); return; }
|
||||||
|
|
||||||
|
/* Erase, write pattern, read back, compare. */
|
||||||
|
const size_t N = 64;
|
||||||
|
uint8_t pattern[N], readback[N];
|
||||||
|
for (size_t i = 0; i < N; i++) pattern[i] = (uint8_t)(i ^ 0xA5);
|
||||||
|
|
||||||
|
esp_err_t e = esp_partition_erase_range(p, 0, 4096);
|
||||||
|
if (e != ESP_OK) { ERR("flash stage=erase err=%s", esp_err_to_name(e)); return; }
|
||||||
|
e = esp_partition_write(p, 0, pattern, N);
|
||||||
|
if (e != ESP_OK) { ERR("flash stage=write err=%s", esp_err_to_name(e)); return; }
|
||||||
|
e = esp_partition_read(p, 0, readback, N);
|
||||||
|
if (e != ESP_OK) { ERR("flash stage=read err=%s", esp_err_to_name(e)); return; }
|
||||||
|
if (memcmp(pattern, readback, N) != 0) {
|
||||||
|
ERR("flash stage=compare mismatch");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
OK("flash post_part=roundtrip log_head=%u log_tail=%u partitions_size=%u",
|
||||||
|
(unsigned)log_get_head(), (unsigned)log_get_tail(),
|
||||||
|
(unsigned)(p->address));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cmd_i2c(char *args)
|
||||||
|
{
|
||||||
|
(void)args;
|
||||||
|
/* i2c_post re-probes TCA9555 by reading its input register. */
|
||||||
|
esp_err_t e = i2c_post();
|
||||||
|
if (e != ESP_OK) { ERR("i2c tca9555=nack err=%s", esp_err_to_name(e)); return; }
|
||||||
|
OK("i2c tca9555=ack");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cmd_led(char *args)
|
||||||
|
{
|
||||||
|
/* BU.LED <mask 0..7> [on|off] — just writes mask if given; ignores the
|
||||||
|
* optional second token and uses it when present to set/clear. */
|
||||||
|
char *s = args;
|
||||||
|
char *tok = next_tok(&s);
|
||||||
|
if (!tok) { ERR("led reason=\"missing mask\""); return; }
|
||||||
|
unsigned mask = (unsigned)strtoul(tok, NULL, 0);
|
||||||
|
if (mask > 7) mask = 7;
|
||||||
|
i2c_set_led1((uint8_t)mask);
|
||||||
|
OK("led mask=%u", mask);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* BU.LED.WATCH
|
||||||
|
* LEDs solid (all on) while the physical button is released.
|
||||||
|
* LEDs waterfall at ~83 ms/step while the button is held.
|
||||||
|
* Runs indefinitely; any UART byte aborts.
|
||||||
|
*/
|
||||||
|
static void cmd_led_watch(char *args)
|
||||||
|
{
|
||||||
|
(void)args;
|
||||||
|
static const uint8_t waterfall[] = {
|
||||||
|
0b001, 0b011, 0b111, 0b110, 0b100, 0b000
|
||||||
|
};
|
||||||
|
const size_t N_WF = sizeof(waterfall) / sizeof(waterfall[0]);
|
||||||
|
const int64_t step_us = 83000; /* ~83 ms — 3× the 250 ms host-side rate */
|
||||||
|
|
||||||
|
int64_t next_step_us = esp_timer_get_time();
|
||||||
|
size_t wf_i = 0;
|
||||||
|
int last_pressed = -1; /* force initial emit */
|
||||||
|
uint8_t last_mask = 0xFF;
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
/* Abort on any UART input. */
|
||||||
|
size_t available = 0;
|
||||||
|
if (uart_get_buffered_data_len(UART_NUM_0, &available) == ESP_OK
|
||||||
|
&& available > 0) {
|
||||||
|
uint8_t drain[64];
|
||||||
|
while (available > 0) {
|
||||||
|
int n = uart_read_bytes(UART_NUM_0, drain, sizeof(drain), 0);
|
||||||
|
if (n <= 0) break;
|
||||||
|
available = (size_t)n < available ? available - (size_t)n : 0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
i2c_poll_buttons();
|
||||||
|
bool pressed = i2c_get_button_state(0);
|
||||||
|
int64_t now_us = esp_timer_get_time();
|
||||||
|
|
||||||
|
uint8_t mask;
|
||||||
|
if (pressed) {
|
||||||
|
if (now_us >= next_step_us) {
|
||||||
|
wf_i = (wf_i + 1) % N_WF;
|
||||||
|
next_step_us = now_us + step_us;
|
||||||
|
}
|
||||||
|
mask = waterfall[wf_i];
|
||||||
|
} else {
|
||||||
|
wf_i = 0;
|
||||||
|
next_step_us = now_us; /* so the first press starts from step 0 */
|
||||||
|
mask = 0b111;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mask != last_mask) {
|
||||||
|
i2c_set_led1(mask);
|
||||||
|
last_mask = mask;
|
||||||
|
}
|
||||||
|
if ((int)pressed != last_pressed) {
|
||||||
|
EVT("led t=%.2f pressed=%d", elapsed_s(), pressed ? 1 : 0);
|
||||||
|
last_pressed = pressed;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_task_wdt_reset();
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(20));
|
||||||
|
}
|
||||||
|
|
||||||
|
i2c_set_led1(0);
|
||||||
|
OK("led.watch done");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cmd_adc_once(void)
|
||||||
|
{
|
||||||
|
int bat_mv = get_bat_raw_mv();
|
||||||
|
/* Bypass the EMA — process_battery_voltage() runs in the FSM task,
|
||||||
|
* which is paused while bring-up is active, so get_battery_V() returns
|
||||||
|
* a stale value that never reflects V_SENS_K / V_SENS_OFFSET writes
|
||||||
|
* issued during calibration. Compute fresh from raw mV + current params. */
|
||||||
|
float bat_V = bat_mv * get_param_value_t(PARAM_V_SENS_K).f32
|
||||||
|
+ get_param_value_t(PARAM_V_SENS_OFFSET).f32;
|
||||||
|
#ifdef BOARD_V5
|
||||||
|
/* VOC and FAULT pins are unusable on V5 (input-only ESP32 GPIOs
|
||||||
|
* without external pulls — see README "V5 hardware caveats"); skip. */
|
||||||
|
int isens_mv = get_isens_raw_mv();
|
||||||
|
float isens_A = -(isens_mv - 1650.0f) / 13.2f;
|
||||||
|
OK("adc bat_mv=%d bat_V=%.3f isens_mv=%d isens_A=%+.2f",
|
||||||
|
bat_mv, bat_V, isens_mv, isens_A);
|
||||||
|
#else
|
||||||
|
OK("adc bat_mv=%d bat_V=%.3f", bat_mv, bat_V);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cmd_adc(char *args)
|
||||||
|
{
|
||||||
|
(void)args;
|
||||||
|
cmd_adc_once();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cmd_adc_stream(char *args)
|
||||||
|
{
|
||||||
|
char *s = args;
|
||||||
|
char *t = next_tok(&s);
|
||||||
|
int sec = t ? atoi(t) : 5;
|
||||||
|
if (sec < 1) sec = 1;
|
||||||
|
if (sec > 60) sec = 60;
|
||||||
|
|
||||||
|
int64_t end_us = esp_timer_get_time() + (int64_t)sec * 1000000;
|
||||||
|
while (esp_timer_get_time() < end_us) {
|
||||||
|
#ifdef BOARD_V5
|
||||||
|
int bat_mv = get_bat_raw_mv();
|
||||||
|
int isens_mv = get_isens_raw_mv();
|
||||||
|
EVT("adc t=%.2f bat_mv=%d isens_mv=%d", elapsed_s(), bat_mv, isens_mv);
|
||||||
|
#else
|
||||||
|
int bat_mv = get_bat_raw_mv();
|
||||||
|
EVT("adc t=%.2f bat_mv=%d", elapsed_s(), bat_mv);
|
||||||
|
#endif
|
||||||
|
esp_task_wdt_reset();
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(200));
|
||||||
|
}
|
||||||
|
OK("adc.stream sec=%d", sec);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cmd_sensors_watch(char *args)
|
||||||
|
{
|
||||||
|
/* BU.SENSORS.WATCH [sec]
|
||||||
|
* sec omitted or 0 → watch indefinitely; exit when any byte arrives
|
||||||
|
* on UART0 (operator hit Enter on the host side).
|
||||||
|
* sec > 0 → watch for that many seconds, then return.
|
||||||
|
*/
|
||||||
|
/* Force the sensor rail (P10) up before we observe — covers cases where
|
||||||
|
* the FSM or sensor task drove it low between boot and BU.BEGIN. */
|
||||||
|
i2c_relays_idle();
|
||||||
|
char *s = args;
|
||||||
|
char *t = next_tok(&s);
|
||||||
|
int sec = t ? atoi(t) : 0;
|
||||||
|
bool indefinite = (sec <= 0);
|
||||||
|
if (!indefinite && sec > 600) sec = 600;
|
||||||
|
|
||||||
|
static const char *names[N_SENSORS] = {"SAFETY", "DRIVE", "JACK", "AUX"};
|
||||||
|
bool last_state[N_SENSORS];
|
||||||
|
bool make_seen[N_SENSORS] = {false};
|
||||||
|
bool break_seen[N_SENSORS] = {false};
|
||||||
|
/* Read the GPIO directly — the normal sensor pipeline runs in the FSM
|
||||||
|
* task (sensors_check()), which is paused while bring-up is active, so
|
||||||
|
* get_sensor() returns stale state. Active-low → inverted. */
|
||||||
|
#define _SENS_RAW(i) (!gpio_get_level(sensor_pins[i]))
|
||||||
|
extern uint8_t sensor_pins[N_SENSORS];
|
||||||
|
for (int i = 0; i < N_SENSORS; i++) last_state[i] = _SENS_RAW(i);
|
||||||
|
|
||||||
|
int64_t end_us = esp_timer_get_time() + (int64_t)sec * 1000000;
|
||||||
|
int64_t next_snapshot_us = esp_timer_get_time();
|
||||||
|
while (indefinite || esp_timer_get_time() < end_us) {
|
||||||
|
/* Abort on any UART input. */
|
||||||
|
size_t available = 0;
|
||||||
|
if (uart_get_buffered_data_len(UART_NUM_0, &available) == ESP_OK
|
||||||
|
&& available > 0) {
|
||||||
|
uint8_t drain[64];
|
||||||
|
while (available > 0) {
|
||||||
|
int n = uart_read_bytes(UART_NUM_0, drain, sizeof(drain), 0);
|
||||||
|
if (n <= 0) break;
|
||||||
|
available = (size_t)n < available ? available - (size_t)n : 0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < N_SENSORS; i++) {
|
||||||
|
bool now = _SENS_RAW(i);
|
||||||
|
if (now != last_state[i]) {
|
||||||
|
const char *edge = now ? "make" : "break";
|
||||||
|
if (now) make_seen[i] = true; else break_seen[i] = true;
|
||||||
|
EVT("sensor name=%s edge=%s t=%.2f",
|
||||||
|
names[i], edge, elapsed_s());
|
||||||
|
last_state[i] = now;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Periodic state snapshot of all four sensors — includes the
|
||||||
|
* no-connect slot so a floating/misrouted pin is visible. */
|
||||||
|
int64_t now_us = esp_timer_get_time();
|
||||||
|
if (now_us >= next_snapshot_us) {
|
||||||
|
next_snapshot_us = now_us + 250000; /* 250 ms */
|
||||||
|
EVT("state t=%.2f SAFETY=%d DRIVE=%d JACK=%d AUX=%d "
|
||||||
|
"isr_s=%u isr_d=%u isr_j=%u isr_a=%u",
|
||||||
|
elapsed_s(),
|
||||||
|
(int)_SENS_RAW(SENSOR_SAFETY),
|
||||||
|
(int)_SENS_RAW(SENSOR_DRIVE),
|
||||||
|
(int)_SENS_RAW(SENSOR_JACK),
|
||||||
|
(int)_SENS_RAW(SENSOR_AUX2),
|
||||||
|
(unsigned)get_sensor_isr_edges(SENSOR_SAFETY),
|
||||||
|
(unsigned)get_sensor_isr_edges(SENSOR_DRIVE),
|
||||||
|
(unsigned)get_sensor_isr_edges(SENSOR_JACK),
|
||||||
|
(unsigned)get_sensor_isr_edges(SENSOR_AUX2));
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_task_wdt_reset();
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(10));
|
||||||
|
}
|
||||||
|
#undef _SENS_RAW
|
||||||
|
|
||||||
|
/* Summary: which sensors saw both edges. */
|
||||||
|
char buf[128];
|
||||||
|
size_t used = 0;
|
||||||
|
for (int i = 0; i < N_SENSORS; i++) {
|
||||||
|
const char *tag =
|
||||||
|
(make_seen[i] && break_seen[i]) ? "both"
|
||||||
|
: make_seen[i] ? "make_only"
|
||||||
|
: break_seen[i] ? "break_only"
|
||||||
|
: "none";
|
||||||
|
int n = snprintf(buf + used, sizeof(buf) - used,
|
||||||
|
"%s%s=%s", used ? " " : "", names[i], tag);
|
||||||
|
if (n < 0 || (size_t)n >= sizeof(buf) - used) break;
|
||||||
|
used += n;
|
||||||
|
}
|
||||||
|
OK("sensors.watch sec=%d %s", indefinite ? -1 : sec, buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool parse_bridge(const char *s, bridge_t *out)
|
||||||
|
{
|
||||||
|
if (strcasecmp(s, "DRIVE") == 0) { *out = BRIDGE_DRIVE; return true; }
|
||||||
|
if (strcasecmp(s, "JACK") == 0) { *out = BRIDGE_JACK; return true; }
|
||||||
|
if (strcasecmp(s, "AUX") == 0) { *out = BRIDGE_AUX; return true; }
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool parse_dir(const char *s, uint8_t *out)
|
||||||
|
{
|
||||||
|
if (strcasecmp(s, "FWD") == 0 || strcasecmp(s, "UP") == 0)
|
||||||
|
{ *out = BRIDGE_FWD; return true; }
|
||||||
|
if (strcasecmp(s, "REV") == 0 || strcasecmp(s, "DOWN") == 0)
|
||||||
|
{ *out = BRIDGE_REV; return true; }
|
||||||
|
if (strcasecmp(s, "ON") == 0)
|
||||||
|
{ *out = BRIDGE_ON; return true; }
|
||||||
|
if (strcasecmp(s, "OFF") == 0)
|
||||||
|
{ *out = BRIDGE_OFF; return true; }
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cmd_relay(char *args)
|
||||||
|
{
|
||||||
|
char *s = args;
|
||||||
|
char *t_bridge = next_tok(&s);
|
||||||
|
char *t_dir = next_tok(&s);
|
||||||
|
char *t_ms = next_tok(&s);
|
||||||
|
if (!t_bridge || !t_dir) { ERR("relay reason=\"usage: <bridge> <dir> [ms]\""); return; }
|
||||||
|
|
||||||
|
int ms = t_ms ? atoi(t_ms) : 150;
|
||||||
|
if (ms < 10) ms = 10;
|
||||||
|
if (ms > 2000) ms = 2000;
|
||||||
|
|
||||||
|
/* Read SAFETY directly: sensors_check() runs in the FSM task, which is
|
||||||
|
* paused while bring-up is active, so is_safe / get_is_safe() are stale.
|
||||||
|
* Safety pin is active-LOW. */
|
||||||
|
extern uint8_t sensor_pins[N_SENSORS];
|
||||||
|
#define _BU_SAFETY_OPEN() (gpio_get_level(sensor_pins[SENSOR_SAFETY]) != 0)
|
||||||
|
if (_BU_SAFETY_OPEN()) { SKIP("relay reason=\"safety open\""); return; }
|
||||||
|
|
||||||
|
/* P10 / sensor power rail. Default is ON; pulse it OFF to prove the line
|
||||||
|
* can be driven, then restore. */
|
||||||
|
if (strcasecmp(t_bridge, "SENSORS") == 0) {
|
||||||
|
i2c_relays_sleep(); /* P10 low */
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(ms));
|
||||||
|
i2c_relays_idle(); /* P10 high (restore) */
|
||||||
|
OK("relay bridge=SENSORS ms=%d", ms);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bridge_t b;
|
||||||
|
uint8_t dir;
|
||||||
|
if (!parse_bridge(t_bridge, &b)) { ERR("relay reason=\"bad bridge\""); return; }
|
||||||
|
if (!parse_dir(t_dir, &dir)) { ERR("relay reason=\"bad dir\""); return; }
|
||||||
|
|
||||||
|
/* Sample current before, pulse, sample at midpoint, release, sample after.
|
||||||
|
* FSM is paused during POST, so we drive process_bridge_current() ourselves
|
||||||
|
* to refresh isens[].current before each read. We also mirror the relay
|
||||||
|
* state into last_relay_state so V5's shared autozero gate (which looks at
|
||||||
|
* last_relay_state to decide if bridges are powered) stays truthful. */
|
||||||
|
extern volatile int64_t fsm_now;
|
||||||
|
extern relay_port_t last_relay_state;
|
||||||
|
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(50)); /* let things settle */
|
||||||
|
fsm_now = esp_timer_get_time();
|
||||||
|
process_bridge_current(b);
|
||||||
|
float I_before = get_bridge_A(b);
|
||||||
|
|
||||||
|
/* Which sensor to count edges on during the pulse — ISR-level counter,
|
||||||
|
* doesn't depend on sensors_check() running. */
|
||||||
|
sensor_t which_sensor = N_SENSORS;
|
||||||
|
if (b == BRIDGE_DRIVE) which_sensor = SENSOR_DRIVE;
|
||||||
|
else if (b == BRIDGE_JACK) which_sensor = SENSOR_JACK;
|
||||||
|
uint32_t edges_before = (which_sensor < N_SENSORS)
|
||||||
|
? get_sensor_isr_edges(which_sensor) : 0;
|
||||||
|
|
||||||
|
relay_port_t rs = {.raw = 0};
|
||||||
|
switch (b) {
|
||||||
|
case BRIDGE_DRIVE: rs.bridges.DRIVE = dir; break;
|
||||||
|
case BRIDGE_JACK: rs.bridges.JACK = dir; break;
|
||||||
|
case BRIDGE_AUX: rs.bridges.AUX = dir; break;
|
||||||
|
default: ERR("relay reason=\"bad bridge idx\""); return;
|
||||||
|
}
|
||||||
|
rs.bridges.SENSORS = 1;
|
||||||
|
last_relay_state = rs;
|
||||||
|
i2c_set_relays(rs);
|
||||||
|
|
||||||
|
/* JACK DOWN should stop as soon as the JACK sensor goes active (LOW) so
|
||||||
|
* the bring-up pulse can't drive the actuator into its mechanical limit.
|
||||||
|
* Other directions/bridges run for the full requested duration. SAFETY
|
||||||
|
* is checked every iteration regardless of bridge — multi-second pulses
|
||||||
|
* during bring-up must still kill the motor on a safety break. */
|
||||||
|
bool jack_down = (b == BRIDGE_JACK && dir == BRIDGE_REV);
|
||||||
|
bool stopped_by_sensor = false;
|
||||||
|
bool stopped_by_safety = false;
|
||||||
|
|
||||||
|
int64_t pulse_start_us = esp_timer_get_time();
|
||||||
|
int64_t mid_us = pulse_start_us + (int64_t)(ms / 2) * 1000;
|
||||||
|
int64_t end_us = pulse_start_us + (int64_t)ms * 1000;
|
||||||
|
|
||||||
|
float I_mid = NAN;
|
||||||
|
while (esp_timer_get_time() < end_us) {
|
||||||
|
if (_BU_SAFETY_OPEN()) {
|
||||||
|
stopped_by_safety = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (jack_down && gpio_get_level(sensor_pins[SENSOR_JACK]) == 0) {
|
||||||
|
stopped_by_sensor = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (isnan(I_mid) && esp_timer_get_time() >= mid_us) {
|
||||||
|
fsm_now = esp_timer_get_time();
|
||||||
|
process_bridge_current(b);
|
||||||
|
I_mid = get_bridge_A(b);
|
||||||
|
}
|
||||||
|
esp_task_wdt_reset();
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(10));
|
||||||
|
}
|
||||||
|
if (isnan(I_mid)) {
|
||||||
|
/* Sensor tripped before we hit the midpoint; sample current now so
|
||||||
|
* the response still has a meaningful I_mid. */
|
||||||
|
fsm_now = esp_timer_get_time();
|
||||||
|
process_bridge_current(b);
|
||||||
|
I_mid = get_bridge_A(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
relay_port_t idle = {.bridges = {.SENSORS = 1}};
|
||||||
|
last_relay_state = idle;
|
||||||
|
i2c_relays_idle();
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(50));
|
||||||
|
fsm_now = esp_timer_get_time();
|
||||||
|
process_bridge_current(b);
|
||||||
|
float I_after = get_bridge_A(b);
|
||||||
|
float heat = efuse_get_heat(b);
|
||||||
|
int tripped = efuse_get(b) ? 1 : 0;
|
||||||
|
uint32_t edges_after = (which_sensor < N_SENSORS)
|
||||||
|
? get_sensor_isr_edges(which_sensor) : 0;
|
||||||
|
uint32_t edges = edges_after - edges_before;
|
||||||
|
|
||||||
|
int actual_ms = (int)((esp_timer_get_time() - pulse_start_us) / 1000);
|
||||||
|
const char *stop_reason =
|
||||||
|
stopped_by_safety ? "safety" :
|
||||||
|
stopped_by_sensor ? "sensor" : "time";
|
||||||
|
OK("relay bridge=%s dir=%s ms=%d actual_ms=%d stop=%s "
|
||||||
|
"I_before=%+.2f I_mid=%+.2f I_after=%+.2f heat=%.3f tripped=%d edges=%u",
|
||||||
|
t_bridge, t_dir, ms, actual_ms, stop_reason,
|
||||||
|
I_before, I_mid, I_after, heat, tripped, (unsigned)edges);
|
||||||
|
#undef _BU_SAFETY_OPEN
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cmd_rf_watch(char *args)
|
||||||
|
{
|
||||||
|
/* BU.RF.WATCH [sec]
|
||||||
|
* sec omitted or 0 → watch indefinitely; exit when any byte arrives
|
||||||
|
* on UART0 (operator hit Enter on the host side).
|
||||||
|
*/
|
||||||
|
char *s = args;
|
||||||
|
char *t = next_tok(&s);
|
||||||
|
int sec = t ? atoi(t) : 0;
|
||||||
|
bool indefinite = (sec <= 0);
|
||||||
|
if (!indefinite && sec > 600) sec = 600;
|
||||||
|
|
||||||
|
int64_t end_us = esp_timer_get_time() + (int64_t)sec * 1000000;
|
||||||
|
int count = 0;
|
||||||
|
/* Drain any stale code from before the watch started. */
|
||||||
|
(void)rf_433_peek_latest();
|
||||||
|
while (indefinite || esp_timer_get_time() < end_us) {
|
||||||
|
/* Abort on any UART input. */
|
||||||
|
size_t available = 0;
|
||||||
|
if (uart_get_buffered_data_len(UART_NUM_0, &available) == ESP_OK
|
||||||
|
&& available > 0) {
|
||||||
|
uint8_t drain[64];
|
||||||
|
while (available > 0) {
|
||||||
|
int n = uart_read_bytes(UART_NUM_0, drain, sizeof(drain), 0);
|
||||||
|
if (n <= 0) break;
|
||||||
|
available = (size_t)n < available ? available - (size_t)n : 0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
uint32_t code = rf_433_peek_latest();
|
||||||
|
if (code) {
|
||||||
|
EVT("rf code=0x%lX t=%.2f", (unsigned long)code, elapsed_s());
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
esp_task_wdt_reset();
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(50));
|
||||||
|
}
|
||||||
|
OK("rf.watch sec=%d seen=%d", indefinite ? -1 : sec, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cmd_wifi_start(char *args)
|
||||||
|
{
|
||||||
|
(void)args;
|
||||||
|
esp_err_t e = webserver_init();
|
||||||
|
if (e != ESP_OK) { ERR("wifi.start err=%s", esp_err_to_name(e)); return; }
|
||||||
|
OK("wifi.start mode=AP ssid=\"%s\" ip=192.168.4.1",
|
||||||
|
get_param_string(PARAM_WIFI_SSID));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cmd_wifi_wait(char *args)
|
||||||
|
{
|
||||||
|
(void)args; /* no timeout — BRINGUP.md §4 Stage 6. Operator aborts via Ctrl+C. */
|
||||||
|
wifi_sta_list_t sta = {0};
|
||||||
|
int last_n = 0;
|
||||||
|
bool aborted = false;
|
||||||
|
while (1) {
|
||||||
|
/* Abort on any UART input — the host sends a stray byte to break out
|
||||||
|
* of the wait so that a follow-up BU.END is actually dispatched
|
||||||
|
* (otherwise the dispatcher stays blocked here forever). */
|
||||||
|
size_t available = 0;
|
||||||
|
if (uart_get_buffered_data_len(UART_NUM_0, &available) == ESP_OK
|
||||||
|
&& available > 0) {
|
||||||
|
uint8_t drain[64];
|
||||||
|
while (available > 0) {
|
||||||
|
int n = uart_read_bytes(UART_NUM_0, drain, sizeof(drain), 0);
|
||||||
|
if (n <= 0) break;
|
||||||
|
available = (size_t)n < available ? available - (size_t)n : 0;
|
||||||
|
}
|
||||||
|
aborted = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (esp_wifi_ap_get_sta_list(&sta) == ESP_OK && sta.num > last_n) {
|
||||||
|
EVT("wifi.assoc n=%d t=%.2f", sta.num, elapsed_s());
|
||||||
|
last_n = sta.num;
|
||||||
|
}
|
||||||
|
/* Bail when at least one client is associated AND the web UI has
|
||||||
|
* issued at least one request (notified by webserver). */
|
||||||
|
if (last_n > 0 && s_http_reqs > 0) break;
|
||||||
|
esp_task_wdt_reset();
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(200));
|
||||||
|
}
|
||||||
|
OK("wifi.wait clients=%d http_reqs=%d aborted=%d",
|
||||||
|
last_n, s_http_reqs, aborted ? 1 : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cmd_fsm(char *args)
|
||||||
|
{
|
||||||
|
char *s = args;
|
||||||
|
char *sub = next_tok(&s);
|
||||||
|
if (!sub) sub = "INFO";
|
||||||
|
str_upper(sub);
|
||||||
|
if (strcmp(sub, "INFO") == 0) {
|
||||||
|
OK("fsm state=%d err=%d idle=%d",
|
||||||
|
(int)fsm_get_state(), (int)fsm_get_error(), fsm_is_idle() ? 1 : 0);
|
||||||
|
} else {
|
||||||
|
ERR("fsm reason=\"unknown subcommand\"");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cmd_solar_tick(char *args)
|
||||||
|
{
|
||||||
|
(void)args;
|
||||||
|
(void)solar_run_fsm();
|
||||||
|
OK("solar tick=ok chg_bulk=%d",
|
||||||
|
gpio_get_level(GPIO_NUM_26));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* BU.PARAM GET <key> | BU.PARAM SET <key> <value> */
|
||||||
|
static void cmd_param(char *args)
|
||||||
|
{
|
||||||
|
char *s = args;
|
||||||
|
char *op = next_tok(&s);
|
||||||
|
char *key = next_tok(&s);
|
||||||
|
if (!op || !key) { ERR("param reason=\"usage: GET <k> | SET <k> <v>\""); return; }
|
||||||
|
str_upper(op);
|
||||||
|
int idx = param_find(key);
|
||||||
|
if (idx < 0) { ERR("param reason=\"unknown key\" key=%s", key); return; }
|
||||||
|
|
||||||
|
param_type_e type = get_param_type((param_idx_t)idx);
|
||||||
|
|
||||||
|
if (strcmp(op, "GET") == 0) {
|
||||||
|
param_value_t v = get_param_value_t((param_idx_t)idx);
|
||||||
|
switch (type) {
|
||||||
|
case PARAM_TYPE_u16: OK("param key=%s value=%u", key, v.u16); break;
|
||||||
|
case PARAM_TYPE_i16: OK("param key=%s value=%d", key, v.i16); break;
|
||||||
|
case PARAM_TYPE_u32: OK("param key=%s value=%u", key, (unsigned)v.u32); break;
|
||||||
|
case PARAM_TYPE_i32: OK("param key=%s value=%d", key, (int)v.i32); break;
|
||||||
|
case PARAM_TYPE_f32: OK("param key=%s value=%.9g", key, v.f32); break;
|
||||||
|
case PARAM_TYPE_f64: OK("param key=%s value=%.17g",key, v.f64); break;
|
||||||
|
case PARAM_TYPE_str: OK("param key=%s value=\"%s\"", key, get_param_string((param_idx_t)idx)); break;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strcmp(op, "SET") == 0) {
|
||||||
|
/* SET writes flash. Require BU.BEGIN to prevent accidental persistence
|
||||||
|
* from stray BU.PARAM lines outside of an active bring-up session. */
|
||||||
|
if (!s_active) {
|
||||||
|
ERR("param reason=\"BU.BEGIN required first\""); return;
|
||||||
|
}
|
||||||
|
char *val = next_tok(&s);
|
||||||
|
if (!val) { ERR("param reason=\"missing value\""); return; }
|
||||||
|
esp_err_t e = ESP_OK;
|
||||||
|
if (type == PARAM_TYPE_str) {
|
||||||
|
e = set_param_string((param_idx_t)idx, val);
|
||||||
|
} else {
|
||||||
|
param_value_t v = {0};
|
||||||
|
switch (type) {
|
||||||
|
case PARAM_TYPE_u16: v.u16 = (uint16_t)strtoul(val, NULL, 0); break;
|
||||||
|
case PARAM_TYPE_i16: v.i16 = (int16_t)strtol(val, NULL, 0); break;
|
||||||
|
case PARAM_TYPE_u32: v.u32 = (uint32_t)strtoul(val, NULL, 0); break;
|
||||||
|
case PARAM_TYPE_i32: v.i32 = (int32_t)strtol(val, NULL, 0); break;
|
||||||
|
case PARAM_TYPE_f32: v.f32 = strtof(val, NULL); break;
|
||||||
|
case PARAM_TYPE_f64: v.f64 = strtod(val, NULL); break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
e = set_param_value_t((param_idx_t)idx, v);
|
||||||
|
}
|
||||||
|
if (e != ESP_OK) { ERR("param reason=\"set failed\" err=%s", esp_err_to_name(e)); return; }
|
||||||
|
e = commit_params();
|
||||||
|
if (e != ESP_OK) { ERR("param reason=\"commit failed\" err=%s", esp_err_to_name(e)); return; }
|
||||||
|
/* If the conversion params changed, refresh the battery EMA so
|
||||||
|
* get_battery_V() returns a value consistent with the new K/OFFSET
|
||||||
|
* immediately rather than decaying through the EMA. */
|
||||||
|
if (idx == PARAM_V_SENS_K || idx == PARAM_V_SENS_OFFSET) {
|
||||||
|
reset_battery_ema();
|
||||||
|
}
|
||||||
|
OK("param key=%s set=ok committed=yes", key);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ERR("param reason=\"unknown op\" op=%s", op);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* BU.FACTORY_RESET — wipe all params back to defaults, erase log partition,
|
||||||
|
* then reboot. Equivalent to the cold-boot button-hold path in main.c, but
|
||||||
|
* reachable from a host without physical access. Destructive — operator
|
||||||
|
* must explicitly invoke it. */
|
||||||
|
static void cmd_factory_reset(char *args)
|
||||||
|
{
|
||||||
|
(void)args;
|
||||||
|
/* Refuse without an explicit BU.BEGIN. Without this guard, any party
|
||||||
|
* that can write to UART0 can wipe params/log just by sending the
|
||||||
|
* command — and uart_comms.c forwards bare BU.* lines to the dispatcher
|
||||||
|
* even when bring-up mode is off. */
|
||||||
|
if (!s_active) {
|
||||||
|
ERR("factory_reset reason=\"BU.BEGIN required first\"");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
OK("factory_reset stage=start");
|
||||||
|
esp_err_t e = factory_reset();
|
||||||
|
if (e != ESP_OK) {
|
||||||
|
ERR("factory_reset stage=apply err=%s", esp_err_to_name(e));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
OK("factory_reset stage=done reboot=2s");
|
||||||
|
fflush(stdout);
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(2000));
|
||||||
|
esp_restart();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------- dispatcher -------- */
|
||||||
|
|
||||||
|
typedef void (*cmd_fn)(char *args);
|
||||||
|
|
||||||
|
struct cmd_entry {
|
||||||
|
const char *name; /* uppercased, no BU. prefix */
|
||||||
|
cmd_fn fn;
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct cmd_entry CMDS[] = {
|
||||||
|
{ "BEGIN", cmd_begin },
|
||||||
|
{ "END", cmd_end },
|
||||||
|
{ "INFO", cmd_info },
|
||||||
|
{ "FLASH", cmd_flash },
|
||||||
|
{ "I2C", cmd_i2c },
|
||||||
|
{ "LED", cmd_led },
|
||||||
|
{ "LED.WATCH", cmd_led_watch },
|
||||||
|
{ "ADC", cmd_adc },
|
||||||
|
{ "ADC.STREAM", cmd_adc_stream },
|
||||||
|
{ "SENSORS.WATCH", cmd_sensors_watch},
|
||||||
|
{ "RELAY", cmd_relay },
|
||||||
|
{ "RF.WATCH", cmd_rf_watch },
|
||||||
|
{ "WIFI.START", cmd_wifi_start },
|
||||||
|
{ "WIFI.WAIT", cmd_wifi_wait },
|
||||||
|
{ "FSM", cmd_fsm },
|
||||||
|
{ "SOLAR.TICK", cmd_solar_tick },
|
||||||
|
{ "PARAM", cmd_param },
|
||||||
|
{ "FACTORY_RESET", cmd_factory_reset},
|
||||||
|
};
|
||||||
|
|
||||||
|
void bringup_handle_line(char *line)
|
||||||
|
{
|
||||||
|
/* Trim leading whitespace. */
|
||||||
|
while (*line == ' ' || *line == '\t') line++;
|
||||||
|
if (*line == '\0') return;
|
||||||
|
|
||||||
|
/* Expect "BU.<CMD> [args]" */
|
||||||
|
if (strncasecmp(line, "BU.", 3) != 0) {
|
||||||
|
ERR("dispatch reason=\"missing BU. prefix\"");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
line += 3;
|
||||||
|
|
||||||
|
/* Split CMD token from args. */
|
||||||
|
char *sp = line;
|
||||||
|
while (*sp && *sp != ' ' && *sp != '\t') sp++;
|
||||||
|
char *args = sp;
|
||||||
|
if (*sp) { *sp = '\0'; args = sp + 1; }
|
||||||
|
|
||||||
|
str_upper(line);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < sizeof(CMDS)/sizeof(CMDS[0]); i++) {
|
||||||
|
if (strcmp(line, CMDS[i].name) == 0) {
|
||||||
|
CMDS[i].fn(args);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ERR("dispatch reason=\"unknown command\" cmd=%s", line);
|
||||||
|
}
|
||||||
35
main/bringup.h
Normal file
35
main/bringup.h
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
/*
|
||||||
|
* bringup.h — manufacturing / bench bring-up protocol over UART.
|
||||||
|
*
|
||||||
|
* See docs/SC-F001/BRINGUP.md for the line protocol and stage procedure.
|
||||||
|
*
|
||||||
|
* While bring-up mode is active:
|
||||||
|
* - uart_comms routes every input line to bringup_handle_line() instead of
|
||||||
|
* the JSON parser.
|
||||||
|
* - control_fsm pauses (stays in STATE_IDLE, skips relay writes).
|
||||||
|
* - BU.* commands drive the hardware directly via i2c_set_relays etc.
|
||||||
|
*
|
||||||
|
* Note: this is distinct from the per-module POST routines (adc_post,
|
||||||
|
* i2c_post, storage_post) which run at normal boot and are genuinely
|
||||||
|
* power-on self-tests.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef MAIN_BRINGUP_H_
|
||||||
|
#define MAIN_BRINGUP_H_
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
void bringup_mode_enter(void);
|
||||||
|
void bringup_mode_exit(void);
|
||||||
|
bool bringup_mode_is_active(void);
|
||||||
|
|
||||||
|
/* Called by uart_comms when a full line has been received in bring-up mode.
|
||||||
|
* `line` is null-terminated, no trailing \r\n. Response is printed via
|
||||||
|
* printf(); caller doesn't need to capture it. */
|
||||||
|
void bringup_handle_line(char *line);
|
||||||
|
|
||||||
|
/* Counted by webserver when the root page is served. BU.WIFI.WAIT uses it
|
||||||
|
* to confirm that an associated client actually loaded the UI. */
|
||||||
|
void bringup_notify_http_request(void);
|
||||||
|
|
||||||
|
#endif /* MAIN_BRINGUP_H_ */
|
||||||
@@ -53,6 +53,7 @@
|
|||||||
#include "esp_timer.h"
|
#include "esp_timer.h"
|
||||||
|
|
||||||
#include "bt_hid.h"
|
#include "bt_hid.h"
|
||||||
|
#include "comms_events.h"
|
||||||
#include "control_fsm.h"
|
#include "control_fsm.h"
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -76,8 +77,8 @@
|
|||||||
* Usage codes sent by generic BLE media remotes (HID Consumer Control page).
|
* Usage codes sent by generic BLE media remotes (HID Consumer Control page).
|
||||||
* Only the four we care about are mapped; everything else is ignored.
|
* Only the four we care about are mapped; everything else is ignored.
|
||||||
*/
|
*/
|
||||||
#define USAGE_VOL_UP 0x00E9u /* -> jack up */
|
#define USAGE_VOL_UP 0x00E9u /* -> jack extend */
|
||||||
#define USAGE_VOL_DOWN 0x00EAu /* -> jack down */
|
#define USAGE_VOL_DOWN 0x00EAu /* -> jack retract */
|
||||||
#define USAGE_PREV 0x00B6u /* -> reverse */
|
#define USAGE_PREV 0x00B6u /* -> reverse */
|
||||||
#define USAGE_NEXT 0x00B5u /* -> forward */
|
#define USAGE_NEXT 0x00B5u /* -> forward */
|
||||||
#define USAGE_NONE 0x0000u
|
#define USAGE_NONE 0x0000u
|
||||||
@@ -581,6 +582,8 @@ esp_err_t bt_hid_init(void)
|
|||||||
*/
|
*/
|
||||||
xTaskCreate(bt_hid_scan_task, "bt_hid_scan", 6 * 1024, NULL, 4, &s_scan_task_handle);
|
xTaskCreate(bt_hid_scan_task, "bt_hid_scan", 6 * 1024, NULL, 4, &s_scan_task_handle);
|
||||||
|
|
||||||
|
if (comms_event_group) xEventGroupSetBits(comms_event_group, BT_READY_BIT);
|
||||||
|
|
||||||
ESP_LOGI(TAG, "BLE HID host initialised");
|
ESP_LOGI(TAG, "BLE HID host initialised");
|
||||||
return ESP_OK;
|
return ESP_OK;
|
||||||
}
|
}
|
||||||
@@ -591,6 +594,7 @@ void bt_hid_stop(void)
|
|||||||
vTaskSuspend(s_scan_task_handle);
|
vTaskSuspend(s_scan_task_handle);
|
||||||
ESP_LOGI(TAG, "BT HID scan task suspended");
|
ESP_LOGI(TAG, "BT HID scan task suspended");
|
||||||
}
|
}
|
||||||
|
if (comms_event_group) xEventGroupClearBits(comms_event_group, BT_READY_BIT);
|
||||||
}
|
}
|
||||||
|
|
||||||
void bt_hid_resume(void)
|
void bt_hid_resume(void)
|
||||||
@@ -599,4 +603,5 @@ void bt_hid_resume(void)
|
|||||||
vTaskResume(s_scan_task_handle);
|
vTaskResume(s_scan_task_handle);
|
||||||
ESP_LOGI(TAG, "BT HID scan task resumed");
|
ESP_LOGI(TAG, "BT HID scan task resumed");
|
||||||
}
|
}
|
||||||
|
if (comms_event_group) xEventGroupSetBits(comms_event_group, BT_READY_BIT);
|
||||||
}
|
}
|
||||||
|
|||||||
96
main/build/default/CMakeCache.txt
Normal file
96
main/build/default/CMakeCache.txt
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
# This is the CMakeCache file.
|
||||||
|
# For build in directory: d:/SC/SC-F001/main/build/default
|
||||||
|
# It was generated by CMake: C:/Espressif/tools/cmake/3.24.0/bin/cmake.exe
|
||||||
|
# You can edit this file to change values found and used by cmake.
|
||||||
|
# If you do not want to change any of the values, simply exit the editor.
|
||||||
|
# If you do want to change a value, simply edit, save, and exit the editor.
|
||||||
|
# The syntax for the file is as follows:
|
||||||
|
# KEY:TYPE=VALUE
|
||||||
|
# KEY is the name of a variable in the cache.
|
||||||
|
# TYPE is a hint to GUIs for the type of VALUE, DO NOT EDIT TYPE!.
|
||||||
|
# VALUE is the current value for the KEY.
|
||||||
|
|
||||||
|
########################
|
||||||
|
# EXTERNAL cache entries
|
||||||
|
########################
|
||||||
|
|
||||||
|
//For backwards compatibility, what version of CMake commands and
|
||||||
|
// syntax should this version of CMake try to support.
|
||||||
|
CMAKE_BACKWARDS_COMPATIBILITY:STRING=2.4
|
||||||
|
|
||||||
|
//No help, variable specified on the command line.
|
||||||
|
CMAKE_EXPORT_COMPILE_COMMANDS:UNINITIALIZED=ON
|
||||||
|
|
||||||
|
//Value Computed by CMake.
|
||||||
|
CMAKE_FIND_PACKAGE_REDIRECTS_DIR:STATIC=D:/SC/SC-F001/main/build/default/CMakeFiles/pkgRedirects
|
||||||
|
|
||||||
|
//Path to a program.
|
||||||
|
CMAKE_MAKE_PROGRAM:FILEPATH=CMAKE_MAKE_PROGRAM-NOTFOUND
|
||||||
|
|
||||||
|
//Value Computed by CMake
|
||||||
|
CMAKE_PROJECT_DESCRIPTION:STATIC=
|
||||||
|
|
||||||
|
//Value Computed by CMake
|
||||||
|
CMAKE_PROJECT_HOMEPAGE_URL:STATIC=
|
||||||
|
|
||||||
|
//Value Computed by CMake
|
||||||
|
CMAKE_PROJECT_NAME:STATIC=Project
|
||||||
|
|
||||||
|
//Single output directory for building all executables.
|
||||||
|
EXECUTABLE_OUTPUT_PATH:PATH=
|
||||||
|
|
||||||
|
//Single output directory for building all libraries.
|
||||||
|
LIBRARY_OUTPUT_PATH:PATH=
|
||||||
|
|
||||||
|
//Value Computed by CMake
|
||||||
|
Project_BINARY_DIR:STATIC=D:/SC/SC-F001/main/build/default
|
||||||
|
|
||||||
|
//Value Computed by CMake
|
||||||
|
Project_IS_TOP_LEVEL:STATIC=ON
|
||||||
|
|
||||||
|
//Value Computed by CMake
|
||||||
|
Project_SOURCE_DIR:STATIC=D:/SC/SC-F001/main
|
||||||
|
|
||||||
|
|
||||||
|
########################
|
||||||
|
# INTERNAL cache entries
|
||||||
|
########################
|
||||||
|
|
||||||
|
//This is the directory where this CMakeCache.txt was created
|
||||||
|
CMAKE_CACHEFILE_DIR:INTERNAL=d:/SC/SC-F001/main/build/default
|
||||||
|
//Major version of cmake used to create the current loaded cache
|
||||||
|
CMAKE_CACHE_MAJOR_VERSION:INTERNAL=3
|
||||||
|
//Minor version of cmake used to create the current loaded cache
|
||||||
|
CMAKE_CACHE_MINOR_VERSION:INTERNAL=24
|
||||||
|
//Patch version of cmake used to create the current loaded cache
|
||||||
|
CMAKE_CACHE_PATCH_VERSION:INTERNAL=0
|
||||||
|
//Path to CMake executable.
|
||||||
|
CMAKE_COMMAND:INTERNAL=C:/Espressif/tools/cmake/3.24.0/bin/cmake.exe
|
||||||
|
//Path to cpack program executable.
|
||||||
|
CMAKE_CPACK_COMMAND:INTERNAL=C:/Espressif/tools/cmake/3.24.0/bin/cpack.exe
|
||||||
|
//Path to ctest program executable.
|
||||||
|
CMAKE_CTEST_COMMAND:INTERNAL=C:/Espressif/tools/cmake/3.24.0/bin/ctest.exe
|
||||||
|
//Path to cache edit program executable.
|
||||||
|
CMAKE_EDIT_COMMAND:INTERNAL=C:/Espressif/tools/cmake/3.24.0/bin/cmake-gui.exe
|
||||||
|
//Name of external makefile project generator.
|
||||||
|
CMAKE_EXTRA_GENERATOR:INTERNAL=
|
||||||
|
//Name of generator.
|
||||||
|
CMAKE_GENERATOR:INTERNAL=MinGW Makefiles
|
||||||
|
//Generator instance identifier.
|
||||||
|
CMAKE_GENERATOR_INSTANCE:INTERNAL=
|
||||||
|
//Name of generator platform.
|
||||||
|
CMAKE_GENERATOR_PLATFORM:INTERNAL=
|
||||||
|
//Name of generator toolset.
|
||||||
|
CMAKE_GENERATOR_TOOLSET:INTERNAL=
|
||||||
|
//Source directory with the top level CMakeLists.txt file for this
|
||||||
|
// project
|
||||||
|
CMAKE_HOME_DIRECTORY:INTERNAL=D:/SC/SC-F001/main
|
||||||
|
//ADVANCED property for variable: CMAKE_MAKE_PROGRAM
|
||||||
|
CMAKE_MAKE_PROGRAM-ADVANCED:INTERNAL=1
|
||||||
|
//number of local generators
|
||||||
|
CMAKE_NUMBER_OF_MAKEFILES:INTERNAL=1
|
||||||
|
//Platform information initialized
|
||||||
|
CMAKE_PLATFORM_INFO_INITIALIZED:INTERNAL=1
|
||||||
|
//Path to CMake installation.
|
||||||
|
CMAKE_ROOT:INTERNAL=C:/Espressif/tools/cmake/3.24.0/share/cmake-3.24
|
||||||
|
|
||||||
15
main/build/default/CMakeFiles/3.24.0/CMakeSystem.cmake
Normal file
15
main/build/default/CMakeFiles/3.24.0/CMakeSystem.cmake
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
set(CMAKE_HOST_SYSTEM "Windows-10.0.19045")
|
||||||
|
set(CMAKE_HOST_SYSTEM_NAME "Windows")
|
||||||
|
set(CMAKE_HOST_SYSTEM_VERSION "10.0.19045")
|
||||||
|
set(CMAKE_HOST_SYSTEM_PROCESSOR "AMD64")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
set(CMAKE_SYSTEM "Windows-10.0.19045")
|
||||||
|
set(CMAKE_SYSTEM_NAME "Windows")
|
||||||
|
set(CMAKE_SYSTEM_VERSION "10.0.19045")
|
||||||
|
set(CMAKE_SYSTEM_PROCESSOR "AMD64")
|
||||||
|
|
||||||
|
set(CMAKE_CROSSCOMPILING "FALSE")
|
||||||
|
|
||||||
|
set(CMAKE_SYSTEM_LOADED 1)
|
||||||
1
main/build/default/CMakeFiles/CMakeOutput.log
Normal file
1
main/build/default/CMakeFiles/CMakeOutput.log
Normal file
@@ -0,0 +1 @@
|
|||||||
|
The system is: Windows - 10.0.19045 - AMD64
|
||||||
1
main/build/default/CMakeFiles/cmake.check_cache
Normal file
1
main/build/default/CMakeFiles/cmake.check_cache
Normal file
@@ -0,0 +1 @@
|
|||||||
|
# This file is generated by cmake for dependency checking of the CMakeCache.txt file
|
||||||
302
main/comms.c
302
main/comms.c
@@ -15,14 +15,38 @@
|
|||||||
|
|
||||||
static const char *TAG = "COMMS";
|
static const char *TAG = "COMMS";
|
||||||
|
|
||||||
|
/* Decode a single JSON value into the parameter table. Returns true on
|
||||||
|
* success (param updated), false if the JSON node type doesn't match the
|
||||||
|
* parameter type (caller bumps params_failed). All numeric integer types
|
||||||
|
* funnel through valueint, floats through valuedouble — matches what
|
||||||
|
* cJSON_AddNumberToObject produced on the way out. */
|
||||||
|
static bool set_param_from_json(param_idx_t idx, cJSON *value_json) {
|
||||||
|
if (get_param_type(idx) == PARAM_TYPE_str) {
|
||||||
|
if (!cJSON_IsString(value_json)) return false;
|
||||||
|
set_param_string(idx, value_json->valuestring);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!cJSON_IsNumber(value_json)) return false;
|
||||||
|
param_value_t v = {0};
|
||||||
|
switch (get_param_type(idx)) {
|
||||||
|
case PARAM_TYPE_u16: v.u16 = (uint16_t)value_json->valueint; break;
|
||||||
|
case PARAM_TYPE_i16: v.i16 = (int16_t)value_json->valueint; break;
|
||||||
|
case PARAM_TYPE_u32: v.u32 = (uint32_t)value_json->valueint; break;
|
||||||
|
case PARAM_TYPE_i32: v.i32 = (int32_t)value_json->valueint; break;
|
||||||
|
case PARAM_TYPE_f32: v.f32 = (float)value_json->valuedouble; break;
|
||||||
|
case PARAM_TYPE_f64: v.f64 = value_json->valuedouble; break;
|
||||||
|
default: return false;
|
||||||
|
}
|
||||||
|
set_param_value_t(idx, v);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build a JSON object containing complete system status
|
* Build the full system-status JSON object WITHOUT touching the shutdown
|
||||||
|
* timer. Used by the 1 Hz WebSocket push, which must not keep the device
|
||||||
|
* awake on its own — only genuine client activity (commands) should.
|
||||||
*/
|
*/
|
||||||
cJSON* comms_handle_get(void) {
|
cJSON* comms_build_status(void) {
|
||||||
//ESP_LOGI(TAG, "GET request");
|
|
||||||
|
|
||||||
rtc_reset_shutdown_timer();
|
|
||||||
|
|
||||||
// Create root JSON object
|
// Create root JSON object
|
||||||
cJSON *root = cJSON_CreateObject();
|
cJSON *root = cJSON_CreateObject();
|
||||||
if (root == NULL) {
|
if (root == NULL) {
|
||||||
@@ -39,7 +63,38 @@ cJSON* comms_handle_get(void) {
|
|||||||
cJSON_AddNumberToObject(root, "voltage", get_battery_V());
|
cJSON_AddNumberToObject(root, "voltage", get_battery_V());
|
||||||
cJSON_AddNumberToObject(root, "remaining_dist", fsm_get_remaining_distance());
|
cJSON_AddNumberToObject(root, "remaining_dist", fsm_get_remaining_distance());
|
||||||
cJSON_AddNumberToObject(root, "next_alarm", (double)rtc_get_next_alarm_s());
|
cJSON_AddNumberToObject(root, "next_alarm", (double)rtc_get_next_alarm_s());
|
||||||
|
cJSON_AddNumberToObject(root, "board_rev", hw_get_board_rev());
|
||||||
|
cJSON_AddNumberToObject(root, "fsm_error", fsm_get_error());
|
||||||
|
cJSON_AddNumberToObject(root, "jack_pos_us", (double)fsm_get_jack_pos_us());
|
||||||
|
cJSON_AddNumberToObject(root, "free_heap", (double)esp_get_free_heap_size());
|
||||||
|
cJSON_AddNumberToObject(root, "min_free_heap", (double)esp_get_minimum_free_heap_size());
|
||||||
|
|
||||||
|
// Structured error flags (match LED error code bits)
|
||||||
|
cJSON *errors = cJSON_CreateObject();
|
||||||
|
bool efuse_trip = any_efuse_tripped();
|
||||||
|
float bat_v = get_battery_V();
|
||||||
|
float low_v = get_param_value_t(PARAM_LOW_PROTECTION_V).f32;
|
||||||
|
bool low_bat = (bat_v > 0 && bat_v < low_v);
|
||||||
|
bool safety_trip = !get_is_safe();
|
||||||
|
bool leash_hit = (fsm_get_remaining_distance() <= 0);
|
||||||
|
|
||||||
|
cJSON_AddBoolToObject(errors, "efuse_aux", efuse_get(BRIDGE_AUX) != 0);
|
||||||
|
cJSON_AddBoolToObject(errors, "efuse_jack", efuse_get(BRIDGE_JACK) != 0);
|
||||||
|
cJSON_AddBoolToObject(errors, "efuse_drive", efuse_get(BRIDGE_DRIVE) != 0);
|
||||||
|
cJSON_AddBoolToObject(errors, "low_battery", low_bat);
|
||||||
|
cJSON_AddBoolToObject(errors, "rtc_not_set", !rtc_is_set());
|
||||||
|
cJSON_AddBoolToObject(errors, "safety_trip", safety_trip);
|
||||||
|
cJSON_AddBoolToObject(errors, "leash_hit", leash_hit);
|
||||||
|
// LED error code: bit0=efuse/battery, bit1=RTC, bit2=safety/leash
|
||||||
|
uint8_t led_code = 0;
|
||||||
|
if (efuse_trip || low_bat) led_code |= 0b001;
|
||||||
|
if (!rtc_is_set()) led_code |= 0b010;
|
||||||
|
if (safety_trip || leash_hit) led_code |= 0b100;
|
||||||
|
if (fsm_get_error() != 0 && led_code == 0) led_code = 0b111;
|
||||||
|
cJSON_AddNumberToObject(errors, "led_code", led_code);
|
||||||
|
cJSON_AddItemToObject(root, "errors", errors);
|
||||||
|
|
||||||
|
// Status message array
|
||||||
cJSON *msg_array = cJSON_CreateArray();
|
cJSON *msg_array = cJSON_CreateArray();
|
||||||
if (msg_array == NULL) {
|
if (msg_array == NULL) {
|
||||||
ESP_LOGE(TAG, "Failed to create msg array");
|
ESP_LOGE(TAG, "Failed to create msg array");
|
||||||
@@ -47,7 +102,6 @@ cJSON* comms_handle_get(void) {
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add state message
|
|
||||||
switch(fsm_get_state()) {
|
switch(fsm_get_state()) {
|
||||||
case STATE_IDLE:
|
case STATE_IDLE:
|
||||||
cJSON_AddItemToArray(msg_array, cJSON_CreateString("IDLE"));
|
cJSON_AddItemToArray(msg_array, cJSON_CreateString("IDLE"));
|
||||||
@@ -60,27 +114,25 @@ cJSON* comms_handle_get(void) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add warning/error messages
|
if (leash_hit)
|
||||||
if (fsm_get_remaining_distance() <= 0) {
|
|
||||||
cJSON_AddItemToArray(msg_array, cJSON_CreateString("DISTANCE LIMIT HIT"));
|
cJSON_AddItemToArray(msg_array, cJSON_CreateString("DISTANCE LIMIT HIT"));
|
||||||
|
// Per-bridge efuse messages. Preserve the original AUX → JACK → DRIVE
|
||||||
|
// order via an explicit walk; bridge_t enum order is the opposite.
|
||||||
|
static const bridge_t efuse_msg_order[] = { BRIDGE_AUX, BRIDGE_JACK, BRIDGE_DRIVE };
|
||||||
|
for (size_t i = 0; i < sizeof(efuse_msg_order)/sizeof(efuse_msg_order[0]); i++) {
|
||||||
|
bridge_t b = efuse_msg_order[i];
|
||||||
|
if (efuse_get(b)) {
|
||||||
|
char msg[32];
|
||||||
|
snprintf(msg, sizeof(msg), "%s EFUSE TRIP", bridge_names[b]);
|
||||||
|
cJSON_AddItemToArray(msg_array, cJSON_CreateString(msg));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (efuse_get(BRIDGE_AUX)) {
|
if (low_bat)
|
||||||
cJSON_AddItemToArray(msg_array, cJSON_CreateString("AUX EFUSE TRIP"));
|
cJSON_AddItemToArray(msg_array, cJSON_CreateString("LOW BATTERY"));
|
||||||
}
|
if (!rtc_is_set())
|
||||||
if (efuse_get(BRIDGE_JACK)) {
|
|
||||||
cJSON_AddItemToArray(msg_array, cJSON_CreateString("JACK EFUSE TRIP"));
|
|
||||||
}
|
|
||||||
if (efuse_get(BRIDGE_DRIVE)) {
|
|
||||||
cJSON_AddItemToArray(msg_array, cJSON_CreateString("DRIVE EFUSE TRIP"));
|
|
||||||
}
|
|
||||||
if (!rtc_is_set()) {
|
|
||||||
cJSON_AddItemToArray(msg_array, cJSON_CreateString("CLOCK NOT SET"));
|
cJSON_AddItemToArray(msg_array, cJSON_CreateString("CLOCK NOT SET"));
|
||||||
}
|
if (safety_trip)
|
||||||
|
|
||||||
if (!get_is_safe()) {
|
|
||||||
cJSON_AddItemToArray(msg_array, cJSON_CreateString("SAFETY SENSOR BREAK"));
|
cJSON_AddItemToArray(msg_array, cJSON_CreateString("SAFETY SENSOR BREAK"));
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
cJSON_AddItemToObject(root, "msg", msg_array);
|
cJSON_AddItemToObject(root, "msg", msg_array);
|
||||||
|
|
||||||
@@ -92,36 +144,15 @@ cJSON* comms_handle_get(void) {
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add all parameters
|
// Add all parameters. Numeric params funnel through param_to_double() —
|
||||||
|
// cJSON stores all numbers as double internally, so the type-specific
|
||||||
|
// accessor was just feeding the same final value.
|
||||||
for (param_idx_t i = 0; i < NUM_PARAMS; i++) {
|
for (param_idx_t i = 0; i < NUM_PARAMS; i++) {
|
||||||
const char *name = get_param_name(i);
|
const char *name = get_param_name(i);
|
||||||
param_value_t value = get_param_value_t(i);
|
if (get_param_type(i) == PARAM_TYPE_str) {
|
||||||
|
cJSON_AddStringToObject(parameters, name, get_param_string(i));
|
||||||
switch (get_param_type(i)) {
|
} else {
|
||||||
case PARAM_TYPE_f32:
|
cJSON_AddNumberToObject(parameters, name, param_to_double(i));
|
||||||
cJSON_AddNumberToObject(parameters, name, value.f32);
|
|
||||||
break;
|
|
||||||
case PARAM_TYPE_f64:
|
|
||||||
cJSON_AddNumberToObject(parameters, name, value.f64);
|
|
||||||
break;
|
|
||||||
case PARAM_TYPE_i32:
|
|
||||||
cJSON_AddNumberToObject(parameters, name, value.i32);
|
|
||||||
break;
|
|
||||||
case PARAM_TYPE_i16:
|
|
||||||
cJSON_AddNumberToObject(parameters, name, value.i16);
|
|
||||||
break;
|
|
||||||
case PARAM_TYPE_u32:
|
|
||||||
cJSON_AddNumberToObject(parameters, name, value.u32);
|
|
||||||
break;
|
|
||||||
case PARAM_TYPE_u16:
|
|
||||||
cJSON_AddNumberToObject(parameters, name, value.u16);
|
|
||||||
break;
|
|
||||||
case PARAM_TYPE_str:
|
|
||||||
cJSON_AddStringToObject(parameters, name, get_param_string(i));
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
cJSON_AddNullToObject(parameters, name);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,6 +161,15 @@ cJSON* comms_handle_get(void) {
|
|||||||
return root;
|
return root;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build a JSON object containing complete system status (GET request).
|
||||||
|
* Resets the shutdown timer because an HTTP GET is a genuine client poll.
|
||||||
|
*/
|
||||||
|
cJSON* comms_handle_get(void) {
|
||||||
|
rtc_reset_shutdown_timer();
|
||||||
|
return comms_build_status();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Process a POST request with JSON data
|
* Process a POST request with JSON data
|
||||||
*/
|
*/
|
||||||
@@ -145,9 +185,12 @@ esp_err_t comms_handle_post(cJSON *root, cJSON **response_json) {
|
|||||||
|
|
||||||
bool cmd_executed = false;
|
bool cmd_executed = false;
|
||||||
bool sleep_requested = false;
|
bool sleep_requested = false;
|
||||||
|
bool hibernate_requested = false;
|
||||||
bool reboot_requested = false;
|
bool reboot_requested = false;
|
||||||
|
bool factory_reset_requested = false;
|
||||||
bool wifi_params_changed = false;
|
bool wifi_params_changed = false;
|
||||||
bool wifi_restart_requested = false;
|
bool wifi_restart_requested = false;
|
||||||
|
bool refresh_battery_ema = false;
|
||||||
const char *error_msg = NULL;
|
const char *error_msg = NULL;
|
||||||
int params_updated = 0;
|
int params_updated = 0;
|
||||||
int params_failed = 0;
|
int params_failed = 0;
|
||||||
@@ -163,8 +206,8 @@ esp_err_t comms_handle_post(cJSON *root, cJSON **response_json) {
|
|||||||
// Process remaining_dist if present
|
// Process remaining_dist if present
|
||||||
cJSON *remaining_dist = cJSON_GetObjectItem(root, "remaining_dist");
|
cJSON *remaining_dist = cJSON_GetObjectItem(root, "remaining_dist");
|
||||||
if (cJSON_IsNumber(remaining_dist)) {
|
if (cJSON_IsNumber(remaining_dist)) {
|
||||||
int64_t new_dist = (int64_t)cJSON_GetNumberValue(remaining_dist);
|
float new_dist = (float)cJSON_GetNumberValue(remaining_dist);
|
||||||
ESP_LOGI(TAG, "Setting remaining_dist to %lld", new_dist);
|
ESP_LOGI(TAG, "Setting remaining_dist to %.3f", new_dist);
|
||||||
fsm_set_remaining_distance(new_dist);
|
fsm_set_remaining_distance(new_dist);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -194,11 +237,11 @@ esp_err_t comms_handle_post(cJSON *root, cJSON **response_json) {
|
|||||||
pulse_override(FSM_OVERRIDE_DRIVE_REV);
|
pulse_override(FSM_OVERRIDE_DRIVE_REV);
|
||||||
cmd_executed = true;
|
cmd_executed = true;
|
||||||
}
|
}
|
||||||
else if (strcmp(cmd_str, "up") == 0) {
|
else if (strcmp(cmd_str, "extend") == 0) {
|
||||||
pulse_override(FSM_OVERRIDE_JACK_UP);
|
pulse_override(FSM_OVERRIDE_JACK_UP);
|
||||||
cmd_executed = true;
|
cmd_executed = true;
|
||||||
}
|
}
|
||||||
else if (strcmp(cmd_str, "down") == 0) {
|
else if (strcmp(cmd_str, "retract") == 0) {
|
||||||
pulse_override(FSM_OVERRIDE_JACK_DOWN);
|
pulse_override(FSM_OVERRIDE_JACK_DOWN);
|
||||||
cmd_executed = true;
|
cmd_executed = true;
|
||||||
}
|
}
|
||||||
@@ -206,14 +249,26 @@ esp_err_t comms_handle_post(cJSON *root, cJSON **response_json) {
|
|||||||
pulse_override(FSM_OVERRIDE_AUX);
|
pulse_override(FSM_OVERRIDE_AUX);
|
||||||
cmd_executed = true;
|
cmd_executed = true;
|
||||||
}
|
}
|
||||||
|
else if (strcmp(cmd_str, "stop_override") == 0) {
|
||||||
|
stop_override();
|
||||||
|
cmd_executed = true;
|
||||||
|
}
|
||||||
else if (strcmp(cmd_str, "reboot") == 0) {
|
else if (strcmp(cmd_str, "reboot") == 0) {
|
||||||
reboot_requested = true;
|
reboot_requested = true;
|
||||||
cmd_executed = true;
|
cmd_executed = true;
|
||||||
}
|
}
|
||||||
|
else if (strcmp(cmd_str, "factory_reset") == 0) {
|
||||||
|
factory_reset_requested = true;
|
||||||
|
cmd_executed = true;
|
||||||
|
}
|
||||||
else if (strcmp(cmd_str, "sleep") == 0) {
|
else if (strcmp(cmd_str, "sleep") == 0) {
|
||||||
sleep_requested = true;
|
sleep_requested = true;
|
||||||
cmd_executed = true;
|
cmd_executed = true;
|
||||||
}
|
}
|
||||||
|
else if (strcmp(cmd_str, "hibernate") == 0) {
|
||||||
|
hibernate_requested = true;
|
||||||
|
cmd_executed = true;
|
||||||
|
}
|
||||||
else if (strcmp(cmd_str, "rf_clear_temp") == 0) {
|
else if (strcmp(cmd_str, "rf_clear_temp") == 0) {
|
||||||
rf_433_clear_temp_keycodes();
|
rf_433_clear_temp_keycodes();
|
||||||
cmd_executed = true;
|
cmd_executed = true;
|
||||||
@@ -275,33 +330,11 @@ esp_err_t comms_handle_post(cJSON *root, cJSON **response_json) {
|
|||||||
ESP_LOGI(TAG, "FSM_CMD_CALIBRATE_JACK_PREP");
|
ESP_LOGI(TAG, "FSM_CMD_CALIBRATE_JACK_PREP");
|
||||||
cmd_executed = true;
|
cmd_executed = true;
|
||||||
}
|
}
|
||||||
else if (strcmp(cmd_str, "cal_jack_finish") == 0) {
|
|
||||||
cJSON *amt = cJSON_GetObjectItem(root, "amt");
|
|
||||||
if (cJSON_IsNumber(amt) && amt->valuedouble >= 0 && amt->valuedouble < 8) {
|
|
||||||
ESP_LOGI(TAG, "FSM_CMD_CALIBRATE_JACK_FINISH");
|
|
||||||
fsm_set_cal_val(amt->valuedouble);
|
|
||||||
fsm_request(FSM_CMD_CALIBRATE_JACK_FINISH);
|
|
||||||
cmd_executed = true;
|
|
||||||
} else {
|
|
||||||
error_msg = "cal_jack_finish requires amt parameter (0-8)";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (strcmp(cmd_str, "cal_drive_start") == 0) {
|
else if (strcmp(cmd_str, "cal_drive_start") == 0) {
|
||||||
fsm_request(FSM_CMD_CALIBRATE_DRIVE_PREP);
|
fsm_request(FSM_CMD_CALIBRATE_DRIVE_PREP);
|
||||||
ESP_LOGI(TAG, "FSM_CMD_CALIBRATE_DRIVE_PREP");
|
ESP_LOGI(TAG, "FSM_CMD_CALIBRATE_DRIVE_PREP");
|
||||||
cmd_executed = true;
|
cmd_executed = true;
|
||||||
}
|
}
|
||||||
else if (strcmp(cmd_str, "cal_drive_finish") == 0) {
|
|
||||||
cJSON *amt = cJSON_GetObjectItem(root, "amt");
|
|
||||||
if (cJSON_IsNumber(amt) && amt->valuedouble >= 0 && amt->valuedouble < 8) {
|
|
||||||
ESP_LOGI(TAG, "FSM_CMD_CALIBRATE_DRIVE_FINISH");
|
|
||||||
fsm_set_cal_val(amt->valuedouble);
|
|
||||||
fsm_request(FSM_CMD_CALIBRATE_DRIVE_FINISH);
|
|
||||||
cmd_executed = true;
|
|
||||||
} else {
|
|
||||||
error_msg = "cal_drive_finish requires amt parameter (0-8)";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (strcmp(cmd_str, "cal_get") == 0) {
|
else if (strcmp(cmd_str, "cal_get") == 0) {
|
||||||
ESP_LOGI(TAG, "CAL_GET");
|
ESP_LOGI(TAG, "CAL_GET");
|
||||||
|
|
||||||
@@ -313,6 +346,20 @@ esp_err_t comms_handle_post(cJSON *root, cJSON **response_json) {
|
|||||||
*response_json = response;
|
*response_json = response;
|
||||||
return ESP_OK;
|
return ESP_OK;
|
||||||
}
|
}
|
||||||
|
else if (strcmp(cmd_str, "set_board_rev") == 0) {
|
||||||
|
cJSON *rev = cJSON_GetObjectItem(root, "board_rev");
|
||||||
|
if (cJSON_IsNumber(rev)) {
|
||||||
|
uint16_t r = (uint16_t)cJSON_GetNumberValue(rev);
|
||||||
|
if (hw_set_board_rev(r) == ESP_OK) {
|
||||||
|
ESP_LOGI(TAG, "Board rev set to %u", r);
|
||||||
|
cmd_executed = true;
|
||||||
|
} else {
|
||||||
|
error_msg = "Failed to write board_rev to NVS";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
error_msg = "set_board_rev requires board_rev parameter";
|
||||||
|
}
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
ESP_LOGW(TAG, "Unknown command: %s", cmd_str);
|
ESP_LOGW(TAG, "Unknown command: %s", cmd_str);
|
||||||
error_msg = "Unknown command";
|
error_msg = "Unknown command";
|
||||||
@@ -351,83 +398,28 @@ esp_err_t comms_handle_post(cJSON *root, cJSON **response_json) {
|
|||||||
wifi_params_changed = true;
|
wifi_params_changed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* If the battery conversion params change, refresh the EMA
|
||||||
|
* so get_battery_V() reflects the new K/OFFSET immediately. */
|
||||||
|
if (param_idx == PARAM_V_SENS_K || param_idx == PARAM_V_SENS_OFFSET) {
|
||||||
|
refresh_battery_ema = true;
|
||||||
|
}
|
||||||
|
|
||||||
cJSON *value_json = cJSON_GetObjectItem(parameters, key);
|
cJSON *value_json = cJSON_GetObjectItem(parameters, key);
|
||||||
|
|
||||||
// Set parameter value based on type
|
if (set_param_from_json(param_idx, value_json)) {
|
||||||
switch (get_param_type(param_idx)) {
|
params_updated++;
|
||||||
case PARAM_TYPE_f32:
|
} else {
|
||||||
if (cJSON_IsNumber(value_json)) {
|
ESP_LOGW(TAG, "Type mismatch for parameter: %s", key);
|
||||||
set_param_value_t(param_idx, (param_value_t){.f32 = value_json->valuedouble});
|
params_failed++;
|
||||||
params_updated++;
|
|
||||||
} else {
|
|
||||||
ESP_LOGW(TAG, "Type mismatch for parameter: %s", key);
|
|
||||||
params_failed++;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case PARAM_TYPE_f64:
|
|
||||||
if (cJSON_IsNumber(value_json)) {
|
|
||||||
set_param_value_t(param_idx, (param_value_t){.f64 = value_json->valuedouble});
|
|
||||||
params_updated++;
|
|
||||||
} else {
|
|
||||||
ESP_LOGW(TAG, "Type mismatch for parameter: %s", key);
|
|
||||||
params_failed++;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case PARAM_TYPE_i32:
|
|
||||||
if (cJSON_IsNumber(value_json)) {
|
|
||||||
set_param_value_t(param_idx, (param_value_t){.i32 = value_json->valueint});
|
|
||||||
params_updated++;
|
|
||||||
} else {
|
|
||||||
ESP_LOGW(TAG, "Type mismatch for parameter: %s", key);
|
|
||||||
params_failed++;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case PARAM_TYPE_i16:
|
|
||||||
if (cJSON_IsNumber(value_json)) {
|
|
||||||
set_param_value_t(param_idx, (param_value_t){.i16 = value_json->valueint});
|
|
||||||
params_updated++;
|
|
||||||
} else {
|
|
||||||
ESP_LOGW(TAG, "Type mismatch for parameter: %s", key);
|
|
||||||
params_failed++;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case PARAM_TYPE_u32:
|
|
||||||
if (cJSON_IsNumber(value_json)) {
|
|
||||||
set_param_value_t(param_idx, (param_value_t){.u32 = value_json->valueint});
|
|
||||||
params_updated++;
|
|
||||||
} else {
|
|
||||||
ESP_LOGW(TAG, "Type mismatch for parameter: %s", key);
|
|
||||||
params_failed++;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case PARAM_TYPE_u16:
|
|
||||||
if (cJSON_IsNumber(value_json)) {
|
|
||||||
set_param_value_t(param_idx, (param_value_t){.u16 = value_json->valueint});
|
|
||||||
params_updated++;
|
|
||||||
} else {
|
|
||||||
ESP_LOGW(TAG, "Type mismatch for parameter: %s", key);
|
|
||||||
params_failed++;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case PARAM_TYPE_str:
|
|
||||||
if (cJSON_IsString(value_json)) {
|
|
||||||
set_param_string(param_idx, value_json->valuestring);
|
|
||||||
params_updated++;
|
|
||||||
} else {
|
|
||||||
ESP_LOGW(TAG, "Type mismatch for parameter: %s", key);
|
|
||||||
params_failed++;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
ESP_LOGW(TAG, "Unknown type for parameter: %s", key);
|
|
||||||
params_failed++;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (params_updated > 0) {
|
if (params_updated > 0) {
|
||||||
rtc_schedule_next_alarm();
|
rtc_schedule_next_alarm();
|
||||||
commit_params();
|
commit_params();
|
||||||
|
if (refresh_battery_ema) {
|
||||||
|
reset_battery_ema();
|
||||||
|
}
|
||||||
if (wifi_params_changed) {
|
if (wifi_params_changed) {
|
||||||
ESP_LOGI(TAG, "WiFi params changed — restarting WiFi AP");
|
ESP_LOGI(TAG, "WiFi params changed — restarting WiFi AP");
|
||||||
wifi_restart_requested = true;
|
wifi_restart_requested = true;
|
||||||
@@ -458,6 +450,14 @@ esp_err_t comms_handle_post(cJSON *root, cJSON **response_json) {
|
|||||||
return ESP_OK;
|
return ESP_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (factory_reset_requested) {
|
||||||
|
cJSON_AddStringToObject(response, "status", "ok");
|
||||||
|
cJSON_AddStringToObject(response, "message", "Factory reset — erasing params and rebooting...");
|
||||||
|
cJSON_AddBoolToObject(response, "factory_reset", true);
|
||||||
|
*response_json = response;
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
if (sleep_requested) {
|
if (sleep_requested) {
|
||||||
cJSON_AddStringToObject(response, "status", "ok");
|
cJSON_AddStringToObject(response, "status", "ok");
|
||||||
cJSON_AddStringToObject(response, "message", "Sleeping...");
|
cJSON_AddStringToObject(response, "message", "Sleeping...");
|
||||||
@@ -466,6 +466,14 @@ esp_err_t comms_handle_post(cJSON *root, cJSON **response_json) {
|
|||||||
return ESP_OK;
|
return ESP_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (hibernate_requested) {
|
||||||
|
cJSON_AddStringToObject(response, "status", "ok");
|
||||||
|
cJSON_AddStringToObject(response, "message", "Hibernating (button to wake)...");
|
||||||
|
cJSON_AddBoolToObject(response, "hibernate", true);
|
||||||
|
*response_json = response;
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
if (error_msg != NULL) {
|
if (error_msg != NULL) {
|
||||||
cJSON_AddStringToObject(response, "status", "error");
|
cJSON_AddStringToObject(response, "status", "error");
|
||||||
cJSON_AddStringToObject(response, "message", error_msg);
|
cJSON_AddStringToObject(response, "message", error_msg);
|
||||||
|
|||||||
13
main/comms.h
13
main/comms.h
@@ -13,13 +13,24 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Process a GET request - returns complete system status as JSON
|
* Process a GET request - returns complete system status as JSON.
|
||||||
|
* Resets the inactivity/shutdown timer (a GET is a real client poll).
|
||||||
*
|
*
|
||||||
* @return cJSON object containing system status, or NULL on error
|
* @return cJSON object containing system status, or NULL on error
|
||||||
* Caller is responsible for deleting the returned object with cJSON_Delete()
|
* Caller is responsible for deleting the returned object with cJSON_Delete()
|
||||||
*/
|
*/
|
||||||
cJSON* comms_handle_get(void);
|
cJSON* comms_handle_get(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build the same system-status JSON as comms_handle_get() but WITHOUT
|
||||||
|
* resetting the shutdown timer. Used by the periodic WebSocket status push
|
||||||
|
* so an open-but-idle browser tab doesn't prevent the device from sleeping.
|
||||||
|
*
|
||||||
|
* @return cJSON object containing system status, or NULL on error
|
||||||
|
* Caller is responsible for deleting the returned object with cJSON_Delete()
|
||||||
|
*/
|
||||||
|
cJSON* comms_build_status(void);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Process a POST request - handles commands, parameter updates, time updates
|
* Process a POST request - handles commands, parameter updates, time updates
|
||||||
*
|
*
|
||||||
|
|||||||
17
main/comms_events.h
Normal file
17
main/comms_events.h
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
#ifndef COMMS_EVENTS_H
|
||||||
|
#define COMMS_EVENTS_H
|
||||||
|
|
||||||
|
#include "freertos/FreeRTOS.h"
|
||||||
|
#include "freertos/event_groups.h"
|
||||||
|
|
||||||
|
// Shared event group for WiFi/BT readiness signaling.
|
||||||
|
// Set by webserver.c and bt_hid.c; waited on by main.c during alarm wake.
|
||||||
|
|
||||||
|
#define WIFI_READY_BIT BIT0 // Set when STA connected or softAP is up
|
||||||
|
#define BT_READY_BIT BIT1 // Set when BT scan task starts
|
||||||
|
#define COMMS_ALL_BITS (WIFI_READY_BIT | BT_READY_BIT)
|
||||||
|
|
||||||
|
// Must be created once (by main.c) before webserver_init() / bt_hid_init()
|
||||||
|
extern EventGroupHandle_t comms_event_group;
|
||||||
|
|
||||||
|
#endif // COMMS_EVENTS_H
|
||||||
@@ -5,11 +5,15 @@
|
|||||||
* Author: Thad
|
* Author: Thad
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
// See README.md for FSM documentation (states, guards, timing).
|
||||||
|
|
||||||
#include "control_fsm.h"
|
#include "control_fsm.h"
|
||||||
#include "esp_task_wdt.h"
|
#include "esp_task_wdt.h"
|
||||||
#include "esp_timer.h"
|
#include "esp_timer.h"
|
||||||
#include "i2c.h"
|
#include "i2c.h"
|
||||||
#include "power_mgmt.h"
|
#include "power_mgmt.h"
|
||||||
|
#include "bringup.h"
|
||||||
#include "rtc_wdt.h"
|
#include "rtc_wdt.h"
|
||||||
#include "driver/gpio.h"
|
#include "driver/gpio.h"
|
||||||
#include "sc_err.h"
|
#include "sc_err.h"
|
||||||
@@ -29,14 +33,21 @@
|
|||||||
|
|
||||||
static QueueHandle_t fsm_cmd_queue = NULL;
|
static QueueHandle_t fsm_cmd_queue = NULL;
|
||||||
|
|
||||||
|
// fsm_init() does not zero these — they persist across panics/WDT resets.
|
||||||
|
// Only cleared by explicit user action (fsm_clear_error, fsm_set_remaining_distance).
|
||||||
RTC_DATA_ATTR esp_err_t fsm_error = ESP_OK;
|
RTC_DATA_ATTR esp_err_t fsm_error = ESP_OK;
|
||||||
esp_err_t fsm_get_error() { return fsm_error; }
|
esp_err_t fsm_get_error() { return fsm_error; }
|
||||||
void fsm_clear_error() { fsm_error = ESP_OK; }
|
void fsm_clear_error() { fsm_error = ESP_OK; }
|
||||||
|
|
||||||
|
|
||||||
|
/* override_time + override_cmd are written from RF/BT/comms tasks and read
|
||||||
|
* from the control task. int64_t isn't atomic on a 32-bit MCU, so we wrap
|
||||||
|
* read/write in a critical section to prevent torn reads (which could land
|
||||||
|
* override_time far in the future and run a motor for seconds longer than
|
||||||
|
* RF_PULSE_LENGTH). */
|
||||||
|
static portMUX_TYPE override_spin = portMUX_INITIALIZER_UNLOCKED;
|
||||||
int64_t override_time = -1;
|
int64_t override_time = -1;
|
||||||
fsm_override_t override_cmd;
|
fsm_override_t override_cmd = FSM_OVERRIDE_DRIVE_FWD;
|
||||||
//int64_t override_cooldown[8] = {-1};
|
|
||||||
bool enabled = false;
|
bool enabled = false;
|
||||||
|
|
||||||
float this_move_dist = 0.0f;
|
float this_move_dist = 0.0f;
|
||||||
@@ -52,6 +63,11 @@ static int64_t jack_start_us = 0;
|
|||||||
static int64_t jack_trans_us = 0;
|
static int64_t jack_trans_us = 0;
|
||||||
static int64_t jack_finish_us = 0;
|
static int64_t jack_finish_us = 0;
|
||||||
|
|
||||||
|
/* Cumulative jack extension estimate in microseconds (0 = fully retracted).
|
||||||
|
* Reset to 0 whenever SENSOR_JACK trips (home position). Persists across
|
||||||
|
* panics/WDT resets so the guard survives a mid-extension reboot. */
|
||||||
|
RTC_DATA_ATTR static int64_t jack_pos_us = 0;
|
||||||
|
|
||||||
volatile fsm_state_t current_state = STATE_IDLE;
|
volatile fsm_state_t current_state = STATE_IDLE;
|
||||||
volatile int64_t fsm_now = 0;
|
volatile int64_t fsm_now = 0;
|
||||||
volatile bool start_running_request = false;
|
volatile bool start_running_request = false;
|
||||||
@@ -61,6 +77,14 @@ fsm_state_t fsm_get_state() {
|
|||||||
return current_state;
|
return current_state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool fsm_is_idle(void) {
|
||||||
|
return current_state == STATE_IDLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t fsm_get_jack_pos_us(void) {
|
||||||
|
return jack_pos_us;
|
||||||
|
}
|
||||||
|
|
||||||
static int64_t timer_end = 0;
|
static int64_t timer_end = 0;
|
||||||
static int64_t timer_start = 0;
|
static int64_t timer_start = 0;
|
||||||
static inline void set_timer(uint64_t us) {
|
static inline void set_timer(uint64_t us) {
|
||||||
@@ -73,17 +97,97 @@ void pulse_override(fsm_override_t cmd) {
|
|||||||
if (soft_idle_is_active()) return;
|
if (soft_idle_is_active()) return;
|
||||||
if (current_state == STATE_IDLE) {
|
if (current_state == STATE_IDLE) {
|
||||||
rtc_reset_shutdown_timer();
|
rtc_reset_shutdown_timer();
|
||||||
|
int64_t deadline = fsm_now + (int64_t)get_param_value_t(PARAM_RF_PULSE_LENGTH).u32;
|
||||||
|
portENTER_CRITICAL(&override_spin);
|
||||||
override_cmd = cmd;
|
override_cmd = cmd;
|
||||||
override_time = fsm_now + get_param_value_t(PARAM_RF_PULSE_LENGTH).u32;
|
override_time = deadline;
|
||||||
|
portEXIT_CRITICAL(&override_spin);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void stop_override(void) {
|
||||||
|
portENTER_CRITICAL(&override_spin);
|
||||||
|
override_time = 0;
|
||||||
|
portEXIT_CRITICAL(&override_spin);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Atomic snapshot of override_time + override_cmd for the control task. */
|
||||||
|
static inline void override_snapshot(int64_t *time_out, fsm_override_t *cmd_out) {
|
||||||
|
portENTER_CRITICAL(&override_spin);
|
||||||
|
*time_out = override_time;
|
||||||
|
*cmd_out = override_cmd;
|
||||||
|
portEXIT_CRITICAL(&override_spin);
|
||||||
|
}
|
||||||
|
|
||||||
int64_t fsm_cal_t, fsm_cal_e;
|
int64_t fsm_cal_t, fsm_cal_e;
|
||||||
float fsm_cal_val;
|
|
||||||
void fsm_set_cal_val(float v) {fsm_cal_val = v;}
|
|
||||||
int64_t fsm_get_cal_t(){return fsm_cal_t;}
|
int64_t fsm_get_cal_t(){return fsm_cal_t;}
|
||||||
int64_t fsm_get_cal_e(){return fsm_cal_e;}
|
int64_t fsm_get_cal_e(){return fsm_cal_e;}
|
||||||
|
|
||||||
|
const char *sc_err_str(esp_err_t e) {
|
||||||
|
switch (e) {
|
||||||
|
case ESP_OK: return "OK";
|
||||||
|
case SC_ERR_EFUSE_TRIP_1: return "EFUSE 1 TRIP";
|
||||||
|
case SC_ERR_EFUSE_TRIP_2: return "EFUSE 2 TRIP";
|
||||||
|
case SC_ERR_EFUSE_TRIP_3: return "EFUSE 3 TRIP";
|
||||||
|
case SC_ERR_SAFETY_TRIP: return "SAFETY NOT SET";
|
||||||
|
case SC_ERR_LEASH_HIT: return "NO REMAINING DISTANCE";
|
||||||
|
case SC_ERR_RTC_NOT_SET: return "CLOCK NOT SET";
|
||||||
|
case SC_ERR_LOW_BATTERY: return "INSUFFICIENT VOLTAGE";
|
||||||
|
default: return "UNKNOWN";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *fsm_state_str(fsm_state_t s) {
|
||||||
|
switch (s) {
|
||||||
|
case STATE_IDLE: return "IDLE";
|
||||||
|
case STATE_MOVE_START_DELAY: return "MOVE_START_DELAY";
|
||||||
|
case STATE_JACK_UP_START: return "JACK_UP_START";
|
||||||
|
case STATE_JACK_UP: return "JACK_UP";
|
||||||
|
case STATE_DRIVE_START_DELAY: return "DRIVE_START_DELAY";
|
||||||
|
case STATE_DRIVE_FLUFF_START: return "DRIVE_FLUFF_START";
|
||||||
|
case STATE_DRIVE: return "DRIVE";
|
||||||
|
case STATE_DRIVE_END_DELAY: return "DRIVE_END_DELAY";
|
||||||
|
case STATE_JACK_DOWN: return "JACK_DOWN";
|
||||||
|
case STATE_UNDO_JACK_START: return "UNDO_JACK_START";
|
||||||
|
case STATE_CALIBRATE_JACK_DELAY: return "CALIBRATE_JACK_DELAY";
|
||||||
|
case STATE_CALIBRATE_JACK_MOVE: return "CALIBRATE_JACK_MOVE";
|
||||||
|
case STATE_CALIBRATE_DRIVE_DELAY: return "CALIBRATE_DRIVE_DELAY";
|
||||||
|
case STATE_CALIBRATE_DRIVE_MOVE: return "CALIBRATE_DRIVE_MOVE";
|
||||||
|
default: return "UNKNOWN";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Preconditions for accepting a START command. Returns ESP_OK if every gate
|
||||||
|
* passes, otherwise the SC_ERR_* code of the first failing gate. Caller is
|
||||||
|
* expected to assign the returned code into `fsm_error` and skip the start.
|
||||||
|
* Order matters: most-actionable error first (voltage → safety → efuses) so
|
||||||
|
* the operator sees the dominant fault when more than one is true. */
|
||||||
|
static esp_err_t fsm_check_start_preconditions(void) {
|
||||||
|
esp_err_t code = ESP_OK;
|
||||||
|
if (get_battery_V() < get_param_value_t(PARAM_LOW_PROTECTION_V).f32) code = SC_ERR_LOW_BATTERY;
|
||||||
|
else if (!get_is_safe()) code = SC_ERR_SAFETY_TRIP;
|
||||||
|
else if (efuse_get(BRIDGE_DRIVE)) code = SC_ERR_EFUSE_TRIP_1;
|
||||||
|
else if (efuse_get(BRIDGE_JACK)) code = SC_ERR_EFUSE_TRIP_2;
|
||||||
|
else if (efuse_get(BRIDGE_AUX)) code = SC_ERR_EFUSE_TRIP_3;
|
||||||
|
if (code != ESP_OK) ESP_LOGI(TAG, "FAILED TO START; %s", sc_err_str(code));
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Gate a calibrate-mode state transition: only accepts the transition from
|
||||||
|
* `expected` to `next`, optionally requiring battery above LOW_PROTECTION_V.
|
||||||
|
* Returns true if the transition was made; caller then does per-case work
|
||||||
|
* (set_timer / save cal data / reset sensor counter) that doesn't fit a
|
||||||
|
* uniform helper. Battery gate is on for PREP and START (we are about to
|
||||||
|
* energize a motor); off for END (no motor action). */
|
||||||
|
static bool fsm_calibrate_transition(fsm_state_t expected, fsm_state_t next,
|
||||||
|
bool require_battery) {
|
||||||
|
if (current_state != expected) return false;
|
||||||
|
if (require_battery &&
|
||||||
|
get_battery_V() <= get_param_value_t(PARAM_LOW_PROTECTION_V).f32) return false;
|
||||||
|
current_state = next;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
void fsm_request(fsm_cmd_t cmd)
|
void fsm_request(fsm_cmd_t cmd)
|
||||||
{
|
{
|
||||||
// STOP always goes through (safety). All other commands are blocked during soft idle —
|
// STOP always goes through (safety). All other commands are blocked during soft idle —
|
||||||
@@ -104,6 +208,7 @@ int8_t fsm_get_current_progress(int8_t denominator) {
|
|||||||
case STATE_JACK_DOWN:
|
case STATE_JACK_DOWN:
|
||||||
case STATE_MOVE_START_DELAY:
|
case STATE_MOVE_START_DELAY:
|
||||||
case STATE_DRIVE_START_DELAY:
|
case STATE_DRIVE_START_DELAY:
|
||||||
|
case STATE_DRIVE_FLUFF_START:
|
||||||
case STATE_DRIVE_END_DELAY:
|
case STATE_DRIVE_END_DELAY:
|
||||||
if (timer_end != timer_start)
|
if (timer_end != timer_start)
|
||||||
x = (fsm_now-timer_start)*denominator/(timer_end-timer_start);
|
x = (fsm_now-timer_start)*denominator/(timer_end-timer_start);
|
||||||
@@ -120,50 +225,66 @@ int8_t fsm_get_current_progress(int8_t denominator) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#define JACK_TIME get_param_value_t(PARAM_JACK_KT).f32 * get_param_value_t(PARAM_JACK_DIST ).f32
|
#define JACK_TIME get_param_value_t(PARAM_JACK_KT).f32 * get_param_value_t(PARAM_JACK_DIST).f32
|
||||||
#define JACK_DOWN_TIME (jack_finish_us - jack_start_us) * 105/100
|
#define JACK_MAX_TIME get_param_value_t(PARAM_JACK_KT).f32 * get_param_value_t(PARAM_JACK_MAX ).f32
|
||||||
|
|
||||||
|
/* Symmetric jack-down duration: how long jack-up actually ran, plus 5%.
|
||||||
|
* If jack_start_us / jack_finish_us are zero or negative (panic recovery,
|
||||||
|
* or a transition that skipped the normal path) the delta is unsafe — fall
|
||||||
|
* back to the parameter-derived JACK_TIME as a floor so we don't either
|
||||||
|
* (a) cut the jack-down to ~0 and leave the actuator extended, or (b) run
|
||||||
|
* forever. */
|
||||||
|
static inline int64_t _jack_down_time_us(void) {
|
||||||
|
int64_t delta = jack_finish_us - jack_start_us;
|
||||||
|
int64_t floor_us = (int64_t)JACK_TIME;
|
||||||
|
if (delta < floor_us) delta = floor_us;
|
||||||
|
return delta * 105 / 100;
|
||||||
|
}
|
||||||
|
#define JACK_DOWN_TIME _jack_down_time_us()
|
||||||
#define DRIVE_TIME get_param_value_t(PARAM_DRIVE_KT).f32 * this_move_dist
|
#define DRIVE_TIME get_param_value_t(PARAM_DRIVE_KT).f32 * this_move_dist
|
||||||
#define DRIVE_DIST get_param_value_t(PARAM_DRIVE_KE).f32 * this_move_dist
|
#define DRIVE_DIST get_param_value_t(PARAM_DRIVE_KE).f32 * this_move_dist
|
||||||
|
|
||||||
int64_t last_log_time = 0;
|
int64_t last_log_time = 0;
|
||||||
#define LOGSIZE 39
|
/* FSM log payload (single current channel — V5 has one shared ACS sensor; V4
|
||||||
|
* had three but the per-bridge values are redundant since only one bridge is
|
||||||
|
* active at a time). Layout:
|
||||||
|
* [0:8] ts_ms u64
|
||||||
|
* [8:12] bat_V f32
|
||||||
|
* [12:16] current_A f32 — sum of bridge currents (mutually exclusive)
|
||||||
|
* [16:18] counter i16
|
||||||
|
* [18:19] sensors u8
|
||||||
|
* [19:23] heat f32 — max bridge heat
|
||||||
|
* [23:25] i2c_out u16 — last 16-bit TCA9555 output state
|
||||||
|
* (high byte = OUTPUT0 / LEDs, low = OUTPUT1 / relays) */
|
||||||
|
#define LOGSIZE 25
|
||||||
esp_err_t send_fsm_log() {
|
esp_err_t send_fsm_log() {
|
||||||
if(!rtc_is_set()) return ESP_OK;
|
if(!rtc_is_set()) return ESP_OK;
|
||||||
|
|
||||||
uint8_t entry[LOGSIZE] = {};
|
uint8_t entry[LOGSIZE] = {};
|
||||||
|
|
||||||
// Pack 64-bit timestamp into bytes 1-8
|
|
||||||
uint64_t be_timestamp = rtc_get_ms();
|
uint64_t be_timestamp = rtc_get_ms();
|
||||||
memcpy(&entry[0], &be_timestamp, 8);
|
memcpy(&entry[0], &be_timestamp, 8);
|
||||||
|
|
||||||
// Pack 32-bit voltages/currents into bytes 9-24
|
|
||||||
float be_voltage = get_battery_V();
|
float be_voltage = get_battery_V();
|
||||||
memcpy(&entry[8], &be_voltage, 4);
|
memcpy(&entry[8], &be_voltage, 4);
|
||||||
float be_current1 = get_bridge_raw_A(BRIDGE_DRIVE);
|
|
||||||
memcpy(&entry[12], &be_current1, 4);
|
float current_A = 0.0f;
|
||||||
float be_current2 = get_bridge_raw_A(BRIDGE_JACK);
|
for (bridge_t b = 0; b < N_BRIDGES; b++) current_A += get_bridge_raw_A(b);
|
||||||
memcpy(&entry[16], &be_current2, 4);
|
memcpy(&entry[12], ¤t_A, 4);
|
||||||
float be_current3 = get_bridge_raw_A(BRIDGE_AUX);
|
|
||||||
memcpy(&entry[20], &be_current3, 4);
|
|
||||||
|
|
||||||
int16_t be_counter = get_sensor_counter(SENSOR_DRIVE);
|
int16_t be_counter = get_sensor_counter(SENSOR_DRIVE);
|
||||||
memcpy(&entry[24], &be_counter, 2);
|
memcpy(&entry[16], &be_counter, 2);
|
||||||
|
|
||||||
entry[26] = pack_sensors();
|
entry[18] = pack_sensors();
|
||||||
|
|
||||||
|
float heat = max_efuse_heat();
|
||||||
|
memcpy(&entry[19], &heat, 4);
|
||||||
|
|
||||||
|
uint16_t i2c_out = i2c_get_outputs();
|
||||||
|
memcpy(&entry[23], &i2c_out, 2);
|
||||||
float heat1 = efuse_get_heat(BRIDGE_DRIVE);
|
|
||||||
memcpy(&entry[27], &heat1, 4);
|
|
||||||
float heat2 = efuse_get_heat(BRIDGE_JACK);
|
|
||||||
memcpy(&entry[31], &heat2, 4);
|
|
||||||
float heat3 = efuse_get_heat(BRIDGE_AUX);
|
|
||||||
memcpy(&entry[35], &heat3, 4);
|
|
||||||
|
|
||||||
last_log_time = esp_timer_get_time();
|
last_log_time = esp_timer_get_time();
|
||||||
|
|
||||||
|
|
||||||
log_write(entry, LOGSIZE, fsm_get_state());
|
log_write(entry, LOGSIZE, fsm_get_state());
|
||||||
|
|
||||||
//ESP_LOGI(TAG, "WROTE LOG; %lld / %ld/%ld; %5.2f %5.2f %5.2f", (long long)rtc_get_ms(), (unsigned long)log_get_tail(), (unsigned long)log_get_head(), heat1, heat2, heat3);
|
//ESP_LOGI(TAG, "WROTE LOG; %lld / %ld/%ld; %5.2f %5.2f %5.2f", (long long)rtc_get_ms(), (unsigned long)log_get_tail(), (unsigned long)log_get_head(), heat1, heat2, heat3);
|
||||||
@@ -178,12 +299,18 @@ void control_task(void *param) {
|
|||||||
const TickType_t xFrequency = pdMS_TO_TICKS(20);
|
const TickType_t xFrequency = pdMS_TO_TICKS(20);
|
||||||
enabled = true;
|
enabled = true;
|
||||||
|
|
||||||
sensors_init();
|
// sensors_init() is called from main.c as a critical init (before FSM starts)
|
||||||
|
|
||||||
while (enabled) {
|
while (enabled) {
|
||||||
vTaskDelayUntil(&xLastWakeTime, xFrequency);
|
vTaskDelayUntil(&xLastWakeTime, xFrequency);
|
||||||
fsm_now = esp_timer_get_time();
|
fsm_now = esp_timer_get_time();
|
||||||
|
|
||||||
|
/* Bring-up tool owns the relays and ADCs while active — skip. */
|
||||||
|
if (bringup_mode_is_active()) {
|
||||||
|
esp_task_wdt_reset();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
bool log = false;
|
bool log = false;
|
||||||
|
|
||||||
/**** READ INPUTS ****/
|
/**** READ INPUTS ****/
|
||||||
@@ -202,48 +329,37 @@ void control_task(void *param) {
|
|||||||
case FSM_CMD_START:
|
case FSM_CMD_START:
|
||||||
// Check if we have remaining distance before starting
|
// Check if we have remaining distance before starting
|
||||||
if (remaining_distance <= 0.0f) {
|
if (remaining_distance <= 0.0f) {
|
||||||
ESP_LOGI(TAG, "FAILED TO START; NO REMAINING DISTANCE");
|
ESP_LOGI(TAG, "FAILED TO START; %s", sc_err_str(SC_ERR_LEASH_HIT));
|
||||||
fsm_error = SC_ERR_LEASH_HIT;
|
fsm_error = SC_ERR_LEASH_HIT;
|
||||||
log = true;
|
log = true;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
this_move_dist = MIN(get_param_value_t(PARAM_DRIVE_DIST).f32, remaining_distance);
|
this_move_dist = MIN(get_param_value_t(PARAM_DRIVE_DIST).f32, remaining_distance);
|
||||||
|
goto do_start;
|
||||||
case FSM_CMD_START_IGNORE_OVERTRAVEL:
|
case FSM_CMD_START_IGNORE_OVERTRAVEL:
|
||||||
this_move_dist = get_param_value_t(PARAM_DRIVE_DIST).f32;
|
this_move_dist = get_param_value_t(PARAM_DRIVE_DIST).f32;
|
||||||
if (current_state == STATE_IDLE) {
|
do_start:
|
||||||
|
/* Silently drop START commands received in any non-idle state
|
||||||
if (get_battery_V() < get_param_value_t(PARAM_LOW_PROTECTION_V).f32) {
|
* (e.g. duplicate request while already moving). Preconditions
|
||||||
ESP_LOGI(TAG, "FAILED TO START; INSUFFICIENT VOLTAGE");
|
* are checked only once we know the state is acceptable. */
|
||||||
fsm_error = SC_ERR_LOW_BATTERY;
|
if (current_state != STATE_IDLE) break;
|
||||||
|
{
|
||||||
|
esp_err_t guard = fsm_check_start_preconditions();
|
||||||
|
if (guard != ESP_OK) {
|
||||||
|
fsm_error = guard;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (!get_is_safe()) {
|
|
||||||
ESP_LOGI(TAG, "FAILED TO START; SAFETY NOT SET");
|
|
||||||
fsm_error = SC_ERR_SAFETY_TRIP;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (efuse_get(BRIDGE_DRIVE)) {
|
|
||||||
ESP_LOGI(TAG, "FAILED TO START; EFUSE 1 TRIP");
|
|
||||||
fsm_error = SC_ERR_EFUSE_TRIP_1;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (efuse_get(BRIDGE_JACK)) {
|
|
||||||
ESP_LOGI(TAG, "FAILED TO START; EFUSE 2 TRIP");
|
|
||||||
fsm_error = SC_ERR_EFUSE_TRIP_2;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (efuse_get(BRIDGE_AUX)) {
|
|
||||||
ESP_LOGI(TAG, "FAILED TO START; EFUSE 3 TRIP");
|
|
||||||
fsm_error = SC_ERR_EFUSE_TRIP_3;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
ESP_LOGI(TAG, "STARTING");
|
ESP_LOGI(TAG, "STARTING");
|
||||||
fsm_error = ESP_OK; // if everything is OK now, we're OK.
|
fsm_error = ESP_OK; // if everything is OK now, we're OK.
|
||||||
|
/* Zero jack timestamps so JACK_DOWN_TIME on this cycle
|
||||||
|
* never inherits a stale value from a prior run. */
|
||||||
|
jack_start_us = 0;
|
||||||
|
jack_trans_us = 0;
|
||||||
|
jack_finish_us = 0;
|
||||||
current_state = STATE_MOVE_START_DELAY;
|
current_state = STATE_MOVE_START_DELAY;
|
||||||
log = true;
|
log = true;
|
||||||
set_timer(TRANSITION_DELAY_US);
|
set_timer(TRANSITION_DELAY_US);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case FSM_CMD_STOP:
|
case FSM_CMD_STOP:
|
||||||
current_state = STATE_IDLE;
|
current_state = STATE_IDLE;
|
||||||
@@ -259,105 +375,89 @@ void control_task(void *param) {
|
|||||||
enabled = false;
|
enabled = false;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
/* Calibration sub-FSM: PREP arms (IDLE → DELAY), START energizes
|
||||||
|
* the motor with a hard timeout (DELAY → MOVE), END records
|
||||||
|
* the result and returns to idle (MOVE → IDLE). PREP/START
|
||||||
|
* require battery; END doesn't (no motor action). */
|
||||||
case FSM_CMD_CALIBRATE_JACK_PREP:
|
case FSM_CMD_CALIBRATE_JACK_PREP:
|
||||||
ESP_LOGI(TAG, "FSM_CMD_CALIBRATE_JACK_PREP");
|
ESP_LOGI(TAG, "FSM_CMD_CALIBRATE_JACK_PREP");
|
||||||
if (current_state == STATE_IDLE
|
if (fsm_calibrate_transition(STATE_IDLE, STATE_CALIBRATE_JACK_DELAY, true))
|
||||||
&& get_battery_V() > get_param_value_t(PARAM_LOW_PROTECTION_V).f32) {
|
log = true;
|
||||||
current_state = STATE_CALIBRATE_JACK_DELAY;
|
|
||||||
log = true;
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case FSM_CMD_CALIBRATE_JACK_START:
|
case FSM_CMD_CALIBRATE_JACK_START:
|
||||||
ESP_LOGI(TAG, "FSM_CMD_CALIBRATE_JACK_START");
|
ESP_LOGI(TAG, "FSM_CMD_CALIBRATE_JACK_START");
|
||||||
if (current_state == STATE_CALIBRATE_JACK_DELAY
|
if (fsm_calibrate_transition(STATE_CALIBRATE_JACK_DELAY,
|
||||||
&& get_battery_V() > get_param_value_t(PARAM_LOW_PROTECTION_V).f32) {
|
STATE_CALIBRATE_JACK_MOVE, true)) {
|
||||||
current_state = STATE_CALIBRATE_JACK_MOVE;
|
|
||||||
log = true;
|
|
||||||
set_timer(CALIBRATE_JACK_MAX_TIME);
|
set_timer(CALIBRATE_JACK_MAX_TIME);
|
||||||
|
log = true;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case FSM_CMD_CALIBRATE_JACK_END:
|
case FSM_CMD_CALIBRATE_JACK_END:
|
||||||
ESP_LOGI(TAG, "FSM_CMD_CALIBRATE_JACK_END");
|
ESP_LOGI(TAG, "FSM_CMD_CALIBRATE_JACK_END");
|
||||||
if (current_state == STATE_CALIBRATE_JACK_MOVE) {
|
if (fsm_calibrate_transition(STATE_CALIBRATE_JACK_MOVE,
|
||||||
|
STATE_IDLE, false)) {
|
||||||
fsm_cal_t = fsm_now - timer_start;
|
fsm_cal_t = fsm_now - timer_start;
|
||||||
current_state = STATE_IDLE;
|
log = true;
|
||||||
log = true;
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case FSM_CMD_CALIBRATE_JACK_FINISH:
|
|
||||||
set_param_value_t(PARAM_JACK_KT,
|
|
||||||
(param_value_t){.f32 = fsm_cal_t / fsm_cal_val});
|
|
||||||
ESP_LOGI(TAG, "FSM_CMD_CALIBRATE_JACK_FINISH -> %f", get_param_value_t(PARAM_JACK_KT).f32);
|
|
||||||
break;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
case FSM_CMD_CALIBRATE_DRIVE_PREP:
|
case FSM_CMD_CALIBRATE_DRIVE_PREP:
|
||||||
ESP_LOGI(TAG, "FSM_CMD_CALIBRATE_DRIVE_PREP");
|
ESP_LOGI(TAG, "FSM_CMD_CALIBRATE_DRIVE_PREP");
|
||||||
if (current_state == STATE_IDLE
|
if (fsm_calibrate_transition(STATE_IDLE, STATE_CALIBRATE_DRIVE_DELAY, true))
|
||||||
&& get_battery_V() > get_param_value_t(PARAM_LOW_PROTECTION_V).f32) {
|
log = true;
|
||||||
current_state = STATE_CALIBRATE_DRIVE_DELAY;
|
|
||||||
log = true;
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case FSM_CMD_CALIBRATE_DRIVE_START:
|
case FSM_CMD_CALIBRATE_DRIVE_START:
|
||||||
ESP_LOGI(TAG, "FSM_CMD_CALIBRATE_DRIVE_START");
|
ESP_LOGI(TAG, "FSM_CMD_CALIBRATE_DRIVE_START");
|
||||||
if (current_state == STATE_CALIBRATE_DRIVE_DELAY
|
if (fsm_calibrate_transition(STATE_CALIBRATE_DRIVE_DELAY,
|
||||||
&& get_battery_V() > get_param_value_t(PARAM_LOW_PROTECTION_V).f32) {
|
STATE_CALIBRATE_DRIVE_MOVE, true)) {
|
||||||
current_state = STATE_CALIBRATE_DRIVE_MOVE;
|
|
||||||
log = true;
|
|
||||||
set_timer(CALIBRATE_DRIVE_MAX_TIME);
|
set_timer(CALIBRATE_DRIVE_MAX_TIME);
|
||||||
set_sensor_counter(SENSOR_DRIVE, 0);
|
set_sensor_counter(SENSOR_DRIVE, 0);
|
||||||
|
log = true;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case FSM_CMD_CALIBRATE_DRIVE_END:
|
case FSM_CMD_CALIBRATE_DRIVE_END:
|
||||||
ESP_LOGI(TAG, "FSM_CMD_CALIBRATE_DRIVE_END");
|
ESP_LOGI(TAG, "FSM_CMD_CALIBRATE_DRIVE_END");
|
||||||
if (current_state == STATE_CALIBRATE_DRIVE_MOVE) {
|
if (fsm_calibrate_transition(STATE_CALIBRATE_DRIVE_MOVE,
|
||||||
|
STATE_IDLE, false)) {
|
||||||
fsm_cal_t = fsm_now - timer_start;
|
fsm_cal_t = fsm_now - timer_start;
|
||||||
fsm_cal_e = get_sensor_counter(SENSOR_DRIVE);
|
fsm_cal_e = get_sensor_counter(SENSOR_DRIVE);
|
||||||
current_state = STATE_IDLE;
|
log = true;
|
||||||
log = true;
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case FSM_CMD_CALIBRATE_DRIVE_FINISH:
|
|
||||||
set_param_value_t(PARAM_DRIVE_KT,
|
|
||||||
(param_value_t){.f32 = fsm_cal_t / fsm_cal_val});
|
|
||||||
set_param_value_t(PARAM_DRIVE_KE,
|
|
||||||
(param_value_t){.f32 = fsm_cal_e / fsm_cal_val});
|
|
||||||
ESP_LOGI(TAG, "FSM_CMD_CALIBRATE_DRIVE_FINISH -> %f / %f",
|
|
||||||
get_param_value_t(PARAM_DRIVE_KT).f32,
|
|
||||||
get_param_value_t(PARAM_DRIVE_KE).f32);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!enabled) break;
|
if (!enabled) break;
|
||||||
|
|
||||||
/**** STATE TRANSITIONS ****/
|
/**** STATE TRANSITIONS ****/
|
||||||
|
// Every active state checks safety first — break triggers UNDO_JACK (emergency lower).
|
||||||
|
// Normal cycle: IDLE → DELAY → JACK_UP_START → JACK_UP → DRIVE → JACK_DOWN → IDLE
|
||||||
switch (current_state) {
|
switch (current_state) {
|
||||||
case STATE_IDLE:
|
case STATE_IDLE:
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case STATE_MOVE_START_DELAY:
|
case STATE_MOVE_START_DELAY:
|
||||||
|
// 1s pause before raising jack — lets operator abort after pressing start
|
||||||
if (!get_is_safe()) {
|
if (!get_is_safe()) {
|
||||||
fsm_error = SC_ERR_SAFETY_TRIP;
|
fsm_error = SC_ERR_SAFETY_TRIP;
|
||||||
current_state = STATE_IDLE;
|
current_state = STATE_IDLE; // haven't raised jack yet, safe to just stop
|
||||||
log = true;
|
log = true;
|
||||||
} else if (timer_done()) {
|
} else if (timer_done()) {
|
||||||
current_state = STATE_JACK_UP_START;
|
current_state = STATE_JACK_UP_START;
|
||||||
set_timer(JACK_TIME / 2); // First phase is half of total jack time
|
set_timer(JACK_TIME / 2); // first phase: detect engagement (half of total jack time)
|
||||||
jack_start_us = fsm_now;
|
jack_start_us = fsm_now;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case STATE_JACK_UP_START:
|
case STATE_JACK_UP_START:
|
||||||
|
// Detect when jack engages the load (current spike, efuse, or timeout)
|
||||||
if (!get_is_safe()) {
|
if (!get_is_safe()) {
|
||||||
fsm_error = SC_ERR_SAFETY_TRIP;
|
fsm_error = SC_ERR_SAFETY_TRIP;
|
||||||
current_state = STATE_UNDO_JACK_START;
|
current_state = STATE_UNDO_JACK_START;
|
||||||
jack_finish_us = fsm_now;
|
jack_finish_us = fsm_now;
|
||||||
log = true;
|
log = true;
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
if (efuse_get(BRIDGE_JACK)) {
|
if (efuse_get(BRIDGE_JACK)) {
|
||||||
ESP_LOGI(TAG, "START->UP BY EFUSE");
|
ESP_LOGI(TAG, "START->UP BY EFUSE");
|
||||||
current_state = STATE_JACK_UP;
|
current_state = STATE_JACK_UP;
|
||||||
@@ -383,7 +483,9 @@ void control_task(void *param) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case STATE_JACK_UP:
|
case STATE_JACK_UP:
|
||||||
|
// Continue raising until timer or efuse — records finish time for symmetric jack-down
|
||||||
if (!get_is_safe()) {
|
if (!get_is_safe()) {
|
||||||
fsm_error = SC_ERR_SAFETY_TRIP;
|
fsm_error = SC_ERR_SAFETY_TRIP;
|
||||||
current_state = STATE_UNDO_JACK_START;
|
current_state = STATE_UNDO_JACK_START;
|
||||||
@@ -392,109 +494,118 @@ void control_task(void *param) {
|
|||||||
log = true;
|
log = true;
|
||||||
} else {
|
} else {
|
||||||
if (timer_done() || efuse_get(BRIDGE_JACK)) {
|
if (timer_done() || efuse_get(BRIDGE_JACK)) {
|
||||||
// Track total time including first phase
|
|
||||||
current_state = STATE_DRIVE_START_DELAY;
|
current_state = STATE_DRIVE_START_DELAY;
|
||||||
jack_finish_us = fsm_now;
|
jack_finish_us = fsm_now; // used to calculate symmetric jack-down duration
|
||||||
log = true;
|
log = true;
|
||||||
set_timer(TRANSITION_DELAY_US);
|
set_timer(TRANSITION_DELAY_US);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case STATE_DRIVE_START_DELAY:
|
case STATE_DRIVE_START_DELAY:
|
||||||
|
// 1s quiet pause between jack-up and fluffer spin-up.
|
||||||
|
// All motors off here so the jack-up current fully settles
|
||||||
|
// before we energize the fluffer.
|
||||||
if (!get_is_safe()) {
|
if (!get_is_safe()) {
|
||||||
fsm_error = SC_ERR_SAFETY_TRIP;
|
fsm_error = SC_ERR_SAFETY_TRIP;
|
||||||
current_state = STATE_UNDO_JACK_START;
|
current_state = STATE_UNDO_JACK_START;
|
||||||
set_timer(JACK_DOWN_TIME);
|
set_timer(JACK_DOWN_TIME);
|
||||||
log = true;
|
log = true;
|
||||||
|
} else if (timer_done()) {
|
||||||
|
current_state = STATE_DRIVE_FLUFF_START;
|
||||||
|
log = true;
|
||||||
|
set_timer((uint64_t)get_param_value_t(PARAM_FLUFF_PREDRIVE_MS).u32 * 1000);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case STATE_DRIVE_FLUFF_START:
|
||||||
|
// Fluffer alone for 1s, then drive+fluffer. Splits the old
|
||||||
|
// "jack-up+fluff concurrent" sequence so aux never overlaps
|
||||||
|
// with jack on V5's shared current sensor.
|
||||||
|
if (!get_is_safe()) {
|
||||||
|
fsm_error = SC_ERR_SAFETY_TRIP;
|
||||||
|
current_state = STATE_UNDO_JACK_START;
|
||||||
|
set_timer(JACK_DOWN_TIME);
|
||||||
|
log = true;
|
||||||
|
} else if (efuse_get(BRIDGE_AUX)) {
|
||||||
|
fsm_error = SC_ERR_EFUSE_TRIP_3;
|
||||||
|
current_state = STATE_UNDO_JACK_START;
|
||||||
|
set_timer(JACK_DOWN_TIME);
|
||||||
|
log = true;
|
||||||
} else if (timer_done()) {
|
} else if (timer_done()) {
|
||||||
current_state = STATE_DRIVE;
|
current_state = STATE_DRIVE;
|
||||||
log = true;
|
log = true;
|
||||||
set_timer(DRIVE_TIME);
|
set_timer(DRIVE_TIME);
|
||||||
// Set the encoder counter to track remaining distance in this move
|
// Encoder counts down from -target to 0 (negative = distance remaining)
|
||||||
set_sensor_counter(SENSOR_DRIVE, -DRIVE_DIST);
|
set_sensor_counter(SENSOR_DRIVE, -DRIVE_DIST);
|
||||||
// Record starting encoder position AFTER setting it
|
|
||||||
move_start_encoder = get_sensor_counter(SENSOR_DRIVE);
|
move_start_encoder = get_sensor_counter(SENSOR_DRIVE);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case STATE_DRIVE:
|
case STATE_DRIVE:
|
||||||
|
// Horizontal travel — stops on timer, encoder target, or efuse trip
|
||||||
if (!get_is_safe()) {
|
if (!get_is_safe()) {
|
||||||
fsm_error = SC_ERR_SAFETY_TRIP;
|
fsm_error = SC_ERR_SAFETY_TRIP;
|
||||||
current_state = STATE_UNDO_JACK_START;
|
current_state = STATE_UNDO_JACK_START;
|
||||||
set_timer(JACK_DOWN_TIME);
|
set_timer(JACK_DOWN_TIME);
|
||||||
log = true;
|
log = true;
|
||||||
} else {
|
} else if (efuse_get(BRIDGE_DRIVE)) {
|
||||||
|
// Fault — deduct actual distance traveled (may be partial).
|
||||||
|
// Checked before the normal-completion branch so a tick
|
||||||
|
// that satisfies both conditions doesn't double-deduct
|
||||||
|
// remaining_distance.
|
||||||
int32_t current_encoder = get_sensor_counter(SENSOR_DRIVE);
|
int32_t current_encoder = get_sensor_counter(SENSOR_DRIVE);
|
||||||
int32_t ticks_traveled = current_encoder - move_start_encoder;
|
int32_t ticks_traveled = current_encoder - move_start_encoder;
|
||||||
float ke = get_param_value_t(PARAM_DRIVE_KE).f32;
|
float ke = get_param_value_t(PARAM_DRIVE_KE).f32;
|
||||||
float distance_traveled = ticks_traveled / ke;
|
float distance_traveled = ticks_traveled / ke;
|
||||||
|
|
||||||
// Stop if timer expires OR encoder target reached OR we've used up remaining distance
|
remaining_distance -= distance_traveled;
|
||||||
|
if (remaining_distance < 0.0f) remaining_distance = 0.0f;
|
||||||
|
|
||||||
|
fsm_error = SC_ERR_EFUSE_TRIP_1;
|
||||||
|
current_state = STATE_UNDO_JACK_START;
|
||||||
|
set_timer(JACK_DOWN_TIME);
|
||||||
|
log = true;
|
||||||
|
} else {
|
||||||
|
int32_t current_encoder = get_sensor_counter(SENSOR_DRIVE);
|
||||||
if (timer_done() || current_encoder > 0) {
|
if (timer_done() || current_encoder > 0) {
|
||||||
// Update remaining distance based on actual travel
|
// Normal completion — deduct planned distance from leash
|
||||||
//if (current_encoder < 0)
|
remaining_distance -= this_move_dist;
|
||||||
remaining_distance -= this_move_dist;
|
|
||||||
//else
|
|
||||||
// remaining_distance -= distance_traveled;
|
|
||||||
|
|
||||||
current_state = STATE_DRIVE_END_DELAY;
|
current_state = STATE_DRIVE_END_DELAY;
|
||||||
log = true;
|
log = true;
|
||||||
set_timer(TRANSITION_DELAY_US);
|
set_timer(TRANSITION_DELAY_US);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (efuse_get(BRIDGE_DRIVE)) {
|
|
||||||
// Update remaining distance even on fault
|
|
||||||
remaining_distance -= distance_traveled;
|
|
||||||
if (remaining_distance < 0.0f) remaining_distance = 0.0f;
|
|
||||||
|
|
||||||
fsm_error = SC_ERR_EFUSE_TRIP_1;
|
|
||||||
current_state = STATE_UNDO_JACK_START;
|
|
||||||
set_timer(JACK_DOWN_TIME);
|
|
||||||
log = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case STATE_DRIVE_END_DELAY:
|
case STATE_DRIVE_END_DELAY:
|
||||||
|
// 1s pause after drive — then lower jack normally.
|
||||||
|
// Goes straight to STATE_JACK_DOWN so the LED/comms message
|
||||||
|
// reads "MOVING…" rather than "CANCELLING MOVE" on a normal
|
||||||
|
// cycle. STATE_UNDO_JACK_START remains the path for explicit
|
||||||
|
// undo / safety-break / efuse-trip recovery.
|
||||||
if (!get_is_safe()) {
|
if (!get_is_safe()) {
|
||||||
fsm_error = SC_ERR_SAFETY_TRIP;
|
fsm_error = SC_ERR_SAFETY_TRIP;
|
||||||
current_state = STATE_UNDO_JACK_START;
|
current_state = STATE_UNDO_JACK_START;
|
||||||
|
set_timer(JACK_DOWN_TIME);
|
||||||
log = true;
|
log = true;
|
||||||
} else if (timer_done()) {
|
} else if (timer_done()) {
|
||||||
current_state = STATE_UNDO_JACK_START;
|
current_state = STATE_JACK_DOWN;
|
||||||
|
set_timer(JACK_DOWN_TIME);
|
||||||
log = true;
|
log = true;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case STATE_JACK_DOWN:
|
case STATE_JACK_DOWN:
|
||||||
|
// Lower jack — stops on efuse (hit ground), position sensor, or timeout
|
||||||
if (efuse_get(BRIDGE_JACK)) {
|
if (efuse_get(BRIDGE_JACK)) {
|
||||||
|
|
||||||
ESP_LOGI(TAG, "DOWN->IDLE BY EFUSE");
|
ESP_LOGI(TAG, "DOWN->IDLE BY EFUSE");
|
||||||
// Current spike detected
|
|
||||||
current_state = STATE_IDLE;
|
current_state = STATE_IDLE;
|
||||||
log = true;
|
log = true;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*if (get_bridge_overcurrent(BRIDGE_JACK, get_param_value_t(PARAM_JACK_I_DOWN).f32)) {
|
|
||||||
|
|
||||||
ESP_LOGI(TAG, "DOWN->IDLE BY OVERCURRENT");
|
|
||||||
// Current spike detected
|
|
||||||
current_state = STATE_IDLE;
|
|
||||||
log = true;
|
|
||||||
break;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
if (get_bridge_spike(BRIDGE_JACK, get_param_value_t(PARAM_JACK_IS_DOWN).f32)) {
|
|
||||||
|
|
||||||
ESP_LOGI(TAG, "DOWN->IDLE BY SPIKE");
|
|
||||||
// Current spike detected
|
|
||||||
current_state = STATE_IDLE;
|
|
||||||
log = true;
|
|
||||||
break;
|
|
||||||
|
|
||||||
}*/
|
|
||||||
|
|
||||||
if (get_sensor(SENSOR_JACK)) {
|
if (get_sensor(SENSOR_JACK)) {
|
||||||
ESP_LOGI(TAG, "DOWN->IDLE BY SENSOR");
|
ESP_LOGI(TAG, "DOWN->IDLE BY SENSOR");
|
||||||
current_state = STATE_IDLE;
|
current_state = STATE_IDLE;
|
||||||
@@ -502,18 +613,16 @@ void control_task(void *param) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (timer_done() ) {
|
if (timer_done()) {
|
||||||
ESP_LOGI(TAG, "DOWN->IDLE BY TIME");
|
ESP_LOGI(TAG, "DOWN->IDLE BY TIME");
|
||||||
current_state = STATE_IDLE;
|
current_state = STATE_IDLE;
|
||||||
log = true;
|
log = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|
||||||
case STATE_UNDO_JACK_START:
|
case STATE_UNDO_JACK_START:
|
||||||
// wait for e-fuse to un-trip
|
// Emergency: wait for jack efuse to cool, then lower
|
||||||
if (!efuse_get(BRIDGE_JACK)) {
|
if (!efuse_get(BRIDGE_JACK)) {
|
||||||
set_timer(JACK_DOWN_TIME);
|
set_timer(JACK_DOWN_TIME);
|
||||||
current_state = STATE_JACK_DOWN;
|
current_state = STATE_JACK_DOWN;
|
||||||
@@ -521,10 +630,8 @@ void control_task(void *param) {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|
||||||
case STATE_CALIBRATE_JACK_DELAY:
|
case STATE_CALIBRATE_JACK_DELAY:
|
||||||
// no way out of this except a command
|
break; // waiting for user command to begin measurement
|
||||||
break;
|
|
||||||
case STATE_CALIBRATE_JACK_MOVE:
|
case STATE_CALIBRATE_JACK_MOVE:
|
||||||
if (timer_done()) {
|
if (timer_done()) {
|
||||||
current_state = STATE_IDLE;
|
current_state = STATE_IDLE;
|
||||||
@@ -532,10 +639,8 @@ void control_task(void *param) {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|
||||||
case STATE_CALIBRATE_DRIVE_DELAY:
|
case STATE_CALIBRATE_DRIVE_DELAY:
|
||||||
// no way out of this except a command
|
break; // waiting for user command to begin measurement
|
||||||
break;
|
|
||||||
case STATE_CALIBRATE_DRIVE_MOVE:
|
case STATE_CALIBRATE_DRIVE_MOVE:
|
||||||
if (!get_is_safe() || timer_done()) {
|
if (!get_is_safe() || timer_done()) {
|
||||||
current_state = STATE_IDLE;
|
current_state = STATE_IDLE;
|
||||||
@@ -549,10 +654,14 @@ void control_task(void *param) {
|
|||||||
|
|
||||||
/**** SET OUTPUTS ****/
|
/**** SET OUTPUTS ****/
|
||||||
switch (current_state) {
|
switch (current_state) {
|
||||||
case STATE_IDLE:
|
case STATE_IDLE: {
|
||||||
// In idle we still accept override commands
|
// In idle we still accept override commands. Snapshot both fields
|
||||||
if (override_time > fsm_now) {
|
// atomically to defend against the int64 torn read on writers.
|
||||||
switch(override_cmd) {
|
int64_t local_time;
|
||||||
|
fsm_override_t local_cmd;
|
||||||
|
override_snapshot(&local_time, &local_cmd);
|
||||||
|
if (local_time > fsm_now) {
|
||||||
|
switch(local_cmd) {
|
||||||
case FSM_OVERRIDE_DRIVE_FWD:
|
case FSM_OVERRIDE_DRIVE_FWD:
|
||||||
if (efuse_get(BRIDGE_DRIVE)){
|
if (efuse_get(BRIDGE_DRIVE)){
|
||||||
drive_relays((relay_port_t){.bridges = {
|
drive_relays((relay_port_t){.bridges = {
|
||||||
@@ -585,7 +694,7 @@ void control_task(void *param) {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case FSM_OVERRIDE_JACK_UP:
|
case FSM_OVERRIDE_JACK_UP:
|
||||||
if (efuse_get(BRIDGE_JACK)){
|
if (efuse_get(BRIDGE_JACK) || jack_pos_us >= (int64_t)JACK_MAX_TIME) {
|
||||||
drive_relays((relay_port_t){.bridges = {
|
drive_relays((relay_port_t){.bridges = {
|
||||||
.DRIVE=BRIDGE_OFF,
|
.DRIVE=BRIDGE_OFF,
|
||||||
.JACK=BRIDGE_OFF,
|
.JACK=BRIDGE_OFF,
|
||||||
@@ -651,14 +760,16 @@ void control_task(void *param) {
|
|||||||
}});
|
}});
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
} /* close STATE_IDLE block scope */
|
||||||
case STATE_CALIBRATE_JACK_MOVE:
|
case STATE_CALIBRATE_JACK_MOVE:
|
||||||
case STATE_JACK_UP_START:
|
case STATE_JACK_UP_START:
|
||||||
case STATE_JACK_UP:
|
case STATE_JACK_UP:
|
||||||
// jack up and fluff
|
// jack up only — fluffer is deferred to STATE_DRIVE_FLUFF_START
|
||||||
|
// so aux and jack never energize together.
|
||||||
drive_relays((relay_port_t){.bridges = {
|
drive_relays((relay_port_t){.bridges = {
|
||||||
.DRIVE=BRIDGE_OFF,
|
.DRIVE=BRIDGE_OFF,
|
||||||
.JACK=BRIDGE_FWD,
|
.JACK=BRIDGE_FWD,
|
||||||
.AUX=BRIDGE_FWD
|
.AUX=BRIDGE_OFF
|
||||||
}});
|
}});
|
||||||
rtc_reset_shutdown_timer();
|
rtc_reset_shutdown_timer();
|
||||||
log = true;
|
log = true;
|
||||||
@@ -683,8 +794,19 @@ void control_task(void *param) {
|
|||||||
rtc_reset_shutdown_timer();
|
rtc_reset_shutdown_timer();
|
||||||
log = true;
|
log = true;
|
||||||
break;
|
break;
|
||||||
case STATE_UNDO_JACK_START:
|
case STATE_DRIVE_START_DELAY:
|
||||||
case STATE_DRIVE_START_DELAY:
|
// Quiet 1s after jack-up — all motors off so jack current
|
||||||
|
// settles before the fluffer starts.
|
||||||
|
drive_relays((relay_port_t){.bridges = {
|
||||||
|
.DRIVE=BRIDGE_OFF,
|
||||||
|
.JACK=BRIDGE_OFF,
|
||||||
|
.AUX=BRIDGE_OFF
|
||||||
|
}});
|
||||||
|
rtc_reset_shutdown_timer();
|
||||||
|
log = true;
|
||||||
|
break;
|
||||||
|
case STATE_DRIVE_FLUFF_START:
|
||||||
|
case STATE_UNDO_JACK_START:
|
||||||
case STATE_DRIVE_END_DELAY:
|
case STATE_DRIVE_END_DELAY:
|
||||||
// only fluffer
|
// only fluffer
|
||||||
drive_relays((relay_port_t){.bridges = {
|
drive_relays((relay_port_t){.bridges = {
|
||||||
@@ -707,6 +829,48 @@ void control_task(void *param) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**** JACK POSITION TRACKING ****/
|
||||||
|
/* Update jack_pos_us each tick based on what the relay outputs just did.
|
||||||
|
* SENSOR_JACK tripping is the definitive home reset (overrides everything). */
|
||||||
|
{
|
||||||
|
const int64_t TICK_US = 20000LL;
|
||||||
|
bridge_dir_t jack_dir = BRIDGE_OFF;
|
||||||
|
|
||||||
|
switch (current_state) {
|
||||||
|
case STATE_JACK_UP_START:
|
||||||
|
case STATE_JACK_UP:
|
||||||
|
case STATE_CALIBRATE_JACK_MOVE:
|
||||||
|
jack_dir = BRIDGE_FWD;
|
||||||
|
break;
|
||||||
|
case STATE_JACK_DOWN:
|
||||||
|
jack_dir = BRIDGE_REV;
|
||||||
|
break;
|
||||||
|
case STATE_IDLE: {
|
||||||
|
int64_t local_time;
|
||||||
|
fsm_override_t local_cmd;
|
||||||
|
override_snapshot(&local_time, &local_cmd);
|
||||||
|
if (local_time > fsm_now && !efuse_get(BRIDGE_JACK)) {
|
||||||
|
if (local_cmd == FSM_OVERRIDE_JACK_UP && jack_pos_us < (int64_t)JACK_MAX_TIME)
|
||||||
|
jack_dir = BRIDGE_FWD;
|
||||||
|
else if (local_cmd == FSM_OVERRIDE_JACK_DOWN)
|
||||||
|
jack_dir = BRIDGE_REV;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (jack_dir == BRIDGE_FWD)
|
||||||
|
jack_pos_us += TICK_US;
|
||||||
|
else if (jack_dir == BRIDGE_REV) {
|
||||||
|
jack_pos_us -= TICK_US;
|
||||||
|
if (jack_pos_us < 0LL) jack_pos_us = 0LL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (get_sensor(SENSOR_JACK))
|
||||||
|
jack_pos_us = 0LL;
|
||||||
|
}
|
||||||
|
|
||||||
/**** LOGGING ****/
|
/**** LOGGING ****/
|
||||||
if (log) send_fsm_log();
|
if (log) send_fsm_log();
|
||||||
|
|
||||||
|
|||||||
@@ -17,12 +17,10 @@ typedef enum {
|
|||||||
FSM_CMD_CALIBRATE_JACK_PREP,
|
FSM_CMD_CALIBRATE_JACK_PREP,
|
||||||
FSM_CMD_CALIBRATE_JACK_START,
|
FSM_CMD_CALIBRATE_JACK_START,
|
||||||
FSM_CMD_CALIBRATE_JACK_END,
|
FSM_CMD_CALIBRATE_JACK_END,
|
||||||
FSM_CMD_CALIBRATE_JACK_FINISH,
|
|
||||||
|
|
||||||
FSM_CMD_CALIBRATE_DRIVE_PREP,
|
FSM_CMD_CALIBRATE_DRIVE_PREP,
|
||||||
FSM_CMD_CALIBRATE_DRIVE_START,
|
FSM_CMD_CALIBRATE_DRIVE_START,
|
||||||
FSM_CMD_CALIBRATE_DRIVE_END,
|
FSM_CMD_CALIBRATE_DRIVE_END
|
||||||
FSM_CMD_CALIBRATE_DRIVE_FINISH
|
|
||||||
} fsm_cmd_t;
|
} fsm_cmd_t;
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
@@ -40,7 +38,13 @@ typedef enum {
|
|||||||
STATE_CALIBRATE_JACK_MOVE,
|
STATE_CALIBRATE_JACK_MOVE,
|
||||||
|
|
||||||
STATE_CALIBRATE_DRIVE_DELAY,
|
STATE_CALIBRATE_DRIVE_DELAY,
|
||||||
STATE_CALIBRATE_DRIVE_MOVE
|
STATE_CALIBRATE_DRIVE_MOVE,
|
||||||
|
|
||||||
|
/* New — appended here to avoid renumbering existing log entries.
|
||||||
|
* Runs the fluffer alone for ~1 s between DRIVE_START_DELAY and DRIVE
|
||||||
|
* so the aux motor never overlaps with the jack (which caused the aux
|
||||||
|
* e-fuse to spuriously trip on V5's shared current sensor). */
|
||||||
|
STATE_DRIVE_FLUFF_START
|
||||||
} fsm_state_t;
|
} fsm_state_t;
|
||||||
#define LOG_TYPE_BAT 100
|
#define LOG_TYPE_BAT 100
|
||||||
#define LOG_TYPE_CRASH 101
|
#define LOG_TYPE_CRASH 101
|
||||||
@@ -86,11 +90,11 @@ typedef enum {
|
|||||||
#define N_BRIDGES 3
|
#define N_BRIDGES 3
|
||||||
|
|
||||||
void pulse_override(fsm_override_t cmd);
|
void pulse_override(fsm_override_t cmd);
|
||||||
|
void stop_override(void);
|
||||||
|
|
||||||
esp_err_t fsm_init();
|
esp_err_t fsm_init();
|
||||||
esp_err_t fsm_stop();
|
esp_err_t fsm_stop();
|
||||||
|
|
||||||
void fsm_set_cal_val(float v);
|
|
||||||
int64_t fsm_get_cal_t();
|
int64_t fsm_get_cal_t();
|
||||||
int64_t fsm_get_cal_e();
|
int64_t fsm_get_cal_e();
|
||||||
void fsm_request(fsm_cmd_t cmd);
|
void fsm_request(fsm_cmd_t cmd);
|
||||||
@@ -107,6 +111,12 @@ void fsm_set_remaining_distance(float x);
|
|||||||
int8_t fsm_get_current_progress(int8_t remainder);
|
int8_t fsm_get_current_progress(int8_t remainder);
|
||||||
|
|
||||||
fsm_state_t fsm_get_state();
|
fsm_state_t fsm_get_state();
|
||||||
|
bool fsm_is_idle(void);
|
||||||
|
int64_t fsm_get_jack_pos_us(void);
|
||||||
|
|
||||||
|
/* Human-readable name for a fsm_state_t value — used by logs and any web
|
||||||
|
* UI surface that wants to render a friendly state name. Returns a literal. */
|
||||||
|
const char *fsm_state_str(fsm_state_t s);
|
||||||
|
|
||||||
int8_t get_bridge_state(bridge_t bridge);
|
int8_t get_bridge_state(bridge_t bridge);
|
||||||
|
|
||||||
|
|||||||
797
main/hard_ui.c
797
main/hard_ui.c
@@ -1,797 +0,0 @@
|
|||||||
/*
|
|
||||||
* lcd.c
|
|
||||||
*
|
|
||||||
* Created on: Dec 12, 2025
|
|
||||||
* Author: Thad
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
/* NOTICE: THIS IS A DUMPING GROUND FOR OBSOLETE CODE SINCE WE NO LONGER HAVE AN LCD
|
|
||||||
NONE OF THIS IS TESTED.
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Debounce & Repeat Settings
|
|
||||||
#define DEBOUNCE_MS 50
|
|
||||||
#define REPEAT_MS 200
|
|
||||||
#define REPEAT_START_MS 700
|
|
||||||
|
|
||||||
static uint8_t lcd_col = 0;
|
|
||||||
static uint8_t lcd_row = 0;
|
|
||||||
|
|
||||||
static bool debounced_state[4] = {false};
|
|
||||||
static bool last_known_state[4] = {false};
|
|
||||||
static uint64_t last_stable_time[4] = {0};
|
|
||||||
static uint64_t last_change_time[4] = {0};
|
|
||||||
static uint8_t claimed_repeats[4] = {0};
|
|
||||||
|
|
||||||
|
|
||||||
// === DELAY HELPERS ===
|
|
||||||
static inline void delay_us(uint32_t us) {
|
|
||||||
esp_rom_delay_us(us);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static esp_err_t tca_write_word_16(uint8_t reg, uint16_t value) {
|
|
||||||
uint8_t data[3] = { reg, (uint8_t)(value & 0xFF), (uint8_t)(value >> 8) };
|
|
||||||
return i2c_master_write_to_device(I2C_PORT, TCA_ADDR, data, 3, pdMS_TO_TICKS(1000));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// === TCA9555 PORT CONTROL ===
|
|
||||||
static esp_err_t tca_set_config_port0(uint16_t config_port0) {
|
|
||||||
return tca_write_word_16(TCA_REG_CONFIG0, config_port0);
|
|
||||||
}
|
|
||||||
|
|
||||||
static esp_err_t tca_port_write(uint8_t value) {
|
|
||||||
return tca_write_word_8(TCA_REG_OUTPUT1, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
static esp_err_t tca_port_read(uint16_t *value) {
|
|
||||||
uint16_t low, high;
|
|
||||||
ESP_ERROR_CHECK(tca_read_word(TCA_REG_INPUT0, &low));
|
|
||||||
ESP_ERROR_CHECK(tca_read_word(TCA_REG_INPUT1, &high));
|
|
||||||
*value = low | (high << 8);
|
|
||||||
return ESP_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// === LCD NIBBLE & COMMAND ===
|
|
||||||
static esp_err_t lcd_write_nibble(uint8_t nibble, bool rs) {
|
|
||||||
uint8_t data_state = 0;
|
|
||||||
if (rs) data_state |= (1 << LCD_RS);
|
|
||||||
if (nibble & 0x01) data_state |= (1 << LCD_D4);
|
|
||||||
if (nibble & 0x02) data_state |= (1 << LCD_D5);
|
|
||||||
if (nibble & 0x04) data_state |= (1 << LCD_D6);
|
|
||||||
if (nibble & 0x08) data_state |= (1 << LCD_D7);
|
|
||||||
|
|
||||||
ESP_ERROR_CHECK(tca_port_write(data_state));
|
|
||||||
ESP_ERROR_CHECK(tca_port_write(data_state | (1 << LCD_E)));
|
|
||||||
ESP_ERROR_CHECK(tca_port_write(data_state));
|
|
||||||
return ESP_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static esp_err_t lcd_command(uint8_t cmd) {
|
|
||||||
ESP_ERROR_CHECK(lcd_write_nibble(cmd >> 4, false));
|
|
||||||
ESP_ERROR_CHECK(lcd_write_nibble(cmd & 0x0F, false));
|
|
||||||
return ESP_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
static esp_err_t lcd_data(uint8_t data) {
|
|
||||||
ESP_ERROR_CHECK(lcd_write_nibble(data >> 4, true));
|
|
||||||
ESP_ERROR_CHECK(lcd_write_nibble(data & 0x0F, true));
|
|
||||||
return ESP_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
void lcd_set_cursor(uint8_t row, uint8_t col) {
|
|
||||||
uint8_t addr = (row == 0) ? 0x00 : 0x40;
|
|
||||||
addr += col;
|
|
||||||
lcd_row = row;
|
|
||||||
lcd_col = col;
|
|
||||||
lcd_command(0x80 | addr);
|
|
||||||
delay_us(50);
|
|
||||||
}
|
|
||||||
|
|
||||||
void lcd_printf(const char *fmt, ...) {
|
|
||||||
char buf[64];
|
|
||||||
va_list args;
|
|
||||||
va_start(args, fmt);
|
|
||||||
vsnprintf(buf, sizeof(buf), fmt, args);
|
|
||||||
va_end(args);
|
|
||||||
|
|
||||||
lcd_set_cursor(0, 0);
|
|
||||||
for (int i = 0; i < 32 && buf[i]; i++) {
|
|
||||||
if (i == 16) lcd_set_cursor(1, 0);
|
|
||||||
lcd_data((uint8_t)buf[i]);
|
|
||||||
delay_us(50);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void lcd_print(const char *str) {
|
|
||||||
lcd_set_cursor(0, 0);
|
|
||||||
for (int i = 0; i < 32 && str[i]; i++) {
|
|
||||||
if (i == 16) lcd_set_cursor(1, 0);
|
|
||||||
lcd_data((uint8_t)str[i]);
|
|
||||||
delay_us(50);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void lcd_off(void) {
|
|
||||||
if (i2c_initted) lcd_command(0x08);
|
|
||||||
}
|
|
||||||
|
|
||||||
esp_err_t lcd_init_4bit(void) {
|
|
||||||
ESP_LOGI("I2C", "Starting LCD init...");
|
|
||||||
ESP_ERROR_CHECK(tca_set_config_port0(0xFF));
|
|
||||||
tca_port_write(0x00);
|
|
||||||
delay_us(50000);
|
|
||||||
|
|
||||||
ESP_ERROR_CHECK(lcd_write_nibble(0x3, false)); delay_us(4500);
|
|
||||||
ESP_ERROR_CHECK(lcd_write_nibble(0x3, false)); delay_us(150);
|
|
||||||
ESP_ERROR_CHECK(lcd_write_nibble(0x3, false)); delay_us(150);
|
|
||||||
ESP_ERROR_CHECK(lcd_write_nibble(0x2, false)); delay_us(150);
|
|
||||||
|
|
||||||
ESP_ERROR_CHECK(lcd_command(0x28)); delay_us(150);
|
|
||||||
ESP_ERROR_CHECK(lcd_command(0x08)); delay_us(150);
|
|
||||||
ESP_ERROR_CHECK(lcd_command(0x01)); delay_us(2000);
|
|
||||||
ESP_ERROR_CHECK(lcd_command(0x06)); delay_us(150);
|
|
||||||
ESP_ERROR_CHECK(lcd_command(0x0C)); delay_us(150);
|
|
||||||
|
|
||||||
ESP_LOGI("I2C", "LCD init complete.");
|
|
||||||
return ESP_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// === BUTTON DEBOUNCE & REPEAT ===
|
|
||||||
void update_buttons(void) {
|
|
||||||
for (uint8_t btn = 0; btn < 4; ++btn) {
|
|
||||||
last_known_state[btn] = debounced_state[btn];
|
|
||||||
}
|
|
||||||
|
|
||||||
uint16_t port_val;
|
|
||||||
ESP_ERROR_CHECK(tca_port_read(&port_val));
|
|
||||||
uint8_t raw_buttons = (uint8_t)(port_val & 0x0F);
|
|
||||||
uint8_t raw_states = ~raw_buttons & 0x0F;
|
|
||||||
|
|
||||||
uint64_t now = esp_timer_get_time() / 1000;
|
|
||||||
|
|
||||||
for (uint8_t btn = 0; btn < 4; ++btn) {
|
|
||||||
bool raw_pressed = (raw_states & (1 << btn)) != 0;
|
|
||||||
|
|
||||||
if (raw_pressed != debounced_state[btn]) {
|
|
||||||
if (now - last_stable_time[btn] >= DEBOUNCE_MS) {
|
|
||||||
debounced_state[btn] = raw_pressed;
|
|
||||||
last_stable_time[btn] = now;
|
|
||||||
last_change_time[btn] = now;
|
|
||||||
claimed_repeats[btn] = 0;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
last_stable_time[btn] = now;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool get_button_tripped(uint8_t button) {
|
|
||||||
return (button < 4) && debounced_state[button] && !last_known_state[button];
|
|
||||||
}
|
|
||||||
|
|
||||||
bool get_button_released(uint8_t button) {
|
|
||||||
return (button < 4) && !debounced_state[button] && last_known_state[button];
|
|
||||||
}
|
|
||||||
|
|
||||||
bool get_button_state(uint8_t button) {
|
|
||||||
return (button < 4) && debounced_state[button];
|
|
||||||
}
|
|
||||||
|
|
||||||
bool get_button_repeat(uint8_t btn) {
|
|
||||||
if (btn >= 4 || !debounced_state[btn]) return false;
|
|
||||||
uint64_t now = esp_timer_get_time() / 1000;
|
|
||||||
if (now + DEBOUNCE_MS < last_change_time[btn]) return false;
|
|
||||||
if ((now - last_change_time[btn]) > (REPEAT_START_MS + REPEAT_MS * claimed_repeats[btn])) {
|
|
||||||
claimed_repeats[btn]++;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
int8_t get_button_repeats(uint8_t btn) {
|
|
||||||
if (!get_button_state(btn))
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
if (btn >= 4 || !debounced_state[btn]) return false;
|
|
||||||
uint64_t now = esp_timer_get_time() / 1000;
|
|
||||||
if (now + DEBOUNCE_MS < last_change_time[btn]) return false;
|
|
||||||
if ((now - last_change_time[btn]) > (REPEAT_START_MS + REPEAT_MS * claimed_repeats[btn])) {
|
|
||||||
claimed_repeats[btn]++;
|
|
||||||
if (claimed_repeats[btn] > 100)
|
|
||||||
claimed_repeats[btn] = 100;
|
|
||||||
ESP_LOGI("BTN", "RPT %d", (uint8_t)claimed_repeats[btn]+2);
|
|
||||||
return claimed_repeats[btn]+1;
|
|
||||||
}
|
|
||||||
if (debounced_state[btn] && !last_known_state[btn]) {
|
|
||||||
|
|
||||||
ESP_LOGI("BTN", "FST %d", 1);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
//ESP_LOGI("BTN", "RPT %d", 0);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int64_t get_button_ms(uint8_t btn) {
|
|
||||||
if (!get_button_state(btn))
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
uint64_t now = esp_timer_get_time() / 1000;
|
|
||||||
return now - last_change_time[btn];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Parameter descriptor structure
|
|
||||||
typedef struct {
|
|
||||||
const char key[24]; // NVS key name (null-terminated)
|
|
||||||
uint8_t type_size; // Size in bytes: 1=uint8_t, 2=uint16_t, 4=uint32_t/float, 8=uint64_t/double
|
|
||||||
uint8_t type_flags; // Bitfield: [0:1] signed, [2] float, [3:7] reserved
|
|
||||||
const void *default_val; // Pointer to default value (matches type)
|
|
||||||
} param_desc_t;
|
|
||||||
|
|
||||||
typedef struct param_group_s param_group_t;
|
|
||||||
typedef struct param_group_s {
|
|
||||||
char* (*formatter)(const param_group_t*, uint8_t idx);
|
|
||||||
const uint8_t num_keys;
|
|
||||||
const uint8_t indices[8][2];
|
|
||||||
const char keys[8][20];
|
|
||||||
void (*launch_functions[8])(char* key, int8_t dir);
|
|
||||||
} param_group_t;
|
|
||||||
|
|
||||||
// temp buffer for formatting stuff onto the LCD
|
|
||||||
static char formatting_buf[LCD_BUFLEN];
|
|
||||||
|
|
||||||
/* MENU DIALOG CONFIG */
|
|
||||||
char* schedule_format(const param_group_t *pg, uint8_t idx);
|
|
||||||
char* dist_format (const param_group_t *pg, uint8_t idx);
|
|
||||||
char* reprog_format (const param_group_t *pg, uint8_t idx);
|
|
||||||
char* override_format(const param_group_t *pg, uint8_t idx);
|
|
||||||
char* status_format (const param_group_t *pg, uint8_t idx);
|
|
||||||
char* cal_format (const param_group_t *pg, uint8_t idx);
|
|
||||||
char* efuse_format (const param_group_t *pg, uint8_t idx);
|
|
||||||
char* ftp_format (const param_group_t *pg, uint8_t idx);
|
|
||||||
|
|
||||||
// Launch functions (forward declarations)
|
|
||||||
void trigger_move(char* key, int8_t dir);
|
|
||||||
void rf_reprogram_remote(char* key, int8_t dir);
|
|
||||||
|
|
||||||
void adjust_hour (char* key, int8_t dir);
|
|
||||||
void adjust_i8_0_99 (char* key, int8_t dir);
|
|
||||||
void adjust_generic (int idx, int8_t amt);
|
|
||||||
void dummy_adjuster (char* key, int8_t dir) {}; // do nothing
|
|
||||||
void launch_ftp (char* key, int8_t dir);
|
|
||||||
void adjust_i32_smart_0_99999(char* key, int8_t dir);
|
|
||||||
void adjust_i32_smart_0_999 (char* key, int8_t dir);
|
|
||||||
|
|
||||||
// Parameter table (legible, declarative)
|
|
||||||
const param_desc_t param_table[] = {
|
|
||||||
{
|
|
||||||
.key = "sched_start",
|
|
||||||
.type_size = TYPE_SIZE_1,
|
|
||||||
.type_flags = TYPE_SIGNED,
|
|
||||||
.default_val = &(int8_t){0}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
.key = "sched_end",
|
|
||||||
.type_size = TYPE_SIZE_1,
|
|
||||||
.type_flags = TYPE_SIGNED,
|
|
||||||
.default_val = &(int8_t){0}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
.key = "sched_num",
|
|
||||||
.type_size = TYPE_SIZE_1,
|
|
||||||
.type_flags = TYPE_SIGNED,
|
|
||||||
.default_val = &(int8_t){0}
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
{
|
|
||||||
.key = "efuse_drive_A",
|
|
||||||
.type_size = TYPE_SIZE_1,
|
|
||||||
.type_flags = TYPE_SIGNED,
|
|
||||||
.default_val = &(int8_t){99}
|
|
||||||
},{
|
|
||||||
.key = "efuse_jack_A",
|
|
||||||
.type_size = TYPE_SIZE_1,
|
|
||||||
.type_flags = TYPE_SIGNED,
|
|
||||||
.default_val = &(int8_t){99}
|
|
||||||
},{
|
|
||||||
.key = "efuse_aux_A",
|
|
||||||
.type_size = TYPE_SIZE_1,
|
|
||||||
.type_flags = TYPE_SIGNED,
|
|
||||||
.default_val = &(int8_t){99}
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
{
|
|
||||||
.key = "drive_dist",
|
|
||||||
.type_size = TYPE_SIZE_1,
|
|
||||||
.type_flags = TYPE_SIGNED,
|
|
||||||
.default_val = &(int16_t){10}
|
|
||||||
},{
|
|
||||||
.key = "drive_tpdf",
|
|
||||||
.type_size = TYPE_SIZE_4,
|
|
||||||
.type_flags = 0,
|
|
||||||
.default_val = &(int32_t){70}
|
|
||||||
},{
|
|
||||||
.key = "drive_mspf",
|
|
||||||
.type_size = TYPE_SIZE_4,
|
|
||||||
.type_flags = 0,
|
|
||||||
.default_val = &(int32_t){1000}
|
|
||||||
},{
|
|
||||||
.key = "jack_mspi",
|
|
||||||
.type_size = TYPE_SIZE_4,
|
|
||||||
.type_flags = 0,
|
|
||||||
.default_val = &(int32_t){1000}
|
|
||||||
},{
|
|
||||||
.key = "jack_dist",
|
|
||||||
.type_size = TYPE_SIZE_1,
|
|
||||||
.type_flags = TYPE_SIGNED,
|
|
||||||
.default_val = &(uint8_t){7}
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{
|
|
||||||
.key = "keycode0",
|
|
||||||
.type_size = TYPE_SIZE_8,
|
|
||||||
.type_flags = 0,
|
|
||||||
.default_val = &(uint8_t){0}
|
|
||||||
},{
|
|
||||||
.key = "keycode1",
|
|
||||||
.type_size = TYPE_SIZE_8,
|
|
||||||
.type_flags = 0,
|
|
||||||
.default_val = &(uint8_t){0}
|
|
||||||
},{
|
|
||||||
.key = "keycode2",
|
|
||||||
.type_size = TYPE_SIZE_8,
|
|
||||||
.type_flags = 0,
|
|
||||||
.default_val = &(uint8_t){0}
|
|
||||||
},{
|
|
||||||
.key = "keycode3",
|
|
||||||
.type_size = TYPE_SIZE_8,
|
|
||||||
.type_flags = 0,
|
|
||||||
.default_val = &(uint8_t){0}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
#define PARAM_COUNT (sizeof(param_table)/sizeof(param_table[0]))
|
|
||||||
|
|
||||||
// Runtime parameter values
|
|
||||||
static param_value_t param_values[PARAM_COUNT];
|
|
||||||
|
|
||||||
const param_group_t param_group_table[] = {
|
|
||||||
{
|
|
||||||
.formatter = status_format,
|
|
||||||
.num_keys = 3,
|
|
||||||
.keys = {"","",""},
|
|
||||||
.launch_functions = {trigger_move, adjust_rtc_hour, adjust_rtc_min}
|
|
||||||
},{
|
|
||||||
.formatter = schedule_format,
|
|
||||||
.num_keys = 3,
|
|
||||||
.keys = {"sched_start", "sched_end", "sched_num"},
|
|
||||||
.launch_functions = {adjust_hour, adjust_hour, adjust_i8_0_99}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
.formatter = dist_format,
|
|
||||||
.num_keys = 2,
|
|
||||||
.keys = {"drive_dist", "jack_dist"},
|
|
||||||
.launch_functions = {adjust_i8_0_99, adjust_i8_0_99}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
.formatter = cal_format,
|
|
||||||
.num_keys = 3,
|
|
||||||
.keys = { "jack_mspi", "drive_mspf", "drive_tpdf"},
|
|
||||||
.launch_functions = {adjust_i32_smart_0_99999, adjust_i32_smart_0_99999, adjust_i32_smart_0_999}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
.formatter = efuse_format,
|
|
||||||
.num_keys = 3,
|
|
||||||
.keys = { "efuse_aux_A", "efuse_jack_A", "efuse_drive_A"},
|
|
||||||
.launch_functions = {adjust_i8_0_99, adjust_i8_0_99, adjust_i8_0_99}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
.formatter = override_format,
|
|
||||||
.num_keys = 3,
|
|
||||||
.keys = {"","",""},
|
|
||||||
.launch_functions = {dummy_adjuster, dummy_adjuster, dummy_adjuster}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
.formatter = reprog_format,
|
|
||||||
.num_keys = 1,
|
|
||||||
.keys = {""},
|
|
||||||
.launch_functions = {rf_reprogram_remote}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
.formatter = ftp_format,
|
|
||||||
.num_keys = 1,
|
|
||||||
.keys = {""},
|
|
||||||
.launch_functions = {launch_ftp}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
#define PARAM_GROUP_RUNMTR 5
|
|
||||||
#define PARAM_GROUP_FTP 7
|
|
||||||
#define PARAM_GROUP_COUNT (sizeof(param_group_table)/sizeof(param_group_table[0]))
|
|
||||||
|
|
||||||
static const char schedule_fmts[3][3][LCD_BUFLEN] = {
|
|
||||||
{
|
|
||||||
"Start/End xTimes [-] - x%-2d ",
|
|
||||||
"Start/End xTimes - [-] x%-2d ",
|
|
||||||
"Start/End xTimes - - [x%-2d]"
|
|
||||||
},{
|
|
||||||
"Start/End xTimes[%2d%cM] - x%-2d ",
|
|
||||||
"Start/End xTimes %2d%cM [-] x%-2d ",
|
|
||||||
"Start/End xTimes %2d%cM - [x%-2d]"
|
|
||||||
},{
|
|
||||||
"Start/End xTimes[%2d%cM]-%2d%cM x%-2d",
|
|
||||||
"Start/End xTimes %2d%cM-[%2d%cM] x%-2d",
|
|
||||||
"Start/End xTimes %2d%cM-%2d%cM [x%-2d]"
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
static const char dist_fmts[3][LCD_BUFLEN] = {
|
|
||||||
"Dist. Drive/Jack[%2d ft] / %2d in ",
|
|
||||||
"Dist. Drive/Jack %2d ft / [%2d in]"
|
|
||||||
};
|
|
||||||
|
|
||||||
static const char override_fmts[3][LCD_BUFLEN] = {
|
|
||||||
" Run Motors [AUX]JACK DRIVE ",
|
|
||||||
" Run Motors AUX[JACK]DRIVE ",
|
|
||||||
" Run Motors AUX JACK[DRIVE]"
|
|
||||||
};
|
|
||||||
|
|
||||||
static const char cal_fmts[3][LCD_BUFLEN] = {
|
|
||||||
"Jack ms/in: [%4ld]%4ld %4ld ",
|
|
||||||
"Drive ms/ft: %4ld[%4ld]%4ld ",
|
|
||||||
"Drive t/10ft: %4ld %4ld[%4ld]"
|
|
||||||
};
|
|
||||||
static const char efuse_fmts[3][LCD_BUFLEN] = {
|
|
||||||
"E-fuse Aux: [%2dA] %2dA %2dA ",
|
|
||||||
"E-fuse Jack: %2dA [%2dA] %2dA ",
|
|
||||||
"E-fuse Drive: %2dA %2dA [%2dA]"
|
|
||||||
};
|
|
||||||
|
|
||||||
/* All function implementations remain unchanged and appear here in original form */
|
|
||||||
char* schedule_format(const param_group_t *pg, uint8_t idx)
|
|
||||||
{
|
|
||||||
/* pg->keys[0..2] → "sched_start", "sched_end", "sched_num" */
|
|
||||||
int8_t start = (int8_t)get_param_i8(pg->keys[0]); // helper, see below
|
|
||||||
int8_t end = (int8_t)get_param_i8(pg->keys[1]);
|
|
||||||
int8_t num = (int8_t)get_param_i8(pg->keys[2]);
|
|
||||||
|
|
||||||
|
|
||||||
char startAP = start<12 ? 'A':'P';
|
|
||||||
char endAP = end<12 ? 'A':'P';
|
|
||||||
start %= 12;
|
|
||||||
end %= 12;
|
|
||||||
|
|
||||||
|
|
||||||
if (start == 0) start = 12;
|
|
||||||
if (end == 0) end = 12;
|
|
||||||
|
|
||||||
if (num == 0) {
|
|
||||||
snprintf(formatting_buf, sizeof(formatting_buf),
|
|
||||||
schedule_fmts[0][idx], num);
|
|
||||||
return formatting_buf;
|
|
||||||
} else if (num == 1) {
|
|
||||||
snprintf(formatting_buf, sizeof(formatting_buf),
|
|
||||||
schedule_fmts[1][idx], start, startAP, num);
|
|
||||||
return formatting_buf;
|
|
||||||
} else {
|
|
||||||
snprintf(formatting_buf, sizeof(formatting_buf),
|
|
||||||
schedule_fmts[2][idx], start, startAP, end, endAP, num);
|
|
||||||
return formatting_buf;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
char* dist_format(const param_group_t *pg, uint8_t idx) {
|
|
||||||
int8_t drive = (int8_t)get_param_i8(pg->keys[0]); // helper, see below
|
|
||||||
int8_t jack = (int8_t)get_param_i8(pg->keys[1]);
|
|
||||||
|
|
||||||
snprintf(formatting_buf, sizeof(formatting_buf),
|
|
||||||
dist_fmts[idx], drive, jack);
|
|
||||||
return formatting_buf;
|
|
||||||
}
|
|
||||||
|
|
||||||
char* reprog_format(const param_group_t *pg, uint8_t idx) {
|
|
||||||
return "Reprogram Keyfob [Press ^ / v ] ";
|
|
||||||
}
|
|
||||||
|
|
||||||
char* override_format(const param_group_t *pg, uint8_t idx) {
|
|
||||||
return override_fmts[idx];
|
|
||||||
}
|
|
||||||
|
|
||||||
char* ftp_format(const param_group_t *pg, uint8_t idx) {
|
|
||||||
return " Start Wifi/FTP [Press ^ / v ] ";
|
|
||||||
}
|
|
||||||
|
|
||||||
char charge_indicators[N_CHARGE_STATES] = {
|
|
||||||
[CHG_STATE_OFF] ='-',
|
|
||||||
[CHG_STATE_FLOAT] ='F',
|
|
||||||
[CHG_STATE_BULK] ='B'
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
static const char status_fmts[4][LCD_BUFLEN] = {
|
|
||||||
"%-6s%2dA %2lu.%02luV[MOVE] %2d:%02d %cM",
|
|
||||||
"%-6s%2dA %2lu.%02luV MOVE [%2d]:%02d %cM",
|
|
||||||
"%-6s%2dA %2lu.%02luV MOVE %2d:[%02d]%cM",
|
|
||||||
"%-6s%2dA %2lu.%02luV[ SET TIME ^/v ]",
|
|
||||||
};
|
|
||||||
|
|
||||||
char* status_format(const param_group_t *pg, uint8_t idx) {
|
|
||||||
uint32_t vbat = get_battery_mV();
|
|
||||||
|
|
||||||
struct tm timeinfo;
|
|
||||||
rtc_get_time(&timeinfo);
|
|
||||||
|
|
||||||
// --- Build 7-char time: " 9:05PM" or "10:05PM" ---
|
|
||||||
int hour12 = timeinfo.tm_hour % 12;
|
|
||||||
if (hour12 == 0) hour12 = 12; // 12-hour format
|
|
||||||
|
|
||||||
int current_draw = abs(get_bridge_mA(BRIDGE_DRIVE)/1000) + abs(get_bridge_mA(BRIDGE_JACK)/1000) + abs(get_bridge_mA(BRIDGE_AUX)/1000);
|
|
||||||
|
|
||||||
if (rtc_is_set())
|
|
||||||
snprintf(formatting_buf, sizeof(formatting_buf),
|
|
||||||
status_fmts[idx],
|
|
||||||
"Idle",
|
|
||||||
current_draw,
|
|
||||||
(unsigned long)(vbat / 1000),
|
|
||||||
(unsigned long)((vbat % 1000) + 99) / 100,
|
|
||||||
hour12,
|
|
||||||
timeinfo.tm_min,
|
|
||||||
timeinfo.tm_hour < 12 ? 'A':'P'
|
|
||||||
);
|
|
||||||
else
|
|
||||||
snprintf(formatting_buf, sizeof(formatting_buf),
|
|
||||||
status_fmts[3],
|
|
||||||
"Idle",
|
|
||||||
current_draw,
|
|
||||||
(unsigned long)(vbat / 1000),
|
|
||||||
(unsigned long)((vbat % 1000) + 99) / 100
|
|
||||||
);
|
|
||||||
|
|
||||||
return formatting_buf;
|
|
||||||
}
|
|
||||||
|
|
||||||
char* cal_format(const param_group_t *pg, uint8_t idx) {
|
|
||||||
int32_t x1 = get_param_i32(pg->keys[0]);
|
|
||||||
int32_t x2 = get_param_i32(pg->keys[1]);
|
|
||||||
int32_t x3 = get_param_i32(pg->keys[2]);
|
|
||||||
|
|
||||||
snprintf(formatting_buf, sizeof(formatting_buf),
|
|
||||||
cal_fmts[idx], x1, x2, x3);
|
|
||||||
return formatting_buf;
|
|
||||||
}
|
|
||||||
|
|
||||||
char* efuse_format(const param_group_t *pg, uint8_t idx) {
|
|
||||||
int32_t x1 = get_param_i32(pg->keys[0]);
|
|
||||||
int32_t x2 = get_param_i32(pg->keys[1]);
|
|
||||||
int32_t x3 = get_param_i32(pg->keys[2]);
|
|
||||||
|
|
||||||
snprintf(formatting_buf, sizeof(formatting_buf),
|
|
||||||
efuse_fmts[idx], x1, x2, x3);
|
|
||||||
return formatting_buf;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generic adjustment fallback
|
|
||||||
void adjust_generic(int idx, int8_t amt) {
|
|
||||||
const param_desc_t *p = ¶m_table[idx];
|
|
||||||
if (p->type_flags & TYPE_FLOAT) {
|
|
||||||
float step = 0.1f;
|
|
||||||
param_values[idx].f32 += amt;
|
|
||||||
} else {
|
|
||||||
switch (p->type_size) {
|
|
||||||
case 1: {
|
|
||||||
int8_t v = (int8_t)param_values[idx].u8;
|
|
||||||
v += amt;
|
|
||||||
param_values[idx].u8 = (int8_t)v;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 2: {
|
|
||||||
int16_t v = (int16_t)param_values[idx].u16;
|
|
||||||
v += amt;
|
|
||||||
param_values[idx].u16 = (int16_t)v;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
params_save(idx);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* adjust_time - Shared adjuster for any time parameter (HH:MM format)
|
|
||||||
* @idx: Index in param_table[]
|
|
||||||
* @dir: +1 = increment, -1 = decrement
|
|
||||||
*
|
|
||||||
* Assumes value stored as minutes since 00:00 (0–1439)
|
|
||||||
* Displays as "HH:MM"
|
|
||||||
*/
|
|
||||||
void adjust_hour(char* key, int8_t dir) {
|
|
||||||
int8_t idx = params_find(key);
|
|
||||||
if (idx<0) return;
|
|
||||||
|
|
||||||
if (dir>0) param_values[idx].i8 += +1;
|
|
||||||
if (dir<0) param_values[idx].i8 += -1;
|
|
||||||
|
|
||||||
// wraparound
|
|
||||||
if (param_values[idx].i8 > 23) param_values[idx].i8 = 0;
|
|
||||||
if (param_values[idx].i8 < 0) param_values[idx].i8 = 23;
|
|
||||||
|
|
||||||
params_save(idx);
|
|
||||||
set_next_alarm();
|
|
||||||
}
|
|
||||||
|
|
||||||
void adjust_i8_0_99(char* key, int8_t dir) {
|
|
||||||
int8_t idx = params_find(key);
|
|
||||||
if (idx<0) return;
|
|
||||||
|
|
||||||
if (dir>0) param_values[idx].i8 += +1;
|
|
||||||
if (dir<0) param_values[idx].i8 += -1;
|
|
||||||
|
|
||||||
// clamp
|
|
||||||
if (param_values[idx].i8 > 99) param_values[idx].i8 = 99;
|
|
||||||
if (param_values[idx].i8 < 0) param_values[idx].i8 = 0;
|
|
||||||
|
|
||||||
params_save(idx);
|
|
||||||
set_next_alarm();
|
|
||||||
}
|
|
||||||
|
|
||||||
void adjust_i16_0_9990_by_10(char* key, int8_t dir) {
|
|
||||||
int8_t idx = params_find(key);
|
|
||||||
if (idx<0) return;
|
|
||||||
|
|
||||||
if (dir>0) param_values[idx].i16 += +1;
|
|
||||||
if (dir<0) param_values[idx].i16 += -1;
|
|
||||||
|
|
||||||
// clamp
|
|
||||||
if (param_values[idx].i16 > 9990) param_values[idx].i16 = 9990;
|
|
||||||
if (param_values[idx].i16 < 0) param_values[idx].i16 = 0;
|
|
||||||
|
|
||||||
params_save(idx);
|
|
||||||
set_next_alarm();
|
|
||||||
}
|
|
||||||
|
|
||||||
//inline static int8_t abs(int8_t x) { return x<0?-x:x; }
|
|
||||||
void adjust_i32_smart_0_99999(char* key, int8_t dir) {
|
|
||||||
int8_t idx = params_find(key);
|
|
||||||
if (idx<0) return;
|
|
||||||
|
|
||||||
int32_t inc = 1;
|
|
||||||
if (abs(dir) > 5) inc = 5;
|
|
||||||
if (abs(dir) > 10) inc = 10;
|
|
||||||
if (abs(dir) > 13) inc = 50;
|
|
||||||
if (abs(dir) > 16) inc = 100;
|
|
||||||
if (abs(dir) > 19) inc = 200;
|
|
||||||
if (abs(dir) > 22) inc = 1000;
|
|
||||||
|
|
||||||
if (dir>0) param_values[idx].i32 += +inc;
|
|
||||||
if (dir<0) param_values[idx].i32 += -inc;
|
|
||||||
param_values[idx].i32 = (param_values[idx].i32/inc)*inc;
|
|
||||||
|
|
||||||
ESP_LOGI("ADJ", "P[%d] += %d => %ld", (int)idx, (int)inc, (long)param_values[idx].i32);
|
|
||||||
|
|
||||||
// clamp
|
|
||||||
if (param_values[idx].i32 > 99999) param_values[idx].i32 = 99999;
|
|
||||||
if (param_values[idx].i32 < 0) param_values[idx].i32 = 0;
|
|
||||||
|
|
||||||
params_save(idx);
|
|
||||||
set_next_alarm();
|
|
||||||
}
|
|
||||||
void adjust_i32_smart_0_999(char* key, int8_t dir) {
|
|
||||||
int8_t idx = params_find(key);
|
|
||||||
if (idx<0) return;
|
|
||||||
|
|
||||||
int32_t inc = 1;
|
|
||||||
if (abs(dir) > 5) inc = 5;
|
|
||||||
if (abs(dir) > 10) inc = 10;
|
|
||||||
if (abs(dir) > 13) inc = 50;
|
|
||||||
if (abs(dir) > 16) inc = 100;
|
|
||||||
if (abs(dir) > 19) inc = 200;
|
|
||||||
if (abs(dir) > 22) inc = 1000;
|
|
||||||
|
|
||||||
if (dir>0) param_values[idx].i32 += +inc;
|
|
||||||
if (dir<0) param_values[idx].i32 += -inc;
|
|
||||||
param_values[idx].i32 = (param_values[idx].i32/inc)*inc;
|
|
||||||
|
|
||||||
ESP_LOGI("ADJ", "p[%d] += %d => %ld", (int)idx, (int)inc, (long)param_values[idx].i32);
|
|
||||||
|
|
||||||
// clamp
|
|
||||||
if (param_values[idx].i32 > 999) param_values[idx].i32 = 999;
|
|
||||||
if (param_values[idx].i32 < 0) param_values[idx].i32 = 0;
|
|
||||||
|
|
||||||
params_save(idx);
|
|
||||||
set_next_alarm();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
static int8_t group_idx=0, entry_idx=0;
|
|
||||||
void run_parameter_ui() {
|
|
||||||
|
|
||||||
if (get_button_repeats(BTN_L)) {
|
|
||||||
reset_shutdown_timer();
|
|
||||||
entry_idx--;
|
|
||||||
if (entry_idx < 0) {
|
|
||||||
group_idx--;
|
|
||||||
if (group_idx < 0) {
|
|
||||||
group_idx = PARAM_GROUP_COUNT-1;
|
|
||||||
}
|
|
||||||
entry_idx = param_group_table[group_idx].num_keys-1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (get_button_repeats(BTN_R)) {
|
|
||||||
reset_shutdown_timer();
|
|
||||||
entry_idx++;
|
|
||||||
if (entry_idx >= param_group_table[group_idx].num_keys) {
|
|
||||||
group_idx++;
|
|
||||||
if (group_idx >= PARAM_GROUP_COUNT) {
|
|
||||||
group_idx = 0;
|
|
||||||
}
|
|
||||||
entry_idx = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Forbid user from doing anything until they set the time
|
|
||||||
if (!rtc_is_set()) {
|
|
||||||
group_idx=0;
|
|
||||||
entry_idx=1;
|
|
||||||
}
|
|
||||||
|
|
||||||
param_group_t pg = param_group_table[group_idx];
|
|
||||||
|
|
||||||
lcd_print(pg.formatter(&pg, entry_idx)); // Formatted with botfmt + values
|
|
||||||
|
|
||||||
int8_t n;
|
|
||||||
if ((n=get_button_repeats(BTN_U))) {
|
|
||||||
reset_shutdown_timer();
|
|
||||||
pg.launch_functions[entry_idx](
|
|
||||||
pg.keys[entry_idx], +n
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if ((n=get_button_repeats(BTN_D))) {
|
|
||||||
reset_shutdown_timer();
|
|
||||||
pg.launch_functions[entry_idx](
|
|
||||||
pg.keys[entry_idx], -n
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*int64_t ut = get_button_ms(BTN_U);
|
|
||||||
if (ut) {
|
|
||||||
reset_shutdown_timer();
|
|
||||||
pg.launch_functions[entry_idx](
|
|
||||||
pg.keys[entry_idx], +ut
|
|
||||||
);
|
|
||||||
}
|
|
||||||
int64_t dt = get_button_ms(BTN_D);
|
|
||||||
if (ut) {
|
|
||||||
reset_shutdown_timer();
|
|
||||||
pg.launch_functions[entry_idx](
|
|
||||||
pg.keys[entry_idx], -ut
|
|
||||||
);
|
|
||||||
}*/
|
|
||||||
}
|
|
||||||
|
|
||||||
int8_t parameter_ux_in_override() {
|
|
||||||
if(group_idx != PARAM_GROUP_RUNMTR)
|
|
||||||
return -1;
|
|
||||||
return entry_idx;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
bool parameter_ux_in_ftp() {
|
|
||||||
return group_idx == PARAM_GROUP_FTP;
|
|
||||||
}
|
|
||||||
83
main/i2c.c
83
main/i2c.c
@@ -12,6 +12,11 @@
|
|||||||
static bool i2c_initted = false;
|
static bool i2c_initted = false;
|
||||||
//static bool safety_ok = false; // Safety interlock
|
//static bool safety_ok = false; // Safety interlock
|
||||||
static uint8_t last_relay_request = 0; // Track last relay request
|
static uint8_t last_relay_request = 0; // Track last relay request
|
||||||
|
/* Cached last-written values for the two TCA9555 output ports. Used by
|
||||||
|
* i2c_get_outputs() so the FSM log can record the full 16-bit output state
|
||||||
|
* without paying for an extra I2C read each tick. */
|
||||||
|
static uint8_t last_output0 = 0;
|
||||||
|
static uint8_t last_output1 = 0;
|
||||||
|
|
||||||
// === I2C LOW-LEVEL ===
|
// === I2C LOW-LEVEL ===
|
||||||
static esp_err_t tca_write_word_8(uint8_t reg, uint8_t value) {
|
static esp_err_t tca_write_word_8(uint8_t reg, uint8_t value) {
|
||||||
@@ -41,7 +46,13 @@ esp_err_t i2c_init(void) {
|
|||||||
ESP_ERROR_CHECK(i2c_param_config(I2C_PORT, &conf));
|
ESP_ERROR_CHECK(i2c_param_config(I2C_PORT, &conf));
|
||||||
ESP_ERROR_CHECK(i2c_driver_install(I2C_PORT, conf.mode, 0, 0, 0));
|
ESP_ERROR_CHECK(i2c_driver_install(I2C_PORT, conf.mode, 0, 0, 0));
|
||||||
|
|
||||||
|
/* Pre-clear OUTPUT latches BEFORE switching pins to output mode.
|
||||||
|
* TCA9555 powers up with OUTPUT0/1 = 0xFF, so configuring CONFIG first
|
||||||
|
* (pins → outputs) would drive every relay + LED on for the few hundred
|
||||||
|
* µs until i2c_relays_idle() runs. Writing 0 first makes the eventual
|
||||||
|
* input→output transition drive 0. */
|
||||||
|
ESP_ERROR_CHECK(tca_write_word_8(TCA_REG_OUTPUT0, 0x00));
|
||||||
|
ESP_ERROR_CHECK(tca_write_word_8(TCA_REG_OUTPUT1, 0x00));
|
||||||
ESP_ERROR_CHECK(tca_write_word_8(TCA_REG_CONFIG0, 0b00000011));
|
ESP_ERROR_CHECK(tca_write_word_8(TCA_REG_CONFIG0, 0b00000011));
|
||||||
ESP_ERROR_CHECK(tca_write_word_8(TCA_REG_CONFIG1, 0b00000000));
|
ESP_ERROR_CHECK(tca_write_word_8(TCA_REG_CONFIG1, 0b00000000));
|
||||||
|
|
||||||
@@ -52,17 +63,53 @@ esp_err_t i2c_init(void) {
|
|||||||
return ESP_OK;
|
return ESP_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
esp_err_t i2c_post(void) {
|
||||||
|
// Verify TCA9555 responds by reading input port 0
|
||||||
|
uint16_t val = 0;
|
||||||
|
esp_err_t err = tca_read_word(TCA_REG_INPUT0, &val);
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGE("I2C", "POST: TCA9555 read failed: %s", esp_err_to_name(err));
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
ESP_LOGI("I2C", "POST: TCA9555 OK (port0=0x%04X)", val);
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
esp_err_t i2c_set_relays(relay_port_t states) {
|
esp_err_t i2c_set_relays(relay_port_t states) {
|
||||||
|
last_output1 = states.raw;
|
||||||
return tca_write_word_8(TCA_REG_OUTPUT1, states.raw);
|
return tca_write_word_8(TCA_REG_OUTPUT1, states.raw);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
esp_err_t i2c_relays_idle(void) {
|
||||||
|
return i2c_set_relays((relay_port_t){.bridges = {.SENSORS = 1}});
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t i2c_relays_sleep(void) {
|
||||||
|
return i2c_set_relays((relay_port_t){.raw = 0});
|
||||||
|
}
|
||||||
|
|
||||||
esp_err_t i2c_set_led1(uint8_t state) {
|
esp_err_t i2c_set_led1(uint8_t state) {
|
||||||
// push 3 LSB to top
|
/* P05-P07 are LEDs (outputs); P00-P04 are buttons / unused INPUTS
|
||||||
return tca_write_word_8(TCA_REG_OUTPUT0, state<<5);
|
* (CONFIG0 = 0b00000011 sets P00/P01 as inputs; P02-P04 are unused
|
||||||
|
* but also configured as inputs). Writing the whole OUTPUT0 register
|
||||||
|
* is therefore safe — the input-bit slots in OUTPUT0 are don't-cares
|
||||||
|
* because the pin direction prevents the value from driving the line.
|
||||||
|
* If P02-P04 ever become outputs, switch this to read-modify-write. */
|
||||||
|
uint8_t v = state << 5;
|
||||||
|
last_output0 = v;
|
||||||
|
return tca_write_word_8(TCA_REG_OUTPUT0, v);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t i2c_get_outputs(void) {
|
||||||
|
/* OUTPUT0 in the high byte (P00..P07), OUTPUT1 in the low byte
|
||||||
|
* (P10..P17). Reflects the last value written by this driver. */
|
||||||
|
return ((uint16_t)last_output0 << 8) | last_output1;
|
||||||
}
|
}
|
||||||
|
|
||||||
esp_err_t i2c_stop() {
|
esp_err_t i2c_stop() {
|
||||||
if (!i2c_initted) return ESP_OK;
|
if (!i2c_initted) return ESP_OK;
|
||||||
|
last_output0 = 0;
|
||||||
|
last_output1 = 0;
|
||||||
tca_write_word_8(TCA_REG_OUTPUT0, 0);
|
tca_write_word_8(TCA_REG_OUTPUT0, 0);
|
||||||
tca_write_word_8(TCA_REG_OUTPUT1, 0);
|
tca_write_word_8(TCA_REG_OUTPUT1, 0);
|
||||||
return ESP_OK;
|
return ESP_OK;
|
||||||
@@ -103,6 +150,23 @@ esp_err_t i2c_poll_buttons() {
|
|||||||
return ESP_OK;
|
return ESP_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* One-shot, un-debounced "is button currently pressed?" read.
|
||||||
|
* Reads NCA9535 INPUT0 directly over I2C; bypasses the polled / debounced
|
||||||
|
* state machine above. Used in spots where the polled state isn't valid
|
||||||
|
* yet (cold-boot factory-reset detection runs before the FSM/main loop
|
||||||
|
* has been polling) or where we deliberately want to check the live wire
|
||||||
|
* (e.g. waiting for a button release before deep sleep).
|
||||||
|
*
|
||||||
|
* Side effect: reading INPUT0 clears the NCA9535 INT line — desirable
|
||||||
|
* before deep-sleep entry so EXT0 wake doesn't trigger immediately. */
|
||||||
|
bool i2c_button_held_raw(uint8_t button) {
|
||||||
|
if (button >= N_BTNS) return false;
|
||||||
|
uint16_t port_val = 0;
|
||||||
|
if (tca_read_word(TCA_REG_INPUT0, &port_val) != ESP_OK) return false;
|
||||||
|
/* Buttons are active-low on P00..P0(N_BTNS-1). */
|
||||||
|
return ((port_val >> button) & 0x01) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
bool i2c_get_button_tripped(uint8_t button) {
|
bool i2c_get_button_tripped(uint8_t button) {
|
||||||
return (button < N_BTNS) && debounced_state[button] && !last_known_state[button];
|
return (button < N_BTNS) && debounced_state[button] && !last_known_state[button];
|
||||||
}
|
}
|
||||||
@@ -127,21 +191,22 @@ bool i2c_get_button_repeat(uint8_t btn) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int8_t i2c_get_button_repeats(uint8_t btn) {
|
int8_t i2c_get_button_repeats(uint8_t btn) {
|
||||||
if (!i2c_get_button_state(btn))
|
/* Returns -1 on out-of-range button index (was previously `false` = 0,
|
||||||
return 0;
|
* which conflated error with "no repeat"). 0 means button not pressed
|
||||||
|
* or no new repeat this poll. >=1 is a valid repeat count. */
|
||||||
|
if (btn >= N_BTNS) return -1;
|
||||||
|
if (!i2c_get_button_state(btn)) return 0;
|
||||||
|
|
||||||
if (btn >= N_BTNS || !debounced_state[btn]) return false;
|
|
||||||
uint64_t now = esp_timer_get_time() / 1000;
|
uint64_t now = esp_timer_get_time() / 1000;
|
||||||
if (now + DEBOUNCE_MS < last_change_time[btn]) return false;
|
if (now + DEBOUNCE_MS < last_change_time[btn]) return 0;
|
||||||
if ((now - last_change_time[btn]) > (REPEAT_START_MS + REPEAT_MS * claimed_repeats[btn])) {
|
if ((now - last_change_time[btn]) > (REPEAT_START_MS + REPEAT_MS * claimed_repeats[btn])) {
|
||||||
claimed_repeats[btn]++;
|
claimed_repeats[btn]++;
|
||||||
if (claimed_repeats[btn] > 100)
|
if (claimed_repeats[btn] > 100)
|
||||||
claimed_repeats[btn] = 100;
|
claimed_repeats[btn] = 100;
|
||||||
ESP_LOGI("BTN", "RPT %d", (uint8_t)claimed_repeats[btn]+2);
|
ESP_LOGI("BTN", "RPT %d", (uint8_t)(claimed_repeats[btn]+1));
|
||||||
return claimed_repeats[btn]+1;
|
return claimed_repeats[btn]+1;
|
||||||
}
|
}
|
||||||
if (debounced_state[btn] && !last_known_state[btn]) {
|
if (debounced_state[btn] && !last_known_state[btn]) {
|
||||||
|
|
||||||
ESP_LOGI("BTN", "FST %d", 1);
|
ESP_LOGI("BTN", "FST %d", 1);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|||||||
19
main/i2c.h
19
main/i2c.h
@@ -49,13 +49,32 @@ typedef union {
|
|||||||
|
|
||||||
// Public Functions
|
// Public Functions
|
||||||
esp_err_t i2c_init(void);
|
esp_err_t i2c_init(void);
|
||||||
|
esp_err_t i2c_post(void);
|
||||||
esp_err_t i2c_stop(void);
|
esp_err_t i2c_stop(void);
|
||||||
|
|
||||||
esp_err_t i2c_set_relays(relay_port_t states);
|
esp_err_t i2c_set_relays(relay_port_t states);
|
||||||
esp_err_t i2c_set_led1(uint8_t state);
|
esp_err_t i2c_set_led1(uint8_t state);
|
||||||
|
|
||||||
|
/* Returns the last-written 16-bit TCA9555 output state.
|
||||||
|
* High byte: OUTPUT0 (P00..P07, LEDs in P05..P07).
|
||||||
|
* Low byte: OUTPUT1 (P10..P17, relay_port_t.raw). */
|
||||||
|
uint16_t i2c_get_outputs(void);
|
||||||
|
|
||||||
|
/* Normal run state: all bridges off, but P10 (sensor rail) held high.
|
||||||
|
* Use whenever "everything off" is really "all motors off, system is still on". */
|
||||||
|
esp_err_t i2c_relays_idle(void);
|
||||||
|
|
||||||
|
/* Sleep state: all bridges off AND P10 low, cutting power to the sensors. */
|
||||||
|
esp_err_t i2c_relays_sleep(void);
|
||||||
|
|
||||||
esp_err_t i2c_poll_buttons();
|
esp_err_t i2c_poll_buttons();
|
||||||
|
|
||||||
|
/* Live, un-debounced read of a single button bit via NCA9535 INPUT0.
|
||||||
|
* Use only when the polled debounced state isn't trustworthy — e.g.
|
||||||
|
* cold-boot factory-reset detection, or before deep sleep when we want
|
||||||
|
* to clear the NCA9535 INT line as a side effect. */
|
||||||
|
bool i2c_button_held_raw(uint8_t button);
|
||||||
|
|
||||||
bool i2c_get_button_tripped(uint8_t button);
|
bool i2c_get_button_tripped(uint8_t button);
|
||||||
bool i2c_get_button_released(uint8_t button);
|
bool i2c_get_button_released(uint8_t button);
|
||||||
bool i2c_get_button_state(uint8_t button);
|
bool i2c_get_button_state(uint8_t button);
|
||||||
|
|||||||
@@ -1,19 +1,6 @@
|
|||||||
## IDF Component Manager Manifest File
|
## IDF Component Manager Manifest File
|
||||||
dependencies:
|
dependencies:
|
||||||
espressif/mdns: "*"
|
espressif/mdns: "~1.9.1"
|
||||||
joltwallet/littlefs: "==1.20.3"
|
|
||||||
esp-idf-lib/tca95x5: "*"
|
|
||||||
## Required IDF version
|
## Required IDF version
|
||||||
idf:
|
idf:
|
||||||
version: ">=4.1.0"
|
version: ">=5.0"
|
||||||
# # Put list of dependencies here
|
|
||||||
# # For components maintained by Espressif:
|
|
||||||
# component: "~1.0.0"
|
|
||||||
# # For 3rd party components:
|
|
||||||
# username/component: ">=1.0.0,<2.0.0"
|
|
||||||
# username2/component2:
|
|
||||||
# version: "~1.0.0"
|
|
||||||
# # For transient dependencies `public` flag can be set.
|
|
||||||
# # `public` flag doesn't have an effect dependencies of the `main` component.
|
|
||||||
# # All dependencies of `main` are public by default.
|
|
||||||
# public: true
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
#include "esp_log.h"
|
#include "esp_log.h"
|
||||||
#include "esp_err.h"
|
#include "esp_err.h"
|
||||||
#include "esp_task_wdt.h"
|
#include "esp_task_wdt.h"
|
||||||
|
#include "esp_timer.h"
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
@@ -1087,6 +1088,87 @@ int count_passed_tests(test_result_t* results, int num_tests) {
|
|||||||
return passed;
|
return passed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Write timing benchmark — measures blocking write duration
|
||||||
|
// ============================================================================
|
||||||
|
void test_log_write_timing(void) {
|
||||||
|
ESP_LOGI(TAG, "");
|
||||||
|
ESP_LOGI(TAG, "=== Log Write Timing Benchmark ===");
|
||||||
|
|
||||||
|
// Erase and reinit to get a clean state
|
||||||
|
esp_err_t err = log_erase_all_sectors();
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGE(TAG, "Failed to erase log for timing test");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
esp_task_wdt_reset();
|
||||||
|
|
||||||
|
// Use a 39-byte payload (typical FSM log entry size)
|
||||||
|
uint8_t payload[39];
|
||||||
|
for (int i = 0; i < 39; i++) payload[i] = (uint8_t)i;
|
||||||
|
|
||||||
|
#define TIMING_ITERATIONS 200
|
||||||
|
|
||||||
|
int64_t min_us = INT64_MAX;
|
||||||
|
int64_t max_us = 0;
|
||||||
|
int64_t total_us = 0;
|
||||||
|
int sector_cross_count = 0;
|
||||||
|
int64_t sector_cross_max_us = 0;
|
||||||
|
|
||||||
|
uint32_t prev_head = log_get_head();
|
||||||
|
|
||||||
|
for (int i = 0; i < TIMING_ITERATIONS; i++) {
|
||||||
|
int64_t t0 = esp_timer_get_time();
|
||||||
|
err = log_write_blocking_test(payload, sizeof(payload), LOG_TYPE_DATA);
|
||||||
|
// Wait for queue flush
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(50));
|
||||||
|
int64_t t1 = esp_timer_get_time();
|
||||||
|
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGE(TAG, "Write %d failed: %s", i, esp_err_to_name(err));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t dt = t1 - t0;
|
||||||
|
total_us += dt;
|
||||||
|
if (dt < min_us) min_us = dt;
|
||||||
|
if (dt > max_us) max_us = dt;
|
||||||
|
|
||||||
|
// Detect sector crossing (head wrapped or jumped by > payload size)
|
||||||
|
uint32_t cur_head = log_get_head();
|
||||||
|
if (cur_head < prev_head || (cur_head - prev_head) > sizeof(payload) + 10) {
|
||||||
|
sector_cross_count++;
|
||||||
|
if (dt > sector_cross_max_us) sector_cross_max_us = dt;
|
||||||
|
}
|
||||||
|
prev_head = cur_head;
|
||||||
|
|
||||||
|
if (i % 50 == 0) esp_task_wdt_reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t avg_us = total_us / TIMING_ITERATIONS;
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "");
|
||||||
|
ESP_LOGI(TAG, "=== WRITE TIMING REPORT ===");
|
||||||
|
ESP_LOGI(TAG, " Iterations: %d", TIMING_ITERATIONS);
|
||||||
|
ESP_LOGI(TAG, " Payload size: %d bytes", (int)sizeof(payload));
|
||||||
|
ESP_LOGI(TAG, " Min: %lld us", (long long)min_us);
|
||||||
|
ESP_LOGI(TAG, " Max: %lld us", (long long)max_us);
|
||||||
|
ESP_LOGI(TAG, " Avg: %lld us", (long long)avg_us);
|
||||||
|
ESP_LOGI(TAG, " Sector crossings: %d (max %lld us)", sector_cross_count, (long long)sector_cross_max_us);
|
||||||
|
ESP_LOGI(TAG, " WDT margin: %.1fs (WDT=5s, worst=%lldus)",
|
||||||
|
5.0 - (double)max_us / 1000000.0, (long long)max_us);
|
||||||
|
if (max_us > 1000000) {
|
||||||
|
ESP_LOGW(TAG, " WARNING: max write > 1s — close to WDT timeout!");
|
||||||
|
} else if (max_us > 100000) {
|
||||||
|
ESP_LOGI(TAG, " Note: max write > 100ms (expected during sector erase)");
|
||||||
|
}
|
||||||
|
ESP_LOGI(TAG, "===========================");
|
||||||
|
ESP_LOGI(TAG, "");
|
||||||
|
|
||||||
|
#undef TIMING_ITERATIONS
|
||||||
|
esp_task_wdt_reset();
|
||||||
|
}
|
||||||
|
|
||||||
// Main test runner
|
// Main test runner
|
||||||
esp_err_t run_all_log_tests(void) {
|
esp_err_t run_all_log_tests(void) {
|
||||||
ESP_LOGI(TAG, "\n\n");
|
ESP_LOGI(TAG, "\n\n");
|
||||||
@@ -1169,10 +1251,10 @@ esp_err_t run_all_log_tests(void) {
|
|||||||
|
|
||||||
if (passed == num_tests) {
|
if (passed == num_tests) {
|
||||||
ESP_LOGI(TAG, "ALL TESTS PASSED!");
|
ESP_LOGI(TAG, "ALL TESTS PASSED!");
|
||||||
|
// Run write timing benchmark as a final report (not a pass/fail test)
|
||||||
|
test_log_write_timing();
|
||||||
} else {
|
} else {
|
||||||
ESP_LOGE(TAG, "SOME TESTS FAILED!");
|
ESP_LOGE(TAG, "SOME TESTS FAILED!");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
while(1) { esp_task_wdt_reset(); vTaskDelay(pdMS_TO_TICKS(100)); }
|
while(1) { esp_task_wdt_reset(); vTaskDelay(pdMS_TO_TICKS(100)); }
|
||||||
|
|||||||
@@ -40,6 +40,9 @@ bool test_log_full_partition(void);
|
|||||||
bool test_log_read_after_write(void);
|
bool test_log_read_after_write(void);
|
||||||
bool test_log_multiple_types(void);
|
bool test_log_multiple_types(void);
|
||||||
|
|
||||||
|
// Write timing benchmark (not a pass/fail test — prints min/max/avg report)
|
||||||
|
void test_log_write_timing(void);
|
||||||
|
|
||||||
// Helper functions for testing
|
// Helper functions for testing
|
||||||
void print_test_results(test_result_t* results, int num_tests);
|
void print_test_results(test_result_t* results, int num_tests);
|
||||||
int count_passed_tests(test_result_t* results, int num_tests);
|
int count_passed_tests(test_result_t* results, int num_tests);
|
||||||
|
|||||||
512
main/main.c
512
main/main.c
@@ -1,13 +1,16 @@
|
|||||||
#include "esp_task_wdt.h"
|
#include "esp_task_wdt.h"
|
||||||
#include "esp_system.h"
|
#include "esp_system.h"
|
||||||
|
#include "esp_ota_ops.h"
|
||||||
#include "i2c.h"
|
#include "i2c.h"
|
||||||
#include "log_test.h"
|
#include "log_test.h"
|
||||||
|
#include "partition_test.h"
|
||||||
#include "storage.h"
|
#include "storage.h"
|
||||||
#include "uart_comms.h"
|
#include "uart_comms.h"
|
||||||
#include "esp_err.h"
|
#include "esp_err.h"
|
||||||
#include "esp_log.h"
|
#include "esp_log.h"
|
||||||
#include "endian.h"
|
#include "endian.h"
|
||||||
#include "control_fsm.h"
|
#include "control_fsm.h"
|
||||||
|
#include "sc_err.h"
|
||||||
#include "power_mgmt.h"
|
#include "power_mgmt.h"
|
||||||
#include "rtc.h"
|
#include "rtc.h"
|
||||||
#include "sensors.h"
|
#include "sensors.h"
|
||||||
@@ -15,19 +18,43 @@
|
|||||||
#include "rf_433.h"
|
#include "rf_433.h"
|
||||||
#include "bt_hid.h"
|
#include "bt_hid.h"
|
||||||
#include "webserver.h"
|
#include "webserver.h"
|
||||||
|
#include "bringup.h"
|
||||||
|
#include "comms_events.h"
|
||||||
#include "version.h"
|
#include "version.h"
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
|
EventGroupHandle_t comms_event_group = NULL; // synchronizing tasks
|
||||||
|
|
||||||
#define TAG "MAIN"
|
#define TAG "MAIN"
|
||||||
|
|
||||||
|
#define POST_MAX_RETRIES 3 // how many times to try an init function
|
||||||
|
#define OTA_ROLLBACK_THRESHOLD 5 // how many resets in a row required to deem the boot partition faulty and switch to the other
|
||||||
|
#define FACTORY_RESET_HOLD_MS 10000 // how many ms is required to hold the button during cold boot to initialize factory reset
|
||||||
|
|
||||||
|
// Survives resets (panic, WDT, sw reset) but NOT power-on or external reset
|
||||||
|
RTC_DATA_ATTR static uint8_t ota_reset_counter = 0;
|
||||||
|
|
||||||
|
// Try an init function up to POST_MAX_RETRIES times. On final failure, reboot.
|
||||||
|
// Critical inits (ADC, I2C, storage, FSM, sensors) use this — a permanent failure
|
||||||
|
// feeds the OTA rollback reset counter via the panic→reboot path.
|
||||||
|
static void init_critical(const char *name, esp_err_t (*fn)(void)) {
|
||||||
|
for (int attempt = 1; attempt <= POST_MAX_RETRIES; attempt++) {
|
||||||
|
esp_err_t err = fn();
|
||||||
|
if (err == ESP_OK) return;
|
||||||
|
ESP_LOGE(TAG, "%s FAILED (attempt %d/%d): %s", name, attempt, POST_MAX_RETRIES, esp_err_to_name(err));
|
||||||
|
if (attempt < POST_MAX_RETRIES) vTaskDelay(pdMS_TO_TICKS(100));
|
||||||
|
}
|
||||||
|
ESP_LOGE(TAG, "%s FAILED after %d attempts — rebooting", name, POST_MAX_RETRIES);
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(500));
|
||||||
|
esp_restart();
|
||||||
|
}
|
||||||
|
|
||||||
int64_t last_bat_log_time = 0;
|
int64_t last_bat_log_time = 0;
|
||||||
esp_err_t send_bat_log() {
|
esp_err_t send_bat_log() {
|
||||||
if(!rtc_is_set()) return ESP_OK;
|
if(!rtc_is_set()) return ESP_OK;
|
||||||
|
|
||||||
uint8_t entry[12] = {};
|
uint8_t entry[12] = {};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Pack 64-bit timestamp into bytes 1-8
|
// Pack 64-bit timestamp into bytes 1-8
|
||||||
uint64_t be_timestamp = rtc_get_ms();
|
uint64_t be_timestamp = rtc_get_ms();
|
||||||
memcpy(&entry[0], &be_timestamp, 8);
|
memcpy(&entry[0], &be_timestamp, 8);
|
||||||
@@ -44,136 +71,172 @@ esp_err_t send_bat_log() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// --- LED Status Indicators ---
|
||||||
|
// See docs/button_behavior.md for button LED feedback.
|
||||||
|
// Status LEDs:
|
||||||
|
// IDLE: LED1 blinks 0.5Hz (1s on / 1s off)
|
||||||
|
// ERROR: 5Hz rapid blink 1s, then hold error code 2s (3s cycle)
|
||||||
|
// Error code bits: LED1=efuse, LED2=RTC/battery, LED3=safety/leash/FSM
|
||||||
|
// WATERFALL: 001→011→111→110→100→000, ~1 cycle/s (moving, delays)
|
||||||
|
// CALIBRATING: all LEDs flash 1Hz (500ms on / 500ms off)
|
||||||
|
// UNDO: solid all LEDs on
|
||||||
|
// BOOTING: LED1 solid
|
||||||
|
|
||||||
|
// LED error code bits: LED1=efuse/battery, LED2=RTC, LED3=safety/leash
|
||||||
|
static uint8_t error_code_from_state(void) {
|
||||||
|
uint8_t code = 0;
|
||||||
|
if (any_efuse_tripped()) code |= 0b001; // LED1: efuse
|
||||||
|
float bat_v = get_battery_V();
|
||||||
|
float low_v = get_param_value_t(PARAM_LOW_PROTECTION_V).f32;
|
||||||
|
if (bat_v > 0 && bat_v < low_v) code |= 0b001; // LED1: low battery
|
||||||
|
if (!rtc_is_set()) code |= 0b010; // LED2: RTC not set
|
||||||
|
esp_err_t fe = fsm_get_error();
|
||||||
|
if (fe == SC_ERR_SAFETY_TRIP) code |= 0b100; // LED3: safety
|
||||||
|
if (fe == SC_ERR_LEASH_HIT) code |= 0b100; // LED3: leash
|
||||||
|
if (fe != ESP_OK && code == 0) code = 0b111; // unknown error
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
LED_STATE_DRIVING,
|
LED_IDLE,
|
||||||
LED_STATE_ERROR,
|
LED_ERROR,
|
||||||
LED_STATE_AWAKE,
|
LED_WATERFALL,
|
||||||
LED_STATE_CANCELLING,
|
LED_CALIBRATING,
|
||||||
LED_STATE_ERRORED,
|
LED_UNDO,
|
||||||
LED_STATE_START1,
|
LED_BOOTING
|
||||||
LED_STATE_START2,
|
} led_mode_t;
|
||||||
LED_STATE_START3,
|
|
||||||
LED_STATE_START4,
|
|
||||||
LED_STATE_BOOTING
|
|
||||||
} led_state_t;
|
|
||||||
|
|
||||||
void drive_leds(led_state_t state) {
|
void drive_leds(led_mode_t mode) {
|
||||||
uint8_t patterns[5][12] = {
|
static const uint8_t waterfall[] = {0b001, 0b011, 0b111, 0b110, 0b100, 0b000};
|
||||||
{1,3,7,6,4,0},
|
int64_t now_us = esp_timer_get_time();
|
||||||
{0b101,0b001},
|
|
||||||
{1,1,1,1,1,1, 1,1,1,3},
|
switch (mode) {
|
||||||
{4,2},
|
case LED_IDLE:
|
||||||
{0b001, 0b101},
|
// 0.5Hz: 1s on, 1s off
|
||||||
};
|
i2c_set_led1((now_us / 1000000) % 2 ? 0b000 : 0b001);
|
||||||
switch(state) {
|
|
||||||
case LED_STATE_DRIVING:
|
|
||||||
i2c_set_led1(patterns[state][(esp_timer_get_time()/100000) % 6]);
|
|
||||||
break;
|
|
||||||
case LED_STATE_ERROR:
|
|
||||||
//ESP_LOGE(TAG, "SOME SORT OF ERROR");
|
|
||||||
i2c_set_led1(patterns[state][(esp_timer_get_time()/1000000) % 2]);
|
|
||||||
break;
|
|
||||||
case LED_STATE_AWAKE:
|
|
||||||
i2c_set_led1(patterns[state][(esp_timer_get_time()/200000) % 10]);
|
|
||||||
break;
|
|
||||||
case LED_STATE_CANCELLING:
|
|
||||||
i2c_set_led1(patterns[state][(esp_timer_get_time()/200000) % 2]);
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case LED_STATE_ERRORED:
|
case LED_ERROR: {
|
||||||
i2c_set_led1(patterns[state][(esp_timer_get_time()/200000) % 2]);
|
// 3s cycle: 1s rapid blink (5Hz) then 2s hold error code
|
||||||
|
int64_t phase_us = now_us % 3000000;
|
||||||
|
if (phase_us < 1000000) {
|
||||||
|
// Rapid blink at 5Hz (100ms per half-cycle)
|
||||||
|
i2c_set_led1((phase_us / 100000) % 2 ? 0b000 : 0b111);
|
||||||
|
} else {
|
||||||
|
i2c_set_led1(error_code_from_state());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case LED_WATERFALL:
|
||||||
|
// ~1 cycle/s: 6 steps at ~167ms each
|
||||||
|
i2c_set_led1(waterfall[(now_us / 167000) % 6]);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case LED_STATE_BOOTING:
|
case LED_CALIBRATING:
|
||||||
i2c_set_led1(0b001);
|
// 1Hz: 500ms on, 500ms off
|
||||||
|
i2c_set_led1((now_us / 500000) % 2 ? 0b000 : 0b111);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case LED_STATE_START1:
|
case LED_UNDO:
|
||||||
i2c_set_led1(0b000);
|
|
||||||
break;
|
|
||||||
case LED_STATE_START2:
|
|
||||||
i2c_set_led1(0b001);
|
|
||||||
break;
|
|
||||||
case LED_STATE_START3:
|
|
||||||
i2c_set_led1(0b011);
|
|
||||||
break;
|
|
||||||
case LED_STATE_START4:
|
|
||||||
i2c_set_led1(0b111);
|
i2c_set_led1(0b111);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case LED_BOOTING:
|
||||||
|
i2c_set_led1(0b001);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void app_main(void) {esp_task_wdt_add(NULL);
|
void app_main(void) {
|
||||||
|
esp_task_wdt_add(NULL);
|
||||||
//run_all_log_tests();
|
|
||||||
|
|
||||||
ESP_LOGI(TAG, "Firmware: %s", FIRMWARE_STRING);
|
ESP_LOGI(TAG, "Firmware: %s", FIRMWARE_STRING);
|
||||||
ESP_LOGI(TAG, "Version: %s", FIRMWARE_VERSION);
|
ESP_LOGI(TAG, "Version: %s", FIRMWARE_VERSION);
|
||||||
ESP_LOGI(TAG, "Branch: %s", FIRMWARE_BRANCH);
|
ESP_LOGI(TAG, "Branch: %s", FIRMWARE_BRANCH);
|
||||||
ESP_LOGI(TAG, "Built: %s", BUILD_DATE);
|
ESP_LOGI(TAG, "Built: %s", BUILD_DATE);
|
||||||
|
|
||||||
|
// I2C first so we can light the LED immediately
|
||||||
|
init_critical("I2C", i2c_init);
|
||||||
|
drive_leds(LED_BOOTING); // LED on ASAP after I2C is up
|
||||||
|
i2c_post(); // verify TCA9555 responds
|
||||||
|
/* Sensors powered from boot; FSM will keep P10 high on every tick.
|
||||||
|
* Drops back to 0 on soft_idle_enter() (sleep). */
|
||||||
|
i2c_relays_idle();
|
||||||
|
|
||||||
if (rtc_xtal_init() != ESP_OK) ESP_LOGE(TAG, "RTC FAILED");
|
if (rtc_xtal_init() != ESP_OK) ESP_LOGE(TAG, "RTC FAILED");
|
||||||
rtc_restore_time(); // Recover time from RTC domain if we crashed
|
|
||||||
|
|
||||||
// Say hello; turn on the lights
|
// Factory reset: cold boot + button held for 10s
|
||||||
rtc_wakeup_cause(); // log wakeup cause (informational only)
|
// LEDs flash while waiting, go solid when triggered
|
||||||
if (i2c_init() != ESP_OK) ESP_LOGE(TAG, "I2C FAILED");
|
|
||||||
i2c_set_relays((relay_port_t){.raw=0});
|
|
||||||
drive_leds(LED_STATE_BOOTING);
|
|
||||||
|
|
||||||
|
|
||||||
// Check for factory reset condition: Cold boot (power-on/ext-reset) + button held
|
|
||||||
esp_reset_reason_t boot_reset_reason = esp_reset_reason();
|
esp_reset_reason_t boot_reset_reason = esp_reset_reason();
|
||||||
if ((boot_reset_reason == ESP_RST_POWERON || boot_reset_reason == ESP_RST_EXT)
|
if ((boot_reset_reason == ESP_RST_POWERON || boot_reset_reason == ESP_RST_EXT)
|
||||||
&& gpio_get_level(GPIO_NUM_13) == 0) {
|
&& i2c_button_held_raw(0)) {
|
||||||
ESP_LOGW(TAG, "FACTORY RESET TRIGGERED - Button held on cold boot");
|
ESP_LOGW(TAG, "Button held on cold boot — hold %ds for factory reset", FACTORY_RESET_HOLD_MS / 1000);
|
||||||
|
|
||||||
// Flash LED pattern to indicate factory reset
|
// Flash all LEDs while user holds button (100ms on/off cycle).
|
||||||
for (int i = 0; i < 10; i++) {
|
// GPIO13 is the NCA9535 INT line on V5 (not a direct button line),
|
||||||
i2c_set_led1(0b111);
|
// so we have to read INPUT0 over I2C to know if the button is
|
||||||
|
// currently held — the GPIO would only pulse low on edges.
|
||||||
|
int held_ms = 0;
|
||||||
|
while (i2c_button_held_raw(0) && held_ms < FACTORY_RESET_HOLD_MS) {
|
||||||
|
i2c_set_led1((held_ms / 100) % 2 ? 0b111 : 0b000);
|
||||||
vTaskDelay(pdMS_TO_TICKS(100));
|
vTaskDelay(pdMS_TO_TICKS(100));
|
||||||
|
held_ms += 100;
|
||||||
|
esp_task_wdt_reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (held_ms < FACTORY_RESET_HOLD_MS) {
|
||||||
|
ESP_LOGI(TAG, "Button released early (%dms) — skipping factory reset", held_ms);
|
||||||
i2c_set_led1(0b000);
|
i2c_set_led1(0b000);
|
||||||
vTaskDelay(pdMS_TO_TICKS(100));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize minimal components needed for factory reset
|
|
||||||
if (storage_init() != ESP_OK) ESP_LOGE(TAG, "STORAGE FAILED");
|
|
||||||
|
|
||||||
// Perform factory reset
|
|
||||||
esp_err_t reset_err = factory_reset();
|
|
||||||
if (reset_err == ESP_OK) {
|
|
||||||
ESP_LOGI(TAG, "Factory reset completed successfully");
|
|
||||||
// Flash success pattern
|
|
||||||
for (int i = 0; i < 5; i++) {
|
|
||||||
i2c_set_led1(0b010);
|
|
||||||
vTaskDelay(pdMS_TO_TICKS(200));
|
|
||||||
i2c_set_led1(0b000);
|
|
||||||
vTaskDelay(pdMS_TO_TICKS(200));
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
ESP_LOGE(TAG, "Factory reset failed!");
|
// Solid LEDs = reset triggered
|
||||||
// Flash error pattern
|
i2c_set_led1(0b111);
|
||||||
for (int i = 0; i < 5; i++) {
|
ESP_LOGW(TAG, "FACTORY RESET TRIGGERED");
|
||||||
i2c_set_led1(0b100);
|
|
||||||
vTaskDelay(pdMS_TO_TICKS(200));
|
|
||||||
i2c_set_led1(0b000);
|
|
||||||
vTaskDelay(pdMS_TO_TICKS(200));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reboot the system
|
// Initialize storage so we can erase it
|
||||||
ESP_LOGI(TAG, "Rebooting system...");
|
if (storage_init() != ESP_OK) ESP_LOGE(TAG, "STORAGE FAILED");
|
||||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
|
||||||
esp_restart();
|
esp_err_t reset_err = factory_reset();
|
||||||
|
if (reset_err == ESP_OK) {
|
||||||
|
ESP_LOGI(TAG, "Factory reset completed successfully");
|
||||||
|
// Success: green blink
|
||||||
|
for (int i = 0; i < 5; i++) {
|
||||||
|
i2c_set_led1(0b010);
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(200));
|
||||||
|
i2c_set_led1(0b000);
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(200));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ESP_LOGE(TAG, "Factory reset failed!");
|
||||||
|
// Error: red blink
|
||||||
|
for (int i = 0; i < 5; i++) {
|
||||||
|
i2c_set_led1(0b100);
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(200));
|
||||||
|
i2c_set_led1(0b000);
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(200));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "Rebooting system...");
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||||
|
esp_restart();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Every boot we load parameters and monitor solar, no matter what
|
// Critical inits — retry up to 3 times, then reboot
|
||||||
if (adc_init() != ESP_OK) ESP_LOGE(TAG, "ADC FAILED");
|
init_critical("ADC", adc_init);
|
||||||
if (storage_init() != ESP_OK) ESP_LOGE(TAG, "STORAGE FAILED");
|
init_critical("STORAGE", storage_init);
|
||||||
if (log_init() != ESP_OK) ESP_LOGE(TAG, "LOG FAILED");
|
rtc_restore_time(); // After NVS is up: try RTC_DATA_ATTR, then NVS fallback
|
||||||
|
init_critical("LOG", log_init);
|
||||||
|
|
||||||
esp_reset_reason_t reset_reason = esp_reset_reason();
|
// POST checks — verify hardware is responding correctly
|
||||||
esp_sleep_wakeup_cause_t wake_cause = esp_sleep_get_wakeup_cause();
|
adc_post(); // ADC channels readable and not frozen
|
||||||
|
storage_post(); // flash write-read-verify on test sector
|
||||||
|
|
||||||
|
//run_all_log_tests();
|
||||||
|
|
||||||
|
esp_reset_reason_t reset_reason = esp_reset_reason();
|
||||||
|
esp_sleep_wakeup_cause_t wake_cause = esp_sleep_get_wakeup_cause();
|
||||||
|
|
||||||
// Log every boot: boot_info = wake_cause[7:4] | reset_reason[3:0]
|
// Log every boot: boot_info = wake_cause[7:4] | reset_reason[3:0]
|
||||||
{
|
{
|
||||||
@@ -184,156 +247,247 @@ void app_main(void) {esp_task_wdt_add(NULL);
|
|||||||
log_write(boot_entry, sizeof(boot_entry), LOG_TYPE_BOOT);
|
log_write(boot_entry, sizeof(boot_entry), LOG_TYPE_BOOT);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write a crash log entry if we rebooted unexpectedly
|
// OTA rollback: count consecutive abnormal resets (panic/WDT).
|
||||||
if (reset_reason == ESP_RST_PANIC ||
|
// Power-on and external resets clear the counter; crashes increment it.
|
||||||
reset_reason == ESP_RST_INT_WDT ||
|
// After OTA_ROLLBACK_THRESHOLD consecutive crashes, roll back to the
|
||||||
reset_reason == ESP_RST_TASK_WDT ||
|
// previous OTA partition (if available).
|
||||||
reset_reason == ESP_RST_WDT) {
|
if (reset_reason == ESP_RST_POWERON || reset_reason == ESP_RST_EXT) {
|
||||||
ESP_LOGW(TAG, "Crash detected! Reset reason: %d", reset_reason);
|
ota_reset_counter = 0;
|
||||||
|
} else if (reset_reason == ESP_RST_PANIC ||
|
||||||
|
reset_reason == ESP_RST_INT_WDT ||
|
||||||
|
reset_reason == ESP_RST_TASK_WDT ||
|
||||||
|
reset_reason == ESP_RST_WDT) {
|
||||||
|
ota_reset_counter++;
|
||||||
|
ESP_LOGW(TAG, "Crash detected (reason=%d), reset counter=%d/%d",
|
||||||
|
reset_reason, ota_reset_counter, OTA_ROLLBACK_THRESHOLD);
|
||||||
|
|
||||||
uint8_t crash_entry[9] = {};
|
uint8_t crash_entry[9] = {};
|
||||||
uint64_t ts = rtc_get_ms();
|
uint64_t ts = rtc_get_ms();
|
||||||
memcpy(&crash_entry[0], &ts, 8);
|
memcpy(&crash_entry[0], &ts, 8);
|
||||||
crash_entry[8] = (uint8_t)reset_reason;
|
crash_entry[8] = (uint8_t)reset_reason;
|
||||||
log_write(crash_entry, sizeof(crash_entry), LOG_TYPE_CRASH);
|
log_write(crash_entry, sizeof(crash_entry), LOG_TYPE_CRASH);
|
||||||
|
|
||||||
|
if (ota_reset_counter >= OTA_ROLLBACK_THRESHOLD) {
|
||||||
|
ESP_LOGE(TAG, "Rollback threshold reached — marking app invalid");
|
||||||
|
esp_ota_mark_app_invalid_rollback_and_reboot();
|
||||||
|
// Does not return — reboots into previous OTA slot
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (solar_run_fsm() != ESP_OK) ESP_LOGE(TAG, "SOLAR FAILED");
|
if (solar_run_fsm() != ESP_OK) ESP_LOGE(TAG, "SOLAR FAILED");
|
||||||
// TODO: Do a 12V check and enter deep sleep if there's a problem
|
|
||||||
|
|
||||||
send_bat_log();
|
send_bat_log();
|
||||||
|
|
||||||
|
/*** FULL BOOT ***/
|
||||||
|
// Critical — must succeed or reboot
|
||||||
|
init_critical("UART", uart_init);
|
||||||
|
init_critical("SENSORS", sensors_init);
|
||||||
|
init_critical("FSM", fsm_init);
|
||||||
|
|
||||||
//send_log();
|
// Create event group before non-critical inits (they set bits on it)
|
||||||
|
comms_event_group = xEventGroupCreate();
|
||||||
|
|
||||||
//write_dummy_log_1();
|
// Non-critical — retry once on failure, then log and continue.
|
||||||
|
// Set event bits even on failure so alarm-wake doesn't block forever.
|
||||||
|
if (rf_433_init() != ESP_OK) {
|
||||||
|
ESP_LOGW(TAG, "RF init failed, retrying...");
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(200));
|
||||||
|
if (rf_433_init() != ESP_OK) ESP_LOGE(TAG, "RF FAILED (continuing without RF)");
|
||||||
|
}
|
||||||
|
if (bt_hid_init() != ESP_OK) {
|
||||||
|
ESP_LOGW(TAG, "BT init failed, retrying...");
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(200));
|
||||||
|
if (bt_hid_init() != ESP_OK) {
|
||||||
|
ESP_LOGE(TAG, "BT HID FAILED (continuing without BT)");
|
||||||
|
if (comms_event_group) xEventGroupSetBits(comms_event_group, BT_READY_BIT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (webserver_init() != ESP_OK) {
|
||||||
|
ESP_LOGW(TAG, "Webserver init failed, retrying...");
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(500));
|
||||||
|
if (webserver_init() != ESP_OK) {
|
||||||
|
ESP_LOGE(TAG, "WEBSERVER FAILED (continuing without WiFi)");
|
||||||
|
if (comms_event_group) xEventGroupSetBits(comms_event_group, WIFI_READY_BIT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*** FULL BOOT — always, every boot ***/
|
// POST + FSM started successfully — this firmware is good.
|
||||||
if (uart_init() != ESP_OK) ESP_LOGE(TAG, "UART FAILED");
|
// Clear the rollback counter and mark the OTA partition as valid.
|
||||||
//if (power_init() != ESP_OK) ESP_LOGE(TAG, "POWER FAILED");
|
ota_reset_counter = 0;
|
||||||
if (rf_433_init() != ESP_OK) ESP_LOGE(TAG, "RF FAILED");
|
esp_ota_mark_app_valid_cancel_rollback();
|
||||||
if (bt_hid_init() != ESP_OK) ESP_LOGE(TAG, "BT HID FAILED");
|
|
||||||
if (fsm_init() != ESP_OK) ESP_LOGE(TAG, "FSM FAILED");
|
|
||||||
//if (sensors_init() != ESP_OK) ESP_LOGE(TAG, "SENSORS FAILED");
|
|
||||||
if (webserver_init() != ESP_OK) ESP_LOGE(TAG, "WEBSERVER FAILED");
|
|
||||||
|
|
||||||
/*** MAIN LOOP ***/
|
/*** MAIN LOOP ***/
|
||||||
|
uint8_t tap_count = 0;
|
||||||
|
int64_t tap_window_start = 0;
|
||||||
|
|
||||||
TickType_t xLastWakeTime = xTaskGetTickCount();
|
TickType_t xLastWakeTime = xTaskGetTickCount();
|
||||||
const TickType_t xFrequency = pdMS_TO_TICKS(50);
|
const TickType_t xFrequency = pdMS_TO_TICKS(50);
|
||||||
|
|
||||||
/*while(true) {
|
|
||||||
ESP_LOGI(TAG, "TICK");
|
|
||||||
vTaskDelayUntil(&xLastWakeTime, pdMS_TO_TICKS(1000));
|
|
||||||
esp_task_wdt_reset();
|
|
||||||
}*/
|
|
||||||
|
|
||||||
while(true) {
|
while(true) {
|
||||||
vTaskDelayUntil(&xLastWakeTime, xFrequency);
|
vTaskDelayUntil(&xLastWakeTime, xFrequency);
|
||||||
|
|
||||||
/* In soft idle: slow poll (5s) via direct GPIO, no I2C. */
|
/* Bring-up tool owns the LEDs, buttons, and relays while active. */
|
||||||
|
if (bringup_mode_is_active()) {
|
||||||
|
esp_task_wdt_reset();
|
||||||
|
continue; // while in bringup, don't do anything more
|
||||||
|
}
|
||||||
|
|
||||||
if (soft_idle_is_active()) {
|
if (soft_idle_is_active()) {
|
||||||
//vTaskDelay(pdMS_TO_TICKS(1000));
|
// Button wake: just exit idle and fall through to the normal main
|
||||||
|
// loop. The user is physically present, so any actual movement
|
||||||
|
// happens later via triple-tap / RF / web — by then WiFi+BT have
|
||||||
|
// had plenty of time to come back up on their own.
|
||||||
if (soft_idle_button_raw()) {
|
if (soft_idle_button_raw()) {
|
||||||
rtc_reset_shutdown_timer();
|
rtc_reset_shutdown_timer();
|
||||||
soft_idle_exit();
|
soft_idle_exit();
|
||||||
i2c_poll_buttons(); /* sync TCA9555 state after idle */
|
i2c_poll_buttons(); /* sync TCA9555 state after idle */
|
||||||
xLastWakeTime = xTaskGetTickCount();
|
xLastWakeTime = xTaskGetTickCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Alarm wake: must immediately issue FSM_CMD_START — nobody is
|
||||||
|
// here to press a button. soft_idle_enter() tore down WiFi+BT
|
||||||
|
// (see rtc.c soft_idle_enter); soft_idle_exit() restarts them
|
||||||
|
// but they come up asynchronously. Block up to 5 s for both
|
||||||
|
// event-group bits so telemetry/abort paths are live before the
|
||||||
|
// automated move begins. Past timeout we start anyway — the
|
||||||
|
// physical safety/efuse interlocks still protect the hardware.
|
||||||
if (rtc_alarm_tripped()) {
|
if (rtc_alarm_tripped()) {
|
||||||
soft_idle_exit();
|
soft_idle_exit();
|
||||||
xLastWakeTime = xTaskGetTickCount();
|
xLastWakeTime = xTaskGetTickCount();
|
||||||
vTaskDelay(pdMS_TO_TICKS(500));
|
if (comms_event_group) {
|
||||||
|
xEventGroupWaitBits(comms_event_group, COMMS_ALL_BITS,
|
||||||
|
pdFALSE, pdTRUE, pdMS_TO_TICKS(5000));
|
||||||
|
}
|
||||||
|
esp_task_wdt_reset();
|
||||||
fsm_request(FSM_CMD_START);
|
fsm_request(FSM_CMD_START);
|
||||||
rtc_schedule_next_alarm();
|
rtc_schedule_next_alarm();
|
||||||
}
|
}
|
||||||
solar_run_fsm();
|
solar_run_fsm();
|
||||||
rtc_check_shutdown_timer();
|
rtc_check_shutdown_timer();
|
||||||
esp_task_wdt_reset();
|
esp_task_wdt_reset();
|
||||||
continue;
|
continue; // while in idle, don't do anything more
|
||||||
}
|
}
|
||||||
|
|
||||||
i2c_poll_buttons();
|
i2c_poll_buttons();
|
||||||
|
|
||||||
if (i2c_get_button_state(0)) {
|
if (i2c_get_button_state(0)) {
|
||||||
rtc_reset_shutdown_timer();
|
rtc_reset_shutdown_timer();
|
||||||
soft_idle_exit();
|
// soft_idle_exit(); // this should be superfluous
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (fsm_get_state()) {
|
// --- Button logic: triple-tap, hold-to-reboot, cancel/stop ---
|
||||||
|
// See docs/button_behavior.md for full spec.
|
||||||
|
fsm_state_t cur_state = fsm_get_state();
|
||||||
|
bool btn_pressed = i2c_get_button_state(0);
|
||||||
|
bool btn_tripped = i2c_get_button_tripped(0);
|
||||||
|
bool btn_released = i2c_get_button_released(0);
|
||||||
|
int64_t btn_held = i2c_get_button_ms(0);
|
||||||
|
|
||||||
|
// Hold-to-reboot (active in IDLE and CALIBRATE states only)
|
||||||
|
bool hold_reboot_active = (cur_state == STATE_IDLE ||
|
||||||
|
cur_state == STATE_CALIBRATE_JACK_DELAY ||
|
||||||
|
cur_state == STATE_CALIBRATE_JACK_MOVE ||
|
||||||
|
cur_state == STATE_CALIBRATE_DRIVE_DELAY ||
|
||||||
|
cur_state == STATE_CALIBRATE_DRIVE_MOVE);
|
||||||
|
|
||||||
|
if (hold_reboot_active && btn_pressed && btn_held > 3000) {
|
||||||
|
// Flash all LEDs then reboot
|
||||||
|
ESP_LOGW(TAG, "Hold-to-reboot triggered");
|
||||||
|
rtc_save_time();
|
||||||
|
for (int i = 0; i < 6; i++) {
|
||||||
|
i2c_set_led1(i % 2 ? 0b000 : 0b111);
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(150));
|
||||||
|
}
|
||||||
|
esp_restart();
|
||||||
|
}
|
||||||
|
|
||||||
|
// LED feedback while holding: off → 1 → 1+2 → 1+2+3
|
||||||
|
if (hold_reboot_active && btn_pressed && btn_held > 100) {
|
||||||
|
if (btn_held > 2250) i2c_set_led1(0b111);
|
||||||
|
else if (btn_held > 1500) i2c_set_led1(0b011);
|
||||||
|
else if (btn_held > 750) i2c_set_led1(0b001);
|
||||||
|
else i2c_set_led1(0b000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tap processing — uses release edge so it doesn't conflict with hold
|
||||||
|
switch (cur_state) {
|
||||||
case STATE_IDLE:
|
case STATE_IDLE:
|
||||||
// LED cue for user
|
// Triple-tap to start (count on release, ignore long presses)
|
||||||
if (i2c_get_button_ms(0) > 1600){
|
if (btn_released && btn_held < 1000) {
|
||||||
drive_leds(LED_STATE_START4);
|
tap_count++;
|
||||||
} else if (i2c_get_button_ms(0) > 1100){
|
if (tap_count == 1) tap_window_start = esp_timer_get_time();
|
||||||
drive_leds(LED_STATE_START3);
|
ESP_LOGI(TAG, "Tap %d/3", tap_count);
|
||||||
} else if (i2c_get_button_ms(0) > 600){
|
if (tap_count >= 3) {
|
||||||
drive_leds(LED_STATE_START2);
|
ESP_LOGI(TAG, "Triple-tap → START");
|
||||||
} else if (i2c_get_button_ms(0) > 100){
|
tap_count = 0;
|
||||||
drive_leds(LED_STATE_START1);
|
fsm_request(FSM_CMD_START);
|
||||||
} else {
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tap window LED feedback + expiry
|
||||||
|
if (tap_count > 0) {
|
||||||
|
if (esp_timer_get_time() - tap_window_start > 2000000) {
|
||||||
|
ESP_LOGI(TAG, "Tap window expired at %d/3", tap_count);
|
||||||
|
tap_count = 0; // window expired
|
||||||
|
} else if (!btn_pressed) {
|
||||||
|
uint8_t led = (tap_count >= 2) ? 0b011 : 0b001;
|
||||||
|
i2c_set_led1(led);
|
||||||
|
break; // skip default LED while showing tap feedback
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default idle LEDs (only when not holding or tap-counting)
|
||||||
|
if (!btn_pressed && tap_count == 0) {
|
||||||
if (
|
if (
|
||||||
rtc_is_set() &&
|
rtc_is_set() &&
|
||||||
efuse_get(BRIDGE_JACK)==EFUSE_OK &&
|
!any_efuse_tripped() &&
|
||||||
efuse_get(BRIDGE_AUX)==EFUSE_OK &&
|
|
||||||
efuse_get(BRIDGE_DRIVE)==EFUSE_OK &&
|
|
||||||
fsm_get_error() == ESP_OK
|
fsm_get_error() == ESP_OK
|
||||||
) {
|
) {
|
||||||
drive_leds(LED_STATE_AWAKE);
|
drive_leds(LED_IDLE);
|
||||||
} else {
|
} else {
|
||||||
drive_leds(LED_STATE_ERROR);
|
drive_leds(LED_ERROR);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
/*int8_t state = 0b001;
|
|
||||||
if (get_is_safe()) state |= 0b010;
|
|
||||||
if (get_sensor(SENSOR_SAFETY)) state |= 0b100;
|
|
||||||
i2c_set_led1(state);*/
|
|
||||||
}
|
|
||||||
|
|
||||||
// when not actively moving we log at a low frequency (every 120s)
|
// when not actively moving we log at a low frequency (every 120s)
|
||||||
if ((esp_timer_get_time() > last_bat_log_time + 120000000ULL))
|
if ((esp_timer_get_time() > last_bat_log_time + 120000000ULL))
|
||||||
send_bat_log();
|
send_bat_log();
|
||||||
|
|
||||||
if(i2c_get_button_ms(0) > 2100)
|
|
||||||
fsm_request(FSM_CMD_START);
|
|
||||||
break;
|
break;
|
||||||
//case STATE_UNDO_JACK:
|
|
||||||
case STATE_UNDO_JACK_START:
|
case STATE_UNDO_JACK_START:
|
||||||
// it's running the jack, but undoing
|
drive_leds(LED_UNDO);
|
||||||
//send_log();
|
if (btn_tripped) {
|
||||||
drive_leds(LED_STATE_CANCELLING);
|
ESP_LOGI(TAG, "STOP");
|
||||||
if (i2c_get_button_tripped(0)) {
|
|
||||||
ESP_LOGI(TAG, "AAAAH STOP!!!");
|
|
||||||
fsm_request(FSM_CMD_STOP);
|
fsm_request(FSM_CMD_STOP);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case STATE_CALIBRATE_JACK_DELAY:
|
case STATE_CALIBRATE_JACK_DELAY:
|
||||||
//send_log();
|
drive_leds(LED_CALIBRATING);
|
||||||
if (i2c_get_button_tripped(0))
|
if (btn_tripped)
|
||||||
fsm_request(FSM_CMD_CALIBRATE_JACK_START);
|
fsm_request(FSM_CMD_CALIBRATE_JACK_START);
|
||||||
break;
|
break;
|
||||||
case STATE_CALIBRATE_JACK_MOVE:
|
case STATE_CALIBRATE_JACK_MOVE:
|
||||||
//send_log();
|
drive_leds(LED_CALIBRATING);
|
||||||
if (i2c_get_button_tripped(0))
|
if (btn_tripped)
|
||||||
fsm_request(FSM_CMD_CALIBRATE_JACK_END);
|
fsm_request(FSM_CMD_CALIBRATE_JACK_END);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|
||||||
case STATE_CALIBRATE_DRIVE_DELAY:
|
case STATE_CALIBRATE_DRIVE_DELAY:
|
||||||
//send_log();
|
drive_leds(LED_CALIBRATING);
|
||||||
if (i2c_get_button_tripped(0))
|
if (btn_tripped)
|
||||||
fsm_request(FSM_CMD_CALIBRATE_DRIVE_START);
|
fsm_request(FSM_CMD_CALIBRATE_DRIVE_START);
|
||||||
break;
|
break;
|
||||||
case STATE_CALIBRATE_DRIVE_MOVE:
|
case STATE_CALIBRATE_DRIVE_MOVE:
|
||||||
//send_log();
|
drive_leds(LED_CALIBRATING);
|
||||||
if (i2c_get_button_tripped(0))
|
if (btn_tripped)
|
||||||
fsm_request(FSM_CMD_CALIBRATE_DRIVE_END);
|
fsm_request(FSM_CMD_CALIBRATE_DRIVE_END);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
// it's running in every other case
|
// Moving — any press cancels
|
||||||
//send_log();
|
drive_leds(LED_WATERFALL);
|
||||||
drive_leds(LED_STATE_DRIVING);
|
if (btn_tripped) {
|
||||||
if (i2c_get_button_tripped(0)) {
|
ESP_LOGI(TAG, "UNDO");
|
||||||
fsm_request(FSM_CMD_UNDO);
|
fsm_request(FSM_CMD_UNDO);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|||||||
467
main/partition_test.c
Normal file
467
main/partition_test.c
Normal file
@@ -0,0 +1,467 @@
|
|||||||
|
#include "partition_test.h"
|
||||||
|
#include "storage.h"
|
||||||
|
#include "esp_partition.h"
|
||||||
|
#include "esp_log.h"
|
||||||
|
#include "esp_task_wdt.h"
|
||||||
|
#include "freertos/FreeRTOS.h"
|
||||||
|
#include "freertos/task.h"
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#define TAG "PART_TEST"
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Test 1: Params partition read/write
|
||||||
|
// ============================================================================
|
||||||
|
bool test_params_partition_rw(void) {
|
||||||
|
ESP_LOGI(TAG, "=== Test: params partition read/write ===");
|
||||||
|
|
||||||
|
const esp_partition_t *part = esp_partition_find_first(
|
||||||
|
ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_ANY, "params");
|
||||||
|
if (part == NULL) {
|
||||||
|
ESP_LOGE(TAG, "FAIL: params partition not found");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "params partition: offset=0x%lx size=%lu",
|
||||||
|
(unsigned long)part->address, (unsigned long)part->size);
|
||||||
|
|
||||||
|
// Erase first sector
|
||||||
|
esp_err_t err = esp_partition_erase_range(part, 0, FLASH_SECTOR_SIZE);
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGE(TAG, "FAIL: erase failed: %s", esp_err_to_name(err));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write test pattern
|
||||||
|
uint8_t write_buf[32];
|
||||||
|
for (int i = 0; i < 32; i++) write_buf[i] = (uint8_t)(0xAA ^ i);
|
||||||
|
|
||||||
|
err = esp_partition_write(part, 0, write_buf, sizeof(write_buf));
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGE(TAG, "FAIL: write failed: %s", esp_err_to_name(err));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read back and verify
|
||||||
|
uint8_t read_buf[32];
|
||||||
|
err = esp_partition_read(part, 0, read_buf, sizeof(read_buf));
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGE(TAG, "FAIL: read failed: %s", esp_err_to_name(err));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (memcmp(write_buf, read_buf, sizeof(write_buf)) != 0) {
|
||||||
|
ESP_LOGE(TAG, "FAIL: data mismatch");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify erased area reads 0xFF
|
||||||
|
uint8_t erased_check;
|
||||||
|
err = esp_partition_read(part, 64, &erased_check, 1);
|
||||||
|
if (err != ESP_OK || erased_check != 0xFF) {
|
||||||
|
ESP_LOGE(TAG, "FAIL: erased area not 0xFF (got 0x%02X)", erased_check);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up
|
||||||
|
esp_partition_erase_range(part, 0, FLASH_SECTOR_SIZE);
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "PASS");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Test 2: Log partition read/write
|
||||||
|
// ============================================================================
|
||||||
|
bool test_log_partition_rw(void) {
|
||||||
|
ESP_LOGI(TAG, "=== Test: log partition read/write ===");
|
||||||
|
|
||||||
|
const esp_partition_t *part = esp_partition_find_first(
|
||||||
|
ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_ANY, "log");
|
||||||
|
if (part == NULL) {
|
||||||
|
ESP_LOGE(TAG, "FAIL: log partition not found");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "log partition: offset=0x%lx size=%lu",
|
||||||
|
(unsigned long)part->address, (unsigned long)part->size);
|
||||||
|
|
||||||
|
// Verify size matches expectations (108K = 27 sectors)
|
||||||
|
uint32_t expected_sectors = part->size / FLASH_SECTOR_SIZE;
|
||||||
|
ESP_LOGI(TAG, "log partition has %lu sectors", (unsigned long)expected_sectors);
|
||||||
|
|
||||||
|
// Test write at start of partition
|
||||||
|
esp_err_t err = esp_partition_erase_range(part, 0, FLASH_SECTOR_SIZE);
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGE(TAG, "FAIL: erase sector 0 failed: %s", esp_err_to_name(err));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t write_buf[16] = {0xDE, 0xAD, 0xBE, 0xEF, 0x01, 0x02, 0x03, 0x04,
|
||||||
|
0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C};
|
||||||
|
err = esp_partition_write(part, 0, write_buf, sizeof(write_buf));
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGE(TAG, "FAIL: write failed: %s", esp_err_to_name(err));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t read_buf[16];
|
||||||
|
err = esp_partition_read(part, 0, read_buf, sizeof(read_buf));
|
||||||
|
if (err != ESP_OK || memcmp(write_buf, read_buf, sizeof(write_buf)) != 0) {
|
||||||
|
ESP_LOGE(TAG, "FAIL: read-back mismatch at offset 0");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test write at last sector
|
||||||
|
uint32_t last_sector = part->size - FLASH_SECTOR_SIZE;
|
||||||
|
err = esp_partition_erase_range(part, last_sector, FLASH_SECTOR_SIZE);
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGE(TAG, "FAIL: erase last sector failed: %s", esp_err_to_name(err));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = esp_partition_write(part, last_sector, write_buf, sizeof(write_buf));
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGE(TAG, "FAIL: write to last sector failed: %s", esp_err_to_name(err));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = esp_partition_read(part, last_sector, read_buf, sizeof(read_buf));
|
||||||
|
if (err != ESP_OK || memcmp(write_buf, read_buf, sizeof(write_buf)) != 0) {
|
||||||
|
ESP_LOGE(TAG, "FAIL: read-back mismatch at last sector");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up
|
||||||
|
esp_partition_erase_range(part, 0, FLASH_SECTOR_SIZE);
|
||||||
|
esp_partition_erase_range(part, last_sector, FLASH_SECTOR_SIZE);
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "PASS");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Test 3: POST test partition read/write
|
||||||
|
// ============================================================================
|
||||||
|
bool test_post_partition_rw(void) {
|
||||||
|
ESP_LOGI(TAG, "=== Test: post_test partition read/write ===");
|
||||||
|
|
||||||
|
const esp_partition_t *part = esp_partition_find_first(
|
||||||
|
ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_ANY, "post_test");
|
||||||
|
if (part == NULL) {
|
||||||
|
ESP_LOGE(TAG, "FAIL: post_test partition not found");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "post_test partition: offset=0x%lx size=%lu",
|
||||||
|
(unsigned long)part->address, (unsigned long)part->size);
|
||||||
|
|
||||||
|
// Verify it's exactly 4K (1 sector)
|
||||||
|
if (part->size != FLASH_SECTOR_SIZE) {
|
||||||
|
ESP_LOGE(TAG, "FAIL: expected 4096 bytes, got %lu", (unsigned long)part->size);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run the actual storage_post() function
|
||||||
|
esp_err_t err = storage_post();
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGE(TAG, "FAIL: storage_post() returned %s", esp_err_to_name(err));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the sector is clean after POST (it erases on completion)
|
||||||
|
uint8_t check;
|
||||||
|
err = esp_partition_read(part, 0, &check, 1);
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGE(TAG, "FAIL: read after POST failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (check != 0xFF) {
|
||||||
|
ESP_LOGE(TAG, "FAIL: POST didn't clean up (byte 0 = 0x%02X)", check);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "PASS");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Test 4: Partitions are independent (writing to one doesn't corrupt another)
|
||||||
|
// ============================================================================
|
||||||
|
bool test_partitions_independent(void) {
|
||||||
|
ESP_LOGI(TAG, "=== Test: partition independence ===");
|
||||||
|
|
||||||
|
const esp_partition_t *params = esp_partition_find_first(
|
||||||
|
ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_ANY, "params");
|
||||||
|
const esp_partition_t *log = esp_partition_find_first(
|
||||||
|
ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_ANY, "log");
|
||||||
|
const esp_partition_t *post = esp_partition_find_first(
|
||||||
|
ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_ANY, "post_test");
|
||||||
|
|
||||||
|
if (!params || !log || !post) {
|
||||||
|
ESP_LOGE(TAG, "FAIL: one or more partitions not found");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify no overlap
|
||||||
|
uint32_t params_end = params->address + params->size;
|
||||||
|
uint32_t log_end = log->address + log->size;
|
||||||
|
uint32_t post_end = post->address + post->size;
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "params: 0x%lx - 0x%lx", (unsigned long)params->address, (unsigned long)params_end);
|
||||||
|
ESP_LOGI(TAG, "log: 0x%lx - 0x%lx", (unsigned long)log->address, (unsigned long)log_end);
|
||||||
|
ESP_LOGI(TAG, "post: 0x%lx - 0x%lx", (unsigned long)post->address, (unsigned long)post_end);
|
||||||
|
|
||||||
|
bool overlap = false;
|
||||||
|
if (params->address < log_end && log->address < params_end) overlap = true;
|
||||||
|
if (params->address < post_end && post->address < params_end) overlap = true;
|
||||||
|
if (log->address < post_end && post->address < log_end) overlap = true;
|
||||||
|
|
||||||
|
if (overlap) {
|
||||||
|
ESP_LOGE(TAG, "FAIL: partitions overlap!");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write a sentinel to each partition, then verify none were corrupted
|
||||||
|
esp_partition_erase_range(params, 0, FLASH_SECTOR_SIZE);
|
||||||
|
esp_partition_erase_range(log, 0, FLASH_SECTOR_SIZE);
|
||||||
|
esp_partition_erase_range(post, 0, FLASH_SECTOR_SIZE);
|
||||||
|
|
||||||
|
uint8_t pat_params[4] = {0x11, 0x22, 0x33, 0x44};
|
||||||
|
uint8_t pat_log[4] = {0x55, 0x66, 0x77, 0x88};
|
||||||
|
uint8_t pat_post[4] = {0x99, 0xAA, 0xBB, 0xCC};
|
||||||
|
|
||||||
|
esp_partition_write(params, 0, pat_params, 4);
|
||||||
|
esp_partition_write(log, 0, pat_log, 4);
|
||||||
|
esp_partition_write(post, 0, pat_post, 4);
|
||||||
|
|
||||||
|
// Read back all three and verify
|
||||||
|
uint8_t rb[4];
|
||||||
|
|
||||||
|
esp_partition_read(params, 0, rb, 4);
|
||||||
|
if (memcmp(rb, pat_params, 4) != 0) {
|
||||||
|
ESP_LOGE(TAG, "FAIL: params sentinel corrupted");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_partition_read(log, 0, rb, 4);
|
||||||
|
if (memcmp(rb, pat_log, 4) != 0) {
|
||||||
|
ESP_LOGE(TAG, "FAIL: log sentinel corrupted");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_partition_read(post, 0, rb, 4);
|
||||||
|
if (memcmp(rb, pat_post, 4) != 0) {
|
||||||
|
ESP_LOGE(TAG, "FAIL: post sentinel corrupted");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up
|
||||||
|
esp_partition_erase_range(params, 0, FLASH_SECTOR_SIZE);
|
||||||
|
esp_partition_erase_range(log, 0, FLASH_SECTOR_SIZE);
|
||||||
|
esp_partition_erase_range(post, 0, FLASH_SECTOR_SIZE);
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "PASS");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Test 5: Parameter commit and reload
|
||||||
|
// ============================================================================
|
||||||
|
bool test_params_persist_after_commit(void) {
|
||||||
|
ESP_LOGI(TAG, "=== Test: params persist after commit ===");
|
||||||
|
|
||||||
|
// Save original value
|
||||||
|
param_value_t original = get_param_value_t(PARAM_DRIVE_DIST);
|
||||||
|
float orig_val = original.f32;
|
||||||
|
|
||||||
|
// Set a distinctive test value
|
||||||
|
float test_val = 99.99f;
|
||||||
|
param_value_t test = {.f32 = test_val};
|
||||||
|
esp_err_t err = set_param_value_t(PARAM_DRIVE_DIST, test);
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGE(TAG, "FAIL: set_param_value_t failed: %s", esp_err_to_name(err));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Commit to flash
|
||||||
|
err = commit_params();
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGE(TAG, "FAIL: commit_params failed: %s", esp_err_to_name(err));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the value stuck in RAM
|
||||||
|
param_value_t readback = get_param_value_t(PARAM_DRIVE_DIST);
|
||||||
|
if (readback.f32 != test_val) {
|
||||||
|
ESP_LOGE(TAG, "FAIL: RAM value mismatch (got %.2f, expected %.2f)",
|
||||||
|
readback.f32, test_val);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Re-init storage to force reload from flash
|
||||||
|
err = storage_init();
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGE(TAG, "FAIL: storage_init failed on reload: %s", esp_err_to_name(err));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
readback = get_param_value_t(PARAM_DRIVE_DIST);
|
||||||
|
if (readback.f32 != test_val) {
|
||||||
|
ESP_LOGE(TAG, "FAIL: flash value mismatch after reload (got %.2f, expected %.2f)",
|
||||||
|
readback.f32, test_val);
|
||||||
|
// Restore original before returning
|
||||||
|
param_value_t orig = {.f32 = orig_val};
|
||||||
|
set_param_value_t(PARAM_DRIVE_DIST, orig);
|
||||||
|
commit_params();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore original value
|
||||||
|
param_value_t orig = {.f32 = orig_val};
|
||||||
|
set_param_value_t(PARAM_DRIVE_DIST, orig);
|
||||||
|
err = commit_params();
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGE(TAG, "WARN: failed to restore original value");
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "PASS");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Test 6: Log write/read cycle through the log API
|
||||||
|
// ============================================================================
|
||||||
|
bool test_log_write_read_cycle(void) {
|
||||||
|
ESP_LOGI(TAG, "=== Test: log write/read cycle ===");
|
||||||
|
|
||||||
|
// Erase and reinit log
|
||||||
|
esp_err_t err = log_erase_all_sectors();
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGE(TAG, "FAIL: log_erase_all_sectors failed: %s", esp_err_to_name(err));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_task_wdt_reset();
|
||||||
|
|
||||||
|
// Write 3 entries with different types
|
||||||
|
uint8_t data1[] = {0x01, 0x02, 0x03, 0x04};
|
||||||
|
uint8_t data2[] = {0xAA, 0xBB, 0xCC};
|
||||||
|
uint8_t data3[] = {0xFF, 0xFE, 0xFD, 0xFC, 0xFB};
|
||||||
|
|
||||||
|
err = log_write_blocking_test(data1, sizeof(data1), LOG_TYPE_DATA);
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGE(TAG, "FAIL: write 1 failed: %s", esp_err_to_name(err));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = log_write_blocking_test(data2, sizeof(data2), LOG_TYPE_EVENT);
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGE(TAG, "FAIL: write 2 failed: %s", esp_err_to_name(err));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = log_write_blocking_test(data3, sizeof(data3), LOG_TYPE_SENSOR);
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGE(TAG, "FAIL: write 3 failed: %s", esp_err_to_name(err));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for writes to flush through the queue
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(500));
|
||||||
|
esp_task_wdt_reset();
|
||||||
|
|
||||||
|
// Read them back
|
||||||
|
log_read_reset();
|
||||||
|
uint8_t read_buf[LOG_MAX_PAYLOAD];
|
||||||
|
uint8_t read_len, read_type;
|
||||||
|
|
||||||
|
// Entry 1
|
||||||
|
err = log_read(&read_len, read_buf, &read_type);
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGE(TAG, "FAIL: read 1 failed: %s", esp_err_to_name(err));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (read_len != sizeof(data1) || read_type != LOG_TYPE_DATA ||
|
||||||
|
memcmp(read_buf, data1, sizeof(data1)) != 0) {
|
||||||
|
ESP_LOGE(TAG, "FAIL: entry 1 mismatch (len=%d type=0x%02X)", read_len, read_type);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Entry 2
|
||||||
|
err = log_read(&read_len, read_buf, &read_type);
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGE(TAG, "FAIL: read 2 failed: %s", esp_err_to_name(err));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (read_len != sizeof(data2) || read_type != LOG_TYPE_EVENT ||
|
||||||
|
memcmp(read_buf, data2, sizeof(data2)) != 0) {
|
||||||
|
ESP_LOGE(TAG, "FAIL: entry 2 mismatch (len=%d type=0x%02X)", read_len, read_type);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Entry 3
|
||||||
|
err = log_read(&read_len, read_buf, &read_type);
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGE(TAG, "FAIL: read 3 failed: %s", esp_err_to_name(err));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (read_len != sizeof(data3) || read_type != LOG_TYPE_SENSOR ||
|
||||||
|
memcmp(read_buf, data3, sizeof(data3)) != 0) {
|
||||||
|
ESP_LOGE(TAG, "FAIL: entry 3 mismatch (len=%d type=0x%02X)", read_len, read_type);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify no more entries
|
||||||
|
err = log_read(&read_len, read_buf, &read_type);
|
||||||
|
if (err != ESP_ERR_NOT_FOUND) {
|
||||||
|
ESP_LOGE(TAG, "FAIL: expected no more entries, got err=%s", esp_err_to_name(err));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "PASS");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Test runner
|
||||||
|
// ============================================================================
|
||||||
|
esp_err_t run_partition_tests(void) {
|
||||||
|
ESP_LOGI(TAG, "");
|
||||||
|
ESP_LOGI(TAG, "=================================================");
|
||||||
|
ESP_LOGI(TAG, " PARTITION VERIFICATION TEST SUITE");
|
||||||
|
ESP_LOGI(TAG, "=================================================");
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
const char *name;
|
||||||
|
bool (*fn)(void);
|
||||||
|
} test_entry_t;
|
||||||
|
|
||||||
|
test_entry_t tests[] = {
|
||||||
|
{"params partition r/w", test_params_partition_rw},
|
||||||
|
{"log partition r/w", test_log_partition_rw},
|
||||||
|
{"post_test partition r/w", test_post_partition_rw},
|
||||||
|
{"partition independence", test_partitions_independent},
|
||||||
|
{"params persist after commit", test_params_persist_after_commit},
|
||||||
|
{"log write/read cycle", test_log_write_read_cycle},
|
||||||
|
};
|
||||||
|
|
||||||
|
int num_tests = sizeof(tests) / sizeof(tests[0]);
|
||||||
|
int passed = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < num_tests; i++) {
|
||||||
|
esp_task_wdt_reset();
|
||||||
|
ESP_LOGI(TAG, "");
|
||||||
|
bool result = tests[i].fn();
|
||||||
|
if (result) passed++;
|
||||||
|
else ESP_LOGE(TAG, "FAILED: %s", tests[i].name);
|
||||||
|
esp_task_wdt_reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "");
|
||||||
|
ESP_LOGI(TAG, "=================================================");
|
||||||
|
ESP_LOGI(TAG, " RESULTS: %d/%d passed", passed, num_tests);
|
||||||
|
ESP_LOGI(TAG, "=================================================");
|
||||||
|
|
||||||
|
return (passed == num_tests) ? ESP_OK : ESP_FAIL;
|
||||||
|
}
|
||||||
19
main/partition_test.h
Normal file
19
main/partition_test.h
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
#ifndef PARTITION_TEST_H
|
||||||
|
#define PARTITION_TEST_H
|
||||||
|
|
||||||
|
#include "esp_err.h"
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
// Run all partition verification tests
|
||||||
|
// Call from app_main() after storage_init() and log_init()
|
||||||
|
esp_err_t run_partition_tests(void);
|
||||||
|
|
||||||
|
// Individual tests
|
||||||
|
bool test_params_partition_rw(void);
|
||||||
|
bool test_log_partition_rw(void);
|
||||||
|
bool test_post_partition_rw(void);
|
||||||
|
bool test_partitions_independent(void);
|
||||||
|
bool test_params_persist_after_commit(void);
|
||||||
|
bool test_log_write_read_cycle(void);
|
||||||
|
|
||||||
|
#endif // PARTITION_TEST_H
|
||||||
@@ -27,6 +27,7 @@
|
|||||||
#include "esp_adc/adc_cali_scheme.h"
|
#include "esp_adc/adc_cali_scheme.h"
|
||||||
#include "esp_timer.h"
|
#include "esp_timer.h"
|
||||||
#include "driver/gpio.h"
|
#include "driver/gpio.h"
|
||||||
|
#include "board_config.h"
|
||||||
#include "control_fsm.h"
|
#include "control_fsm.h"
|
||||||
#include "i2c.h"
|
#include "i2c.h"
|
||||||
#include "sensors.h"
|
#include "sensors.h"
|
||||||
@@ -39,10 +40,22 @@
|
|||||||
#define TAG "POWER"
|
#define TAG "POWER"
|
||||||
|
|
||||||
// === GPIO Pin Definitions ===
|
// === GPIO Pin Definitions ===
|
||||||
|
#ifdef BOARD_V5
|
||||||
|
// V5: single ACS37220LEZATR-100B3 for all motors.
|
||||||
|
// GPIO34 (ADC1_CH6) = VOUT (main current reading)
|
||||||
|
// GPIO36 / VP (ADC1_CH0) = VOC (OC-threshold sense, diagnostic)
|
||||||
|
// GPIO39 / VN = FAULT (digital, active-low, open-drain — external pull-up on board)
|
||||||
|
// GPIO35 (ADC1_CH7) = battery voltage (moved from GPIO39)
|
||||||
|
#define PIN_V_ISENS_MAIN ADC_CHANNEL_6 // GPIO34
|
||||||
|
#define PIN_V_VOC ADC_CHANNEL_0 // GPIO36 / VP
|
||||||
|
#define PIN_V_BATTERY ADC_CHANNEL_7 // GPIO35
|
||||||
|
#define PIN_FAULT_GPIO GPIO_NUM_39 // digital input
|
||||||
|
#else // BOARD_V4
|
||||||
#define PIN_V_ISENS1 ADC_CHANNEL_0 // GPIO36 / VP
|
#define PIN_V_ISENS1 ADC_CHANNEL_0 // GPIO36 / VP
|
||||||
#define PIN_V_ISENS2 ADC_CHANNEL_6 // GPIO34
|
#define PIN_V_ISENS2 ADC_CHANNEL_6 // GPIO34
|
||||||
#define PIN_V_ISENS3 ADC_CHANNEL_7 // GPIO35
|
#define PIN_V_ISENS3 ADC_CHANNEL_7 // GPIO35
|
||||||
#define PIN_V_BATTERY ADC_CHANNEL_3 // GPIO39 / VN
|
#define PIN_V_BATTERY ADC_CHANNEL_3 // GPIO39 / VN
|
||||||
|
#endif
|
||||||
#define PIN_V_SENS_BAT PIN_V_BATTERY
|
#define PIN_V_SENS_BAT PIN_V_BATTERY
|
||||||
|
|
||||||
// map from relay number to bridge
|
// map from relay number to bridge
|
||||||
@@ -130,6 +143,18 @@ esp_err_t drive_relays(relay_port_t relay_state) {
|
|||||||
BRIDGE_TRANSITION_LOGIC(JACK)
|
BRIDGE_TRANSITION_LOGIC(JACK)
|
||||||
BRIDGE_TRANSITION_LOGIC(AUX)
|
BRIDGE_TRANSITION_LOGIC(AUX)
|
||||||
|
|
||||||
|
/* In soft idle / hibernate the device is sleeping — don't drive any
|
||||||
|
* relay outputs, including the sensor rail. soft_idle_enter() already
|
||||||
|
* pushed the chip into the all-off state via i2c_relays_sleep(); the
|
||||||
|
* FSM keeps ticking but must not undo that. */
|
||||||
|
if (soft_idle_is_active()) {
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Sensor rail (P10) is on whenever the device is awake — including
|
||||||
|
* STATE_IDLE — so the SAFETY input can be observed continuously.
|
||||||
|
* It is dropped only in soft_idle_enter() (sleep) via i2c_relays_sleep,
|
||||||
|
* and toggled explicitly by the bring-up tool's BU.RELAY SENSORS cmd. */
|
||||||
relay_state.bridges.SENSORS = 1;
|
relay_state.bridges.SENSORS = 1;
|
||||||
|
|
||||||
if (!get_is_safe())
|
if (!get_is_safe())
|
||||||
@@ -163,10 +188,27 @@ esp_err_t adc_init() {
|
|||||||
.bitwidth = ADC_BITWIDTH_12,
|
.bitwidth = ADC_BITWIDTH_12,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#ifdef BOARD_V5
|
||||||
|
ESP_ERROR_CHECK(adc_oneshot_config_channel(adc1_handle, PIN_V_ISENS_MAIN, &chan_cfg));
|
||||||
|
ESP_ERROR_CHECK(adc_oneshot_config_channel(adc1_handle, PIN_V_VOC, &chan_cfg));
|
||||||
|
ESP_ERROR_CHECK(adc_oneshot_config_channel(adc1_handle, PIN_V_SENS_BAT, &chan_cfg));
|
||||||
|
|
||||||
|
// FAULT is open-drain on the sensor; ESP32 GPIO39 has no internal pull —
|
||||||
|
// V5 board MUST provide an external pull-up to VDD.
|
||||||
|
gpio_config_t fault_cfg = {
|
||||||
|
.pin_bit_mask = 1ULL << PIN_FAULT_GPIO,
|
||||||
|
.mode = GPIO_MODE_INPUT,
|
||||||
|
.pull_up_en = GPIO_PULLUP_DISABLE,
|
||||||
|
.pull_down_en = GPIO_PULLDOWN_DISABLE,
|
||||||
|
.intr_type = GPIO_INTR_DISABLE,
|
||||||
|
};
|
||||||
|
ESP_ERROR_CHECK(gpio_config(&fault_cfg));
|
||||||
|
#else
|
||||||
ESP_ERROR_CHECK(adc_oneshot_config_channel(adc1_handle, PIN_V_ISENS1, &chan_cfg));
|
ESP_ERROR_CHECK(adc_oneshot_config_channel(adc1_handle, PIN_V_ISENS1, &chan_cfg));
|
||||||
ESP_ERROR_CHECK(adc_oneshot_config_channel(adc1_handle, PIN_V_ISENS2, &chan_cfg));
|
ESP_ERROR_CHECK(adc_oneshot_config_channel(adc1_handle, PIN_V_ISENS2, &chan_cfg));
|
||||||
ESP_ERROR_CHECK(adc_oneshot_config_channel(adc1_handle, PIN_V_ISENS3, &chan_cfg));
|
ESP_ERROR_CHECK(adc_oneshot_config_channel(adc1_handle, PIN_V_ISENS3, &chan_cfg));
|
||||||
ESP_ERROR_CHECK(adc_oneshot_config_channel(adc1_handle, PIN_V_SENS_BAT, &chan_cfg));
|
ESP_ERROR_CHECK(adc_oneshot_config_channel(adc1_handle, PIN_V_SENS_BAT, &chan_cfg));
|
||||||
|
#endif
|
||||||
|
|
||||||
// Line fitting calibration (modern scheme)
|
// Line fitting calibration (modern scheme)
|
||||||
adc_cali_line_fitting_config_t cali_cfg = {
|
adc_cali_line_fitting_config_t cali_cfg = {
|
||||||
@@ -176,9 +218,65 @@ esp_err_t adc_init() {
|
|||||||
};
|
};
|
||||||
ESP_ERROR_CHECK(adc_cali_create_scheme_line_fitting(&cali_cfg, &adc_cali_handle));
|
ESP_ERROR_CHECK(adc_cali_create_scheme_line_fitting(&cali_cfg, &adc_cali_handle));
|
||||||
|
|
||||||
|
#ifdef BOARD_V5
|
||||||
|
// Diagnostic: log configured VOC — resistor on board sets OC threshold.
|
||||||
|
// Datasheet: VVOC [V] = RL_VOC [Ω] × 1e-5, linear 0.33–0.66 V on 3.3V variant.
|
||||||
|
int voc_raw = 0, voc_mv = 0;
|
||||||
|
if (adc_oneshot_read(adc1_handle, PIN_V_VOC, &voc_raw) == ESP_OK &&
|
||||||
|
adc_cali_raw_to_voltage(adc_cali_handle, voc_raw, &voc_mv) == ESP_OK) {
|
||||||
|
ESP_LOGI(TAG, "ACS37220 VOC = %d mV (OC threshold config)", voc_mv);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
return ESP_OK;
|
return ESP_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
esp_err_t adc_post(void) {
|
||||||
|
#ifdef BOARD_V5
|
||||||
|
const adc_channel_t channels[] = { PIN_V_ISENS_MAIN, PIN_V_SENS_BAT };
|
||||||
|
const char *names[] = { "ISENS", "BATTERY" };
|
||||||
|
const int n = 2;
|
||||||
|
#else
|
||||||
|
const adc_channel_t channels[] = { PIN_V_ISENS1, PIN_V_ISENS2, PIN_V_ISENS3, PIN_V_SENS_BAT };
|
||||||
|
const char *names[] = { "ISENS1", "ISENS2", "ISENS3", "BATTERY" };
|
||||||
|
const int n = 4;
|
||||||
|
#endif
|
||||||
|
int first[4], second[4];
|
||||||
|
|
||||||
|
for (int i = 0; i < n; i++) {
|
||||||
|
if (adc_oneshot_read(adc1_handle, channels[i], &first[i]) != ESP_OK) {
|
||||||
|
ESP_LOGE(TAG, "POST: ADC read failed on %s", names[i]);
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(5));
|
||||||
|
|
||||||
|
for (int i = 0; i < n; i++) {
|
||||||
|
if (adc_oneshot_read(adc1_handle, channels[i], &second[i]) != ESP_OK) {
|
||||||
|
ESP_LOGE(TAG, "POST: ADC read failed on %s (2nd)", names[i]);
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Frozen-ADC check on current-sense channels only (battery can legitimately be stable)
|
||||||
|
for (int i = 0; i < n - 1; i++) {
|
||||||
|
if (first[i] == second[i] && first[i] != 0) {
|
||||||
|
ESP_LOGW(TAG, "POST: ADC %s may be frozen (both reads = %d)", names[i], first[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef BOARD_V5
|
||||||
|
ESP_LOGI(TAG, "POST: ADC OK (BAT=%d/%d, I=%d/%d) FAULT=%d",
|
||||||
|
first[1], second[1], first[0], second[0],
|
||||||
|
gpio_get_level(PIN_FAULT_GPIO));
|
||||||
|
#else
|
||||||
|
ESP_LOGI(TAG, "POST: ADC OK (BAT=%d/%d, I1=%d/%d, I2=%d/%d, I3=%d/%d)",
|
||||||
|
first[3], second[3], first[0], second[0], first[1], second[1], first[2], second[2]);
|
||||||
|
#endif
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
float get_raw_battery_voltage(void) {
|
float get_raw_battery_voltage(void) {
|
||||||
int adc_raw = 0;
|
int adc_raw = 0;
|
||||||
int voltage_mv = 0;
|
int voltage_mv = 0;
|
||||||
@@ -192,6 +290,20 @@ float get_raw_battery_voltage(void) {
|
|||||||
return voltage_mv * get_param_value_t(PARAM_V_SENS_K).f32 + get_param_value_t(PARAM_V_SENS_OFFSET).f32; // same as / 1000.0 * 1150.0 / 150.0;
|
return voltage_mv * get_param_value_t(PARAM_V_SENS_K).f32 + get_param_value_t(PARAM_V_SENS_OFFSET).f32; // same as / 1000.0 * 1150.0 / 150.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void reset_battery_ema(void)
|
||||||
|
{
|
||||||
|
/* Next process_battery_voltage() call re-seeds from raw. Also refresh
|
||||||
|
* immediately from a fresh raw read so callers that read get_battery_V()
|
||||||
|
* before the FSM ticks again (e.g. during bringup) see the new value. */
|
||||||
|
float raw = get_raw_battery_voltage();
|
||||||
|
if (!isnan(raw)) {
|
||||||
|
ema_battery = raw;
|
||||||
|
ema_battery_init = true;
|
||||||
|
} else {
|
||||||
|
ema_battery_init = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
esp_err_t process_battery_voltage(void)
|
esp_err_t process_battery_voltage(void)
|
||||||
{
|
{
|
||||||
float raw = get_raw_battery_voltage();
|
float raw = get_raw_battery_voltage();
|
||||||
@@ -234,21 +346,100 @@ bool get_bridge_spike(bridge_t bridge, float threshold) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef BOARD_V5
|
||||||
|
// V5 has a single current sensor shared by all bridges. Cache the read
|
||||||
|
// per fsm tick so three process_bridge_current() calls in the same tick
|
||||||
|
// don't hit the ADC three times.
|
||||||
|
static int64_t v5_isens_cache_time = INT64_MIN;
|
||||||
|
static int v5_isens_mv_cache = 0;
|
||||||
|
static bool v5_isens_cache_ok = false;
|
||||||
|
|
||||||
|
static bool v5_read_isens_mv(int *out_mv) {
|
||||||
|
if (v5_isens_cache_time != fsm_now) {
|
||||||
|
v5_isens_cache_time = fsm_now;
|
||||||
|
int raw = 0;
|
||||||
|
int mv = 0;
|
||||||
|
v5_isens_cache_ok = (adc_oneshot_read(adc1_handle, PIN_V_ISENS_MAIN, &raw) == ESP_OK) &&
|
||||||
|
(adc_cali_raw_to_voltage(adc_cali_handle, raw, &mv) == ESP_OK);
|
||||||
|
v5_isens_mv_cache = mv;
|
||||||
|
}
|
||||||
|
*out_mv = v5_isens_mv_cache;
|
||||||
|
return v5_isens_cache_ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool v5_bridge_is_active(bridge_t b) {
|
||||||
|
switch (b) {
|
||||||
|
case BRIDGE_DRIVE: return last_relay_state.bridges.DRIVE != BRIDGE_OFF;
|
||||||
|
case BRIDGE_JACK: return last_relay_state.bridges.JACK != BRIDGE_OFF;
|
||||||
|
case BRIDGE_AUX: return last_relay_state.bridges.AUX != BRIDGE_OFF;
|
||||||
|
default: return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool v5_any_bridge_active(void) {
|
||||||
|
return v5_bridge_is_active(BRIDGE_DRIVE) ||
|
||||||
|
v5_bridge_is_active(BRIDGE_JACK) ||
|
||||||
|
v5_bridge_is_active(BRIDGE_AUX);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* True if any currently-active bridge is still inside its INRUSH_US window.
|
||||||
|
* The shared ACS reading is unattributable per-bridge during a co-active
|
||||||
|
* inrush — the full combined current is attributed to each active bridge in
|
||||||
|
* process_bridge_current(), so a quieter bridge (e.g. AUX during DRIVE start)
|
||||||
|
* sees an inflated I_norm and would spuriously instant-trip on KINST. */
|
||||||
|
static bool v5_any_bridge_in_inrush(void) {
|
||||||
|
int64_t inrush_us = (int64_t)get_param_value_t(PARAM_EFUSE_INRUSH_US).u32;
|
||||||
|
for (bridge_t b = 0; b < N_BRIDGES; b++) {
|
||||||
|
if (!v5_bridge_is_active(b)) continue;
|
||||||
|
if (fsm_now < isens[b].on_us + inrush_us) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
esp_err_t process_bridge_current(bridge_t bridge) {
|
esp_err_t process_bridge_current(bridge_t bridge) {
|
||||||
if (bridge < 0 || bridge >= NUM_BRIDGES) return ESP_ERR_INVALID_ARG;
|
if (bridge < 0 || bridge >= NUM_BRIDGES) return ESP_ERR_INVALID_ARG;
|
||||||
|
|
||||||
|
isens_channel_t *channel = &isens[bridge];
|
||||||
|
|
||||||
|
#ifdef BOARD_V5
|
||||||
|
int voltage_mv = 0;
|
||||||
|
if (!v5_read_isens_mv(&voltage_mv)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
float last_current = channel->raw_current;
|
||||||
|
channel->raw_current = NAN;
|
||||||
|
|
||||||
|
// Single ACS37220LEZATR-100B3 for all motors: 13.2 mV/A, Vqvo=1.65 V.
|
||||||
|
// Sign convention matches the old V4 DRIVE wiring (ACS37220 oriented such
|
||||||
|
// that forward motor current gives negative delta from Vqvo).
|
||||||
|
float measured_A = -(voltage_mv - 1650.0f) / 13.2f;
|
||||||
|
|
||||||
|
// Per-bridge attribution:
|
||||||
|
// - bridge active and alone → it owns the entire reading
|
||||||
|
// - bridge active, others active → attribute full reading to each active
|
||||||
|
// bridge (worst-case; protects hardware). Jack/drive are mutually
|
||||||
|
// exclusive per design, so this only affects drive+aux overlap.
|
||||||
|
// - bridge OFF → no current from this bridge
|
||||||
|
// TODO(V5): better drive+aux simultaneous attribution (e.g. subtract the
|
||||||
|
// quieter bridge's nominal draw from the total).
|
||||||
|
if (v5_bridge_is_active(bridge)) {
|
||||||
|
channel->raw_current = measured_A;
|
||||||
|
} else {
|
||||||
|
channel->raw_current = 0.0f;
|
||||||
|
}
|
||||||
|
#else
|
||||||
int adc_raw = 0;
|
int adc_raw = 0;
|
||||||
int voltage_mv = 0;
|
int voltage_mv = 0;
|
||||||
|
|
||||||
isens_channel_t *channel = &isens[bridge];
|
static const adc_channel_t bridge_isens_pins[N_BRIDGES] = {
|
||||||
|
[BRIDGE_DRIVE] = PIN_V_ISENS1,
|
||||||
adc_channel_t pin;
|
[BRIDGE_JACK] = PIN_V_ISENS2,
|
||||||
switch(bridge) {
|
[BRIDGE_AUX] = PIN_V_ISENS3,
|
||||||
case BRIDGE_DRIVE: pin = PIN_V_ISENS1; break;
|
};
|
||||||
case BRIDGE_JACK: pin = PIN_V_ISENS2; break;
|
if (bridge >= N_BRIDGES) return ESP_ERR_INVALID_ARG;
|
||||||
case BRIDGE_AUX: pin = PIN_V_ISENS3; break;
|
adc_channel_t pin = bridge_isens_pins[bridge];
|
||||||
default: return ESP_ERR_INVALID_ARG;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (adc_oneshot_read(adc1_handle, pin, &adc_raw) != ESP_OK) {
|
if (adc_oneshot_read(adc1_handle, pin, &adc_raw) != ESP_OK) {
|
||||||
return 0;
|
return 0;
|
||||||
@@ -272,6 +463,7 @@ esp_err_t process_bridge_current(bridge_t bridge) {
|
|||||||
break;
|
break;
|
||||||
default: break;
|
default: break;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
if (!channel->ema_init) {
|
if (!channel->ema_init) {
|
||||||
channel->ema_current = channel->raw_current;
|
channel->ema_current = channel->raw_current;
|
||||||
@@ -279,10 +471,11 @@ esp_err_t process_bridge_current(bridge_t bridge) {
|
|||||||
} else {
|
} else {
|
||||||
float alpha = get_param_value_t(PARAM_ADC_ALPHA_ISENS).f32;
|
float alpha = get_param_value_t(PARAM_ADC_ALPHA_ISENS).f32;
|
||||||
if (isnan(channel->raw_current)) {
|
if (isnan(channel->raw_current)) {
|
||||||
//ESP_LOGI(TAG, "RAW BATTERY IS NAN");
|
|
||||||
channel->ema_current = NAN;
|
channel->ema_current = NAN;
|
||||||
} else {
|
} else {
|
||||||
if (isnan(ema_battery) || isnan(alpha)) {
|
/* Reset the per-bridge EMA if it (or alpha) is NaN — using
|
||||||
|
* the per-bridge value, not the unrelated battery EMA. */
|
||||||
|
if (isnan(channel->ema_current) || isnan(alpha)) {
|
||||||
channel->ema_current = channel->raw_current;
|
channel->ema_current = channel->raw_current;
|
||||||
} else {
|
} else {
|
||||||
channel->ema_current = alpha * channel->raw_current + (1.0f - alpha) * channel->ema_current;
|
channel->ema_current = alpha * channel->raw_current + (1.0f - alpha) * channel->ema_current;
|
||||||
@@ -291,7 +484,14 @@ esp_err_t process_bridge_current(bridge_t bridge) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// === AUTO-ZERO LEARNING PHASE ===
|
// === AUTO-ZERO LEARNING PHASE ===
|
||||||
if (fsm_now > channel->az_enable_time) {
|
// On V5, the single ADC reads aggregate motor current. A channel's
|
||||||
|
// "quiet" periods are when ALL bridges are off — not just this one.
|
||||||
|
#ifdef BOARD_V5
|
||||||
|
bool az_allowed = (fsm_now > channel->az_enable_time) && !v5_any_bridge_active();
|
||||||
|
#else
|
||||||
|
bool az_allowed = (fsm_now > channel->az_enable_time);
|
||||||
|
#endif
|
||||||
|
if (az_allowed) {
|
||||||
//ESP_LOGI(TAG, "AZING %d", bridge);
|
//ESP_LOGI(TAG, "AZING %d", bridge);
|
||||||
float db = get_param_value_t(PARAM_ADC_DB_IAZ).f32;
|
float db = get_param_value_t(PARAM_ADC_DB_IAZ).f32;
|
||||||
if (isnan(db) || fabsf(channel->ema_current) <= db) {
|
if (isnan(db) || fabsf(channel->ema_current) <= db) {
|
||||||
@@ -302,9 +502,10 @@ esp_err_t process_bridge_current(bridge_t bridge) {
|
|||||||
} else {
|
} else {
|
||||||
float alpha = get_param_value_t(PARAM_ADC_ALPHA_IAZ).f32;
|
float alpha = get_param_value_t(PARAM_ADC_ALPHA_IAZ).f32;
|
||||||
if (isnan(channel->raw_current)) {
|
if (isnan(channel->raw_current)) {
|
||||||
//ESP_LOGI(TAG, "RAW BATTERY IS NAN");
|
/* skip — no fresh sample */
|
||||||
} else {
|
} else {
|
||||||
if (isnan(ema_battery) || isnan(alpha)) {
|
/* Reset the autozero offset if it (or alpha) is NaN. */
|
||||||
|
if (isnan(channel->az_offset) || isnan(alpha)) {
|
||||||
channel->az_offset = channel->ema_current;
|
channel->az_offset = channel->ema_current;
|
||||||
} else {
|
} else {
|
||||||
channel->az_offset = alpha * channel->ema_current +
|
channel->az_offset = alpha * channel->ema_current +
|
||||||
@@ -327,26 +528,29 @@ esp_err_t process_bridge_current(bridge_t bridge) {
|
|||||||
// PARAM_EFUSE_TAUCOOL : speed of cooldown for heating (units are 1/s; bigger = faster cooldown)
|
// PARAM_EFUSE_TAUCOOL : speed of cooldown for heating (units are 1/s; bigger = faster cooldown)
|
||||||
|
|
||||||
// Monitor E-fusing
|
// Monitor E-fusing
|
||||||
float I_nominal = NAN;
|
static const param_idx_t bridge_inom[N_BRIDGES] = {
|
||||||
switch(bridge) {
|
[BRIDGE_DRIVE] = PARAM_EFUSE_INOM_1,
|
||||||
case BRIDGE_DRIVE:
|
[BRIDGE_JACK] = PARAM_EFUSE_INOM_2,
|
||||||
I_nominal = get_param_value_t(PARAM_EFUSE_INOM_1).f32;
|
[BRIDGE_AUX] = PARAM_EFUSE_INOM_3,
|
||||||
break;
|
};
|
||||||
case BRIDGE_JACK:
|
float I_nominal = (bridge < N_BRIDGES)
|
||||||
I_nominal = get_param_value_t(PARAM_EFUSE_INOM_2).f32;
|
? get_param_value_t(bridge_inom[bridge]).f32
|
||||||
break;
|
: NAN;
|
||||||
case BRIDGE_AUX:
|
|
||||||
I_nominal = get_param_value_t(PARAM_EFUSE_INOM_3).f32;
|
|
||||||
break;
|
|
||||||
default: break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Normalize the current as a fraction of rated current
|
// Normalize the current as a fraction of rated current
|
||||||
float I_norm = fabsf(channel->current / I_nominal);
|
float I_norm = fabsf(channel->current / I_nominal);
|
||||||
|
|
||||||
// Instant trip on extreme overcurrent
|
// Instant trip on extreme overcurrent. On V5, also require that no
|
||||||
|
// *other* active bridge is still in its inrush window — during a
|
||||||
|
// co-active inrush the shared ACS reading is attributed to each
|
||||||
|
// active bridge, which inflates the quieter bridge's I_norm and
|
||||||
|
// would otherwise cause a spurious instant-trip there.
|
||||||
if (fsm_now > channel->on_us + get_param_value_t(PARAM_EFUSE_INRUSH_US).u32
|
if (fsm_now > channel->on_us + get_param_value_t(PARAM_EFUSE_INRUSH_US).u32
|
||||||
&& I_norm >= get_param_value_t(PARAM_EFUSE_KINST).f32) {
|
&& I_norm >= get_param_value_t(PARAM_EFUSE_KINST).f32
|
||||||
|
#ifdef BOARD_V5
|
||||||
|
&& !v5_any_bridge_in_inrush()
|
||||||
|
#endif
|
||||||
|
) {
|
||||||
// Check if overcurrent has persisted long enough
|
// Check if overcurrent has persisted long enough
|
||||||
channel->tripped = true;
|
channel->tripped = true;
|
||||||
channel->trip_time = fsm_now;
|
channel->trip_time = fsm_now;
|
||||||
@@ -415,6 +619,47 @@ float get_battery_V(void)
|
|||||||
return get_raw_battery_voltage();
|
return get_raw_battery_voltage();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool get_hw_overcurrent_fault(void)
|
||||||
|
{
|
||||||
|
#ifdef BOARD_V5
|
||||||
|
// ACS37220 FAULT is active-low, open-drain, not latched.
|
||||||
|
return gpio_get_level(PIN_FAULT_GPIO) == 0;
|
||||||
|
#else
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static int read_mv_channel(adc_channel_t ch)
|
||||||
|
{
|
||||||
|
int raw = 0, mv = 0;
|
||||||
|
if (adc_oneshot_read(adc1_handle, ch, &raw) != ESP_OK) return 0;
|
||||||
|
if (adc_cali_raw_to_voltage(adc_cali_handle, raw, &mv) != ESP_OK) return 0;
|
||||||
|
return mv;
|
||||||
|
}
|
||||||
|
|
||||||
|
int get_bat_raw_mv(void)
|
||||||
|
{
|
||||||
|
return read_mv_channel(PIN_V_SENS_BAT);
|
||||||
|
}
|
||||||
|
|
||||||
|
int get_isens_raw_mv(void)
|
||||||
|
{
|
||||||
|
#ifdef BOARD_V5
|
||||||
|
return read_mv_channel(PIN_V_ISENS_MAIN);
|
||||||
|
#else
|
||||||
|
return 0;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
int get_voc_raw_mv(void)
|
||||||
|
{
|
||||||
|
#ifdef BOARD_V5
|
||||||
|
return read_mv_channel(PIN_V_VOC);
|
||||||
|
#else
|
||||||
|
return 0;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
efuse_trip_t efuse_get(bridge_t bridge)
|
efuse_trip_t efuse_get(bridge_t bridge)
|
||||||
{
|
{
|
||||||
if (bridge >= N_BRIDGES) return false;
|
if (bridge >= N_BRIDGES) return false;
|
||||||
@@ -426,3 +671,25 @@ void efuse_set(bridge_t bridge, efuse_trip_t state)
|
|||||||
isens[bridge].tripped = state;
|
isens[bridge].tripped = state;
|
||||||
isens[bridge].trip_time = fsm_now;
|
isens[bridge].trip_time = fsm_now;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const char *const bridge_names[N_BRIDGES] = {
|
||||||
|
[BRIDGE_DRIVE] = "DRIVE",
|
||||||
|
[BRIDGE_JACK] = "JACK",
|
||||||
|
[BRIDGE_AUX] = "AUX",
|
||||||
|
};
|
||||||
|
|
||||||
|
bool any_efuse_tripped(void) {
|
||||||
|
for (bridge_t b = 0; b < N_BRIDGES; b++) {
|
||||||
|
if (efuse_get(b)) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
float max_efuse_heat(void) {
|
||||||
|
float m = efuse_get_heat(0);
|
||||||
|
for (bridge_t b = 1; b < N_BRIDGES; b++) {
|
||||||
|
float h = efuse_get_heat(b);
|
||||||
|
if (h > m) m = h;
|
||||||
|
}
|
||||||
|
return m;
|
||||||
|
}
|
||||||
@@ -26,10 +26,28 @@ efuse_trip_t efuse_get (bridge_t bridge); // Query if bridge is currently
|
|||||||
float efuse_get_heat(bridge_t bridge);
|
float efuse_get_heat(bridge_t bridge);
|
||||||
void efuse_set(bridge_t bridge, efuse_trip_t state);
|
void efuse_set(bridge_t bridge, efuse_trip_t state);
|
||||||
|
|
||||||
|
/* True if any of the N_BRIDGES bridges is currently tripped. */
|
||||||
|
bool any_efuse_tripped(void);
|
||||||
|
|
||||||
|
/* Max heat across all bridges — used for telemetry logging. */
|
||||||
|
float max_efuse_heat(void);
|
||||||
|
|
||||||
|
/* Human name per bridge_t index ("DRIVE" / "JACK" / "AUX"). */
|
||||||
|
extern const char *const bridge_names[N_BRIDGES];
|
||||||
|
|
||||||
float get_bridge_A(bridge_t bridge);
|
float get_bridge_A(bridge_t bridge);
|
||||||
float get_bridge_raw_A(bridge_t bridge);
|
float get_bridge_raw_A(bridge_t bridge);
|
||||||
float get_battery_V();
|
float get_battery_V();
|
||||||
|
|
||||||
|
// V5 only: hardware overcurrent FAULT line from the ACS37220 (active when true).
|
||||||
|
// Always false on V4.
|
||||||
|
bool get_hw_overcurrent_fault(void);
|
||||||
|
|
||||||
|
// Raw, unfiltered ADC reads — used by POST. Return 0 on error.
|
||||||
|
int get_bat_raw_mv(void);
|
||||||
|
int get_isens_raw_mv(void); // V5 only — returns 0 on V4
|
||||||
|
int get_voc_raw_mv(void); // V5 only — returns 0 on V4
|
||||||
|
|
||||||
void disable_autozero(bridge_t bridge);
|
void disable_autozero(bridge_t bridge);
|
||||||
bool get_bridge_overcurrent(bridge_t bridge, float threshold);
|
bool get_bridge_overcurrent(bridge_t bridge, float threshold);
|
||||||
bool get_bridge_spike(bridge_t bridge, float threshold);
|
bool get_bridge_spike(bridge_t bridge, float threshold);
|
||||||
@@ -37,7 +55,15 @@ bool get_bridge_spike(bridge_t bridge, float threshold);
|
|||||||
esp_err_t process_bridge_current(bridge_t bridge);
|
esp_err_t process_bridge_current(bridge_t bridge);
|
||||||
esp_err_t process_battery_voltage();
|
esp_err_t process_battery_voltage();
|
||||||
|
|
||||||
|
/* Force the battery EMA to re-seed from the next raw read. Call after a
|
||||||
|
* V_SENS_K / V_SENS_OFFSET change so get_battery_V() reflects the new
|
||||||
|
* calibration immediately instead of decaying through the EMA — which
|
||||||
|
* otherwise stays stale across bringup mode (FSM is paused, so
|
||||||
|
* process_battery_voltage doesn't tick) until alpha catches up. */
|
||||||
|
void reset_battery_ema(void);
|
||||||
|
|
||||||
esp_err_t adc_init();
|
esp_err_t adc_init();
|
||||||
|
esp_err_t adc_post(void);
|
||||||
esp_err_t power_init();
|
esp_err_t power_init();
|
||||||
esp_err_t power_stop();
|
esp_err_t power_stop();
|
||||||
|
|
||||||
|
|||||||
@@ -37,12 +37,26 @@ typedef struct {
|
|||||||
size_t num_symbols;
|
size_t num_symbols;
|
||||||
} rf_code_t;
|
} rf_code_t;
|
||||||
|
|
||||||
int learn_flag = -1;
|
/* These are written by the comms task (HTTP/UART) and read by the RF
|
||||||
bool controls_enabled = true;
|
* receiver task. `volatile` forces the RF task to re-read each iteration
|
||||||
|
* rather than caching in a register; on a dual-core ESP32 the writer's
|
||||||
|
* memory is also flushed by the FreeRTOS task switch / queue ops the
|
||||||
|
* comms task does around updates. */
|
||||||
|
volatile int learn_flag = -1;
|
||||||
|
volatile bool controls_enabled = true;
|
||||||
|
|
||||||
// Temporary storage for learned keycodes (not committed to params yet)
|
// Temporary storage for learned keycodes (not committed to params yet)
|
||||||
static int64_t temp_keycodes[NUM_RF_BUTTONS] = {0};
|
static int64_t temp_keycodes[NUM_RF_BUTTONS] = {0};
|
||||||
|
|
||||||
|
// Most recently decoded raw code, read-and-clear via rf_433_peek_latest().
|
||||||
|
static volatile uint32_t latest_code = 0;
|
||||||
|
|
||||||
|
uint32_t rf_433_peek_latest(void) {
|
||||||
|
uint32_t c = latest_code;
|
||||||
|
latest_code = 0;
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
// For rmt_rx_register_event_callbacks
|
// For rmt_rx_register_event_callbacks
|
||||||
static bool rfrx_done(rmt_channel_handle_t channel, const rmt_rx_done_event_data_t *edata, void *udata) {
|
static bool rfrx_done(rmt_channel_handle_t channel, const rmt_rx_done_event_data_t *edata, void *udata) {
|
||||||
BaseType_t high_task_wakeup = pdFALSE;
|
BaseType_t high_task_wakeup = pdFALSE;
|
||||||
@@ -137,6 +151,7 @@ static void rf_433_receiver_task(void* param) {
|
|||||||
|
|
||||||
// If we got a valid code, process it
|
// If we got a valid code, process it
|
||||||
if (code) {
|
if (code) {
|
||||||
|
latest_code = code;
|
||||||
ESP_LOGI(TAG, "GOT KEYCODE 0x%lx [%d]", (long) code, len);
|
ESP_LOGI(TAG, "GOT KEYCODE 0x%lx [%d]", (long) code, len);
|
||||||
|
|
||||||
if (learn_flag >= 0) {
|
if (learn_flag >= 0) {
|
||||||
@@ -145,15 +160,6 @@ static void rf_433_receiver_task(void* param) {
|
|||||||
ESP_LOGI(TAG, "LEARNED KEYCODE (temp storage)");
|
ESP_LOGI(TAG, "LEARNED KEYCODE (temp storage)");
|
||||||
learn_flag = -1;
|
learn_flag = -1;
|
||||||
} else if (controls_enabled) {
|
} else if (controls_enabled) {
|
||||||
// Only process RF commands if controls are enabled
|
|
||||||
rf_code_t rf_msg = {
|
|
||||||
.code = code,
|
|
||||||
.high_avg = high / 24,
|
|
||||||
.low_avg = low / 24,
|
|
||||||
.errors = err,
|
|
||||||
.num_symbols = len
|
|
||||||
};
|
|
||||||
|
|
||||||
for (uint8_t i = 0; i < NUM_RF_BUTTONS; i++) {
|
for (uint8_t i = 0; i < NUM_RF_BUTTONS; i++) {
|
||||||
uint32_t match = get_param_value_t(PARAM_KEYCODE_0+i).u32;
|
uint32_t match = get_param_value_t(PARAM_KEYCODE_0+i).u32;
|
||||||
// Compare just the code (lower 32 bits)
|
// Compare just the code (lower 32 bits)
|
||||||
@@ -220,10 +226,6 @@ esp_err_t rf_433_init() {
|
|||||||
|
|
||||||
esp_err_t rf_433_stop() { return ESP_OK; }
|
esp_err_t rf_433_stop() { return ESP_OK; }
|
||||||
|
|
||||||
void rf_433_set_keycode(uint8_t index, uint32_t code) {
|
|
||||||
set_param_value_t(PARAM_KEYCODE_0+index, (param_value_t){.u32=code});
|
|
||||||
}
|
|
||||||
|
|
||||||
void rf_433_learn_keycode(uint8_t index) {
|
void rf_433_learn_keycode(uint8_t index) {
|
||||||
if (index >= 8) return;
|
if (index >= 8) return;
|
||||||
learn_flag = index;
|
learn_flag = index;
|
||||||
|
|||||||
@@ -11,17 +11,13 @@
|
|||||||
|
|
||||||
#define NUM_RF_BUTTONS 8
|
#define NUM_RF_BUTTONS 8
|
||||||
|
|
||||||
int64_t receive_keycode(void);
|
|
||||||
|
|
||||||
esp_err_t rf_433_init();
|
esp_err_t rf_433_init();
|
||||||
esp_err_t rf_433_stop();
|
esp_err_t rf_433_stop();
|
||||||
|
|
||||||
void rf_433_set_keycode(uint8_t index, uint32_t code);
|
/* Consume-once peek of the most recently decoded raw code. Returns 0 if
|
||||||
|
* none has arrived since the last call. Used by the bring-up RF.WATCH
|
||||||
/*
|
* stage; does not interfere with the normal decode/dispatch path. */
|
||||||
int8_t rf_433_get_keycode();
|
uint32_t rf_433_peek_latest(void);
|
||||||
int64_t rf_433_get_raw_keycode();
|
|
||||||
*/
|
|
||||||
|
|
||||||
void rf_433_learn_keycode(uint8_t index);
|
void rf_433_learn_keycode(uint8_t index);
|
||||||
void rf_433_cancel_learn_keycode();
|
void rf_433_cancel_learn_keycode();
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user