Compare commits
14 Commits
cdb3b11db1
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1846fa7b36 | ||
|
|
46f9bada4f | ||
|
|
20afd3d9ef | ||
|
|
85206e1dca | ||
|
|
666750f710 | ||
|
|
ef1f3e4e85 | ||
|
|
f47a29205e | ||
|
|
9f4362b5fd | ||
|
|
3774cde506 | ||
|
|
a775999c87 | ||
|
|
b0b317a0fe | ||
|
|
837ec18fad | ||
|
|
9eb283420a | ||
|
|
77548e7e9f |
@@ -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.
28
CLAUDE.md
28
CLAUDE.md
@@ -26,6 +26,8 @@ See `README.md` for full project documentation (hardware, architecture, protocol
|
|||||||
| Setting | Value | Why |
|
| Setting | Value | Why |
|
||||||
|---------|-------|-----|
|
|---------|-------|-----|
|
||||||
| `CONFIG_ESP_TASK_WDT_PANIC` | y | WDT timeout → panic → reboot (feeds OTA rollback counter) |
|
| `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):**
|
**Already correct at IDF defaults (verified, no override needed):**
|
||||||
| Setting | Value | Status |
|
| Setting | Value | Status |
|
||||||
@@ -65,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
|
||||||
|
|
||||||
@@ -88,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):
|
||||||
@@ -105,5 +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
|
||||||
|
|||||||
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`).
|
||||||
253
README.md
253
README.md
@@ -1,8 +1,6 @@
|
|||||||
# SC-F001 Firmware
|
# SC-F001 Firmware
|
||||||
|
|
||||||
**Solar-powered automated crop harvesting robot** built on the ESP32. Drives a carriage horizontally via a drive motor, 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.
|
**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.
|
||||||
|
|
||||||
**Primary operational cycle:** Idle → Move Start Delay → Jack Up → Drive → Jack Down → Idle
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -12,29 +10,39 @@
|
|||||||
|
|
||||||
**GPIO Map:**
|
**GPIO Map:**
|
||||||
| GPIO | Function |
|
| GPIO | Function |
|
||||||
|------|----------|
|
|---------|-------------------------------------------------------------------------|
|
||||||
| 13 | Button interrupt (active low, pull-up) — also EXT0 wakeup |
|
| 13 | Button interrupt (active low, pull-up) |
|
||||||
| 14 | Jack position sensor / encoder |
|
| 14 | Jack position sensor |
|
||||||
| 16 | Drive encoder |
|
| 16 | Not Used |
|
||||||
| 19 | Aux sensor 2 (reserved) |
|
| 19 | Drive encoder |
|
||||||
| 21/22 | I2C SDA/SCL (400kHz) → TCA9555 I/O expander |
|
| 21/22 | I2C SDA/SCL (400kHz) → TCA9555 I/O expander |
|
||||||
| 25 | 433MHz RF receiver (RMT input) |
|
| 25 | 433MHz RF receiver (RMT input) |
|
||||||
| 26 | Solar charger bulk enable (RTC GPIO, holds across deep sleep) |
|
| 26 | Solar charger bulk enable (RTC GPIO) |
|
||||||
| 27 | Safety sensor (active low) |
|
| 27 | Safety sensor (active low) |
|
||||||
| 32/33 | External 32.768 kHz RTC crystal (standard watch crystal, 2¹⁵ Hz) |
|
| 32/33 | External 32.768 kHz RTC crystal (on PCB, not used — see RTC section) |
|
||||||
| 36 (VP) | ADC: drive current sense |
|
| 34 | ADC: Current Sensor |
|
||||||
| 39 (VN) | ADC: battery voltage |
|
| 35 | ADC: Battery Voltage |
|
||||||
| 34 | ADC: jack current sense |
|
| 36 (VP) | ADC: Current Sensor VOC |
|
||||||
| 35 | ADC: aux current sense |
|
| 39 (VN) | ADC: Current Sensor FAULT |
|
||||||
|
|
||||||
**TCA9555 (I2C at 0x20):**
|
**TCA9555 (I2C at 0x21):**
|
||||||
- Port 0 (input): 2 physical buttons + 2 additional inputs
|
- Port 0 (input): 2 physical buttons + 2 additional inputs + LEDs
|
||||||
- Port 1 (output): 3× H-bridge relay pairs (DRIVE, JACK, AUX) + LEDs
|
- Port 1 (output): 3× H-bridge relay pairs (DRIVE, JACK, AUX)
|
||||||
|
|
||||||
**Motor / Bridge Specs:**
|
- P00: SW1 (has external 4.7kOhm pullup)
|
||||||
- `BRIDGE_DRIVE` — 100A max, ACS37220 sense chip (13.2 mV/A, inverted polarity)
|
- P01: SW2 (not populated on SC-B001-V5)
|
||||||
- `BRIDGE_JACK` — 30A max, ACS37042 sense chip (44 mV/A)
|
- P02-P04: N/C
|
||||||
- `BRIDGE_AUX` — 30A max, ACS37042 sense chip (44 mV/A)
|
- 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)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -42,28 +50,39 @@
|
|||||||
|
|
||||||
```
|
```
|
||||||
app_main()
|
app_main()
|
||||||
├── rtc_xtal_init() RTC crystal + EXT0 wakeup + sleep wakeup check
|
|
||||||
├── i2c_init() TCA9555 init (relays off, LEDs off)
|
├── 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)
|
├── adc_init() ADC1 calibration (12dB attenuation, line-fit)
|
||||||
├── storage_init() Flash params + circular log buffer
|
├── storage_init() Flash params
|
||||||
|
├── log_init() Circular log buffer
|
||||||
|
├── adc_post()
|
||||||
|
├── storage_post()
|
||||||
├── solar_run_fsm() (called in main loop too)
|
├── solar_run_fsm() (called in main loop too)
|
||||||
├── uart_init() Serial JSON API task
|
├── 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
|
├── rf_433_init() 433MHz RMT receiver task
|
||||||
├── bt_hid_init() BLE HID host scanner task
|
├── bt_hid_init() BLE HID host scanner task
|
||||||
├── fsm_init() Control FSM task (priority 10, 20ms tick)
|
└── webserver_init() WiFi softAP + HTTP + WebSocket + mDNS + DNS
|
||||||
└── webserver_init() WiFi softAP + HTTP + mDNS + DNS
|
|
||||||
|
|
||||||
Main loop (50ms):
|
Main loop (50ms):
|
||||||
|
soft-idle check
|
||||||
|
button hold-to-reboot
|
||||||
|
triple-tap detection
|
||||||
|
alarm detection
|
||||||
|
periodic send_bat_log
|
||||||
i2c_poll_buttons()
|
i2c_poll_buttons()
|
||||||
fsm_request() based on button events
|
fsm_request() based on button events
|
||||||
solar_run_fsm()
|
solar_run_fsm()
|
||||||
driveLEDs() status animation
|
drive_leds() status animation
|
||||||
rtc_check_shutdown_timer() → deep sleep on inactivity (180s)
|
rtc_check_shutdown_timer() → soft idle after INACTIVITY_TIMEOUT_S (default 300s)
|
||||||
|
esp_task_wdt_reset()
|
||||||
```
|
```
|
||||||
|
|
||||||
**FreeRTOS Tasks:**
|
**FreeRTOS Tasks:**
|
||||||
| Task | Created by | Priority | Tick | Purpose |
|
| Task | Created by | Priority | Tick | Purpose |
|
||||||
|------|-----------|----------|------|---------|
|
|---------------------------|--------------------|---------------|----------------|----------------------------------------------------------------------|
|
||||||
| `app_main` (main loop) | system | 1 (default) | 50ms | Button polling, LED animation, solar FSM, shutdown timer |
|
| `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 |
|
| `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 |
|
| UART task | `uart_init()` | default | event-driven | Serial JSON command processing |
|
||||||
@@ -76,23 +95,22 @@ Main loop (50ms):
|
|||||||
## Key Files
|
## Key Files
|
||||||
|
|
||||||
| File | Purpose |
|
| File | Purpose |
|
||||||
|------|---------|
|
|----------------------|----------------------------------------------------------------------|
|
||||||
| `main.c` | Entry point, 50ms main loop, factory reset, LED animation |
|
| `main.c` | Entry point, 50ms main loop, factory reset, LED animation |
|
||||||
| `control_fsm.c/h` | State machine, relay control, current monitoring, calibration |
|
| `control_fsm.c/h` | State machine, relay control, current monitoring, calibration |
|
||||||
| `power_mgmt.c/h` | ADC reading, e-fuse thermal algorithm, battery voltage |
|
| `power_mgmt.c/h` | ADC reading, e-fuse thermal algorithm, battery voltage |
|
||||||
| `sensors.c/h` | GPIO ISR-based sensor debouncing, encoder counters |
|
| `sensors.c/h` | GPIO ISR-based sensor debouncing, encoder counters |
|
||||||
| `i2c.c/h` | TCA9555 relay/LED/button control |
|
| `i2c.c/h` | TCA9555 relay/LED/button control |
|
||||||
| `storage.c/h` | 47-param NVM table + circular binary log buffer |
|
| `storage.c/h` | NVM table + circular binary log buffer |
|
||||||
| `comms.c/h` | Unified GET/POST JSON API (shared by HTTP and UART) |
|
| `comms.c/h` | Unified GET/POST JSON API (shared by HTTP and UART) |
|
||||||
| `webserver.c/h` | WiFi softAP, HTTP server, embedded gzip webpage |
|
| `webserver.c/h` | WiFi softAP, HTTP server, embedded gzip webpage |
|
||||||
| `uart_comms.c/h` | Serial JSON interface (115200 8N1) |
|
| `uart_comms.c/h` | Serial JSON interface (115200 8N1) |
|
||||||
| `rf_433.c/h` | 433MHz OOK receiver, keycode learn/match |
|
| `rf_433.c/h` | 433MHz OOK receiver, keycode learn/match |
|
||||||
| `bt_hid.c/h` | BLE HID host, media remote button mapping |
|
| `bt_hid.c/h` | BLE HID host, media remote button mapping |
|
||||||
| `rtc.c/h` | Unix time, harvest alarms, deep sleep scheduling |
|
| `rtc.c/h` | Unix time, harvest alarms, soft idle, inactivity timer |
|
||||||
| `solar.c/h` | Simple FLOAT/BULK solar charge state machine |
|
| `solar.c/h` | Simple FLOAT/BULK solar charge state machine |
|
||||||
| `sc_err.h` | Error code definitions |
|
| `sc_err.h` | Error code definitions |
|
||||||
| `log_test.c/h` | Flash log unit tests |
|
| `log_test.c/h` | Flash log unit tests |
|
||||||
| `hard_ui.c` | Legacy LCD code (unused/obsolete) |
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -104,17 +122,17 @@ STATE_MOVE_START_DELAY (1s)
|
|||||||
STATE_JACK_UP_START (detect current spike → jack engaged)
|
STATE_JACK_UP_START (detect current spike → jack engaged)
|
||||||
STATE_JACK_UP (continue until timer/e-fuse)
|
STATE_JACK_UP (continue until timer/e-fuse)
|
||||||
STATE_DRIVE_START_DELAY (1s)
|
STATE_DRIVE_START_DELAY (1s)
|
||||||
|
STATE_DRIVE_FLUFF_START
|
||||||
STATE_DRIVE (encoder-based distance control)
|
STATE_DRIVE (encoder-based distance control)
|
||||||
STATE_DRIVE_END_DELAY (1s)
|
STATE_DRIVE_END_DELAY (1s)
|
||||||
STATE_JACK_DOWN (reverse until e-fuse/sensor)
|
STATE_JACK_DOWN (reverse until e-fuse/sensor)
|
||||||
→ back to STATE_IDLE
|
→ back to STATE_IDLE
|
||||||
|
|
||||||
STATE_UNDO_JACK_START (emergency: reverse jack immediately)
|
STATE_UNDO_JACK_START (emergency: reverse jack, run until e-fuse/sensor)
|
||||||
STATE_UNDO_JACK (run until e-fuse/sensor)
|
|
||||||
→ back to STATE_IDLE
|
→ back to STATE_IDLE
|
||||||
|
|
||||||
CAL_JACK_DELAY / CAL_JACK_MOVE (jack calibration sequence)
|
STATE_CALIBRATE_JACK_DELAY / STATE_CALIBRATE_JACK_MOVE (jack calibration sequence)
|
||||||
CAL_DRIVE_DELAY / CAL_DRIVE_MOVE (drive calibration sequence)
|
STATE_CALIBRATE_DRIVE_DELAY / STATE_CALIBRATE_DRIVE_MOVE (drive calibration sequence)
|
||||||
```
|
```
|
||||||
|
|
||||||
**Guards before START:**
|
**Guards before START:**
|
||||||
@@ -128,8 +146,8 @@ CAL_DRIVE_DELAY / CAL_DRIVE_MOVE (drive calibration sequence)
|
|||||||
2. `process_battery_voltage()` — ADC → EMA
|
2. `process_battery_voltage()` — ADC → EMA
|
||||||
3. `sensors_check()` — drain ISR queue, update counters/debounce
|
3. `sensors_check()` — drain ISR queue, update counters/debounce
|
||||||
4. State machine transitions (timer + sensor + efuse checks)
|
4. State machine transitions (timer + sensor + efuse checks)
|
||||||
5. `driveRelays()` — write relay output from current state
|
5. `drive_relays()` — write relay output from current state
|
||||||
6. `send_fsm_log()` — 39-byte timestamped entry to flash
|
6. `send_fsm_log()` — timestamped entry to flash
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -163,18 +181,31 @@ Safety break → immediate `STATE_UNDO_JACK_START`.
|
|||||||
- mDNS hostname: `sc.local`
|
- mDNS hostname: `sc.local`
|
||||||
- Captive portal DNS: all queries → 192.168.4.1
|
- Captive portal DNS: all queries → 192.168.4.1
|
||||||
- HTTP port 80
|
- 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)
|
### HTTP API (port 80)
|
||||||
| Endpoint | Method | Description |
|
| Endpoint | Method | Description |
|
||||||
|----------|--------|-------------|
|
|------------|--------|----------------------------------------------------------------------|
|
||||||
| `/` | GET | Embedded gzip HTML webpage |
|
| `/` | GET | Embedded gzip HTML webpage |
|
||||||
| `/get` | GET | JSON system status |
|
| `/get` | GET | JSON system status (polling fallback when the WebSocket is down) |
|
||||||
| `/set` | POST | JSON commands + parameter updates |
|
| `/post` | POST | JSON commands + parameter updates |
|
||||||
| `/log` | GET | Binary log download (4B JSON len + JSON + 8B offsets + log data) |
|
| `/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)
|
### UART (115200 8N1)
|
||||||
- `GET` → same as HTTP GET /get
|
- `GET` → same as HTTP GET /get
|
||||||
- `POST: {json}` → same as HTTP POST /set
|
- `POST: {json}` → same as HTTP POST /post
|
||||||
|
- `RTCDEBUG` → dump RTC timekeeping state (time, backup, sleep entry, clock source)
|
||||||
- `HELP` → command reference
|
- `HELP` → command reference
|
||||||
- Shares `comms_handle_get()` / `comms_handle_post()` with HTTP
|
- Shares `comms_handle_get()` / `comms_handle_post()` with HTTP
|
||||||
|
|
||||||
@@ -196,61 +227,116 @@ Safety break → immediate `STATE_UNDO_JACK_START`.
|
|||||||
|
|
||||||
## Storage Layout
|
## Storage Layout
|
||||||
|
|
||||||
**Flash partition "storage":**
|
**Flash partitions (8MB flash):**
|
||||||
```
|
|
||||||
0x0000 – 0x0FFF Parameters (4 sectors, CRC32-protected, 47 params)
|
|
||||||
0x1000 – end Circular log buffer (head/tail tracked)
|
|
||||||
```
|
|
||||||
|
|
||||||
**Log entry format (39 bytes typical):**
|
| Partition | Offset | Size | Purpose |
|
||||||
```
|
|-------------|-----------|-----------|----------------------------------------------------------------|
|
||||||
[0:8] Timestamp ms (u64 BE)
|
| nvs | 0x9000 | 16K | WiFi/BT config, board revision, RTC time backup |
|
||||||
[8:12] Battery voltage (f32)
|
| otadata | 0xD000 | 8K | OTA boot selection |
|
||||||
[12:16] Drive current (f32)
|
| phy_init | 0xF000 | 4K | RF calibration data |
|
||||||
[16:20] Jack current (f32)
|
| ota_0 | 0x10000 | 1984K | Factory / primary app slot |
|
||||||
[20:24] Aux current (f32)
|
| ota_1 | 0x200000 | 1984K | OTA update slot |
|
||||||
[24:26] Drive encoder count (i16)
|
| post_test | 0x3F0000 | 4K | Power-on self-test scratch sector |
|
||||||
[26] Sensor states (packed)
|
| params | 0x3F1000 | 32K | CRC32-protected parameter storage (49 params) |
|
||||||
[27:31] Drive heat (f32)
|
| log | 0x400000 | 4096K | Circular binary log buffer (head/tail tracked) |
|
||||||
[31:35] Jack heat (f32)
|
|
||||||
[35:39] Aux heat (f32)
|
|
||||||
```
|
|
||||||
|
|
||||||
**Key Parameters:**
|
**Log entry format (25 bytes typical):**
|
||||||
- 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`
|
[0:8] ts_ms (u64)
|
||||||
- Safety: `SAFETY_BREAK_US`, `SAFETY_MAKE_US`, `LOW_PROTECTION_V`
|
[8:12] bat_V (f32)
|
||||||
- RF: `KEYCODE_0` … `KEYCODE_7`
|
[12:16] current_A (f32) — combined, not per-bridge
|
||||||
- WiFi: `WIFI_SSID`, `WIFI_PASS`, `WIFI_CHANNEL`
|
[16:18] counter (i16)
|
||||||
- Schedule: `NUM_MOVES`, `MOVE_START`, `MOVE_END` (seconds-since-midnight)
|
[18:19] sensors (u8)
|
||||||
|
[19:23] heat (f32) — max across bridges
|
||||||
|
[23:25] i2c_out (u16)
|
||||||
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## RTC & 32.768 kHz Crystal
|
## RTC & Timekeeping
|
||||||
|
|
||||||
**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.
|
**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.
|
||||||
|
|
||||||
**sdkconfig.defaults settings:**
|
**`rtc_xtal_init()` in `rtc.c`:** Configures the button GPIO (GPIO13); no crystal bootstrap or sleep wakeup sources.
|
||||||
- `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.
|
**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.
|
||||||
|
|
||||||
**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 time issues:** Run `RTCDEBUG` over UART. Reports current time, sync time, elapsed since sync, next alarm, uptime, and soft idle state.
|
||||||
|
|
||||||
**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.
|
## 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
|
## Power Management
|
||||||
|
|
||||||
- **Battery voltage:** GPIO39, divider → `V = raw × 0.00767 + 0.4`
|
- **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
|
- **Solar charger:** GPIO26 (RTC hold) — FLOAT/BULK FSM, bulk for 20s when V < 5V for 5s
|
||||||
- **Inactivity shutdown:** 180s → deep sleep
|
- **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.
|
||||||
- **Deep sleep wakeup:** RTC timer (120s), RTC alarm (next harvest), EXT0 GPIO13 (button)
|
- **RTC_DATA_ATTR:** Sync timestamps, alarm times, charge state — survive software resets (panics, WDT)
|
||||||
- **RTC_DATA_ATTR:** FSM state, errors, alarm times, charge state — survive deep sleep
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -270,9 +356,8 @@ SC_ERR_LOW_BATTERY = 0x230 // Voltage below threshold
|
|||||||
|
|
||||||
## Build System
|
## Build System
|
||||||
|
|
||||||
- **Framework:** ESP-IDF (>=4.1.0)
|
- **Framework:** ESP-IDF (>=5.0)
|
||||||
- **Component deps** (`idf_component.yml`): `espressif/mdns`, `joltwallet/littlefs`, `esp-idf-lib/tca95x5`
|
- **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`
|
- **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.**
|
- **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`
|
- **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
|
|
||||||
|
|||||||
BIN
SC-F001-released.bin
Normal file
BIN
SC-F001-released.bin
Normal file
Binary file not shown.
62
TODO.md
62
TODO.md
@@ -1,62 +0,0 @@
|
|||||||
# SC-F001 Firmware — TODO
|
|
||||||
|
|
||||||
1. - [clauded] sdkconfig audit
|
|
||||||
- [clauded] Enable `CONFIG_ESP_TASK_WDT_PANIC=y` — added to sdkconfig.defaults and sdkconfig
|
|
||||||
- [clauded] Verify `CONFIG_FREERTOS_CHECK_STACKOVERFLOW=2` — confirmed canary method active
|
|
||||||
- [clauded] Verify `CONFIG_ESP_SYSTEM_PANIC_PRINT_REBOOT` — confirmed active
|
|
||||||
- [clauded] Confirm brownout detector level — ~2.43V is correct (ESP32 rail protection; battery low-V handled by FSM's `LOW_PROTECTION_V`)
|
|
||||||
- [clauded] Research sdkconfig management best practices — documented in CLAUDE.md "sdkconfig Management" section
|
|
||||||
2. - [clauded] Fix managed_components: removed unused `littlefs` and `tca95x5` deps, pinned `mdns` to `~1.9.1`, bumped IDF min to `>=5.0`; documented in CLAUDE.md
|
|
||||||
3. - [clauded] OTA rollback via consecutive-reset counter
|
|
||||||
- [clauded] Add `RTC_DATA_ATTR uint8_t ota_reset_counter` — incremented on panic/WDT resets, cleared on power-on/ext reset
|
|
||||||
- [clauded] On counter ≥ 5, call `esp_ota_mark_app_invalid_rollback_and_reboot()`
|
|
||||||
- [clauded] After POST passes and FSM starts, call `esp_ota_mark_app_valid_cancel_rollback()` and clear counter
|
|
||||||
- [clauded] Health check = POST passes + all critical inits + FSM task started + non-critical inits attempted
|
|
||||||
4. - [clauded] Critical init failures (ADC, storage, log, I2C, FSM, UART) → `init_critical()` retries 3×, then `esp_restart()`
|
|
||||||
5. - [clauded] Non-critical init failures (RF, BT, webserver) → log error, continue booting
|
|
||||||
- [clauded] WiFi/BT/RF retry once on init failure at boot (200ms delay for RF/BT, 500ms for WiFi), then log and continue
|
|
||||||
6. - [clauded] Power-on self-test (POST) — `init_critical()` wrapper + dedicated POST checks after init
|
|
||||||
- [clauded] ADC: `adc_post()` reads all 4 channels twice with 5ms delay, warns if frozen
|
|
||||||
- [clauded] I2C: `i2c_post()` verifies TCA9555 responds (read port 0)
|
|
||||||
- [clauded] Flash: `storage_post()` write-read-verify on last sector of storage partition
|
|
||||||
7. - [clauded] Parameter validation
|
|
||||||
- [clauded] Add per-param bounds to `PARAM_LIST` macro (min, max) — extended PARAM_DEF 6-arg macro
|
|
||||||
- [clauded] NaN/Inf → reset to default; out-of-range → clamp to min/max — `validate_param()` in storage.c
|
|
||||||
- [clauded] Enforce validation in `storage_init()` (after flash load) and `commit_params()` (before flash write)
|
|
||||||
- [clauded] Audit `set_param_value_t` calls outside comms.c — deleted dead code: `rf_433_set_keycode()`, `FSM_CMD_CALIBRATE_*_FINISH` handlers + FSM cases + `fsm_set_cal_val()` (web JS does cal math client-side, commits via standard param POST)
|
|
||||||
- [clauded] Audit abandoned parameters — `JACK_IS_DOWN` marked deprecated (may duplicate `JACK_I_DOWN`); `BOOT_TIME` is informational-only
|
|
||||||
8. - [clauded] Factory reset: erases params + log + post_test partitions, requires 10s button hold on cold boot, LEDs flash during hold → solid when triggered
|
|
||||||
9. - [clauded] Ensure RTC_DATA_ATTR variables survive panics/WDT resets
|
|
||||||
- [clauded] Verified `sync_unix_us`, `sync_rtc_us`, `rtc_set` — no init path zeroes them; `rtc_restore_time()` recovers via RTC HW counter
|
|
||||||
- [clauded] Verified `remaining_distance`, `fsm_error` — `fsm_init()` does not touch them; only cleared by explicit user action
|
|
||||||
- [clauded] Verified `log_head_offset`, `log_tail_offset` — `log_init()` always recovers from flash scan; RTC_DATA_ATTR is historical/harmless
|
|
||||||
10. - [clauded] Measure flash log write duration — `test_log_write_timing()` in log_test.c, runs 200 iterations of 39-byte writes, reports min/max/avg/sector-crossing times, compares to 5s WDT
|
|
||||||
11. - [clauded] WiFi STA mode with event-group signaling
|
|
||||||
- [clauded] STA-first with softAP fallback was already implemented in `start_wifi()`
|
|
||||||
- [clauded] Added `EventGroupHandle_t comms_event_group` in `comms_events.h` with `WIFI_READY_BIT` / `BT_READY_BIT`
|
|
||||||
- [clauded] Replaced blind 500ms `vTaskDelay` on alarm wake with `xEventGroupWaitBits(COMMS_ALL_BITS, 5s timeout)`
|
|
||||||
- [clauded] `soft_idle_exit()` → `webserver_restart_wifi()` / `bt_hid_resume()` set bits; `webserver_stop()` / `bt_hid_stop()` clear bits
|
|
||||||
- [clauded] Bits set even on permanent init failure so alarm-wake never blocks forever
|
|
||||||
12. - [clauded] Verify `sensors_init()` placement and ISR safety
|
|
||||||
- [clauded] Moved `sensors_init()` to main.c as `init_critical("SENSORS", sensors_init)` — runs before FSM
|
|
||||||
- [clauded] Removed dead commented-out `sensors_init()` / `sensors_stop()` from sensors.c
|
|
||||||
- [clauded] Audited ISR: `sensor_isr_handler` is IRAM_ATTR, uses only `esp_timer_get_time()` (IRAM-safe), `gpio_get_level()`, `xQueueSendFromISR()` — no logging/malloc/flash
|
|
||||||
- [clauded] `sensors_init()` failure is now critical (→ reboot via `init_critical`)
|
|
||||||
13. - [clauded] External 32kHz crystal not needed (deep sleep disabled, soft idle instead) — removed crystal config from sdkconfig.defaults; `rtc_xtal_init()` already a no-op; crystal remains on PCB but unused
|
|
||||||
14. - [clauded] Removed `rtc_wakeup_cause()` — was unused (informational only, never called)
|
|
||||||
15. - [clauded] Confirmed `rtc_check_shutdown_timer()` uses unsigned `TickType_t` subtraction — wraps correctly; removed esp_timer overflow TODO comment from main.c
|
|
||||||
16. - [test] Logtool GUI output (matplotlib)
|
|
||||||
17. - [test] Verify naming convention adherence across codebase
|
|
||||||
18. - [test] Verify WiFi SSID rename triggers comms reboot
|
|
||||||
19. - [clauded] Documentation restructure
|
|
||||||
- [clauded] Move project/hardware documentation from CLAUDE.md → README.md; keep CLAUDE.md for AI-specific instructions and conventions only
|
|
||||||
- [clauded] Document all FreeRTOS tasks and priorities in README.md
|
|
||||||
- [clauded] Add terse comments to FSM state transitions in `control_fsm.c` (focus on "why", not "what")
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
20. - [ ] Extract pure logic (e-fuse thermal model, param serialization, sensor debounce) into host-testable modules with Unity/CMock
|
|
||||||
21. - [ ] UART integration test framework: Python runner + ESP-side test commands
|
|
||||||
22. - [ ] Fix compile warnings
|
|
||||||
23. - [ ] Check if NVS needed for wifi/bluetooth (research first; what is it actually used for? can it be done without?)
|
|
||||||
24. - [ ] If NVS needed for wifi/bluetooth, compare its space efficiency, runtime efficiency, and security (errorchecking/crashes) to current params architecture
|
|
||||||
224
TODO.verbose.md
224
TODO.verbose.md
@@ -1,224 +0,0 @@
|
|||||||
# SC-F001 Firmware — TODO Tracker
|
|
||||||
|
|
||||||
Extracted from `// TODO` comments across the codebase plus legacy items. Excludes third-party managed components.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Legacy Items (from previous TODO.md)
|
|
||||||
|
|
||||||
- [test] Seamless crashing — crashes need to not cause RTC to lose time; remaining_distance unaffected; equivalent of try-catch on whole program; should also make a log
|
|
||||||
- [test] Logtool GUI output (matplotlib)
|
|
||||||
- [test] Refactor; make sure everything adheres to naming conventions
|
|
||||||
- [test] Renaming wifi (should reboot the wifi/web comms to take effect)
|
|
||||||
- [ ] WiFi Network Connection — try STA first, fall back to softAP
|
|
||||||
- [ ] Hard Reset
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. Reliability & Crash Recovery
|
|
||||||
|
|
||||||
| File | Line | TODO |
|
|
||||||
|------|------|------|
|
|
||||||
| `main.c` | 192 | Make sure that this is "crash proof" |
|
|
||||||
| `main.c` | 193 | OTA rollback (triggered how? preferably with hardware... or if there are 5 resets in a row. also need way to nuke the storage partition or safe boot) |
|
|
||||||
| `main.c` | 194 | (maybe) recovery partition that allows uploading firmware |
|
|
||||||
| `storage.c` | 409 | WIPE ENTIRE PARTITION (factory reset only resets params, doesn't erase log) |
|
|
||||||
|
|
||||||
**Commentary:**
|
|
||||||
- OTA rollback is critical for field-deployed devices. ESP-IDF has built-in rollback support via `esp_ota_mark_app_valid_cancel_rollback()` — the app marks itself valid after a health check, otherwise the bootloader reverts on next reboot. This is the standard approach; a "5 resets in a row" counter can be stored in RTC_DATA_ATTR or an NVS counter.
|
|
||||||
- A recovery partition (minimal firmware with just WiFi + OTA upload) is a strong safety net. ESP-IDF's factory/OTA partition scheme supports this natively — the factory partition acts as the recovery image.
|
|
||||||
- Factory reset should absolutely erase the log partition. A `esp_partition_erase_range()` over the full log region is straightforward and should be added.
|
|
||||||
- "Crash proof" is vague — consider defining what this means concretely: e.g., no stuck relays after watchdog reset (already handled by TCA9555 power-on defaults), no corrupted params (CRC32 already protects), log head/tail consistency after power loss.
|
|
||||||
|
|
||||||
**Path Forwards:**
|
|
||||||
1. Implement a 5 reset in a row counter in RTC_DATA_ATTR that will rollback to the previous
|
|
||||||
2. Make the factory reset erase the storage partition. It should be triggered by the button being held on power on for at least 10 seconds, and should give LED indication (flash all LEDs off and on, then hold on once the reset is triggered)
|
|
||||||
3. crashproofing should mainly keep any RTC_DATA_ATTR variables from getting reset if anything panics - namely no losing time, no losing remaining position (other RTC_DATA_ATTR vars less important)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. Error Handling & Logging
|
|
||||||
|
|
||||||
| File | Line | TODO |
|
|
||||||
|------|------|------|
|
|
||||||
| `main.c` | 174 | Do things with errors (put in real log? then reset. "assert with LOGE"?) |
|
|
||||||
| `main.c` | 228 | Seriously, log all the errors on bluetooth |
|
|
||||||
| `main.c` | 178 | Figure out how long logging takes (for reference, and comp to wdt) |
|
|
||||||
|
|
||||||
**Commentary:**
|
|
||||||
- Currently, init failures are logged but execution continues — this is the right pattern for a field device (degrade gracefully), but errors should be persisted. Use the existing `LOG_TYPE_ERROR` entry type to write a structured error log entry on each init failure, including the module ID and error code.
|
|
||||||
- BT errors are particularly important because BLE stacks fail silently in many edge cases (connection drops, service discovery timeouts, pairing failures). At minimum, log connection/disconnection events and HID report parse failures.
|
|
||||||
- Logging duration: flash writes on ESP32 typically take 5-20ms per sector erase + write. With a 10s WDT and 20ms FSM tick, this is fine, but verify empirically with `esp_timer_get_time()` bracketing. Consider using the async log queue (already in architecture) to keep flash writes off the FSM task.
|
|
||||||
|
|
||||||
**Path Forwards:**
|
|
||||||
1. wifi, webserver, rf, and bluetooth failures are acceptable gracefully, though they should try to start again if it still makes sense. All other failures are not acceptable and should cause a system reset.
|
|
||||||
2. Need to actually test and time the logging duration.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. Safety & Robustness Audits
|
|
||||||
|
|
||||||
| File | Line | TODO |
|
|
||||||
|------|------|------|
|
|
||||||
| `main.c` | 113 | Check wdt stuff |
|
|
||||||
| `main.c` | 114 | Stack Overflow Detection |
|
|
||||||
| `main.c` | 273 | Make sure all ISRs are clean (very tight, no blocking functions) |
|
|
||||||
| `control_fsm.c` | 99 | Make sure this is threadsafe (fsm_request / xQueueSend) |
|
|
||||||
|
|
||||||
**Commentary:**
|
|
||||||
- **WDT:** The 10s timeout is already configured. Verify every task calls `esp_task_wdt_reset()` within its loop. Consider enabling `CONFIG_ESP_TASK_WDT_PANIC=y` in sdkconfig so a WDT timeout triggers a core dump rather than a silent reset.
|
|
||||||
- **Stack overflow:** Enable `CONFIG_FREERTOS_CHECK_STACKOVERFLOW=2` (canary method) in sdkconfig. This catches overflows at context switch time. Also use `uxTaskGetStackHighWaterMark()` during development to right-size stacks.
|
|
||||||
- **ISR audit:** ESP-IDF provides `ESP_INTR_FLAG_IRAM` for ISR placement. Ensure no ISR calls `ESP_LOGx`, `printf`, `malloc`, or any flash-access function. The sensor ISR should only do `xQueueSendFromISR()`.
|
|
||||||
- **Thread safety of `fsm_request`:** `xQueueSend` is inherently thread-safe in FreeRTOS — it's designed for cross-task and ISR-to-task communication. This TODO can likely be closed. Just confirm `fsm_cmd_queue` is created before any caller can invoke `fsm_request()`.
|
|
||||||
|
|
||||||
**Path Forwards:**
|
|
||||||
1. Make the necessary changes to sdkconfig
|
|
||||||
2. Look at ISRs (I actually don't think we have any but double check)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. Power Management & Sleep
|
|
||||||
|
|
||||||
| File | Line | TODO |
|
|
||||||
|------|------|------|
|
|
||||||
| `main.c` | 208 | Is this reasonable now that we eliminated deep sleep? (solar FSM call) |
|
|
||||||
| `main.c` | 210 | Do a 12V check and enter deep sleep if there's a problem |
|
|
||||||
| `main.c` | 365 | Will esp_timer overflow? Handle overflow if needed |
|
|
||||||
|
|
||||||
**Commentary:**
|
|
||||||
- **Solar FSM after eliminating deep sleep:** If deep sleep is no longer used, `solar_run_fsm()` still makes sense — it controls GPIO26 (bulk/float charge switching). Review whether the FLOAT→BULK transitions still trigger correctly without the deep sleep wake cycle.
|
|
||||||
- **12V check:** A critical low-voltage protection. If battery voltage is dangerously low (below the charger's cutoff), the ESP32 should enter deep sleep to let the panel charge without load. This prevents brown-out damage to flash. Threshold should be configurable via a parameter (it already has `LOW_PROTECTION_V`).
|
|
||||||
- **`esp_timer_get_time()` overflow:** Returns `int64_t` microseconds. It overflows after ~292,000 years — this is a non-issue. This TODO can be closed after confirming `rtc_check_shutdown_timer()` uses signed subtraction.
|
|
||||||
|
|
||||||
**Path Forwards:**
|
|
||||||
1. Keep solar FSM just in the main loop
|
|
||||||
2. Don't do any low-voltage checking
|
|
||||||
3. Confirm that rtc_check_shutdown_timer() uses signed subtraction.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. Startup & Initialization Hygiene
|
|
||||||
|
|
||||||
| File | Line | TODO |
|
|
||||||
|------|------|------|
|
|
||||||
| `main.c` | 115 | Remove XTAL crystal stuff |
|
|
||||||
| `main.c` | 120 | `rtc_wakeup_cause()` shouldn't be needed anymore |
|
|
||||||
| `main.c` | 125 | How many tasks do we have? |
|
|
||||||
| `main.c` | 232 | `sensors_init()` — Why is this off? |
|
|
||||||
| `control_fsm.c` | 185 | Why is `sensors_init()` here rather than in main? |
|
|
||||||
|
|
||||||
**Commentary:**
|
|
||||||
- **XTAL removal:** The CLAUDE.md documents the crystal bootstrap workaround as essential for RTC accuracy. Don't remove `rtc_xtal_init()` unless you've confirmed the hardware no longer uses the external crystal — removing it would silently degrade RTC accuracy to ±5%.
|
|
||||||
- **`rtc_wakeup_cause()`:** If it's purely informational logging, it's harmless. Either delete it or keep it — it's one log line at boot.
|
|
||||||
- **Task count:** Document the task list: main loop (implicit), `control_task`, UART task, RF 433 task, BT HID task, HTTP server workers. Use `uxTaskGetNumberOfTasks()` at boot to log the count.
|
|
||||||
- **`sensors_init()` location:** It's called inside `control_task()` (line 185) and commented out in `main.c` (line 232). This is intentional — sensors are initialized in the FSM task context because the ISR handlers need to send to queues created in that scope. The comment in main.c should be removed and the one in control_fsm.c clarified.
|
|
||||||
|
|
||||||
**Path Forwards:**
|
|
||||||
1. Confirm that we can stop using the RTC and just use the internal crystal (never go into low power state)
|
|
||||||
2. Remove wakeup cause
|
|
||||||
3. Document a list of tasks and put in README.md
|
|
||||||
4. Double check that sensors_init() shouldn't be called in main. Make sure that the error code from it is handled appropriately (it MUST init properly; if it doesn't, reboot)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 6. Soft Idle & Wake Behavior
|
|
||||||
|
|
||||||
| File | Line | TODO |
|
|
||||||
|------|------|------|
|
|
||||||
| `main.c` | 243 | Critique & confirm what we do in idle |
|
|
||||||
| `main.c` | 256 | Do a hard wait until wifi and bluetooth come up, not just blindly wait; might be better to be non-blocking |
|
|
||||||
|
|
||||||
**Commentary:**
|
|
||||||
- **Idle behavior:** The soft idle mode polls at 5s via direct GPIO (no I2C). This saves power but means TCA9555 button state is stale. Confirm that the only wake sources (GPIO13 button, RTC alarm) don't depend on I2C. The current design looks correct.
|
|
||||||
- **WiFi/BT wake wait:** The 500ms `vTaskDelay` is fragile — WiFi association can take 1-5s depending on the AP. Use event groups: `wifi_event_group` with a `CONNECTED_BIT`, wait with `xEventGroupWaitBits()` and a timeout. This is the standard ESP-IDF pattern. For BT, the scan is already async so it doesn't need blocking.
|
|
||||||
|
|
||||||
**Path Forwards:**
|
|
||||||
1. Do a hard wait until wifi and bluetooth come up rather than blindly. Needs to be non-blocking. Need bluetooth to finish initting, and wifi to have connected to a network or brought up the softap.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 7. Testing & Verification
|
|
||||||
|
|
||||||
| File | Line | TODO |
|
|
||||||
|------|------|------|
|
|
||||||
| `main.c` | 214 | Test strategy!!! (software verification, and unit bringup) |
|
|
||||||
| `main.c` | 215 | A→D bringup; sanity check (sum up all inputs, wait 5ms, sum again, make sure there is a change (not frozen)) |
|
|
||||||
|
|
||||||
**Commentary:**
|
|
||||||
- **Test strategy:** For embedded firmware, consider three layers:
|
|
||||||
1. **Host-side unit tests** — extract pure logic (e-fuse thermal model, param serialization, log format) into testable modules. Run with Unity or CMock on the host.
|
|
||||||
2. **On-target POST** — a power-on self-test routine that validates ADC readings are in-range, I2C responds, flash is accessible. Run once at boot, log results.
|
|
||||||
3. **Integration tests** — scripted UART commands that exercise the FSM cycle end-to-end. The existing `log_test.c` is a start.
|
|
||||||
- **ADC sanity check:** Good idea. Read all ADC channels twice with a short delay; if any channel returns identical values both times (especially the current sense channels, which should have noise), flag it. This catches stuck ADC mux, disconnected sense resistors, or frozen DMA.
|
|
||||||
|
|
||||||
**Path Forwards:**
|
|
||||||
1. Make pure logic for sensors, FSMs, e-fusing. This is an entire project.
|
|
||||||
2. Make a POST: that would include ADC, I2C, flash accessibility
|
|
||||||
3. Make a UART integration test, both a python runner, and the requesite ESP code
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 8. Storage & Parameters
|
|
||||||
|
|
||||||
| File | Line | TODO |
|
|
||||||
|------|------|------|
|
|
||||||
| `storage.h` | 7 | Sanity check that the EEPROM is working (sacrifice sector 0?) |
|
|
||||||
| `storage.h` | 57 | Bounds checking / constraints (especially no division by zero, no NaNs, no infs) |
|
|
||||||
| `storage.h` | 58 | Abandoned parameters (esp. jack current) |
|
|
||||||
|
|
||||||
**Commentary:**
|
|
||||||
- **Flash health check:** A simple write-read-verify test on a dedicated test sector at boot is a good idea. Alternatively, rely on the CRC32 check already protecting the param sector — if CRC fails, you know flash is degraded.
|
|
||||||
- **Parameter validation is the highest-priority item here.** Any f32 parameter used as a divisor (e.g., `DRIVE_KT`, `DRIVE_KE`, `EFUSE_INOM_*`) must be validated on load: reject NaN, Inf, and zero. Clamp to sane ranges. A `param_validate()` function called after `storage_init()` and after any `/set` POST would prevent field bricking from a bad parameter write.
|
|
||||||
- **Abandoned parameters:** Audit the PARAM_LIST. If `JACK_CURRENT` or similar params are no longer used by any code path, remove them to avoid confusion. Verify with grep first — sometimes params are only referenced from the web UI.
|
|
||||||
|
|
||||||
**Path Forwards:**
|
|
||||||
1. Utilize the last sector of the flash as a read-write-verify test
|
|
||||||
2. Add bounds in the PARAMS_LIST. It should accept a function or macro with arguments. Per-parameter we might want different min/max. and some parameters we will want to check and ensure it is NaN/inf. If Nan/Inf/etc set to default, otherwise clamp to min/max
|
|
||||||
3. Check in code - is there anywhere we don't immediately commit after setting a parameter? add bounds checking to the commit. This should do it
|
|
||||||
4. Audit abandoned parameters. Don't outright remove them just put a comment for now
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 9. Build & Configuration
|
|
||||||
|
|
||||||
| File | Line | TODO |
|
|
||||||
|------|------|------|
|
|
||||||
| `main.c` | 217 | Make sure sdkconfig is sane. Make notes. |
|
|
||||||
| `main.c` | 218 | Fix managed_components |
|
|
||||||
|
|
||||||
**Commentary:**
|
|
||||||
- **sdkconfig audit — key settings to verify:**
|
|
||||||
- `CONFIG_ESP_TASK_WDT_PANIC=y` (WDT triggers panic + core dump, not silent reset)
|
|
||||||
- `CONFIG_FREERTOS_CHECK_STACKOVERFLOW=2` (canary-based detection)
|
|
||||||
- `CONFIG_ESP_SYSTEM_PANIC_PRINT_REBOOT` (print backtrace then reboot)
|
|
||||||
- `CONFIG_PARTITION_TABLE_CUSTOM=y` with correct factory/OTA layout
|
|
||||||
- `CONFIG_RTC_CLK_SRC_EXT_CRYS=y` (already set per CLAUDE.md)
|
|
||||||
- Confirm brownout detector level matches your minimum operating voltage
|
|
||||||
- **managed_components:** ESP-IDF's component manager can cause build reproducibility issues. Pin exact versions in `idf_component.yml`. Consider vendoring critical components (mdns, littlefs, tca95x5) into the repo if version drift causes problems.
|
|
||||||
|
|
||||||
**Path Forwards:**
|
|
||||||
1. Research best practices to manage sdkconfig; document in CLAUDE.md
|
|
||||||
2. Fix managed_components and remove anything that is not being utilized and then make idf.py happy; document in CLAUDE.md
|
|
||||||
3. Apply best practices to manage sdkconfig
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 10. Documentation
|
|
||||||
|
|
||||||
| File | Line | TODO |
|
|
||||||
|------|------|------|
|
|
||||||
| `control_fsm.c` | 9 | Comment, and even better, produce a README for this. |
|
|
||||||
|
|
||||||
**Commentary:**
|
|
||||||
- The CLAUDE.md already serves as comprehensive FSM documentation. Adding inline comments to `control_fsm.c` describing each state transition's guard conditions and timing rationale would be more valuable than a separate README. Focus comments on the "why" — the state names and relay outputs are self-documenting for the "what."
|
|
||||||
|
|
||||||
**Path Forwards:**
|
|
||||||
1. Copy FSM documentation to README.md
|
|
||||||
2. Add comments (terse but helpful) to the FSM
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 11. OTHER
|
|
||||||
|
|
||||||
**Path Forwards:**
|
|
||||||
1. Fix docs; CLAUDE.md should be claude-specific instructions and such; README.md should contain everything else.
|
|
||||||
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
|
||||||
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()
|
||||||
@@ -141,17 +135,11 @@ def live_plot(url: str, interval_s: float = 2.0):
|
|||||||
|
|
||||||
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],
|
|
||||||
'aux': axes[1].plot([], [], label='Aux', linewidth=1)[0],
|
|
||||||
'state': axes[2].step([], [], where='post', linewidth=1, color='navy')[0],
|
'state': axes[2].step([], [], where='post', linewidth=1, color='navy')[0],
|
||||||
'drheat': axes[3].plot([], [], label='Drive', linewidth=1)[0],
|
'heat': axes[3].plot([], [], color='red', 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
|
||||||
|
|
||||||
|
# Detect entry format: with type byte (total = len+1) or without (total = len).
|
||||||
|
# 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]
|
payload = data[i + 1 : i + 1 + payload_size]
|
||||||
entry_type = data[type_offset]
|
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 partition_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_ */
|
||||||
@@ -77,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
|
||||||
|
|||||||
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
|
||||||
268
main/comms.c
268
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)) {
|
|
||||||
cJSON_AddItemToArray(msg_array, cJSON_CreateString("AUX EFUSE TRIP"));
|
|
||||||
}
|
}
|
||||||
if (efuse_get(BRIDGE_JACK)) {
|
if (low_bat)
|
||||||
cJSON_AddItemToArray(msg_array, cJSON_CreateString("JACK EFUSE TRIP"));
|
cJSON_AddItemToArray(msg_array, cJSON_CreateString("LOW BATTERY"));
|
||||||
}
|
if (!rtc_is_set())
|
||||||
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) {
|
||||||
|
|
||||||
switch (get_param_type(i)) {
|
|
||||||
case PARAM_TYPE_f32:
|
|
||||||
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));
|
cJSON_AddStringToObject(parameters, name, get_param_string(i));
|
||||||
break;
|
} else {
|
||||||
default:
|
cJSON_AddNumberToObject(parameters, name, param_to_double(i));
|
||||||
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;
|
||||||
@@ -291,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";
|
||||||
@@ -329,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)) {
|
|
||||||
case PARAM_TYPE_f32:
|
|
||||||
if (cJSON_IsNumber(value_json)) {
|
|
||||||
set_param_value_t(param_idx, (param_value_t){.f32 = value_json->valuedouble});
|
|
||||||
params_updated++;
|
params_updated++;
|
||||||
} else {
|
} else {
|
||||||
ESP_LOGW(TAG, "Type mismatch for parameter: %s", key);
|
ESP_LOGW(TAG, "Type mismatch for parameter: %s", key);
|
||||||
params_failed++;
|
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;
|
||||||
@@ -436,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...");
|
||||||
@@ -444,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
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
#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"
|
||||||
@@ -32,15 +33,21 @@
|
|||||||
|
|
||||||
static QueueHandle_t fsm_cmd_queue = NULL;
|
static QueueHandle_t fsm_cmd_queue = NULL;
|
||||||
|
|
||||||
// AUDIT: fsm_init() does not zero these — they persist across panics/WDT resets.
|
// 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).
|
// 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;
|
||||||
bool enabled = false;
|
bool enabled = false;
|
||||||
|
|
||||||
float this_move_dist = 0.0f;
|
float this_move_dist = 0.0f;
|
||||||
@@ -56,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;
|
||||||
@@ -65,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) {
|
||||||
@@ -77,15 +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;
|
||||||
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 —
|
||||||
@@ -95,7 +197,6 @@ void fsm_request(fsm_cmd_t cmd)
|
|||||||
rtc_reset_shutdown_timer(); // any accepted command extends the wake period
|
rtc_reset_shutdown_timer(); // any accepted command extends the wake period
|
||||||
if (fsm_cmd_queue != NULL)
|
if (fsm_cmd_queue != NULL)
|
||||||
xQueueSend(fsm_cmd_queue, &cmd, 0); // safe from any context
|
xQueueSend(fsm_cmd_queue, &cmd, 0); // safe from any context
|
||||||
// TODO: Make sure this is threadsafe
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int8_t fsm_get_current_progress(int8_t denominator) {
|
int8_t fsm_get_current_progress(int8_t denominator) {
|
||||||
@@ -107,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);
|
||||||
@@ -123,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);
|
||||||
@@ -187,6 +305,12 @@ void control_task(void *param) {
|
|||||||
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 ****/
|
||||||
@@ -205,44 +329,33 @@ 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);
|
||||||
@@ -262,57 +375,53 @@ 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) {
|
|
||||||
current_state = STATE_CALIBRATE_JACK_DELAY;
|
|
||||||
log = true;
|
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_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) {
|
|
||||||
current_state = STATE_CALIBRATE_DRIVE_DELAY;
|
|
||||||
log = true;
|
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;
|
||||||
@@ -394,12 +503,35 @@ void control_task(void *param) {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case STATE_DRIVE_START_DELAY:
|
case STATE_DRIVE_START_DELAY:
|
||||||
// 1s pause between jack-up and drive — mechanical settling
|
// 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;
|
||||||
@@ -417,12 +549,25 @@ void control_task(void *param) {
|
|||||||
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;
|
||||||
|
|
||||||
|
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) {
|
||||||
// Normal completion — deduct planned distance from leash
|
// Normal completion — deduct planned distance from leash
|
||||||
remaining_distance -= this_move_dist;
|
remaining_distance -= this_move_dist;
|
||||||
@@ -431,28 +576,23 @@ void control_task(void *param) {
|
|||||||
log = true;
|
log = true;
|
||||||
set_timer(TRANSITION_DELAY_US);
|
set_timer(TRANSITION_DELAY_US);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (efuse_get(BRIDGE_DRIVE)) {
|
|
||||||
// Fault — deduct actual distance traveled (may be partial)
|
|
||||||
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
|
// 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;
|
||||||
@@ -514,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 = {
|
||||||
@@ -550,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,
|
||||||
@@ -616,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;
|
||||||
@@ -648,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 = {
|
||||||
@@ -672,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();
|
||||||
|
|
||||||
|
|||||||
@@ -38,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
|
||||||
@@ -84,6 +90,7 @@ 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();
|
||||||
@@ -104,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;
|
|
||||||
}
|
|
||||||
71
main/i2c.c
71
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));
|
||||||
|
|
||||||
@@ -65,16 +76,40 @@ esp_err_t i2c_post(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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;
|
||||||
@@ -115,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];
|
||||||
}
|
}
|
||||||
@@ -139,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;
|
||||||
}
|
}
|
||||||
|
|||||||
18
main/i2c.h
18
main/i2c.h
@@ -55,8 +55,26 @@ 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);
|
||||||
|
|||||||
300
main/main.c
300
main/main.c
@@ -10,6 +10,7 @@
|
|||||||
#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"
|
||||||
@@ -17,17 +18,18 @@
|
|||||||
#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 "comms_events.h"
|
||||||
#include "version.h"
|
#include "version.h"
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
EventGroupHandle_t comms_event_group = NULL;
|
EventGroupHandle_t comms_event_group = NULL; // synchronizing tasks
|
||||||
|
|
||||||
#define TAG "MAIN"
|
#define TAG "MAIN"
|
||||||
|
|
||||||
#define POST_MAX_RETRIES 3
|
#define POST_MAX_RETRIES 3 // how many times to try an init function
|
||||||
#define OTA_ROLLBACK_THRESHOLD 5
|
#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
|
#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
|
// Survives resets (panic, WDT, sw reset) but NOT power-on or external reset
|
||||||
RTC_DATA_ATTR static uint8_t ota_reset_counter = 0;
|
RTC_DATA_ATTR static uint8_t ota_reset_counter = 0;
|
||||||
@@ -69,93 +71,114 @@ 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);
|
||||||
|
|
||||||
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);
|
||||||
|
|
||||||
// TODO: Confirm whether external RTC crystal can be dropped (see TODO.md #13)
|
// I2C first so we can light the LED immediately
|
||||||
if (rtc_xtal_init() != ESP_OK) ESP_LOGE(TAG, "RTC FAILED");
|
|
||||||
rtc_restore_time(); // Recover time from RTC domain if we crashed
|
|
||||||
|
|
||||||
// Critical inits — retry up to 3 times, then reboot (feeds OTA rollback counter)
|
|
||||||
init_critical("I2C", i2c_init);
|
init_critical("I2C", i2c_init);
|
||||||
|
drive_leds(LED_BOOTING); // LED on ASAP after I2C is up
|
||||||
i2c_post(); // verify TCA9555 responds
|
i2c_post(); // verify TCA9555 responds
|
||||||
i2c_set_relays((relay_port_t){.raw=0});
|
/* Sensors powered from boot; FSM will keep P10 high on every tick.
|
||||||
drive_leds(LED_STATE_BOOTING);
|
* Drops back to 0 on soft_idle_enter() (sleep). */
|
||||||
|
i2c_relays_idle();
|
||||||
|
|
||||||
|
if (rtc_xtal_init() != ESP_OK) ESP_LOGE(TAG, "RTC FAILED");
|
||||||
|
|
||||||
// Factory reset: cold boot + button held for 10s
|
// Factory reset: cold boot + button held for 10s
|
||||||
// LEDs flash while waiting, go solid when triggered
|
// LEDs flash while waiting, go solid when triggered
|
||||||
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, "Button held on cold boot — hold %ds for factory reset", FACTORY_RESET_HOLD_MS / 1000);
|
ESP_LOGW(TAG, "Button held on cold boot — hold %ds for factory reset", FACTORY_RESET_HOLD_MS / 1000);
|
||||||
|
|
||||||
// Flash all LEDs while user holds button (100ms on/off cycle)
|
// Flash all LEDs while user holds button (100ms on/off cycle).
|
||||||
|
// GPIO13 is the NCA9535 INT line on V5 (not a direct button line),
|
||||||
|
// 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;
|
int held_ms = 0;
|
||||||
while (gpio_get_level(GPIO_NUM_13) == 0 && held_ms < FACTORY_RESET_HOLD_MS) {
|
while (i2c_button_held_raw(0) && held_ms < FACTORY_RESET_HOLD_MS) {
|
||||||
i2c_set_led1((held_ms / 100) % 2 ? 0b111 : 0b000);
|
i2c_set_led1((held_ms / 100) % 2 ? 0b111 : 0b000);
|
||||||
vTaskDelay(pdMS_TO_TICKS(100));
|
vTaskDelay(pdMS_TO_TICKS(100));
|
||||||
held_ms += 100;
|
held_ms += 100;
|
||||||
@@ -203,6 +226,7 @@ void app_main(void) {esp_task_wdt_add(NULL);
|
|||||||
// Critical inits — retry up to 3 times, then reboot
|
// Critical inits — retry up to 3 times, then reboot
|
||||||
init_critical("ADC", adc_init);
|
init_critical("ADC", adc_init);
|
||||||
init_critical("STORAGE", storage_init);
|
init_critical("STORAGE", storage_init);
|
||||||
|
rtc_restore_time(); // After NVS is up: try RTC_DATA_ATTR, then NVS fallback
|
||||||
init_critical("LOG", log_init);
|
init_critical("LOG", log_init);
|
||||||
|
|
||||||
// POST checks — verify hardware is responding correctly
|
// POST checks — verify hardware is responding correctly
|
||||||
@@ -293,26 +317,43 @@ void app_main(void) {esp_task_wdt_add(NULL);
|
|||||||
esp_ota_mark_app_valid_cancel_rollback();
|
esp_ota_mark_app_valid_cancel_rollback();
|
||||||
|
|
||||||
/*** 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) {
|
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. */
|
||||||
// TODO: Critique & confirm what we do in idle
|
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();
|
||||||
// Wait for WiFi + BT to come back up (or timeout after 5s)
|
|
||||||
if (comms_event_group) {
|
if (comms_event_group) {
|
||||||
xEventGroupWaitBits(comms_event_group, COMMS_ALL_BITS,
|
xEventGroupWaitBits(comms_event_group, COMMS_ALL_BITS,
|
||||||
pdFALSE, pdTRUE, pdMS_TO_TICKS(5000));
|
pdFALSE, pdTRUE, pdMS_TO_TICKS(5000));
|
||||||
@@ -324,94 +365,129 @@ void app_main(void) {esp_task_wdt_add(NULL);
|
|||||||
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
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Make sure all ISRs are clean (very tight, no blocking functions)
|
// --- 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);
|
||||||
|
|
||||||
switch (fsm_get_state()) {
|
// 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;
|
||||||
|
|||||||
@@ -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,16 +218,32 @@ 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) {
|
esp_err_t adc_post(void) {
|
||||||
// Read all 4 channels twice with a short delay; flag if frozen or wildly out of range
|
#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 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 char *names[] = { "ISENS1", "ISENS2", "ISENS3", "BATTERY" };
|
||||||
|
const int n = 4;
|
||||||
|
#endif
|
||||||
int first[4], second[4];
|
int first[4], second[4];
|
||||||
|
|
||||||
for (int i = 0; i < 4; i++) {
|
for (int i = 0; i < n; i++) {
|
||||||
if (adc_oneshot_read(adc1_handle, channels[i], &first[i]) != ESP_OK) {
|
if (adc_oneshot_read(adc1_handle, channels[i], &first[i]) != ESP_OK) {
|
||||||
ESP_LOGE(TAG, "POST: ADC read failed on %s", names[i]);
|
ESP_LOGE(TAG, "POST: ADC read failed on %s", names[i]);
|
||||||
return ESP_FAIL;
|
return ESP_FAIL;
|
||||||
@@ -194,23 +252,28 @@ esp_err_t adc_post(void) {
|
|||||||
|
|
||||||
vTaskDelay(pdMS_TO_TICKS(5));
|
vTaskDelay(pdMS_TO_TICKS(5));
|
||||||
|
|
||||||
for (int i = 0; i < 4; i++) {
|
for (int i = 0; i < n; i++) {
|
||||||
if (adc_oneshot_read(adc1_handle, channels[i], &second[i]) != ESP_OK) {
|
if (adc_oneshot_read(adc1_handle, channels[i], &second[i]) != ESP_OK) {
|
||||||
ESP_LOGE(TAG, "POST: ADC read failed on %s (2nd)", names[i]);
|
ESP_LOGE(TAG, "POST: ADC read failed on %s (2nd)", names[i]);
|
||||||
return ESP_FAIL;
|
return ESP_FAIL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for frozen ADC (identical readings on noise-bearing current sense channels)
|
// Frozen-ADC check on current-sense channels only (battery can legitimately be stable)
|
||||||
for (int i = 0; i < 3; i++) { // only current sense, not battery (battery can be stable)
|
for (int i = 0; i < n - 1; i++) {
|
||||||
if (first[i] == second[i] && first[i] != 0) {
|
if (first[i] == second[i] && first[i] != 0) {
|
||||||
ESP_LOGW(TAG, "POST: ADC %s may be frozen (both reads = %d)", names[i], first[i]);
|
ESP_LOGW(TAG, "POST: ADC %s may be frozen (both reads = %d)", names[i], first[i]);
|
||||||
// Warning only — a truly stuck ADC will trip efuse protections anyway
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#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)",
|
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]);
|
first[3], second[3], first[0], second[0], first[1], second[1], first[2], second[2]);
|
||||||
|
#endif
|
||||||
return ESP_OK;
|
return ESP_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -227,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();
|
||||||
@@ -269,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;
|
||||||
@@ -307,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;
|
||||||
@@ -314,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;
|
||||||
@@ -326,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) {
|
||||||
@@ -337,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 +
|
||||||
@@ -362,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;
|
||||||
@@ -450,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;
|
||||||
@@ -461,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,6 +55,13 @@ 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 adc_post(void);
|
||||||
esp_err_t power_init();
|
esp_err_t power_init();
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -11,11 +11,14 @@
|
|||||||
|
|
||||||
#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();
|
||||||
|
|
||||||
|
/* 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. */
|
||||||
|
uint32_t rf_433_peek_latest(void);
|
||||||
|
|
||||||
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();
|
||||||
|
|
||||||
|
|||||||
225
main/rtc.c
225
main/rtc.c
@@ -19,6 +19,7 @@
|
|||||||
#include "esp_timer.h"
|
#include "esp_timer.h"
|
||||||
#include "i2c.h"
|
#include "i2c.h"
|
||||||
#include "driver/gpio.h"
|
#include "driver/gpio.h"
|
||||||
|
#include "driver/rtc_io.h"
|
||||||
#include "rtc_wdt.h"
|
#include "rtc_wdt.h"
|
||||||
#include "esp_log.h"
|
#include "esp_log.h"
|
||||||
#include "freertos/FreeRTOS.h"
|
#include "freertos/FreeRTOS.h"
|
||||||
@@ -27,11 +28,15 @@
|
|||||||
#include "soc/rtc.h"
|
#include "soc/rtc.h"
|
||||||
#include "solar.h"
|
#include "solar.h"
|
||||||
#include "storage.h"
|
#include "storage.h"
|
||||||
|
#include "nvs.h"
|
||||||
#include "webserver.h"
|
#include "webserver.h"
|
||||||
#include "bt_hid.h"
|
#include "bt_hid.h"
|
||||||
|
|
||||||
#define PIN_BTN_INTERRUPT GPIO_NUM_13
|
#define PIN_BTN_INTERRUPT GPIO_NUM_13
|
||||||
|
|
||||||
|
#define RTC_NVS_NAMESPACE "hw"
|
||||||
|
#define RTC_NVS_KEY "rtc_time"
|
||||||
|
|
||||||
// Return microseconds from the RTC hardware timer.
|
// Return microseconds from the RTC hardware timer.
|
||||||
// Used ONLY in rtc_restore_time() for crash-recovery (survives panics/WDT via RTC domain).
|
// Used ONLY in rtc_restore_time() for crash-recovery (survives panics/WDT via RTC domain).
|
||||||
// RC oscillator drift (~150 kHz, ±5%) is negligible over a <30s crash restart (~1.5s worst case).
|
// RC oscillator drift (~150 kHz, ±5%) is negligible over a <30s crash restart (~1.5s worst case).
|
||||||
@@ -45,7 +50,7 @@ static uint64_t rtc_hw_time_us(void)
|
|||||||
uint64_t last_activity_tick = 0;
|
uint64_t last_activity_tick = 0;
|
||||||
|
|
||||||
// RTC_DATA_ATTR keeps these in RTC memory; persists across software resets (panics, WDT).
|
// RTC_DATA_ATTR keeps these in RTC memory; persists across software resets (panics, WDT).
|
||||||
// AUDIT: no init path zeroes these — rtc_restore_time() recovers via RTC HW counter,
|
// no init path zeroes these — rtc_restore_time() recovers via RTC HW counter,
|
||||||
// rtc_set_s() is only called explicitly by the user. Verified 2026-03-12.
|
// rtc_set_s() is only called explicitly by the user. Verified 2026-03-12.
|
||||||
RTC_DATA_ATTR int64_t next_alarm_time_s = -1;
|
RTC_DATA_ATTR int64_t next_alarm_time_s = -1;
|
||||||
RTC_DATA_ATTR bool rtc_set = false;
|
RTC_DATA_ATTR bool rtc_set = false;
|
||||||
@@ -72,6 +77,13 @@ esp_err_t rtc_xtal_init(void) {
|
|||||||
|
|
||||||
void rtc_reset_shutdown_timer(void)
|
void rtc_reset_shutdown_timer(void)
|
||||||
{
|
{
|
||||||
|
/* Any genuine activity (HTTP/WS request, command, new association) flows
|
||||||
|
* through here. If we're soft-idle, that activity is the wake trigger —
|
||||||
|
* this is what lets an already-associated client reconnect and revive the
|
||||||
|
* device WITHOUT having to re-associate (which would be the only other
|
||||||
|
* wake signal). The HTTP server stays up during soft idle precisely so it
|
||||||
|
* can receive that reconnect and call us. */
|
||||||
|
if (in_soft_idle) soft_idle_exit();
|
||||||
last_activity_tick = xTaskGetTickCount();
|
last_activity_tick = xTaskGetTickCount();
|
||||||
rtc_wdt_feed();
|
rtc_wdt_feed();
|
||||||
}
|
}
|
||||||
@@ -80,10 +92,11 @@ void soft_idle_enter(void)
|
|||||||
{
|
{
|
||||||
if (in_soft_idle) return;
|
if (in_soft_idle) return;
|
||||||
in_soft_idle = true;
|
in_soft_idle = true;
|
||||||
ESP_LOGI("RTC", "Entering soft idle (WiFi/BT off, LEDs off)");
|
ESP_LOGI("RTC", "Entering soft idle (BT off, LEDs off, sensors off; WiFi AP + HTTP stay up for wake-on-request)");
|
||||||
webserver_stop();
|
|
||||||
bt_hid_stop();
|
bt_hid_stop();
|
||||||
i2c_set_led1(0);
|
i2c_set_led1(0);
|
||||||
|
/* Drop P10 to kill sensor rail power while we're asleep. */
|
||||||
|
i2c_relays_sleep();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool soft_idle_is_active(void) { return in_soft_idle; }
|
bool soft_idle_is_active(void) { return in_soft_idle; }
|
||||||
@@ -94,9 +107,90 @@ void soft_idle_exit(void)
|
|||||||
if (!in_soft_idle) return;
|
if (!in_soft_idle) return;
|
||||||
in_soft_idle = false;
|
in_soft_idle = false;
|
||||||
ESP_LOGI("RTC", "Exiting soft idle");
|
ESP_LOGI("RTC", "Exiting soft idle");
|
||||||
webserver_restart_wifi();
|
/* Bring sensor rail back before anything else tries to read sensors. */
|
||||||
|
i2c_relays_idle();
|
||||||
bt_hid_resume();
|
bt_hid_resume();
|
||||||
rtc_reset_shutdown_timer();
|
/* Reset the timer directly rather than via rtc_reset_shutdown_timer() —
|
||||||
|
* in_soft_idle is already false so it wouldn't re-enter, but being explicit
|
||||||
|
* keeps the wake path obvious and avoids any future recursion footgun. */
|
||||||
|
last_activity_tick = xTaskGetTickCount();
|
||||||
|
rtc_wdt_feed();
|
||||||
|
}
|
||||||
|
|
||||||
|
void hibernate_enter(void)
|
||||||
|
{
|
||||||
|
ESP_LOGI("RTC", "Entering hibernate (deep sleep, EXT0 button wake, RTC discarded)");
|
||||||
|
|
||||||
|
/* Reuse the soft-idle teardown:
|
||||||
|
* - sets in_soft_idle = true, which gates the main-task LED loop and
|
||||||
|
* the FSM's drive_relays() so neither overwrites our pre-sleep
|
||||||
|
* output state during the wait-for-button-release window;
|
||||||
|
* - stops webserver + BT;
|
||||||
|
* - drives LEDs to 0 and writes i2c_relays_sleep() (sensor rail off,
|
||||||
|
* all bridges off). */
|
||||||
|
soft_idle_enter();
|
||||||
|
|
||||||
|
/* Discard saved RTC time so the next boot comes up with rtc_set=false.
|
||||||
|
* RTC slow memory keeps its contents across deep sleep on ESP32 (the
|
||||||
|
* RTC clock and slow-mem domain stay alive for EXT0 to work), so we
|
||||||
|
* also zero the RTC_DATA_ATTR globals here. Together with the NVS
|
||||||
|
* erase, this guarantees the next boot has no surviving time state. */
|
||||||
|
nvs_handle_t h;
|
||||||
|
if (nvs_open(RTC_NVS_NAMESPACE, NVS_READWRITE, &h) == ESP_OK) {
|
||||||
|
nvs_erase_key(h, RTC_NVS_KEY);
|
||||||
|
nvs_commit(h);
|
||||||
|
nvs_close(h);
|
||||||
|
}
|
||||||
|
rtc_set = false;
|
||||||
|
sync_unix_us = 0;
|
||||||
|
sync_rtc_us = 0;
|
||||||
|
next_alarm_time_s = -1;
|
||||||
|
|
||||||
|
/* If the operator is still pressing the button (web-UI path: they
|
||||||
|
* shouldn't be; cmd-line path: maybe), wait for release. The button
|
||||||
|
* is on NCA9535 P00; GPIO13 is the chip's INT line, which only
|
||||||
|
* pulses low on input changes — so we MUST read the actual button
|
||||||
|
* state via I2C, not the GPIO level. Capped so we don't loop forever. */
|
||||||
|
int waited_ms = 0;
|
||||||
|
while (i2c_button_held_raw(0) && waited_ms < 5000) {
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(50));
|
||||||
|
waited_ms += 50;
|
||||||
|
}
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(100));
|
||||||
|
|
||||||
|
/* Final TCA9555/NCA9535 write right before we halt the CPU — covers
|
||||||
|
* any stale-state edge cases (e.g. a write that snuck in before the
|
||||||
|
* FSM gate latched). */
|
||||||
|
i2c_set_led1(0);
|
||||||
|
i2c_relays_sleep();
|
||||||
|
|
||||||
|
/* Read NCA9535 INPUT0 to clear any pending INT (which would hold
|
||||||
|
* GPIO13 low and instantly satisfy our EXT0 wake-on-zero condition).
|
||||||
|
* i2c_button_held_raw() does the read as a side effect. */
|
||||||
|
(void)i2c_button_held_raw(0);
|
||||||
|
|
||||||
|
/* Clear any lingering wake sources from earlier configuration before
|
||||||
|
* enabling the only one we want. */
|
||||||
|
esp_sleep_disable_wakeup_source(ESP_SLEEP_WAKEUP_ALL);
|
||||||
|
|
||||||
|
/* GPIO13 carries the NCA9535 INT line (open-drain, asserts low on any
|
||||||
|
* input change, clears on INPUT0 read). EXT0 wake on level=0 fires
|
||||||
|
* the moment the user presses the button — INT pulls low, ESP wakes,
|
||||||
|
* the post-boot i2c_poll_buttons() read clears INT. */
|
||||||
|
esp_sleep_enable_ext0_wakeup(PIN_BTN_INTERRUPT, 0);
|
||||||
|
rtc_gpio_pullup_en(PIN_BTN_INTERRUPT);
|
||||||
|
rtc_gpio_pulldown_dis(PIN_BTN_INTERRUPT);
|
||||||
|
|
||||||
|
/* Note: we deliberately do NOT call esp_sleep_pd_config(... OFF). On
|
||||||
|
* ESP-IDF v5.3 that path is reference-counted and asserts when the
|
||||||
|
* counter would go negative; since nothing has previously called ON
|
||||||
|
* for these domains, OFF would abort. ESP-IDF picks the deepest
|
||||||
|
* compatible power state automatically given the wake source we set. */
|
||||||
|
|
||||||
|
ESP_LOGI("RTC", "esp_deep_sleep_start int_level=%d btn=%d",
|
||||||
|
gpio_get_level(PIN_BTN_INTERRUPT),
|
||||||
|
(int)i2c_button_held_raw(0));
|
||||||
|
esp_deep_sleep_start(); /* never returns */
|
||||||
}
|
}
|
||||||
|
|
||||||
int64_t rtc_get_s(void)
|
int64_t rtc_get_s(void)
|
||||||
@@ -129,25 +223,44 @@ void rtc_set_s(int64_t tv_sec)
|
|||||||
|
|
||||||
void rtc_save_time(void)
|
void rtc_save_time(void)
|
||||||
{
|
{
|
||||||
// No-op: time is always derivable from sync_unix_us + rtc_hw_time_us() delta,
|
if (!rtc_set) return;
|
||||||
// both of which survive deep sleep and crashes via RTC_DATA_ATTR / RTC hardware.
|
int64_t now = rtc_get_s();
|
||||||
|
nvs_handle_t h;
|
||||||
|
if (nvs_open(RTC_NVS_NAMESPACE, NVS_READWRITE, &h) == ESP_OK) {
|
||||||
|
nvs_set_i64(h, RTC_NVS_KEY, now);
|
||||||
|
nvs_commit(h);
|
||||||
|
nvs_close(h);
|
||||||
|
ESP_LOGI("RTC", "Saved time to NVS: %lld", (long long)now);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void rtc_restore_time(void)
|
void rtc_restore_time(void)
|
||||||
{
|
{
|
||||||
if (!rtc_set) return;
|
// Try RTC_DATA_ATTR first (survives SW reset if RTC memory is intact)
|
||||||
// Recover time via RTC hardware counter (survives panics/WDT resets via RTC domain).
|
if (rtc_set) {
|
||||||
// RC drift during a <30s crash restart is ~1.5s worst case — acceptable.
|
|
||||||
int64_t t = (sync_unix_us + (int64_t)(rtc_hw_time_us() - sync_rtc_us)) / 1000000LL;
|
int64_t t = (sync_unix_us + (int64_t)(rtc_hw_time_us() - sync_rtc_us)) / 1000000LL;
|
||||||
// Anchor esp_timer tracking to recovered time — APB timer resets on every boot.
|
|
||||||
sync_unix_us = t * 1000000LL;
|
sync_unix_us = t * 1000000LL;
|
||||||
sync_esp_us = (uint64_t)esp_timer_get_time();
|
sync_esp_us = (uint64_t)esp_timer_get_time();
|
||||||
// Re-sync the stdlib clock (gettimeofday) for gmtime_r() etc.
|
|
||||||
settimeofday(&(struct timeval){.tv_sec = t, .tv_usec = 0}, NULL);
|
settimeofday(&(struct timeval){.tv_sec = t, .tv_usec = 0}, NULL);
|
||||||
|
ESP_LOGI("RTC", "TIME unix=%lld src=RTC_MEM uptime=%llds",
|
||||||
|
(long long)t, (long long)(esp_timer_get_time() / 1000000ULL));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
ESP_LOGI("RTC", "TIME unix=%lld src=CRASH uptime=%llds",
|
// Fall back to NVS (survives any reset type)
|
||||||
(long long)t,
|
nvs_handle_t h;
|
||||||
(long long)(esp_timer_get_time() / 1000000ULL));
|
int64_t saved = 0;
|
||||||
|
if (nvs_open(RTC_NVS_NAMESPACE, NVS_READONLY, &h) == ESP_OK) {
|
||||||
|
if (nvs_get_i64(h, RTC_NVS_KEY, &saved) == ESP_OK && saved > 0) {
|
||||||
|
nvs_close(h);
|
||||||
|
// Time will be slightly stale (by the reboot duration), but close enough
|
||||||
|
rtc_set_s(saved);
|
||||||
|
ESP_LOGI("RTC", "TIME unix=%lld src=NVS uptime=%llds",
|
||||||
|
(long long)saved, (long long)(esp_timer_get_time() / 1000000ULL));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
nvs_close(h);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int64_t rtc_get_ms(void)
|
int64_t rtc_get_ms(void)
|
||||||
@@ -169,9 +282,11 @@ void rtc_check_shutdown_timer(void)
|
|||||||
{
|
{
|
||||||
// Unsigned subtraction handles TickType_t (uint32_t) wraparound correctly:
|
// Unsigned subtraction handles TickType_t (uint32_t) wraparound correctly:
|
||||||
// e.g. if tick wrapped from 0xFFFFFFFE to 5, elapsed = 5 - 0xFFFFFFFE = 7.
|
// e.g. if tick wrapped from 0xFFFFFFFE to 5, elapsed = 5 - 0xFFFFFFFE = 7.
|
||||||
// At 1ms/tick, uint32_t wraps after ~49.7 days — well beyond the 180s timeout.
|
// At 1ms/tick, uint32_t wraps after ~49.7 days — well beyond any reasonable timeout.
|
||||||
TickType_t elapsed = xTaskGetTickCount() - last_activity_tick;
|
TickType_t elapsed = xTaskGetTickCount() - last_activity_tick;
|
||||||
if (elapsed * portTICK_PERIOD_MS >= POWER_INACTIVITY_TIMEOUT_MS)
|
uint32_t timeout_ms = get_param_value_t(PARAM_INACTIVITY_TIMEOUT_S).u32 * 1000u;
|
||||||
|
if (timeout_ms == 0) timeout_ms = POWER_INACTIVITY_TIMEOUT_MS; // guard against zero
|
||||||
|
if (elapsed * portTICK_PERIOD_MS >= timeout_ms)
|
||||||
soft_idle_enter();
|
soft_idle_enter();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -204,62 +319,50 @@ void adjust_rtc_min(char *key, int8_t dir)
|
|||||||
|
|
||||||
|
|
||||||
void rtc_schedule_next_alarm(void) {
|
void rtc_schedule_next_alarm(void) {
|
||||||
int64_t start_sec = get_param_value_t(PARAM_MOVE_START).u32;
|
/* Walk MOVE_TIME_00..MOVE_TIME_(NUM_MOVE_TIMES-1). Each slot is either
|
||||||
int64_t end_sec = get_param_value_t(PARAM_MOVE_END).u32;
|
* -1 (disabled) or a 0..86399 seconds-into-day offset. For each enabled
|
||||||
int16_t num = get_param_value_t(PARAM_NUM_MOVES).i16;
|
* slot we compute its absolute Unix time for today and tomorrow, keep
|
||||||
|
* whichever is the soonest still-future timestamp, and take the minimum
|
||||||
|
* across all enabled slots. If no slot is enabled, the device has no
|
||||||
|
* schedule and next_alarm_time_s is set to -1 (web UI renders DISABLED).
|
||||||
|
*
|
||||||
|
* The slots are sorted by commit_params() so the first non-negative is
|
||||||
|
* the smallest seconds-into-day, but we DON'T short-circuit on the first
|
||||||
|
* future hit — slot[0]'s "today" can already be past while a later slot
|
||||||
|
* (smaller offset that wrapped) could be the soonest. Walking all 12 is
|
||||||
|
* cheap (~1µs) and removes that subtlety entirely. */
|
||||||
|
|
||||||
if (num <= 0) {
|
if (!rtc_is_set()) {
|
||||||
|
/* Without a valid wall clock, "seconds into day" is meaningless. */
|
||||||
next_alarm_time_s = -1;
|
next_alarm_time_s = -1;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Current time info
|
|
||||||
int64_t s_into_day = rtc_get_s_in_day();
|
int64_t s_into_day = rtc_get_s_in_day();
|
||||||
time_t current_time = rtc_get_s();
|
time_t current_time = rtc_get_s();
|
||||||
time_t today_midnight = current_time - s_into_day;
|
time_t today_midnight = current_time - s_into_day;
|
||||||
|
|
||||||
bool overnight = (start_sec > end_sec);
|
time_t best = -1;
|
||||||
int64_t total_duration = overnight ? (86400 - start_sec) + end_sec : end_sec - start_sec;
|
for (int i = 0; i < NUM_MOVE_TIMES; i++) {
|
||||||
|
int32_t slot = get_param_value_t(PARAM_MOVE_TIME_00 + i).i32;
|
||||||
|
if (slot < 0) continue; // disabled
|
||||||
|
|
||||||
// Determine period start
|
/* Candidate is today's occurrence if still in the future, else
|
||||||
time_t period_start;
|
* tomorrow's occurrence. >= keeps a "fire exactly at the matching
|
||||||
if (overnight && s_into_day < end_sec) {
|
* second" semantic without arming twice. */
|
||||||
// Current time is within overnight period → started yesterday
|
time_t candidate = today_midnight + slot;
|
||||||
period_start = (today_midnight - 86400) + start_sec;
|
if (candidate <= current_time) candidate += 86400;
|
||||||
|
|
||||||
|
if (best < 0 || candidate < best) best = candidate;
|
||||||
|
}
|
||||||
|
|
||||||
|
next_alarm_time_s = best;
|
||||||
|
|
||||||
|
if (best > 0) {
|
||||||
|
ESP_LOGI("ALARM", "SET FOR %lld (in %lld s)", (long long)best, (long long)(best - current_time));
|
||||||
} else {
|
} else {
|
||||||
// Normal or after end → starts today
|
ESP_LOGI("ALARM", "No enabled MOVE_TIME_* slots — schedule disabled");
|
||||||
period_start = today_midnight + start_sec;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//time_t period_end = period_start + total_duration;
|
|
||||||
|
|
||||||
if (num == 1) {
|
|
||||||
// Single alarm: at period start, if passed, next day
|
|
||||||
next_alarm_time_s = (current_time < period_start) ? period_start : period_start + 86400;
|
|
||||||
ESP_LOGI("ALARM", "SET FOR %lld (in %lld s)", next_alarm_time_s, next_alarm_time_s - current_time);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find next alarm
|
|
||||||
int64_t spacing = total_duration / (num - 1);
|
|
||||||
time_t next_alarm = -1;
|
|
||||||
|
|
||||||
for (int16_t i = 0; i < num; i++) {
|
|
||||||
time_t alarm_time = period_start + spacing * i;
|
|
||||||
if (alarm_time > current_time) {
|
|
||||||
next_alarm = alarm_time;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If all passed, first of next period
|
|
||||||
if (next_alarm == -1) {
|
|
||||||
next_alarm = period_start + 86400;
|
|
||||||
}
|
|
||||||
|
|
||||||
next_alarm_time_s = next_alarm;
|
|
||||||
|
|
||||||
ESP_LOGI("ALARM", "SET FOR %lld (in %lld s)", next_alarm_time_s, next_alarm_time_s - current_time);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int64_t rtc_get_next_alarm_s() {
|
int64_t rtc_get_next_alarm_s() {
|
||||||
|
|||||||
13
main/rtc.h
13
main/rtc.h
@@ -19,7 +19,8 @@
|
|||||||
#include <time.h>
|
#include <time.h>
|
||||||
|
|
||||||
|
|
||||||
#define POWER_INACTIVITY_TIMEOUT_MS 180000
|
/* Fallback only — runtime value comes from PARAM_INACTIVITY_TIMEOUT_S in storage. */
|
||||||
|
#define POWER_INACTIVITY_TIMEOUT_MS 300000
|
||||||
|
|
||||||
/* -------------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------------- */
|
||||||
/* Public API */
|
/* Public API */
|
||||||
@@ -37,6 +38,12 @@ void soft_idle_exit(void);
|
|||||||
bool soft_idle_is_active(void);
|
bool soft_idle_is_active(void);
|
||||||
bool soft_idle_button_raw(void); /* direct GPIO read, no I2C */
|
bool soft_idle_button_raw(void); /* direct GPIO read, no I2C */
|
||||||
|
|
||||||
|
/* Deepest practical sleep with button wake (EXT0 on PIN_BTN_INTERRUPT).
|
||||||
|
* Discards the saved RTC time (NVS entry erased) — wake boots cold. RTC
|
||||||
|
* fast/slow memory are powered down too, so RTC_DATA_ATTR globals are lost.
|
||||||
|
* Does not return. */
|
||||||
|
void hibernate_enter(void);
|
||||||
|
|
||||||
/*void adjust_rtc_hour(char *key, int8_t dir);
|
/*void adjust_rtc_hour(char *key, int8_t dir);
|
||||||
void adjust_rtc_min(char *key, int8_t dir);*/
|
void adjust_rtc_min(char *key, int8_t dir);*/
|
||||||
|
|
||||||
@@ -44,8 +51,8 @@ int64_t rtc_get_s (void);
|
|||||||
int64_t rtc_get_ms(void);
|
int64_t rtc_get_ms(void);
|
||||||
void rtc_set_s(int64_t);
|
void rtc_set_s(int64_t);
|
||||||
|
|
||||||
void rtc_save_time(void); // No-op: time is always live via rtc_get_s()
|
void rtc_save_time(void); // Writes current time to NVS (call before esp_restart)
|
||||||
void rtc_restore_time(void); // Re-syncs stdlib clock on boot; emits TIME log marker
|
void rtc_restore_time(void); // Recovers time from RTC memory or NVS; requires NVS init first
|
||||||
|
|
||||||
void rtc_schedule_next_alarm(void);
|
void rtc_schedule_next_alarm(void);
|
||||||
int64_t rtc_get_next_alarm_s();
|
int64_t rtc_get_next_alarm_s();
|
||||||
|
|||||||
@@ -22,5 +22,12 @@
|
|||||||
#define SC_ERR_RTC_NOT_SET 0x220
|
#define SC_ERR_RTC_NOT_SET 0x220
|
||||||
#define SC_ERR_LOW_BATTERY 0x230
|
#define SC_ERR_LOW_BATTERY 0x230
|
||||||
|
|
||||||
|
#include "esp_err.h"
|
||||||
|
|
||||||
|
/* Human-readable name for an SC_ERR_* code (or ESP_OK). Used by FSM log
|
||||||
|
* messages and the web UI so error strings live in one place. Returns a
|
||||||
|
* literal — no allocation, safe to use anywhere. */
|
||||||
|
const char *sc_err_str(esp_err_t e);
|
||||||
|
|
||||||
|
|
||||||
#endif /* MAIN_SC_ERR_H_ */
|
#endif /* MAIN_SC_ERR_H_ */
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
#include "sensors.h"
|
#include "sensors.h"
|
||||||
|
#include "board_config.h"
|
||||||
#include "esp_log.h"
|
#include "esp_log.h"
|
||||||
#include "esp_task_wdt.h"
|
#include "esp_task_wdt.h"
|
||||||
#include "driver/gpio.h"
|
#include "driver/gpio.h"
|
||||||
@@ -15,9 +16,22 @@
|
|||||||
|
|
||||||
static const char* TAG = "SENS";
|
static const char* TAG = "SENS";
|
||||||
|
|
||||||
|
#ifdef BOARD_V5
|
||||||
|
// V5 physical connectors:
|
||||||
|
// J1 = IO27 → SAFETY
|
||||||
|
// J2 = IO14 → JACK
|
||||||
|
// J3 = IO23 → n/c (AUX) (J3 unreliable on the V5 board, moved DRIVE off)
|
||||||
|
// J4 = IO19 → DRIVE
|
||||||
|
// Array order matches sensor_t: SAFETY, DRIVE, JACK, AUX2
|
||||||
|
uint8_t sensor_pins[N_SENSORS] = {GPIO_NUM_27, GPIO_NUM_19, GPIO_NUM_14, GPIO_NUM_23};
|
||||||
|
#else // BOARD_V4
|
||||||
uint8_t sensor_pins[N_SENSORS] = {GPIO_NUM_27, GPIO_NUM_14, GPIO_NUM_16, GPIO_NUM_19};
|
uint8_t sensor_pins[N_SENSORS] = {GPIO_NUM_27, GPIO_NUM_14, GPIO_NUM_16, GPIO_NUM_19};
|
||||||
|
#endif
|
||||||
|
|
||||||
volatile int16_t sensor_count[N_SENSORS] = {0};
|
volatile int16_t sensor_count[N_SENSORS] = {0};
|
||||||
|
/* Bumped directly in the ISR on every edge — does not require sensors_check()
|
||||||
|
* to run, so it works even while bring-up pauses the FSM task. */
|
||||||
|
volatile uint32_t sensor_isr_edge_count[N_SENSORS] = {0};
|
||||||
static volatile uint64_t sensor_last_isr_time[N_SENSORS] = {0};
|
static volatile uint64_t sensor_last_isr_time[N_SENSORS] = {0};
|
||||||
static volatile bool sensor_stable_state[N_SENSORS] = {false};
|
static volatile bool sensor_stable_state[N_SENSORS] = {false};
|
||||||
static QueueHandle_t sensor_event_queue = NULL;
|
static QueueHandle_t sensor_event_queue = NULL;
|
||||||
@@ -52,6 +66,8 @@ static void IRAM_ATTR sensor_isr_handler(void* arg) {
|
|||||||
uint64_t now = esp_timer_get_time();
|
uint64_t now = esp_timer_get_time();
|
||||||
sensor_last_isr_time[i] = now;
|
sensor_last_isr_time[i] = now;
|
||||||
|
|
||||||
|
sensor_isr_edge_count[i]++;
|
||||||
|
|
||||||
sensor_event_t evt = {.sensor_id = i, .level = !gpio_get_level(gpio_num)};
|
sensor_event_t evt = {.sensor_id = i, .level = !gpio_get_level(gpio_num)};
|
||||||
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
|
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
|
||||||
xQueueSendFromISR(sensor_event_queue, &evt, &xHigherPriorityTaskWoken);
|
xQueueSendFromISR(sensor_event_queue, &evt, &xHigherPriorityTaskWoken);
|
||||||
@@ -60,8 +76,21 @@ static void IRAM_ATTR sensor_isr_handler(void* arg) {
|
|||||||
|
|
||||||
esp_err_t sensors_init() {
|
esp_err_t sensors_init() {
|
||||||
|
|
||||||
|
uint64_t pin_mask = 0;
|
||||||
|
for (uint8_t i = 0; i < N_SENSORS; i++) pin_mask |= (1ULL << sensor_pins[i]);
|
||||||
|
|
||||||
|
/* Belt-and-suspenders: force each sensor pin into digital-GPIO mode with
|
||||||
|
* pull-up explicitly applied. gpio_config()'s pull_up_en is known to be
|
||||||
|
* shadowed by RTC-subsystem settings on RTC-capable pins (IO27, 32, 33,
|
||||||
|
* 34–39). gpio_reset_pin() detaches any lingering RTC/peripheral mux,
|
||||||
|
* and the explicit gpio_set_pull_mode() call goes through the right
|
||||||
|
* path regardless of which sub-block owns the pin. */
|
||||||
|
for (uint8_t i = 0; i < N_SENSORS; i++) {
|
||||||
|
gpio_reset_pin(sensor_pins[i]);
|
||||||
|
}
|
||||||
|
|
||||||
gpio_config_t io_conf = {
|
gpio_config_t io_conf = {
|
||||||
.pin_bit_mask = (1ULL << sensor_pins[0]) | (1ULL << sensor_pins[1]),
|
.pin_bit_mask = pin_mask,
|
||||||
.mode = GPIO_MODE_INPUT,
|
.mode = GPIO_MODE_INPUT,
|
||||||
.pull_up_en = GPIO_PULLUP_ENABLE,
|
.pull_up_en = GPIO_PULLUP_ENABLE,
|
||||||
.pull_down_en = GPIO_PULLDOWN_DISABLE,
|
.pull_down_en = GPIO_PULLDOWN_DISABLE,
|
||||||
@@ -69,6 +98,10 @@ esp_err_t sensors_init() {
|
|||||||
};
|
};
|
||||||
ESP_ERROR_CHECK(gpio_config(&io_conf));
|
ESP_ERROR_CHECK(gpio_config(&io_conf));
|
||||||
|
|
||||||
|
for (uint8_t i = 0; i < N_SENSORS; i++) {
|
||||||
|
ESP_ERROR_CHECK(gpio_set_pull_mode(sensor_pins[i], GPIO_PULLUP_ONLY));
|
||||||
|
}
|
||||||
|
|
||||||
sensor_event_queue = xQueueCreate(16, sizeof(sensor_event_t));
|
sensor_event_queue = xQueueCreate(16, sizeof(sensor_event_t));
|
||||||
if (!sensor_event_queue) {
|
if (!sensor_event_queue) {
|
||||||
ESP_LOGE(TAG, "Failed to create sensor queue");
|
ESP_LOGE(TAG, "Failed to create sensor queue");
|
||||||
@@ -107,28 +140,36 @@ esp_err_t sensors_init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void sensors_check() {
|
void sensors_check() {
|
||||||
|
/* Drain ALL queued events with a non-blocking receive. Previously this
|
||||||
|
* was a single blocking receive with a 10 ms timeout, which (a) chewed up
|
||||||
|
* half the FSM tick window waiting on quiet sensors, and (b) consumed
|
||||||
|
* only one edge per tick — encoder bursts above 50 Hz overflowed the
|
||||||
|
* 16-entry queue and undercounted distance. Now each tick consumes the
|
||||||
|
* full queue contents and applies a per-sensor debounce. */
|
||||||
sensor_event_t evt;
|
sensor_event_t evt;
|
||||||
|
static uint64_t last_counted_us[N_SENSORS] = {0};
|
||||||
|
while (xQueueReceive(sensor_event_queue, &evt, 0) == pdTRUE) {
|
||||||
|
uint8_t i = evt.sensor_id;
|
||||||
|
if (i >= N_SENSORS) continue;
|
||||||
|
|
||||||
uint8_t i = 0;
|
/* Use the ISR-captured level (snapshot at edge time) rather than a
|
||||||
|
* fresh GPIO read here — by the time the FSM tick drains the queue,
|
||||||
|
* the line may have toggled again and a re-read would miss the
|
||||||
|
* transition we're processing. */
|
||||||
|
bool current_raw = evt.level;
|
||||||
|
|
||||||
if (xQueueReceive(sensor_event_queue, &evt, pdMS_TO_TICKS(10)) == pdTRUE) {
|
/* Software debounce on non-safety sensors. The safety sensor has its
|
||||||
i = evt.sensor_id;
|
* own asymmetric debouncer below, so we don't double-count there. */
|
||||||
|
uint64_t now = esp_timer_get_time();
|
||||||
ESP_LOGI("SENS", "EVENT %d", i);
|
if (i != SENSOR_SAFETY) {
|
||||||
|
if (now - last_counted_us[i] < DEBOUNCE_TIME_US) continue;
|
||||||
bool current_raw = !gpio_get_level(sensor_pins[i]);
|
}
|
||||||
|
last_counted_us[i] = now;
|
||||||
|
|
||||||
sensor_stable_state[i] = current_raw;
|
sensor_stable_state[i] = current_raw;
|
||||||
|
if (current_raw != last_raw_state[i]) {
|
||||||
if (current_raw && !last_raw_state[i]){
|
|
||||||
ESP_LOGI("SENS", "FALLING");
|
|
||||||
sensor_count[i]++;
|
sensor_count[i]++;
|
||||||
}
|
}
|
||||||
if (!current_raw && last_raw_state[i]){
|
|
||||||
ESP_LOGI("SENS", "RISING");
|
|
||||||
sensor_count[i]++;
|
|
||||||
}
|
|
||||||
|
|
||||||
last_raw_state[i] = current_raw;
|
last_raw_state[i] = current_raw;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,7 +197,9 @@ void sensors_check() {
|
|||||||
ESP_LOGI(TAG, "Safety sensor went HIGH, starting break timer");
|
ESP_LOGI(TAG, "Safety sensor went HIGH, starting break timer");
|
||||||
} else if (is_safe && (now - safety_high_start_time >= SAFETY_BREAK_DEBOUNCE_US)) {
|
} else if (is_safe && (now - safety_high_start_time >= SAFETY_BREAK_DEBOUNCE_US)) {
|
||||||
is_safe = false;
|
is_safe = false;
|
||||||
i2c_set_relays((relay_port_t){.raw=0});
|
/* Kill all bridges but leave the sensor rail up — we still
|
||||||
|
* want to observe the safety input. */
|
||||||
|
i2c_relays_idle();
|
||||||
ESP_LOGI(TAG, "SAFETY BREAK - Relays disabled");
|
ESP_LOGI(TAG, "SAFETY BREAK - Relays disabled");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -179,8 +222,7 @@ bool get_sensor(sensor_t i) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool get_is_safe(void) {
|
bool get_is_safe(void) {
|
||||||
return true;
|
return is_safe;
|
||||||
//return is_safe;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int16_t get_sensor_counter(sensor_t i) {
|
int16_t get_sensor_counter(sensor_t i) {
|
||||||
@@ -190,3 +232,8 @@ int16_t get_sensor_counter(sensor_t i) {
|
|||||||
void set_sensor_counter(sensor_t i, int16_t to) {
|
void set_sensor_counter(sensor_t i, int16_t to) {
|
||||||
sensor_count[i] = to;
|
sensor_count[i] = to;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint32_t get_sensor_isr_edges(sensor_t i) {
|
||||||
|
if (i >= N_SENSORS) return 0;
|
||||||
|
return sensor_isr_edge_count[i];
|
||||||
|
}
|
||||||
@@ -17,9 +17,9 @@
|
|||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
SENSOR_SAFETY = 0, // IO27
|
SENSOR_SAFETY = 0, // IO27
|
||||||
SENSOR_JACK = 1, // IO14
|
SENSOR_DRIVE = 1, // IO14
|
||||||
SENSOR_DRIVE = 2, // IO16 on V4
|
SENSOR_JACK = 2, // IO16
|
||||||
SENSOR_AUX2 = 3, // IO19 on V4
|
SENSOR_AUX2 = 3, // IO19
|
||||||
N_SENSORS = 4
|
N_SENSORS = 4
|
||||||
} sensor_t;
|
} sensor_t;
|
||||||
|
|
||||||
@@ -27,6 +27,11 @@ void reset_sensor_counter(sensor_t i);
|
|||||||
void set_sensor_counter(sensor_t i, int16_t to);
|
void set_sensor_counter(sensor_t i, int16_t to);
|
||||||
int16_t get_sensor_counter(sensor_t i);
|
int16_t get_sensor_counter(sensor_t i);
|
||||||
|
|
||||||
|
/* Raw ISR-level edge count. Bumped on every GPIO transition by the
|
||||||
|
* sensor ISR regardless of whether sensors_check() is running; safe to
|
||||||
|
* read during bring-up while the FSM task is paused. */
|
||||||
|
uint32_t get_sensor_isr_edges(sensor_t i);
|
||||||
|
|
||||||
bool get_sensor(sensor_t i);
|
bool get_sensor(sensor_t i);
|
||||||
bool get_is_safe(void);
|
bool get_is_safe(void);
|
||||||
|
|
||||||
|
|||||||
11
main/solar.c
11
main/solar.c
@@ -37,7 +37,11 @@ esp_err_t solar_run_fsm() {
|
|||||||
|
|
||||||
int64_t now = rtc_get_ms();
|
int64_t now = rtc_get_ms();
|
||||||
|
|
||||||
//ESP_LOGI("BAT", "FSM STATE %d", current_charge_state);
|
/* `now` is in milliseconds; CHG_BULK_S / CHG_LOW_S are in seconds.
|
||||||
|
* Scale the parameter to ms before comparing. Initialize the timer on
|
||||||
|
* first run rather than leaving it at -1 (which would otherwise make
|
||||||
|
* `now > timer + threshold` true after just `threshold` ms). */
|
||||||
|
if (timer < 0) timer = now;
|
||||||
|
|
||||||
float vbat = get_battery_V();
|
float vbat = get_battery_V();
|
||||||
|
|
||||||
@@ -47,10 +51,9 @@ esp_err_t solar_run_fsm() {
|
|||||||
- After a period of time in bulk, switch to float
|
- After a period of time in bulk, switch to float
|
||||||
*/
|
*/
|
||||||
|
|
||||||
//if (rtc_is_set()) {
|
|
||||||
switch(current_charge_state) {
|
switch(current_charge_state) {
|
||||||
case CHG_STATE_BULK:
|
case CHG_STATE_BULK:
|
||||||
if (now > timer+get_param_value_t(PARAM_CHG_BULK_S).u32) {
|
if (now > timer + (int64_t)get_param_value_t(PARAM_CHG_BULK_S).u32 * 1000) {
|
||||||
current_charge_state = CHG_STATE_FLOAT;
|
current_charge_state = CHG_STATE_FLOAT;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,7 +64,7 @@ esp_err_t solar_run_fsm() {
|
|||||||
timer = now;
|
timer = now;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (now > timer+get_param_value_t(PARAM_CHG_LOW_S).u32) {
|
if (now > timer + (int64_t)get_param_value_t(PARAM_CHG_LOW_S).u32 * 1000) {
|
||||||
timer = now;
|
timer = now;
|
||||||
current_charge_state = CHG_STATE_BULK;
|
current_charge_state = CHG_STATE_BULK;
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user