mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2026-05-09 07:25:49 +00:00
@@ -33,6 +33,8 @@ For more details go to [docs.emsesp.org](https://docs.emsesp.org/).
|
||||
- pumpmode enum for HT3 boilers, add commands for manual defrost, chimneysweeper [#2727](https://github.com/emsesp/EMS-ESP32/issues/2727)
|
||||
- pid settings [#2735](https://github.com/emsesp/EMS-ESP32/issues/2735)
|
||||
- refresh MQTT button added to MQTT Settings page
|
||||
- heating assistance, rounding custum settings [#2763](https://github.com/emsesp/EMS-ESP32/discussions/2763)
|
||||
- add counter 0..2 for short pulses, high frequency [#2758](https://github.com/emsesp/EMS-ESP32/issues/2758)
|
||||
- added LWT (Last Will and Testament) to MQTT entities in Home Assistant
|
||||
- added api/metrics endpoint for prometheus integration by @gr3enk
|
||||
[#2774](https://github.com/emsesp/EMS-ESP32/pull/2774)
|
||||
|
||||
@@ -182,9 +182,9 @@
|
||||
| dhw.comfoff | comfort switch off | uint8 (>=15<=65) | C | true | DHW | 18 | 1 | 1 |
|
||||
| dhw.ecooff | eco switch off | uint8 (>=15<=65) | C | true | DHW | 19 | 1 | 1 |
|
||||
| dhw.ecoplusoff | eco+ switch off | uint8 (>=48<=63) | C | true | DHW | 20 | 1 | 1 |
|
||||
| dhw.comfdiff | comfort diff | uint8 (>=4<=12) | K | true | DHW | 21 | 1 | 1 |
|
||||
| dhw.ecodiff | eco diff | uint8 (>=4<=12) | K | true | DHW | 22 | 1 | 1 |
|
||||
| dhw.ecoplusdiff | eco+ diff | uint8 (>=6<=12) | K | true | DHW | 23 | 1 | 1 |
|
||||
| dhw.comfdiff | comfort diff | uint8 (>=4<=15) | K | true | DHW | 21 | 1 | 1 |
|
||||
| dhw.ecodiff | eco diff | uint8 (>=4<=15) | K | true | DHW | 22 | 1 | 1 |
|
||||
| dhw.ecoplusdiff | eco+ diff | uint8 (>=4<=15) | K | true | DHW | 23 | 1 | 1 |
|
||||
| dhw.comfstop | comfort stop temp | uint8 (>=0<=254) | C | true | DHW | 24 | 1 | 1 |
|
||||
| dhw.ecostop | eco stop temp | uint8 (>=0<=254) | C | true | DHW | 25 | 1 | 1 |
|
||||
| dhw.ecoplusstop | eco+ stop temp | uint8 (>=0<=254) | C | true | DHW | 26 | 1 | 1 |
|
||||
@@ -2821,9 +2821,9 @@
|
||||
| dhw.comfoff | comfort switch off | uint8 (>=15<=65) | C | true | DHW | 18 | 1 | 1 |
|
||||
| dhw.ecooff | eco switch off | uint8 (>=15<=65) | C | true | DHW | 19 | 1 | 1 |
|
||||
| dhw.ecoplusoff | eco+ switch off | uint8 (>=48<=63) | C | true | DHW | 20 | 1 | 1 |
|
||||
| dhw.comfdiff | comfort diff | uint8 (>=4<=12) | K | true | DHW | 21 | 1 | 1 |
|
||||
| dhw.ecodiff | eco diff | uint8 (>=4<=12) | K | true | DHW | 22 | 1 | 1 |
|
||||
| dhw.ecoplusdiff | eco+ diff | uint8 (>=6<=12) | K | true | DHW | 23 | 1 | 1 |
|
||||
| dhw.comfdiff | comfort diff | uint8 (>=4<=15) | K | true | DHW | 21 | 1 | 1 |
|
||||
| dhw.ecodiff | eco diff | uint8 (>=4<=15) | K | true | DHW | 22 | 1 | 1 |
|
||||
| dhw.ecoplusdiff | eco+ diff | uint8 (>=4<=15) | K | true | DHW | 23 | 1 | 1 |
|
||||
| dhw.comfstop | comfort stop temp | uint8 (>=0<=254) | C | true | DHW | 24 | 1 | 1 |
|
||||
| dhw.ecostop | eco stop temp | uint8 (>=0<=254) | C | true | DHW | 25 | 1 | 1 |
|
||||
| dhw.ecoplusstop | eco+ stop temp | uint8 (>=0<=254) | C | true | DHW | 26 | 1 | 1 |
|
||||
@@ -3040,9 +3040,9 @@
|
||||
| dhw.comfoff | comfort switch off | uint8 (>=15<=65) | C | true | DHW | 18 | 1 | 1 |
|
||||
| dhw.ecooff | eco switch off | uint8 (>=15<=65) | C | true | DHW | 19 | 1 | 1 |
|
||||
| dhw.ecoplusoff | eco+ switch off | uint8 (>=48<=63) | C | true | DHW | 20 | 1 | 1 |
|
||||
| dhw.comfdiff | comfort diff | uint8 (>=4<=12) | K | true | DHW | 21 | 1 | 1 |
|
||||
| dhw.ecodiff | eco diff | uint8 (>=4<=12) | K | true | DHW | 22 | 1 | 1 |
|
||||
| dhw.ecoplusdiff | eco+ diff | uint8 (>=6<=12) | K | true | DHW | 23 | 1 | 1 |
|
||||
| dhw.comfdiff | comfort diff | uint8 (>=4<=15) | K | true | DHW | 21 | 1 | 1 |
|
||||
| dhw.ecodiff | eco diff | uint8 (>=4<=15) | K | true | DHW | 22 | 1 | 1 |
|
||||
| dhw.ecoplusdiff | eco+ diff | uint8 (>=4<=15) | K | true | DHW | 23 | 1 | 1 |
|
||||
| dhw.comfstop | comfort stop temp | uint8 (>=0<=254) | C | true | DHW | 24 | 1 | 1 |
|
||||
| dhw.ecostop | eco stop temp | uint8 (>=0<=254) | C | true | DHW | 25 | 1 | 1 |
|
||||
| dhw.ecoplusstop | eco+ stop temp | uint8 (>=0<=254) | C | true | DHW | 26 | 1 | 1 |
|
||||
@@ -5815,6 +5815,8 @@
|
||||
| heatcnt | heat counter impulses | uint8 (>=0<=254) | | false | DEVICE_DATA | 67 | 1 | 1 |
|
||||
| swapflowtemp | swap flow temperature (TS14) | uint16 (>=0<=3199) | C | false | DEVICE_DATA | 68 | 1 | 1/10 |
|
||||
| swaprettemp | swap return temperature (TS15) | uint16 (>=0<=3199) | C | false | DEVICE_DATA | 69 | 1 | 1/10 |
|
||||
| heatassiston | heat assistance on | int8 (>=-12<=12) | K | true | DEVICE_DATA | 70 | 1 | 1/10 |
|
||||
| heatassistoff | heat assistance off | int8 (>=-12<=12) | K | true | DEVICE_DATA | 71 | 1 | 1/10 |
|
||||
|
||||
### SM100, MS100
|
||||
| shortname | fullname | type | uom | writeable | tag type | register offset | register count | scale factor |
|
||||
@@ -5880,6 +5882,8 @@
|
||||
| heatcnt | heat counter impulses | uint8 (>=0<=254) | | false | DEVICE_DATA | 67 | 1 | 1 |
|
||||
| swapflowtemp | swap flow temperature (TS14) | uint16 (>=0<=3199) | C | false | DEVICE_DATA | 68 | 1 | 1/10 |
|
||||
| swaprettemp | swap return temperature (TS15) | uint16 (>=0<=3199) | C | false | DEVICE_DATA | 69 | 1 | 1/10 |
|
||||
| heatassiston | heat assistance on | int8 (>=-12<=12) | K | true | DEVICE_DATA | 70 | 1 | 1/10 |
|
||||
| heatassistoff | heat assistance off | int8 (>=-12<=12) | K | true | DEVICE_DATA | 71 | 1 | 1/10 |
|
||||
|
||||
### SM200, MS200
|
||||
| shortname | fullname | type | uom | writeable | tag type | register offset | register count | scale factor |
|
||||
@@ -5945,6 +5949,8 @@
|
||||
| heatcnt | heat counter impulses | uint8 (>=0<=254) | | false | DEVICE_DATA | 67 | 1 | 1 |
|
||||
| swapflowtemp | swap flow temperature (TS14) | uint16 (>=0<=3199) | C | false | DEVICE_DATA | 68 | 1 | 1/10 |
|
||||
| swaprettemp | swap return temperature (TS15) | uint16 (>=0<=3199) | C | false | DEVICE_DATA | 69 | 1 | 1/10 |
|
||||
| heatassiston | heat assistance on | int8 (>=-12<=12) | K | true | DEVICE_DATA | 70 | 1 | 1/10 |
|
||||
| heatassistoff | heat assistance off | int8 (>=-12<=12) | K | true | DEVICE_DATA | 71 | 1 | 1/10 |
|
||||
|
||||
## Devices of type *heatpump*
|
||||
### HP Module
|
||||
|
||||
@@ -166,9 +166,9 @@ device name,device type,product id,shortname,fullname,type [options...] \| (min/
|
||||
"CS5800i, CS6800i, WLW176i, WLW186i",boiler,8,dhw.comfoff,comfort switch off,uint8 (>=15<=65),C,true,number.boiler_dhw_comfort_switch_off,number.boiler_dhw_comfoff,5,9,1,18,1
|
||||
"CS5800i, CS6800i, WLW176i, WLW186i",boiler,8,dhw.ecooff,eco switch off,uint8 (>=15<=65),C,true,number.boiler_dhw_eco_switch_off,number.boiler_dhw_ecooff,5,9,1,19,1
|
||||
"CS5800i, CS6800i, WLW176i, WLW186i",boiler,8,dhw.ecoplusoff,eco+ switch off,uint8 (>=48<=63),C,true,number.boiler_dhw_eco+_switch_off,number.boiler_dhw_ecoplusoff,5,9,1,20,1
|
||||
"CS5800i, CS6800i, WLW176i, WLW186i",boiler,8,dhw.comfdiff,comfort diff,uint8 (>=4<=12),K,true,number.boiler_dhw_comfort_diff,number.boiler_dhw_comfdiff,5,9,1,21,1
|
||||
"CS5800i, CS6800i, WLW176i, WLW186i",boiler,8,dhw.ecodiff,eco diff,uint8 (>=4<=12),K,true,number.boiler_dhw_eco_diff,number.boiler_dhw_ecodiff,5,9,1,22,1
|
||||
"CS5800i, CS6800i, WLW176i, WLW186i",boiler,8,dhw.ecoplusdiff,eco+ diff,uint8 (>=6<=12),K,true,number.boiler_dhw_eco+_diff,number.boiler_dhw_ecoplusdiff,5,9,1,23,1
|
||||
"CS5800i, CS6800i, WLW176i, WLW186i",boiler,8,dhw.comfdiff,comfort diff,uint8 (>=4<=15),K,true,number.boiler_dhw_comfort_diff,number.boiler_dhw_comfdiff,5,9,1,21,1
|
||||
"CS5800i, CS6800i, WLW176i, WLW186i",boiler,8,dhw.ecodiff,eco diff,uint8 (>=4<=15),K,true,number.boiler_dhw_eco_diff,number.boiler_dhw_ecodiff,5,9,1,22,1
|
||||
"CS5800i, CS6800i, WLW176i, WLW186i",boiler,8,dhw.ecoplusdiff,eco+ diff,uint8 (>=4<=15),K,true,number.boiler_dhw_eco+_diff,number.boiler_dhw_ecoplusdiff,5,9,1,23,1
|
||||
"CS5800i, CS6800i, WLW176i, WLW186i",boiler,8,dhw.comfstop,comfort stop temp,uint8 (>=0<=254),C,true,number.boiler_dhw_comfort_stop_temp,number.boiler_dhw_comfstop,5,9,1,24,1
|
||||
"CS5800i, CS6800i, WLW176i, WLW186i",boiler,8,dhw.ecostop,eco stop temp,uint8 (>=0<=254),C,true,number.boiler_dhw_eco_stop_temp,number.boiler_dhw_ecostop,5,9,1,25,1
|
||||
"CS5800i, CS6800i, WLW176i, WLW186i",boiler,8,dhw.ecoplusstop,eco+ stop temp,uint8 (>=0<=254),C,true,number.boiler_dhw_eco+_stop_temp,number.boiler_dhw_ecoplusstop,5,9,1,26,1
|
||||
@@ -2721,9 +2721,9 @@ device name,device type,product id,shortname,fullname,type [options...] \| (min/
|
||||
"Enviline, Compress 6000AW, Hybrid 3000-7000iAW, SupraEco/Geo 5xx, WLW196i/WSW196i",boiler,172,dhw.comfoff,comfort switch off,uint8 (>=15<=65),C,true,number.boiler_dhw_comfort_switch_off,number.boiler_dhw_comfoff,5,9,1,18,1
|
||||
"Enviline, Compress 6000AW, Hybrid 3000-7000iAW, SupraEco/Geo 5xx, WLW196i/WSW196i",boiler,172,dhw.ecooff,eco switch off,uint8 (>=15<=65),C,true,number.boiler_dhw_eco_switch_off,number.boiler_dhw_ecooff,5,9,1,19,1
|
||||
"Enviline, Compress 6000AW, Hybrid 3000-7000iAW, SupraEco/Geo 5xx, WLW196i/WSW196i",boiler,172,dhw.ecoplusoff,eco+ switch off,uint8 (>=48<=63),C,true,number.boiler_dhw_eco+_switch_off,number.boiler_dhw_ecoplusoff,5,9,1,20,1
|
||||
"Enviline, Compress 6000AW, Hybrid 3000-7000iAW, SupraEco/Geo 5xx, WLW196i/WSW196i",boiler,172,dhw.comfdiff,comfort diff,uint8 (>=4<=12),K,true,number.boiler_dhw_comfort_diff,number.boiler_dhw_comfdiff,5,9,1,21,1
|
||||
"Enviline, Compress 6000AW, Hybrid 3000-7000iAW, SupraEco/Geo 5xx, WLW196i/WSW196i",boiler,172,dhw.ecodiff,eco diff,uint8 (>=4<=12),K,true,number.boiler_dhw_eco_diff,number.boiler_dhw_ecodiff,5,9,1,22,1
|
||||
"Enviline, Compress 6000AW, Hybrid 3000-7000iAW, SupraEco/Geo 5xx, WLW196i/WSW196i",boiler,172,dhw.ecoplusdiff,eco+ diff,uint8 (>=6<=12),K,true,number.boiler_dhw_eco+_diff,number.boiler_dhw_ecoplusdiff,5,9,1,23,1
|
||||
"Enviline, Compress 6000AW, Hybrid 3000-7000iAW, SupraEco/Geo 5xx, WLW196i/WSW196i",boiler,172,dhw.comfdiff,comfort diff,uint8 (>=4<=15),K,true,number.boiler_dhw_comfort_diff,number.boiler_dhw_comfdiff,5,9,1,21,1
|
||||
"Enviline, Compress 6000AW, Hybrid 3000-7000iAW, SupraEco/Geo 5xx, WLW196i/WSW196i",boiler,172,dhw.ecodiff,eco diff,uint8 (>=4<=15),K,true,number.boiler_dhw_eco_diff,number.boiler_dhw_ecodiff,5,9,1,22,1
|
||||
"Enviline, Compress 6000AW, Hybrid 3000-7000iAW, SupraEco/Geo 5xx, WLW196i/WSW196i",boiler,172,dhw.ecoplusdiff,eco+ diff,uint8 (>=4<=15),K,true,number.boiler_dhw_eco+_diff,number.boiler_dhw_ecoplusdiff,5,9,1,23,1
|
||||
"Enviline, Compress 6000AW, Hybrid 3000-7000iAW, SupraEco/Geo 5xx, WLW196i/WSW196i",boiler,172,dhw.comfstop,comfort stop temp,uint8 (>=0<=254),C,true,number.boiler_dhw_comfort_stop_temp,number.boiler_dhw_comfstop,5,9,1,24,1
|
||||
"Enviline, Compress 6000AW, Hybrid 3000-7000iAW, SupraEco/Geo 5xx, WLW196i/WSW196i",boiler,172,dhw.ecostop,eco stop temp,uint8 (>=0<=254),C,true,number.boiler_dhw_eco_stop_temp,number.boiler_dhw_ecostop,5,9,1,25,1
|
||||
"Enviline, Compress 6000AW, Hybrid 3000-7000iAW, SupraEco/Geo 5xx, WLW196i/WSW196i",boiler,172,dhw.ecoplusstop,eco+ stop temp,uint8 (>=0<=254),C,true,number.boiler_dhw_eco+_stop_temp,number.boiler_dhw_ecoplusstop,5,9,1,26,1
|
||||
@@ -2936,9 +2936,9 @@ device name,device type,product id,shortname,fullname,type [options...] \| (min/
|
||||
"Geo 5xx",boiler,173,dhw.comfoff,comfort switch off,uint8 (>=15<=65),C,true,number.boiler_dhw_comfort_switch_off,number.boiler_dhw_comfoff,5,9,1,18,1
|
||||
"Geo 5xx",boiler,173,dhw.ecooff,eco switch off,uint8 (>=15<=65),C,true,number.boiler_dhw_eco_switch_off,number.boiler_dhw_ecooff,5,9,1,19,1
|
||||
"Geo 5xx",boiler,173,dhw.ecoplusoff,eco+ switch off,uint8 (>=48<=63),C,true,number.boiler_dhw_eco+_switch_off,number.boiler_dhw_ecoplusoff,5,9,1,20,1
|
||||
"Geo 5xx",boiler,173,dhw.comfdiff,comfort diff,uint8 (>=4<=12),K,true,number.boiler_dhw_comfort_diff,number.boiler_dhw_comfdiff,5,9,1,21,1
|
||||
"Geo 5xx",boiler,173,dhw.ecodiff,eco diff,uint8 (>=4<=12),K,true,number.boiler_dhw_eco_diff,number.boiler_dhw_ecodiff,5,9,1,22,1
|
||||
"Geo 5xx",boiler,173,dhw.ecoplusdiff,eco+ diff,uint8 (>=6<=12),K,true,number.boiler_dhw_eco+_diff,number.boiler_dhw_ecoplusdiff,5,9,1,23,1
|
||||
"Geo 5xx",boiler,173,dhw.comfdiff,comfort diff,uint8 (>=4<=15),K,true,number.boiler_dhw_comfort_diff,number.boiler_dhw_comfdiff,5,9,1,21,1
|
||||
"Geo 5xx",boiler,173,dhw.ecodiff,eco diff,uint8 (>=4<=15),K,true,number.boiler_dhw_eco_diff,number.boiler_dhw_ecodiff,5,9,1,22,1
|
||||
"Geo 5xx",boiler,173,dhw.ecoplusdiff,eco+ diff,uint8 (>=4<=15),K,true,number.boiler_dhw_eco+_diff,number.boiler_dhw_ecoplusdiff,5,9,1,23,1
|
||||
"Geo 5xx",boiler,173,dhw.comfstop,comfort stop temp,uint8 (>=0<=254),C,true,number.boiler_dhw_comfort_stop_temp,number.boiler_dhw_comfstop,5,9,1,24,1
|
||||
"Geo 5xx",boiler,173,dhw.ecostop,eco stop temp,uint8 (>=0<=254),C,true,number.boiler_dhw_eco_stop_temp,number.boiler_dhw_ecostop,5,9,1,25,1
|
||||
"Geo 5xx",boiler,173,dhw.ecoplusstop,eco+ stop temp,uint8 (>=0<=254),C,true,number.boiler_dhw_eco+_stop_temp,number.boiler_dhw_ecoplusstop,5,9,1,26,1
|
||||
@@ -5468,6 +5468,8 @@ device name,device type,product id,shortname,fullname,type [options...] \| (min/
|
||||
"SM50",solar,162,heatcnt,heat counter impulses,uint8 (>=0<=254), ,false,sensor.solar_heat_counter_impulses,sensor.solar_heatcnt,8,0,1,67,1
|
||||
"SM50",solar,162,swapflowtemp,swap flow temperature (TS14),uint16 (>=0<=3199),C,false,sensor.solar_swap_flow_temperature_(TS14),sensor.solar_swapflowtemp,8,0,1/10,68,1
|
||||
"SM50",solar,162,swaprettemp,swap return temperature (TS15),uint16 (>=0<=3199),C,false,sensor.solar_swap_return_temperature_(TS15),sensor.solar_swaprettemp,8,0,1/10,69,1
|
||||
"SM50",solar,162,heatassiston,heat assistance on,int8 (>=-12<=12),K,true,number.solar_heat_assistance_on,number.solar_heatassiston,8,0,1/10,70,1
|
||||
"SM50",solar,162,heatassistoff,heat assistance off,int8 (>=-12<=12),K,true,number.solar_heat_assistance_off,number.solar_heatassistoff,8,0,1/10,71,1
|
||||
"SM100, MS100",solar,163,collectortemp,collector temperature (TS1),int16 (>=-3199<=3199),C,false,sensor.solar_collector_temperature_(TS1),sensor.solar_collectortemp,8,0,1/10,0,1
|
||||
"SM100, MS100",solar,163,cylbottomtemp,cylinder bottom temperature (TS2),int16 (>=-3199<=3199),C,false,sensor.solar_cylinder_bottom_temperature_(TS2),sensor.solar_cylbottomtemp,8,0,1/10,1,1
|
||||
"SM100, MS100",solar,163,solarpump,pump (PS1),boolean, ,false,binary_sensor.solar_pump_(PS1),binary_sensor.solar_solarpump,8,0,1,2,1
|
||||
@@ -5529,6 +5531,8 @@ device name,device type,product id,shortname,fullname,type [options...] \| (min/
|
||||
"SM100, MS100",solar,163,heatcnt,heat counter impulses,uint8 (>=0<=254), ,false,sensor.solar_heat_counter_impulses,sensor.solar_heatcnt,8,0,1,67,1
|
||||
"SM100, MS100",solar,163,swapflowtemp,swap flow temperature (TS14),uint16 (>=0<=3199),C,false,sensor.solar_swap_flow_temperature_(TS14),sensor.solar_swapflowtemp,8,0,1/10,68,1
|
||||
"SM100, MS100",solar,163,swaprettemp,swap return temperature (TS15),uint16 (>=0<=3199),C,false,sensor.solar_swap_return_temperature_(TS15),sensor.solar_swaprettemp,8,0,1/10,69,1
|
||||
"SM100, MS100",solar,163,heatassiston,heat assistance on,int8 (>=-12<=12),K,true,number.solar_heat_assistance_on,number.solar_heatassiston,8,0,1/10,70,1
|
||||
"SM100, MS100",solar,163,heatassistoff,heat assistance off,int8 (>=-12<=12),K,true,number.solar_heat_assistance_off,number.solar_heatassistoff,8,0,1/10,71,1
|
||||
"SM200, MS200",solar,164,collectortemp,collector temperature (TS1),int16 (>=-3199<=3199),C,false,sensor.solar_collector_temperature_(TS1),sensor.solar_collectortemp,8,0,1/10,0,1
|
||||
"SM200, MS200",solar,164,cylbottomtemp,cylinder bottom temperature (TS2),int16 (>=-3199<=3199),C,false,sensor.solar_cylinder_bottom_temperature_(TS2),sensor.solar_cylbottomtemp,8,0,1/10,1,1
|
||||
"SM200, MS200",solar,164,solarpump,pump (PS1),boolean, ,false,binary_sensor.solar_pump_(PS1),binary_sensor.solar_solarpump,8,0,1,2,1
|
||||
@@ -5590,6 +5594,8 @@ device name,device type,product id,shortname,fullname,type [options...] \| (min/
|
||||
"SM200, MS200",solar,164,heatcnt,heat counter impulses,uint8 (>=0<=254), ,false,sensor.solar_heat_counter_impulses,sensor.solar_heatcnt,8,0,1,67,1
|
||||
"SM200, MS200",solar,164,swapflowtemp,swap flow temperature (TS14),uint16 (>=0<=3199),C,false,sensor.solar_swap_flow_temperature_(TS14),sensor.solar_swapflowtemp,8,0,1/10,68,1
|
||||
"SM200, MS200",solar,164,swaprettemp,swap return temperature (TS15),uint16 (>=0<=3199),C,false,sensor.solar_swap_return_temperature_(TS15),sensor.solar_swaprettemp,8,0,1/10,69,1
|
||||
"SM200, MS200",solar,164,heatassiston,heat assistance on,int8 (>=-12<=12),K,true,number.solar_heat_assistance_on,number.solar_heatassiston,8,0,1/10,70,1
|
||||
"SM200, MS200",solar,164,heatassistoff,heat assistance off,int8 (>=-12<=12),K,true,number.solar_heat_assistance_off,number.solar_heatassistoff,8,0,1/10,71,1
|
||||
"HP Module",heatpump,252,airhumidity,relative air humidity,uint8 (>=0<=100),%,false,sensor.heatpump_relative_air_humidity,sensor.heatpump_airhumidity,9,0,1,0,1
|
||||
"HP Module",heatpump,252,dewtemperature,dew point temperature,uint8 (>=0<=254),C,false,sensor.heatpump_dew_point_temperature,sensor.heatpump_dewtemperature,9,0,1,1,1
|
||||
"HP Module",heatpump,252,curflowtemp,current flow temperature,int16 (>=-3199<=3199),C,false,sensor.heatpump_current_flow_temperature,sensor.heatpump_curflowtemp,9,0,1/10,2,1
|
||||
|
||||
|
Can't render this file because it is too large.
|
@@ -586,6 +586,7 @@ const Sensors = () => {
|
||||
creating={creating}
|
||||
selectedItem={selectedAnalogSensor}
|
||||
analogGPIOList={sensorData.available_gpios}
|
||||
disabledTypeList={sensorData.exclude_types}
|
||||
validator={analogSensorItemValidation(sensorData.as, selectedAnalogSensor)}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -35,6 +35,7 @@ interface DashboardSensorsAnalogDialogProps {
|
||||
creating: boolean;
|
||||
selectedItem: AnalogSensor;
|
||||
analogGPIOList: number[];
|
||||
disabledTypeList: number[];
|
||||
validator: Schema;
|
||||
}
|
||||
|
||||
@@ -45,6 +46,7 @@ const SensorsAnalogDialog = ({
|
||||
creating,
|
||||
selectedItem,
|
||||
analogGPIOList,
|
||||
disabledTypeList,
|
||||
validator
|
||||
}: DashboardSensorsAnalogDialogProps) => {
|
||||
const { LL } = useI18nContext();
|
||||
@@ -66,7 +68,16 @@ const SensorsAnalogDialog = ({
|
||||
|
||||
// Memoize helper functions to check sensor type conditions
|
||||
const isCounterOrRate = useMemo(
|
||||
() => editItem.t >= AnalogType.COUNTER && editItem.t <= AnalogType.RATE,
|
||||
() =>
|
||||
editItem.t === AnalogType.COUNTER ||
|
||||
editItem.t === AnalogType.RATE ||
|
||||
(editItem.t >= AnalogType.CNT_0 && editItem.t <= AnalogType.CNT_2),
|
||||
[editItem.t]
|
||||
);
|
||||
const isCounter = useMemo(
|
||||
() =>
|
||||
editItem.t === AnalogType.COUNTER ||
|
||||
(editItem.t >= AnalogType.CNT_0 && editItem.t <= AnalogType.CNT_2),
|
||||
[editItem.t]
|
||||
);
|
||||
const isFreqType = useMemo(
|
||||
@@ -80,13 +91,13 @@ const SensorsAnalogDialog = ({
|
||||
editItem.t === AnalogType.PWM_2,
|
||||
[editItem.t]
|
||||
);
|
||||
const isDigitalOutGPIO = useMemo(
|
||||
const isDACOutGPIO = useMemo(
|
||||
() =>
|
||||
editItem.t === AnalogType.DIGITAL_OUT &&
|
||||
(editItem.g === 25 || editItem.g === 26),
|
||||
[editItem.t, editItem.g]
|
||||
);
|
||||
const isDigitalOutNonGPIO = useMemo(
|
||||
const isDigitalOutGPIO = useMemo(
|
||||
() =>
|
||||
editItem.t === AnalogType.DIGITAL_OUT &&
|
||||
editItem.g !== 25 &&
|
||||
@@ -98,7 +109,11 @@ const SensorsAnalogDialog = ({
|
||||
const analogTypeMenuItems = useMemo(
|
||||
() =>
|
||||
AnalogTypeNames.map((val, i) => (
|
||||
<MenuItem key={val} value={i + 1}>
|
||||
<MenuItem
|
||||
key={val}
|
||||
value={i + 1}
|
||||
disabled={disabledTypeList.includes(i + 1)}
|
||||
>
|
||||
{val}
|
||||
</MenuItem>
|
||||
)),
|
||||
@@ -264,7 +279,7 @@ const SensorsAnalogDialog = ({
|
||||
/>
|
||||
</Grid>
|
||||
)}
|
||||
{editItem.t === AnalogType.COUNTER && (
|
||||
{isCounter && (
|
||||
<Grid>
|
||||
<ValidatedTextField
|
||||
name="o"
|
||||
@@ -309,7 +324,7 @@ const SensorsAnalogDialog = ({
|
||||
/>
|
||||
</Grid>
|
||||
)}
|
||||
{isDigitalOutGPIO && (
|
||||
{isDACOutGPIO && (
|
||||
<Grid>
|
||||
<ValidatedTextField
|
||||
name="o"
|
||||
@@ -325,7 +340,7 @@ const SensorsAnalogDialog = ({
|
||||
/>
|
||||
</Grid>
|
||||
)}
|
||||
{isDigitalOutNonGPIO && (
|
||||
{isDigitalOutGPIO && (
|
||||
<>
|
||||
<Grid>
|
||||
<ValidatedTextField
|
||||
|
||||
@@ -112,6 +112,7 @@ export interface SensorData {
|
||||
as: AnalogSensor[];
|
||||
analog_enabled: boolean;
|
||||
available_gpios: number[];
|
||||
exclude_types: number[];
|
||||
platform: string;
|
||||
}
|
||||
|
||||
@@ -245,7 +246,10 @@ export enum AnalogType {
|
||||
PULSE = 12,
|
||||
FREQ_0 = 13,
|
||||
FREQ_1 = 14,
|
||||
FREQ_2 = 15
|
||||
FREQ_2 = 15,
|
||||
CNT_0 = 16,
|
||||
CNT_1 = 17,
|
||||
CNT_2 = 18
|
||||
}
|
||||
|
||||
export const AnalogTypeNames = [
|
||||
@@ -263,7 +267,10 @@ export const AnalogTypeNames = [
|
||||
'Pulse', // 12
|
||||
'Freq 0', // 13
|
||||
'Freq 1', // 14
|
||||
'Freq 2' // 15
|
||||
'Freq 2', // 15
|
||||
'Counter 0', // 16
|
||||
'Counter 1', // 17
|
||||
'Counter2' // 18
|
||||
] as const;
|
||||
|
||||
export const BOARD_PROFILES = {
|
||||
|
||||
@@ -328,9 +328,9 @@ const ApplicationSettings = () => {
|
||||
>
|
||||
<MenuItem value={-1}>OFF</MenuItem>
|
||||
<MenuItem value={3}>ERR</MenuItem>
|
||||
<MenuItem value={4}>WARN</MenuItem>
|
||||
<MenuItem value={5}>NOTICE</MenuItem>
|
||||
<MenuItem value={6}>INFO</MenuItem>
|
||||
<MenuItem value={7}>DEBUG</MenuItem>
|
||||
<MenuItem value={9}>ALL</MenuItem>
|
||||
</TextField>
|
||||
</Grid>
|
||||
@@ -602,6 +602,7 @@ const ApplicationSettings = () => {
|
||||
<MenuItem value={0}>{LL.DISABLED(1)}</MenuItem>
|
||||
<MenuItem value={1}>LAN8720</MenuItem>
|
||||
<MenuItem value={2}>TLK110</MenuItem>
|
||||
<MenuItem value={3}>RTL8201</MenuItem>
|
||||
</TextField>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
91
lib/esp32-psram/README.md
Normal file
91
lib/esp32-psram/README.md
Normal file
@@ -0,0 +1,91 @@
|
||||
# ESP32-PSRAM Library
|
||||
|
||||
[](https://www.arduino.cc/reference/en/libraries/)
|
||||
[](https://opensource.org/licenses/MIT)
|
||||
|
||||
A comprehensive memory management library for ESP32 microcontrollers, providing efficient data structures that leverage __PSRAM__ and __HIMEM__ for expanded storage capabilities.
|
||||
|
||||
|
||||
## Overview
|
||||
|
||||
The ESP32 can only address 4MB of PSRAM directly, so the remaining 4MB (HIMEM) are usually unsued!
|
||||
|
||||
__esp32-psram__ is a C++ library that helps you manage large amounts of data on ESP32 devices by utilizing the full external PSRAM (SPI RAM) supporting the high memory (HIMEM) regions. This allows your applications to work with much larger datasets than would be possible using just internal RAM.
|
||||
|
||||
- **Use any STL Class in PSRAM**:
|
||||
- Adapt any [STL class](https://en.wikipedia.org/wiki/Standard_Template_Library) that accepts with an Allocator by using the [AllocatorPSRAM](https://pschatzmann.github.io/esp32-psram/html/classesp32__psram_1_1_p_s_r_a_m_allocator.html)
|
||||
|
||||
- **Memory-Efficient Data Structures**:
|
||||
- `VectorPSRAM`: Vector implementation that automatically stores data in PSRAM
|
||||
- `VectorHIMEM`: Vector implementation for extremely large datasets using ESP32's high memory region
|
||||
|
||||
- **File System Abstractions**:
|
||||
- `FilePSRAM`: File-like interface backed by PSRAM
|
||||
- `FileHIMEM`: File-like interface backed by high memory
|
||||
- SD card-like API using familiar file operations to write to PSRAM or HIMEM.
|
||||
|
||||
- **Streaming Data Handling**:
|
||||
- `RingBufferStreamRAM`: Circular buffer implementation in RAM (Stream-based)
|
||||
- `RingBufferStreamPSRAM`: Circular buffer implementation in PSRAM (Stream-based)
|
||||
- `RingBufferStreamHIMEM`: Circular buffer implementation in high memory (Stream-based)
|
||||
- Fully compatible with Arduino's Stream class
|
||||
|
||||
- **Typed Ring Buffers**:
|
||||
- `TypedRingBufferRAM<T>`: Type-safe circular buffer for any data type using RAM
|
||||
- `TypedRingBufferPSRAM<T>`: PSRAM version for storing complex data structures
|
||||
- `TypedRingBufferHIMEM<T>`: High memory version for storing complex data structures
|
||||
- Optimized for struct/class storage with proper memory management
|
||||
|
||||
|
||||
## Installation
|
||||
|
||||
1. Download the latest release from GitHub
|
||||
2. Extract the ZIP file
|
||||
3. Move the folder to your Arduino libraries directory (typically `~/Arduino/libraries/` on Linux/macOS or `Documents\Arduino\libraries\` on Windows)
|
||||
4. Restart Arduino IDE
|
||||
|
||||
|
||||
## API Reference
|
||||
|
||||
- [Class Reference](https://pschatzmann.github.io/esp32-psram/html/namespaceesp32__psram.html)
|
||||
- [Further Information](https://github.com/pschatzmann/esp32-psram/wiki)
|
||||
|
||||
## Memory Performance Comparison
|
||||
|
||||
| Memory Type | Access Speed | Capacity | Use Case |
|
||||
|-------------|--------------|----------|----------|
|
||||
| RAM (DRAM) | Very Fast | ~200-300KB | Small, performance-critical data |
|
||||
| PSRAM | Fast | Up to 4MB | Medium-sized datasets, frequent access |
|
||||
| HIMEM | Medium | Up to 8MB | Large datasets, less frequent access |
|
||||
|
||||
## Hardware Compatibility
|
||||
|
||||
| ESP32 Board | PSRAM Size | HIMEM Support | Notes |
|
||||
|-------------|------------|--------------|-------|
|
||||
| ESP32 | Up to 4MB | Yes | Most common ESP32 modules |
|
||||
| ESP32-S2 | Up to 2MB | Limited | Check board specifications |
|
||||
| ESP32-S3 | Up to 8MB | Yes | Best memory capabilities |
|
||||
| ESP32-C3 | None | No | Not compatible with this library |
|
||||
|
||||
## Design Philosophy
|
||||
|
||||
The ESP32-PSRAM library is designed with these principles in mind:
|
||||
|
||||
1. **Familiar interfaces**: API design mimics standard C++ containers and Arduino libraries
|
||||
2. **Memory efficiency**: Automatic use of appropriate memory regions
|
||||
3. **Type safety**: Template-based to work with any data type
|
||||
4. **Flexibility**: Configurable for different memory types and use cases
|
||||
5. **Zero overhead**: Direct memory access with minimal abstraction layers
|
||||
|
||||
|
||||
## License
|
||||
|
||||
MIT License
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions are welcome! Please feel free to submit a Pull Request.
|
||||
|
||||
## Acknowledgements
|
||||
|
||||
This library was inspired by the need for better memory management solutions on ESP32, especially for audio, image processing, and data logging applications.
|
||||
10
lib/esp32-psram/library.properties
Normal file
10
lib/esp32-psram/library.properties
Normal file
@@ -0,0 +1,10 @@
|
||||
name=ESP32-PSRAM
|
||||
version=0.1.3
|
||||
author=Phil Schatzmann
|
||||
maintainer=https://github.com/pschatzmann
|
||||
sentence=Memory management library for ESP32 using PSRAM and HIMEM
|
||||
paragraph=A comprehensive memory management library that provides vector, file, and ring buffer implementations using ESP32's PSRAM and HIMEM. Allows applications to work with large datasets beyond internal memory limitations.
|
||||
category=Data Storage
|
||||
url=https://github.com/pschatzmann/esp32-psram
|
||||
architectures=esp32
|
||||
depends=
|
||||
21
lib/esp32-psram/license.txt
Normal file
21
lib/esp32-psram/license.txt
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 Phil Schatzmann
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
31
lib/esp32-psram/src/esp32-psram.h
Normal file
31
lib/esp32-psram/src/esp32-psram.h
Normal file
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef ESP32
|
||||
/**
|
||||
* @file ESP32-PSRAM.h
|
||||
* @brief Main include file for the ESP32-PSRAM library
|
||||
*
|
||||
* This header includes all components of the ESP32-PSRAM library and
|
||||
* provides a using namespace directive for easier access to library features.
|
||||
*/
|
||||
|
||||
// Include all library components
|
||||
#include "esp32-psram/AllocatorPSRAM.h" // PSRAM-backed vector
|
||||
#include "esp32-psram/VectorPSRAM.h" // PSRAM-backed vector
|
||||
#include "esp32-psram/VectorHIMEM.h" // HIMEM-backed vector
|
||||
// #include "esp32-psram/InMemoryFile.h" // File interface using vectors
|
||||
// #include "esp32-psram/PSRAM.h" // PSRAM file system
|
||||
// #include "esp32-psram/HIMEM.h" // HIMEM file system
|
||||
#include "esp32-psram/RingBufferStream.h" // Stream-based ring buffer
|
||||
#include "esp32-psram/TypedRingBuffer.h" // Typed ring buffer for structured data
|
||||
|
||||
#define stringPSRAM std::basic_string<char, std::char_traits<char>, AllocatorPSRAM<char>>
|
||||
|
||||
#ifndef ESP32_PSRAM_NO_NAMESPACE
|
||||
using namespace esp32_psram;
|
||||
#endif
|
||||
|
||||
#else
|
||||
#error "This library is only compatible with ESP32 platforms."
|
||||
#endif // ESP32
|
||||
|
||||
159
lib/esp32-psram/src/esp32-psram/AllocatorPSRAM.h
Normal file
159
lib/esp32-psram/src/esp32-psram/AllocatorPSRAM.h
Normal file
@@ -0,0 +1,159 @@
|
||||
#pragma once
|
||||
|
||||
#include <esp_heap_caps.h>
|
||||
|
||||
/**
|
||||
* @namespace esp32_psram
|
||||
* @brief Namespace containing ESP32 PSRAM-specific implementations
|
||||
*/
|
||||
namespace esp32_psram {
|
||||
|
||||
/**
|
||||
* @class AllocatorPSRAM
|
||||
* @brief Custom allocator that uses ESP32's PSRAM for memory allocation
|
||||
* @tparam T Type of elements to allocate
|
||||
*
|
||||
* This allocator uses ESP32's heap_caps_malloc with MALLOC_CAP_SPIRAM flag
|
||||
* to ensure all memory is allocated in PSRAM instead of regular RAM. If PSRAM
|
||||
* allocation fails, it falls back to regular RAM.
|
||||
*/
|
||||
template <typename T>
|
||||
class AllocatorPSRAM {
|
||||
public:
|
||||
using value_type = T;
|
||||
using pointer = T*;
|
||||
using const_pointer = const T*;
|
||||
using reference = T&;
|
||||
using const_reference = const T&;
|
||||
using size_type = std::size_t;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
|
||||
/**
|
||||
* @brief Default constructor
|
||||
*/
|
||||
AllocatorPSRAM() noexcept {}
|
||||
|
||||
/**
|
||||
* @brief Copy constructor from another allocator type
|
||||
* @tparam U Type of the other allocator
|
||||
* @param other The other allocator
|
||||
*/
|
||||
template <typename U>
|
||||
AllocatorPSRAM(const AllocatorPSRAM<U>&) noexcept {}
|
||||
|
||||
/**
|
||||
* @brief Allocate memory from PSRAM
|
||||
* @param n Number of elements to allocate
|
||||
* @return Pointer to allocated memory
|
||||
* @throws std::bad_alloc If allocation fails or size is too large
|
||||
*/
|
||||
pointer allocate(size_type n) {
|
||||
// if (n > std::numeric_limits<size_type>::max() / sizeof(T))
|
||||
// throw std::bad_alloc();
|
||||
// in Arduino excepitons are disabled!
|
||||
assert(n <= std::numeric_limits<size_type>::max() / sizeof(T));
|
||||
|
||||
pointer p = static_cast<pointer>(
|
||||
heap_caps_malloc(n * sizeof(T), MALLOC_CAP_SPIRAM));
|
||||
if (p == nullptr) {
|
||||
p = static_cast<pointer>(malloc(n * sizeof(T)));
|
||||
}
|
||||
|
||||
// if (!p) throw std::bad_alloc();
|
||||
|
||||
// in Arduino excepitons are disabled!
|
||||
assert(p);
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Deallocate memory
|
||||
* @param p Pointer to memory to deallocate
|
||||
* @param size Size of allocation (unused)
|
||||
*/
|
||||
void deallocate(pointer p, size_type) noexcept { heap_caps_free(p); }
|
||||
|
||||
/**
|
||||
* @brief Rebind allocator to another type
|
||||
* @tparam U Type to rebind the allocator to
|
||||
*/
|
||||
template <typename U>
|
||||
struct rebind {
|
||||
using other = AllocatorPSRAM<U>;
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* @class AllocatorOnlyPSRAM
|
||||
* @brief Custom allocator that uses ESP32's PSRAM for memory allocation
|
||||
* @tparam T Type of elements to allocate
|
||||
*
|
||||
* This allocator uses ESP32's heap_caps_malloc with MALLOC_CAP_SPIRAM flag
|
||||
* to ensure all memory is allocated in PSRAM instead of regular RAM.
|
||||
* If PSRAM allocation fails, it does not fall back to regular RAM.
|
||||
*/
|
||||
template <typename T>
|
||||
class AllocatorOnlyPSRAM {
|
||||
public:
|
||||
using value_type = T;
|
||||
using pointer = T*;
|
||||
using const_pointer = const T*;
|
||||
using reference = T&;
|
||||
using const_reference = const T&;
|
||||
using size_type = std::size_t;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
|
||||
/**
|
||||
* @brief Default constructor
|
||||
*/
|
||||
AllocatorOnlyPSRAM() noexcept {}
|
||||
|
||||
/**
|
||||
* @brief Copy constructor from another allocator type
|
||||
* @tparam U Type of the other allocator
|
||||
* @param other The other allocator
|
||||
*/
|
||||
template <typename U>
|
||||
AllocatorOnlyPSRAM(const AllocatorOnlyPSRAM<U>&) noexcept {}
|
||||
|
||||
/**
|
||||
* @brief Allocate memory from PSRAM
|
||||
* @param n Number of elements to allocate
|
||||
* @return Pointer to allocated memory
|
||||
* @throws std::bad_alloc If allocation fails or size is too large
|
||||
*/
|
||||
pointer allocate(size_type n) {
|
||||
// if (n > std::numeric_limits<size_type>::max() / sizeof(T))
|
||||
// throw std::bad_alloc();
|
||||
// in Arduino excepitons are disabled!
|
||||
assert(n <= std::numeric_limits<size_type>::max() / sizeof(T));
|
||||
|
||||
pointer p = static_cast<pointer>(
|
||||
heap_caps_malloc(n * sizeof(T), MALLOC_CAP_SPIRAM));
|
||||
// if (!p) throw std::bad_alloc();
|
||||
|
||||
// in Arduino excepitons are disabled!
|
||||
assert(p);
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Deallocate memory
|
||||
* @param p Pointer to memory to deallocate
|
||||
* @param size Size of allocation (unused)
|
||||
*/
|
||||
void deallocate(pointer p, size_type) noexcept { heap_caps_free(p); }
|
||||
|
||||
/**
|
||||
* @brief Rebind allocator to another type
|
||||
* @tparam U Type to rebind the allocator to
|
||||
*/
|
||||
template <typename U>
|
||||
struct rebind {
|
||||
using other = AllocatorOnlyPSRAM<U>;
|
||||
};
|
||||
};
|
||||
|
||||
} // namespace esp32_psram
|
||||
54
lib/esp32-psram/src/esp32-psram/HIMEM.h
Normal file
54
lib/esp32-psram/src/esp32-psram/HIMEM.h
Normal file
@@ -0,0 +1,54 @@
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "InMemoryFS.h"
|
||||
#include "VectorHIMEM.h"
|
||||
|
||||
namespace esp32_psram {
|
||||
|
||||
/**
|
||||
* @class HIMEMClass
|
||||
* @brief Class for managing files stored in ESP32's High Memory (HIMEM)
|
||||
*
|
||||
* This class provides an interface similar to SD.h for managing files
|
||||
* that are stored in HIMEM memory (beyond the 4MB boundary) rather than on an SD card.
|
||||
* HIMEM offers larger storage capacity but slightly slower access than regular PSRAM.
|
||||
*/
|
||||
class HIMEMClass : public InMemoryFS<VectorHIMEM<uint8_t>, FileHIMEM> {
|
||||
public:
|
||||
/**
|
||||
* @brief Initialize the HIMEM filesystem
|
||||
* @return true if initialization was successful, false otherwise
|
||||
*/
|
||||
bool begin() override {
|
||||
if (esp_himem_get_free_size() > 0) {
|
||||
initialized = true;
|
||||
return true;
|
||||
}
|
||||
initialized = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get total space (returns available HIMEM)
|
||||
* @return Total HIMEM size in bytes
|
||||
*/
|
||||
uint64_t totalBytes() override {
|
||||
return esp_himem_get_phys_size();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get free space (returns free HIMEM)
|
||||
* @return Free HIMEM size in bytes
|
||||
*/
|
||||
uint64_t freeBytes() override {
|
||||
return esp_himem_get_free_size();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Global instance of HIMEMClass for easy access
|
||||
*/
|
||||
static HIMEMClass HIMEM;
|
||||
|
||||
} // namespace esp32_psram
|
||||
362
lib/esp32-psram/src/esp32-psram/HimemBlock.h
Normal file
362
lib/esp32-psram/src/esp32-psram/HimemBlock.h
Normal file
@@ -0,0 +1,362 @@
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
// ESP32 HIMEM headers - using conditional inclusion for compatibility
|
||||
#if __has_include("esp32/himem.h")
|
||||
#include "esp32/himem.h"
|
||||
#elif __has_include("esp_himem.h")
|
||||
#include "esp_himem.h"
|
||||
#else
|
||||
// Fall back to Arduino ESP32 core path
|
||||
extern "C" {
|
||||
#include "esp32/himem.h"
|
||||
}
|
||||
|
||||
// Define missing constants if needed
|
||||
#ifndef ESP_HIMEM_BLKSZ
|
||||
#define ESP_HIMEM_BLKSZ (32 * 1024)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// Make sure ESP_HIMEM_PROT_RW is defined
|
||||
#ifndef ESP_HIMEM_PROT_RW
|
||||
#define ESP_HIMEM_PROT_RW 0
|
||||
#endif
|
||||
|
||||
namespace esp32_psram {
|
||||
|
||||
static constexpr const char* TAG = "HIMEM"; // Tag for ESP logging
|
||||
|
||||
/**
|
||||
* @class HimemBlock
|
||||
* @brief Manages a block of himem memory with mapping functionality
|
||||
*/
|
||||
class HimemBlock {
|
||||
public:
|
||||
/**
|
||||
* @brief Default constructor
|
||||
*/
|
||||
HimemBlock() { ESP_LOGD(TAG, "HimemBlock constructor called"); }
|
||||
|
||||
/**
|
||||
* @brief Allocate a block of himem
|
||||
* @param block_size Size of memory to allocate in bytes
|
||||
* @return the allocated (block) size in bytes, 0 otherwise
|
||||
*/
|
||||
size_t allocate(size_t block_size) {
|
||||
ESP_LOGI(TAG, "HimemBlock::allocate(%u bytes) - Current handle: %p",
|
||||
block_size, handle);
|
||||
|
||||
if (handle != 0) {
|
||||
ESP_LOGW(TAG, "Cannot allocate: Block already allocated");
|
||||
return false; // Already allocated
|
||||
}
|
||||
|
||||
// Round up to the nearest multiple of ESP_HIMEM_BLKSZ (32K)
|
||||
block_size = ((block_size + ESP_HIMEM_BLKSZ - 1) / ESP_HIMEM_BLKSZ) *
|
||||
ESP_HIMEM_BLKSZ;
|
||||
|
||||
size = block_size;
|
||||
esp_err_t err = esp_himem_alloc(block_size, &handle);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "HIMEM allocation failed - error: %d, size: %d", err,
|
||||
block_size);
|
||||
return 0;
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "- Successfully allocated %u bytes, handle: %p", block_size,
|
||||
handle);
|
||||
return block_size;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Read data from HIMEM at specified offset
|
||||
* @param dest Destination buffer
|
||||
* @param offset Offset in HIMEM to read from
|
||||
* @param length Number of bytes to read
|
||||
* @return Number of bytes actually read
|
||||
*/
|
||||
size_t read(void* dest, size_t offset, size_t length) {
|
||||
ESP_LOGD(TAG, "HimemBlock::read(dst=%p, offset=%u, length=%u)", dest,
|
||||
offset, length);
|
||||
|
||||
if (!handle || offset >= size) {
|
||||
ESP_LOGW(TAG, "Read failed: %s",
|
||||
!handle ? "Invalid handle" : "Offset beyond size");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Ensure we don't read past the end
|
||||
length = std::min(length, size - offset);
|
||||
if (length == 0) {
|
||||
ESP_LOGW(TAG, "Read failed: Zero length after bounds check");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Calculate which block this belongs to
|
||||
size_t block_index = offset / ESP_HIMEM_BLKSZ;
|
||||
size_t block_offset = offset % ESP_HIMEM_BLKSZ;
|
||||
size_t bytes_read = 0;
|
||||
|
||||
ESP_LOGD(TAG, "- Reading from block %u, offset %u", block_index,
|
||||
block_offset);
|
||||
uint8_t* dest_ptr = static_cast<uint8_t*>(dest);
|
||||
|
||||
while (bytes_read < length) {
|
||||
// Ensure the correct block is mapped
|
||||
if (!ensure_block_mapped(block_index)) {
|
||||
return bytes_read;
|
||||
}
|
||||
|
||||
// Calculate how much to read from this block
|
||||
size_t block_remain = ESP_HIMEM_BLKSZ - block_offset;
|
||||
size_t to_read = std::min(block_remain, length - bytes_read);
|
||||
ESP_LOGD(TAG, "- Reading %u bytes from window at offset %u", to_read,
|
||||
block_offset);
|
||||
|
||||
// Copy the data
|
||||
memcpy(dest_ptr + bytes_read,
|
||||
static_cast<uint8_t*>(mapped_ptr) + block_offset, to_read);
|
||||
|
||||
bytes_read += to_read;
|
||||
block_index++;
|
||||
block_offset = 0; // Reset offset for next blocks
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "- Successfully read %u bytes", bytes_read);
|
||||
return bytes_read;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Write data to HIMEM at specified offset
|
||||
* @param src Source buffer
|
||||
* @param offset Offset in HIMEM to write to
|
||||
* @param length Number of bytes to write
|
||||
* @return Number of bytes actually written
|
||||
*/
|
||||
size_t write(const void* src, size_t offset, size_t length) {
|
||||
ESP_LOGD(TAG, "HimemBlock::write(src=%p, offset=%u, length=%u)", src,
|
||||
offset, length);
|
||||
|
||||
if (!handle || offset >= size) {
|
||||
ESP_LOGW(TAG, "Write failed: %s",
|
||||
!handle ? "Invalid handle" : "Offset beyond size");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Ensure we don't write past the end
|
||||
length = std::min(length, size - offset);
|
||||
if (length == 0) {
|
||||
ESP_LOGW(TAG, "Write failed: Zero length after bounds check");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Calculate which block this belongs to
|
||||
size_t block_index = offset / ESP_HIMEM_BLKSZ;
|
||||
size_t block_offset = offset % ESP_HIMEM_BLKSZ;
|
||||
size_t bytes_written = 0;
|
||||
|
||||
ESP_LOGD(TAG, "- Writing to block %u, offset %u", block_index,
|
||||
block_offset);
|
||||
const uint8_t* src_ptr = static_cast<const uint8_t*>(src);
|
||||
|
||||
while (bytes_written < length) {
|
||||
// Ensure the correct block is mapped
|
||||
if (!ensure_block_mapped(block_index)) {
|
||||
return bytes_written;
|
||||
}
|
||||
|
||||
// Calculate how much to write to this block
|
||||
size_t block_remain = ESP_HIMEM_BLKSZ - block_offset;
|
||||
size_t to_write = std::min(block_remain, length - bytes_written);
|
||||
ESP_LOGD(TAG, "- Writing %u bytes to window at offset %u", to_write,
|
||||
block_offset);
|
||||
|
||||
// Copy the data
|
||||
memcpy(static_cast<uint8_t*>(mapped_ptr) + block_offset,
|
||||
src_ptr + bytes_written, to_write);
|
||||
|
||||
bytes_written += to_write;
|
||||
block_index++;
|
||||
block_offset = 0; // Reset offset for next blocks
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "- Successfully wrote %u bytes", bytes_written);
|
||||
return bytes_written;
|
||||
}
|
||||
|
||||
bool getAddress(size_t offset, void* &result, size_t &available) {
|
||||
size_t block_index = offset / ESP_HIMEM_BLKSZ;
|
||||
size_t block_offset = offset % ESP_HIMEM_BLKSZ;
|
||||
if (!ensure_block_mapped(block_index)) return false;
|
||||
available = ESP_HIMEM_BLKSZ - block_offset;
|
||||
result = static_cast<uint8_t*>(mapped_ptr)+block_offset;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Unmap the himem block
|
||||
*/
|
||||
void unmap() {
|
||||
ESP_LOGD(TAG, "HimemBlock::unmap() - mapped_ptr=%p, range=%p", mapped_ptr,
|
||||
range);
|
||||
|
||||
if (mapped_ptr && range) {
|
||||
ESP_LOGD(TAG, "- Unmapping memory and freeing range");
|
||||
esp_himem_unmap(range, mapped_ptr, ESP_HIMEM_BLKSZ);
|
||||
esp_himem_free_map_range(range);
|
||||
mapped_ptr = nullptr;
|
||||
range = 0;
|
||||
current_mapped_block = SIZE_MAX; // Reset currently mapped block
|
||||
ESP_LOGD(TAG, "- Unmapped successfully");
|
||||
} else {
|
||||
ESP_LOGD(TAG, "- Nothing to unmap");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Free the himem block
|
||||
*/
|
||||
void free() {
|
||||
ESP_LOGD(TAG, "HimemBlock::free() - handle=%p", handle);
|
||||
|
||||
if (handle) {
|
||||
ESP_LOGD(TAG, "- Unmapping before freeing");
|
||||
unmap();
|
||||
ESP_LOGD(TAG, "- Freeing HIMEM handle %p", handle);
|
||||
esp_himem_free(handle);
|
||||
handle = 0;
|
||||
size = 0;
|
||||
ESP_LOGD(TAG, " - Successfully freed HIMEM block");
|
||||
} else {
|
||||
ESP_LOGD(TAG, "- Nothing to free");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the size of the allocated block
|
||||
* @return Size of the allocated block in bytes
|
||||
*/
|
||||
size_t get_size() const {
|
||||
ESP_LOGD(TAG, "HimemBlock::get_size() = %u", size);
|
||||
return size;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Destructor - ensures memory is properly freed
|
||||
*/
|
||||
~HimemBlock() {
|
||||
ESP_LOGD(TAG, "HimemBlock destructor called");
|
||||
free();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Copy constructor (deleted)
|
||||
*/
|
||||
HimemBlock(const HimemBlock&) = delete;
|
||||
|
||||
/**
|
||||
* @brief Move constructor
|
||||
*/
|
||||
HimemBlock(HimemBlock&& other) noexcept
|
||||
: handle(other.handle),
|
||||
range(other.range),
|
||||
mapped_ptr(other.mapped_ptr),
|
||||
size(other.size),
|
||||
current_mapped_block(other.current_mapped_block) {
|
||||
ESP_LOGD(TAG, "HimemBlock move constructor - moving handle=%p, size=%u",
|
||||
other.handle, other.size);
|
||||
other.handle = 0;
|
||||
other.range = 0;
|
||||
other.mapped_ptr = nullptr;
|
||||
other.size = 0;
|
||||
other.current_mapped_block = SIZE_MAX;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Copy assignment operator (deleted)
|
||||
*/
|
||||
HimemBlock& operator=(const HimemBlock&) = delete;
|
||||
|
||||
/**
|
||||
* @brief Move assignment operator
|
||||
*/
|
||||
HimemBlock& operator=(HimemBlock&& other) noexcept {
|
||||
ESP_LOGD(TAG, "HimemBlock move assignment - from handle=%p to handle=%p",
|
||||
other.handle, handle);
|
||||
|
||||
if (this != &other) {
|
||||
ESP_LOGD(TAG, "Freeing current resources before move assignment");
|
||||
free();
|
||||
handle = other.handle;
|
||||
range = other.range;
|
||||
mapped_ptr = other.mapped_ptr;
|
||||
size = other.size;
|
||||
current_mapped_block = other.current_mapped_block;
|
||||
ESP_LOGD(TAG, "Moved resources, new size=%u", size);
|
||||
other.handle = 0;
|
||||
other.range = 0;
|
||||
other.mapped_ptr = nullptr;
|
||||
other.size = 0;
|
||||
other.current_mapped_block = SIZE_MAX;
|
||||
} else {
|
||||
ESP_LOGD(TAG, "Self-assignment detected, no action taken");
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
protected:
|
||||
esp_himem_handle_t handle = 0;
|
||||
esp_himem_rangehandle_t range = 0;
|
||||
void* mapped_ptr = nullptr;
|
||||
size_t size = 0;
|
||||
size_t current_mapped_block =
|
||||
SIZE_MAX; // Track which block is currently mapped
|
||||
|
||||
/**
|
||||
* @brief Ensure a specific block is mapped into memory
|
||||
* @param block_index The index of the block to map
|
||||
* @return true if mapping successful, false otherwise
|
||||
*/
|
||||
bool ensure_block_mapped(size_t block_index) {
|
||||
// If the requested block is already mapped, we're done
|
||||
if (block_index == current_mapped_block) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Unmap previous block if any
|
||||
if (mapped_ptr) {
|
||||
ESP_LOGD(TAG, "- Unmapping block %u before mapping new block %u",
|
||||
current_mapped_block, block_index);
|
||||
unmap(); // Unmap previous block
|
||||
}
|
||||
|
||||
// Allocate map range
|
||||
ESP_LOGD(TAG, "- Allocating map range for block %u", block_index);
|
||||
esp_err_t err = esp_himem_alloc_map_range(ESP_HIMEM_BLKSZ, &range);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to allocate map range: %d", err);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Map the current block
|
||||
ESP_LOGD(TAG, "- Mapping block %u (offset %u)", block_index,
|
||||
block_index * ESP_HIMEM_BLKSZ);
|
||||
err = esp_himem_map(handle, range, block_index * ESP_HIMEM_BLKSZ, 0,
|
||||
ESP_HIMEM_BLKSZ, ESP_HIMEM_PROT_RW, &mapped_ptr);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to map memory: %d", err);
|
||||
esp_himem_free_map_range(range);
|
||||
range = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
current_mapped_block = block_index;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace esp32_psram
|
||||
251
lib/esp32-psram/src/esp32-psram/InMemoryFS.h_
Normal file
251
lib/esp32-psram/src/esp32-psram/InMemoryFS.h_
Normal file
@@ -0,0 +1,251 @@
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
#include <map>
|
||||
|
||||
#include "InMemoryFile.h"
|
||||
|
||||
// Define Arduino file mode constants if not already defined
|
||||
#ifndef FILE_READ
|
||||
#define FILE_READ 0
|
||||
#endif
|
||||
#ifndef FILE_WRITE
|
||||
#define FILE_WRITE 1
|
||||
#endif
|
||||
#ifndef FILE_APPEND
|
||||
#define FILE_APPEND 2
|
||||
#endif
|
||||
|
||||
namespace esp32_psram {
|
||||
|
||||
/**
|
||||
* @class InMemoryFS
|
||||
* @brief Base class for memory-based file systems like PSRAM and HIMEM
|
||||
*
|
||||
* This class provides a common interface for in-memory file systems,
|
||||
* with methods for file management and traversal.
|
||||
*
|
||||
* @tparam VectorType The vector implementation to use (VectorPSRAM or
|
||||
* VectorHIMEM)
|
||||
* @tparam FileType The file implementation to return (FilePSRAM or FileHIMEM)
|
||||
*/
|
||||
template <typename VectorType, typename FileType>
|
||||
class InMemoryFS {
|
||||
public:
|
||||
/**
|
||||
* @brief Initialize the file system
|
||||
* @return true if initialization was successful, false otherwise
|
||||
*/
|
||||
virtual bool begin() = 0;
|
||||
|
||||
/**
|
||||
* @brief Check if a file exists
|
||||
* @param filename Name of the file to check
|
||||
* @return true if the file exists, false otherwise
|
||||
*/
|
||||
bool exists(const char* filename) {
|
||||
if (!initialized) return false;
|
||||
return fileData.find(std::string(filename)) != fileData.end();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Open a file
|
||||
* @param filename Name of the file to open
|
||||
* @param mode Mode to open the file in (FILE_READ, FILE_WRITE, etc.)
|
||||
* @return A file object for the opened file
|
||||
*/
|
||||
FileType open(const char* filename, uint8_t mode) {
|
||||
ESP_LOGD("InMemoryFS", "Opening file %s with mode %d", filename, mode);
|
||||
|
||||
if (!initialized) {
|
||||
ESP_LOGW("InMemoryFS", "Filesystem not initialized");
|
||||
FileType emptyFile;
|
||||
return emptyFile;
|
||||
}
|
||||
|
||||
// Convert Arduino file modes to our enum
|
||||
FileMode fileMode;
|
||||
if (mode == FILE_READ) {
|
||||
fileMode = FileMode::READ;
|
||||
} else if (mode == FILE_WRITE) {
|
||||
fileMode = FileMode::WRITE;
|
||||
} else if (mode == FILE_APPEND) {
|
||||
fileMode = FileMode::APPEND;
|
||||
} else {
|
||||
fileMode = FileMode::READ_WRITE;
|
||||
}
|
||||
|
||||
std::string filenameStr(filename);
|
||||
auto it = fileData.find(filenameStr);
|
||||
FileType file;
|
||||
|
||||
if (it != fileData.end()) {
|
||||
// File exists, create a new file pointing to existing data
|
||||
ESP_LOGD("InMemoryFS", "File exists, connecting to existing data");
|
||||
file.setVector(&(it->second));
|
||||
} else if (mode != FILE_READ) {
|
||||
// File doesn't exist, create it for writing or appending
|
||||
ESP_LOGD("InMemoryFS", "Creating new file for writing");
|
||||
fileData[filenameStr] = VectorType(); // Create empty vector in the map
|
||||
file.setVector(&(fileData[filenameStr]));
|
||||
} else {
|
||||
// File doesn't exist and mode is READ
|
||||
ESP_LOGW("InMemoryFS", "File doesn't exist and mode is READ");
|
||||
return file; // Return empty file
|
||||
}
|
||||
|
||||
// Configure the file
|
||||
file.setName(filename);
|
||||
file.open(fileMode);
|
||||
|
||||
// Set up the next file callback
|
||||
file.setNextFileCallback(
|
||||
[this](const char* currentFileName, FileMode currentMode) -> FileType {
|
||||
ESP_LOGD("InMemoryFS", "NextFile callback called for %s",
|
||||
currentFileName);
|
||||
|
||||
// Get the next filename
|
||||
String nextName = this->getNextFileName(currentFileName);
|
||||
if (nextName.isEmpty()) {
|
||||
ESP_LOGD("InMemoryFS", "No next file found");
|
||||
return FileType(); // Return empty file if no next file
|
||||
}
|
||||
|
||||
// Convert FileMode to Arduino mode
|
||||
uint8_t arduinoMode;
|
||||
if (currentMode == FileMode::READ)
|
||||
arduinoMode = FILE_READ;
|
||||
else if (currentMode == FileMode::WRITE)
|
||||
arduinoMode = FILE_WRITE;
|
||||
else if (currentMode == FileMode::APPEND)
|
||||
arduinoMode = FILE_APPEND;
|
||||
else
|
||||
arduinoMode = FILE_READ | FILE_WRITE;
|
||||
|
||||
// Open and return the next file
|
||||
ESP_LOGD("InMemoryFS", "Returning next file: %s", nextName.c_str());
|
||||
return this->open(nextName.c_str(), arduinoMode);
|
||||
});
|
||||
|
||||
ESP_LOGD("InMemoryFS", "File opened successfully");
|
||||
return file;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Remove a file
|
||||
* @param filename Name of the file to remove
|
||||
* @return true if the file was removed, false otherwise
|
||||
*/
|
||||
bool remove(const char* filename) {
|
||||
if (!initialized) return false;
|
||||
|
||||
std::string filenameStr(filename);
|
||||
auto it = fileData.find(filenameStr);
|
||||
|
||||
if (it != fileData.end()) {
|
||||
fileData.erase(it);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Create a directory (no-op for compatibility)
|
||||
* @param dirname Name of the directory
|
||||
* @return Always returns true for compatibility
|
||||
*/
|
||||
bool mkdir(const char* dirname) {
|
||||
// No directories in this implementation, just for compatibility
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Remove a directory (no-op for compatibility)
|
||||
* @param dirname Name of the directory
|
||||
* @return Always returns true for compatibility
|
||||
*/
|
||||
bool rmdir(const char* dirname) {
|
||||
// No directories in this implementation, just for compatibility
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the name of the next file after the specified file
|
||||
* @param currentFileName Name of the current file
|
||||
* @return Name of the next file, or empty string if there are no more files
|
||||
*/
|
||||
String getNextFileName(const char* currentFileName) {
|
||||
if (!initialized || fileData.empty()) {
|
||||
return String();
|
||||
}
|
||||
|
||||
if (currentFileName == nullptr || strlen(currentFileName) == 0 ||
|
||||
strcmp(currentFileName, "/") == 0) {
|
||||
// Return the first file if current is empty or is root directory
|
||||
return String(fileData.begin()->first.c_str());
|
||||
}
|
||||
|
||||
std::string current(currentFileName);
|
||||
auto it = fileData.find(current);
|
||||
|
||||
if (it == fileData.end()) {
|
||||
// Current file not found, find the next one alphabetically
|
||||
for (auto& entry : fileData) {
|
||||
if (entry.first > current) {
|
||||
return String(entry.first.c_str());
|
||||
}
|
||||
}
|
||||
// No file after the specified name
|
||||
return String();
|
||||
} else {
|
||||
// Found the current file, get the next one
|
||||
++it;
|
||||
if (it != fileData.end()) {
|
||||
return String(it->first.c_str());
|
||||
} else {
|
||||
// Was the last file
|
||||
return String();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the first file in the filesystem
|
||||
* @return Name of the first file, or empty string if there are no files
|
||||
*/
|
||||
String getFirstFileName() {
|
||||
if (!initialized || fileData.empty()) {
|
||||
return String();
|
||||
}
|
||||
return String(fileData.begin()->first.c_str());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the total number of files
|
||||
* @return Number of files in the filesystem
|
||||
*/
|
||||
size_t fileCount() const {
|
||||
if (!initialized) return 0;
|
||||
return fileData.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get total space
|
||||
* @return Total memory size in bytes
|
||||
*/
|
||||
virtual uint64_t totalBytes() = 0;
|
||||
|
||||
/**
|
||||
* @brief Get free space
|
||||
* @return Free memory size in bytes
|
||||
*/
|
||||
virtual uint64_t freeBytes() = 0;
|
||||
|
||||
protected:
|
||||
bool initialized = false;
|
||||
std::map<std::string, VectorType> fileData;
|
||||
};
|
||||
|
||||
} // namespace esp32_psram
|
||||
421
lib/esp32-psram/src/esp32-psram/InMemoryFile.h_
Normal file
421
lib/esp32-psram/src/esp32-psram/InMemoryFile.h_
Normal file
@@ -0,0 +1,421 @@
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
#include "VectorHIMEM.h"
|
||||
#include "VectorPSRAM.h"
|
||||
|
||||
namespace esp32_psram {
|
||||
|
||||
// Define file modes
|
||||
enum class FileMode { READ, WRITE, APPEND, READ_WRITE };
|
||||
|
||||
/**
|
||||
* @class InMemoryFile
|
||||
* @brief A file-like interface for vector-backed storage in memory
|
||||
*
|
||||
* This class provides a file-like interface (compatible with Arduino's Stream)
|
||||
* for reading and writing data to vector-backed storage. It can use either
|
||||
* standard PSRAM or ESP32's HIMEM (high memory beyond the 4MB boundary)
|
||||
* as its underlying storage mechanism through the VectorPSRAM and VectorHIMEM
|
||||
* implementations.
|
||||
*
|
||||
* InMemoryFile offers familiar file operations like open, close, read, write,
|
||||
* seek, and truncate while managing the data in memory rather than on disk.
|
||||
* This makes it useful for temporary storage, data processing, or situations
|
||||
* where file operations are needed but filesystem access is not available
|
||||
* or desirable.
|
||||
*
|
||||
* The class can either manage its own internal vector or reference an external
|
||||
* vector supplied by another component (like PSRAMClass or HIMEMClass).
|
||||
*
|
||||
* @tparam VectorType The vector implementation to use for storage (typically
|
||||
* VectorPSRAM<uint8_t> or VectorHIMEM<uint8_t>)
|
||||
*
|
||||
* @note The performance characteristics will depend on the underlying vector
|
||||
* implementation. HIMEM operations are generally slower than regular
|
||||
* PSRAM operations but allow for larger storage capacity.
|
||||
*/
|
||||
template <typename VectorType>
|
||||
class InMemoryFile : public Stream {
|
||||
public:
|
||||
// Define a single callback type that returns the next file directly
|
||||
using NextFileCallback =
|
||||
std::function<InMemoryFile<VectorType>(const char*, FileMode)>;
|
||||
/**
|
||||
* @brief Default constructor - initializes with internal vector
|
||||
*/
|
||||
InMemoryFile() : data_ptr(&internal_data) {
|
||||
ESP_LOGD(TAG, "InMemoryFile constructor called");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Constructor with filename and mode
|
||||
* @param filename Name of the file
|
||||
* @param mode Mode to open the file in
|
||||
*/
|
||||
InMemoryFile(const char* filename, FileMode mode)
|
||||
: data_ptr(&internal_data), name_(filename) {
|
||||
ESP_LOGD(TAG, "InMemoryFile constructor with name and mode called");
|
||||
open(mode);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Destructor - ensures proper cleanup
|
||||
*/
|
||||
virtual ~InMemoryFile() {
|
||||
ESP_LOGD(TAG, "InMemoryFile destructor called");
|
||||
close();
|
||||
if (!using_external_vector) {
|
||||
data_ptr = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set the vector to use for this file
|
||||
* @param vec Pointer to the vector to use
|
||||
*/
|
||||
void setVector(VectorType* vec) {
|
||||
ESP_LOGD(TAG, "Setting external vector pointer: %p", vec);
|
||||
if (vec) {
|
||||
data_ptr = vec;
|
||||
using_external_vector = true;
|
||||
} else {
|
||||
data_ptr = &internal_data;
|
||||
using_external_vector = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set the name of this file
|
||||
* @param name The name to set
|
||||
*/
|
||||
void setName(const char* name) {
|
||||
ESP_LOGD(TAG, "Setting file name: %s", name);
|
||||
name_ = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Open the file with the specified mode
|
||||
* @param mode Mode to open the file in
|
||||
* @return true if opened successfully, false otherwise
|
||||
*/
|
||||
bool open(FileMode mode) {
|
||||
ESP_LOGD(TAG, "Opening file '%s' with mode %d", name_.c_str(),
|
||||
static_cast<int>(mode));
|
||||
this->mode = mode;
|
||||
|
||||
if (mode == FileMode::WRITE) {
|
||||
// Clear the vector for writing
|
||||
data_ptr->clear();
|
||||
position_ = 0;
|
||||
} else if (mode == FileMode::APPEND) {
|
||||
// Position at end for appending
|
||||
position_ = data_ptr->size();
|
||||
} else {
|
||||
// READ or READ_WRITE - position at beginning
|
||||
position_ = 0;
|
||||
}
|
||||
|
||||
open_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Close the file
|
||||
*/
|
||||
void close() {
|
||||
ESP_LOGD(TAG, "Closing file '%s'", name_.c_str());
|
||||
open_ = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if file is open
|
||||
* @return true if open, false otherwise
|
||||
*/
|
||||
bool isOpen() const { return open_; }
|
||||
|
||||
/**
|
||||
* @brief Get the name of the file
|
||||
* @return File name
|
||||
*/
|
||||
String name() const { return name_; }
|
||||
|
||||
/**
|
||||
* @brief Get the size of the file
|
||||
* @return File size in bytes
|
||||
*/
|
||||
size_t size() const { return data_ptr->size(); }
|
||||
|
||||
// Stream interface implementation
|
||||
|
||||
/**
|
||||
* @brief Read a single byte from the file
|
||||
* @return The next byte, or -1 if no data is available
|
||||
*/
|
||||
int read() override {
|
||||
ESP_LOGD(TAG, "InMemoryFile::read() - position %u", (unsigned)position_);
|
||||
if (!open_ || (mode != FileMode::READ && mode != FileMode::READ_WRITE)) {
|
||||
ESP_LOGE(TAG, "read failed: file not open for reading");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (position_ >= data_ptr->size()) {
|
||||
ESP_LOGD(TAG, "read at EOF: pos=%u, size=%u", (unsigned)position_,
|
||||
(unsigned)data_ptr->size());
|
||||
return -1; // EOF
|
||||
}
|
||||
|
||||
uint8_t byte = (*data_ptr)[position_++];
|
||||
return byte;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Read multiple bytes from the file
|
||||
* @param buffer Buffer to store the read data
|
||||
* @param size Maximum number of bytes to read
|
||||
* @return Number of bytes actually read
|
||||
*/
|
||||
size_t readBytes(char* buffer, size_t size) override {
|
||||
ESP_LOGD(TAG, "InMemoryFile::readBytes: %u", (unsigned)size);
|
||||
if (!open_ || (mode != FileMode::READ && mode != FileMode::READ_WRITE)) {
|
||||
ESP_LOGE(TAG, "read failed: file not open for reading");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Calculate how many bytes we can actually read
|
||||
size_t available_bytes = data_ptr->size() - position_;
|
||||
size_t bytes_to_read = min(size, available_bytes);
|
||||
|
||||
// Read the bytes
|
||||
for (size_t i = 0; i < bytes_to_read; i++) {
|
||||
buffer[i] = (*data_ptr)[position_ + i];
|
||||
}
|
||||
|
||||
position_ += bytes_to_read;
|
||||
return bytes_to_read;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the number of bytes available to read
|
||||
* @return Number of bytes available
|
||||
*/
|
||||
int available() override {
|
||||
if (!open_) return 0;
|
||||
return data_ptr->size() - position_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Peek at the next byte without advancing the position
|
||||
* @return The next byte, or -1 if no data is available
|
||||
*/
|
||||
int peek() override {
|
||||
if (!open_ || (mode != FileMode::READ && mode != FileMode::READ_WRITE)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (position_ >= data_ptr->size()) {
|
||||
return -1; // EOF
|
||||
}
|
||||
|
||||
return (*data_ptr)[position_];
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Ensure all data is committed to the storage
|
||||
* No-op for this implementation since data is in memory
|
||||
*/
|
||||
void flush() override {
|
||||
// No-op for in-memory implementation
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Write a single byte to the file
|
||||
* @param b The byte to write
|
||||
* @return 1 if the byte was written, 0 otherwise
|
||||
*/
|
||||
size_t write(uint8_t b) override {
|
||||
ESP_LOGD(TAG, "InMemoryFile::write: 1 byte");
|
||||
if (!open_ || (mode != FileMode::WRITE && mode != FileMode::APPEND &&
|
||||
mode != FileMode::READ_WRITE)) {
|
||||
ESP_LOGE(TAG, "write failed: file not open for writing");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (position_ >= data_ptr->size()) {
|
||||
// Append to the end
|
||||
data_ptr->push_back(b);
|
||||
} else {
|
||||
// Replace existing byte
|
||||
(*data_ptr)[position_] = b;
|
||||
}
|
||||
|
||||
position_++;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Write multiple bytes to the file
|
||||
* @param buffer Buffer containing the data to write
|
||||
* @param size Number of bytes to write
|
||||
* @return Number of bytes actually written
|
||||
*/
|
||||
size_t write(const uint8_t* buffer, size_t size) override {
|
||||
ESP_LOGD(TAG, "InMemoryFile::write: %u", (unsigned)size);
|
||||
if (!open_ || (mode != FileMode::WRITE && mode != FileMode::APPEND &&
|
||||
mode != FileMode::READ_WRITE)) {
|
||||
ESP_LOGE(TAG, "write failed: file not open for writing");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (position_ >= data_ptr->size()) {
|
||||
// Append to the end
|
||||
data_ptr->reserve(data_ptr->size() + size); // Optimize capacity
|
||||
for (size_t i = 0; i < size; i++) {
|
||||
data_ptr->push_back(buffer[i]);
|
||||
}
|
||||
} else {
|
||||
// Replace/insert bytes
|
||||
size_t space_available = data_ptr->size() - position_;
|
||||
size_t bytes_to_replace = min(size, space_available);
|
||||
size_t bytes_to_append = size - bytes_to_replace;
|
||||
|
||||
// Replace existing bytes
|
||||
for (size_t i = 0; i < bytes_to_replace; i++) {
|
||||
(*data_ptr)[position_ + i] = buffer[i];
|
||||
}
|
||||
|
||||
// Append remaining bytes
|
||||
for (size_t i = 0; i < bytes_to_append; i++) {
|
||||
data_ptr->push_back(buffer[bytes_to_replace + i]);
|
||||
}
|
||||
}
|
||||
|
||||
position_ += size;
|
||||
return size;
|
||||
}
|
||||
|
||||
// File-specific methods
|
||||
|
||||
/**
|
||||
* @brief Set the current position in the file
|
||||
* @param pos The position to seek to
|
||||
* @return true if successful, false otherwise
|
||||
*/
|
||||
bool seek(size_t pos) {
|
||||
ESP_LOGD(TAG, "InMemoryFile::seek: %u", (unsigned)pos);
|
||||
if (!open_ || pos > data_ptr->size()) {
|
||||
ESP_LOGE(TAG, "seek failed: file not open or position beyond size");
|
||||
return false;
|
||||
}
|
||||
|
||||
position_ = pos;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the current position in the file
|
||||
* @return Current position
|
||||
*/
|
||||
size_t position() const { return position_; }
|
||||
|
||||
/**
|
||||
* @brief Truncate the file to the current position
|
||||
*/
|
||||
void truncate() {
|
||||
ESP_LOGD(TAG, "InMemoryFile::truncate at position %u", (unsigned)position_);
|
||||
if (!open_ || (mode != FileMode::WRITE && mode != FileMode::READ_WRITE)) {
|
||||
ESP_LOGE(TAG, "truncate failed: file not open for writing");
|
||||
return;
|
||||
}
|
||||
|
||||
if (position_ < data_ptr->size()) {
|
||||
// Create a new vector with just the data up to position_
|
||||
VectorType new_data;
|
||||
new_data.reserve(position_);
|
||||
for (size_t i = 0; i < position_; i++) {
|
||||
new_data.push_back((*data_ptr)[i]);
|
||||
}
|
||||
|
||||
// Replace the old data
|
||||
*data_ptr = std::move(new_data);
|
||||
}
|
||||
}
|
||||
|
||||
operator bool() const { return open_; }
|
||||
/**
|
||||
* @brief Set callback for navigating to the next file
|
||||
*
|
||||
* This function sets a callback that will be used to retrieve the next
|
||||
* file when getNextFile() is called. The callback takes the current file name
|
||||
* and mode, and returns the next file object directly.
|
||||
*
|
||||
* @param callback Function that returns the next file
|
||||
*/
|
||||
void setNextFileCallback(NextFileCallback callback) {
|
||||
ESP_LOGD(TAG, "Setting next file callback");
|
||||
nextFileCallback = callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the next file in the filesystem
|
||||
*
|
||||
* This method uses the registered callback to get the next file
|
||||
* after this one in the filesystem.
|
||||
*
|
||||
* @return The next file, or an empty file if there is no next file or
|
||||
* callback isn't set
|
||||
*/
|
||||
InMemoryFile<VectorType> getNextFile() {
|
||||
if (!nextFileCallback || name_.isEmpty()) {
|
||||
// Return empty file if callback isn't set or no name
|
||||
InMemoryFile<VectorType> emptyFile;
|
||||
return emptyFile;
|
||||
}
|
||||
|
||||
// Get the next file directly using the callback
|
||||
return nextFileCallback(name_.c_str(), mode);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Reserve storage
|
||||
* @param new_cap The new capacity of the file
|
||||
*/
|
||||
bool reserve(size_t new_cap) {
|
||||
if (data_ptr == nullptr) return false;
|
||||
data_ptr->reserve(new_cap);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the number of bytes that can be held in current storage.
|
||||
* Please note that this might dynamically grow!
|
||||
* @return The capacity of the file
|
||||
*/
|
||||
size_t capacity() const {
|
||||
if (data_ptr == nullptr) return 0;
|
||||
return data_ptr->capacity();
|
||||
}
|
||||
|
||||
private:
|
||||
VectorType* data_ptr =
|
||||
nullptr; // Pointer to vector data (internal or external)
|
||||
VectorType internal_data; // Internal vector for standalone use
|
||||
bool using_external_vector = false;
|
||||
size_t position_ = 0;
|
||||
bool open_ = false;
|
||||
FileMode mode = FileMode::READ;
|
||||
String name_;
|
||||
|
||||
// Single callback for getting the next file
|
||||
NextFileCallback nextFileCallback = nullptr;
|
||||
|
||||
// Tags for debug logging
|
||||
static constexpr const char* TAG = "InMemoryFile";
|
||||
};
|
||||
|
||||
// Type aliases for convenience
|
||||
using FilePSRAM = InMemoryFile<VectorPSRAM<uint8_t>>;
|
||||
using FileHIMEM = InMemoryFile<VectorHIMEM<uint8_t>>;
|
||||
|
||||
} // namespace esp32_psram
|
||||
54
lib/esp32-psram/src/esp32-psram/PSRAM.h_
Normal file
54
lib/esp32-psram/src/esp32-psram/PSRAM.h_
Normal file
@@ -0,0 +1,54 @@
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "InMemoryFS.h"
|
||||
#include "VectorPSRAM.h"
|
||||
|
||||
|
||||
namespace esp32_psram {
|
||||
|
||||
/**
|
||||
* @class PSRAMClass
|
||||
* @brief Class for managing files stored in ESP32's PSRAM
|
||||
*
|
||||
* This class provides an interface similar to SD.h for managing files
|
||||
* that are stored in PSRAM memory rather than on an SD card.
|
||||
*/
|
||||
class PSRAMClass : public InMemoryFS<VectorPSRAM<uint8_t>, FilePSRAM> {
|
||||
public:
|
||||
/**
|
||||
* @brief Initialize the PSRAM filesystem
|
||||
* @return true if initialization was successful, false otherwise
|
||||
*/
|
||||
bool begin() override {
|
||||
if (ESP.getFreePsram() > 0) {
|
||||
initialized = true;
|
||||
return true;
|
||||
}
|
||||
initialized = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get total space (returns available PSRAM)
|
||||
* @return Total PSRAM size in bytes
|
||||
*/
|
||||
uint64_t totalBytes() override {
|
||||
return ESP.getPsramSize();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get free space (returns free PSRAM)
|
||||
* @return Free PSRAM size in bytes
|
||||
*/
|
||||
uint64_t freeBytes() override {
|
||||
return ESP.getFreePsram();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Global instance of PSRAMClass for easy access
|
||||
*/
|
||||
static PSRAMClass PSRAM;
|
||||
|
||||
} // namespace esp32_psram
|
||||
250
lib/esp32-psram/src/esp32-psram/RingBufferStream.h
Normal file
250
lib/esp32-psram/src/esp32-psram/RingBufferStream.h
Normal file
@@ -0,0 +1,250 @@
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <Stream.h>
|
||||
#include "VectorPSRAM.h"
|
||||
#include "VectorHIMEM.h"
|
||||
|
||||
namespace esp32_psram {
|
||||
|
||||
/**
|
||||
* @class RingBufferStream
|
||||
* @brief A circular buffer implementation that extends Arduino's Stream
|
||||
* @tparam VectorType The vector type to use as underlying storage
|
||||
*
|
||||
* This class implements a ring buffer (circular buffer) that uses a vector container
|
||||
* for storage. It extends Arduino's Stream class to provide standard stream functionality.
|
||||
* The buffer can be used with any vector type, including VectorPSRAM and VectorHIMEM.
|
||||
*/
|
||||
template <typename VectorType>
|
||||
class RingBufferStream : public Stream {
|
||||
private:
|
||||
VectorType buffer;
|
||||
size_t readIndex = 0;
|
||||
size_t writeIndex = 0;
|
||||
bool full = false;
|
||||
size_t maxSize;
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor with specified buffer size
|
||||
* @param size The size of the buffer in bytes
|
||||
*/
|
||||
RingBufferStream(size_t size) : maxSize(size) {
|
||||
buffer.resize(size);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the number of bytes available for reading
|
||||
* @return Number of bytes available
|
||||
*/
|
||||
int available() override {
|
||||
if (full) {
|
||||
return maxSize;
|
||||
}
|
||||
if (writeIndex >= readIndex) {
|
||||
return writeIndex - readIndex;
|
||||
} else {
|
||||
return maxSize - (readIndex - writeIndex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Read a byte from the buffer
|
||||
* @return The byte read, or -1 if the buffer is empty
|
||||
*/
|
||||
int read() override {
|
||||
if (isEmpty()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
uint8_t value = buffer[readIndex];
|
||||
readIndex = (readIndex + 1) % maxSize;
|
||||
full = false;
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Look at the next byte in the buffer without removing it
|
||||
* @return The next byte to be read, or -1 if the buffer is empty
|
||||
*/
|
||||
int peek() override {
|
||||
if (isEmpty()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return buffer[readIndex];
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Write a byte to the buffer
|
||||
* @param value The byte to write
|
||||
* @return 1 if the byte was written, 0 if the buffer is full
|
||||
*/
|
||||
size_t write(uint8_t value) override {
|
||||
if (full) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
buffer[writeIndex] = value;
|
||||
writeIndex = (writeIndex + 1) % maxSize;
|
||||
|
||||
// Check if buffer is now full
|
||||
if (writeIndex == readIndex) {
|
||||
full = true;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Write multiple bytes to the buffer
|
||||
* @param data Pointer to the data to write
|
||||
* @param size Number of bytes to write
|
||||
* @return Number of bytes written
|
||||
*/
|
||||
size_t write(const uint8_t *data, size_t size) {
|
||||
size_t bytesWritten = 0;
|
||||
|
||||
for (size_t i = 0; i < size; i++) {
|
||||
if (write(data[i])) {
|
||||
bytesWritten++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return bytesWritten;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Clear the buffer, removing all content
|
||||
*/
|
||||
void flush() override {
|
||||
readIndex = 0;
|
||||
writeIndex = 0;
|
||||
full = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if the buffer is empty
|
||||
* @return true if the buffer is empty, false otherwise
|
||||
*/
|
||||
bool isEmpty() const {
|
||||
return !full && (readIndex == writeIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if the buffer is full
|
||||
* @return true if the buffer is full, false otherwise
|
||||
*/
|
||||
bool isFull() const {
|
||||
return full;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the number of bytes that can be written without blocking
|
||||
* @return Number of bytes available for writing
|
||||
*/
|
||||
int availableForWrite() override {
|
||||
if (full) {
|
||||
return 0;
|
||||
}
|
||||
if (readIndex > writeIndex) {
|
||||
return readIndex - writeIndex;
|
||||
} else {
|
||||
return maxSize - (writeIndex - readIndex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the total capacity of the buffer
|
||||
* @return Total buffer capacity in bytes
|
||||
*/
|
||||
size_t size() const {
|
||||
return maxSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the number of bytes currently in the buffer
|
||||
* @return Number of bytes in the buffer
|
||||
*/
|
||||
size_t used() const {
|
||||
return available();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the number of bytes still available in the buffer
|
||||
* @return Number of free bytes in the buffer
|
||||
*/
|
||||
size_t free() const {
|
||||
return maxSize - available();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Read multiple bytes from the buffer
|
||||
* @param buffer Buffer to store the read data
|
||||
* @param size Maximum number of bytes to read
|
||||
* @return Number of bytes actually read
|
||||
*/
|
||||
size_t readBytes(char* buffer, size_t size) override {
|
||||
size_t bytesRead = 0;
|
||||
|
||||
for (size_t i = 0; i < size; i++) {
|
||||
int value = read();
|
||||
if (value >= 0) {
|
||||
buffer[i] = (char)value;
|
||||
bytesRead++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return bytesRead;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Read multiple bytes from the buffer
|
||||
* @param buffer Buffer to store the read data
|
||||
* @param size Maximum number of bytes to read
|
||||
* @return Number of bytes actually read
|
||||
*/
|
||||
size_t readBytes(uint8_t* buffer, size_t size) {
|
||||
return readBytes(reinterpret_cast<char*>(buffer), size);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get direct access to the underlying vector
|
||||
* @return Reference to the underlying vector
|
||||
*/
|
||||
VectorType& getVector() {
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get const access to the underlying vector
|
||||
* @return Const reference to the underlying vector
|
||||
*/
|
||||
const VectorType& getVector() const {
|
||||
return buffer;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Type alias for a RingBufferStream that uses PSRAM-backed vector storage
|
||||
*/
|
||||
using RingBufferStreamPSRAM = RingBufferStream<VectorPSRAM<uint8_t>>;
|
||||
|
||||
/**
|
||||
* @brief Type alias for a RingBufferStream that uses HIMEM-backed vector storage
|
||||
*/
|
||||
using RingBufferStreamHIMEM = RingBufferStream<VectorHIMEM<uint8_t>>;
|
||||
|
||||
/**
|
||||
* @brief Type alias for a RingBufferStream that uses std::vector storage
|
||||
*/
|
||||
|
||||
using RingBufferStreamRAM = RingBufferStream<std::vector<uint8_t>>;
|
||||
|
||||
|
||||
} // namespace esp32_psram
|
||||
227
lib/esp32-psram/src/esp32-psram/TypedRingBuffer.h
Normal file
227
lib/esp32-psram/src/esp32-psram/TypedRingBuffer.h
Normal file
@@ -0,0 +1,227 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <vector>
|
||||
#include "VectorPSRAM.h"
|
||||
#include "VectorHIMEM.h"
|
||||
|
||||
namespace esp32_psram {
|
||||
|
||||
/**
|
||||
* @class TypedRingBuffer
|
||||
* @brief A generic ring buffer implementation for any data type
|
||||
* @tparam T The data type to store in the buffer
|
||||
* @tparam VectorType The vector type to use as underlying storage
|
||||
*
|
||||
* This class implements a ring buffer (circular buffer) that uses a vector container
|
||||
* for storage. Unlike the Stream-based RingBuffer, this can store any data type.
|
||||
*/
|
||||
template <typename T, typename VectorType>
|
||||
class TypedRingBuffer {
|
||||
private:
|
||||
VectorType buffer;
|
||||
size_t readIndex = 0;
|
||||
size_t writeIndex = 0;
|
||||
bool full = false;
|
||||
size_t maxSize;
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor with specified buffer capacity
|
||||
* @param capacity The maximum number of elements the buffer can hold
|
||||
*/
|
||||
TypedRingBuffer(size_t capacity) : maxSize(capacity) {
|
||||
buffer.resize(capacity);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Push an element to the buffer
|
||||
* @param value The value to add
|
||||
* @return true if the element was added, false if the buffer is full
|
||||
*/
|
||||
bool push(const T& value) {
|
||||
if (full) {
|
||||
return false;
|
||||
}
|
||||
|
||||
buffer[writeIndex] = value;
|
||||
advanceWriteIndex();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Push an element to the buffer, overwriting oldest data if full
|
||||
* @param value The value to add
|
||||
* @return true if an old element was overwritten, false otherwise
|
||||
*/
|
||||
bool pushOverwrite(const T& value) {
|
||||
bool overwritten = full;
|
||||
|
||||
buffer[writeIndex] = value;
|
||||
advanceWriteIndex();
|
||||
|
||||
return overwritten;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Pop an element from the buffer
|
||||
* @param value Reference to store the popped value
|
||||
* @return true if an element was popped, false if the buffer is empty
|
||||
*/
|
||||
bool pop(T& value) {
|
||||
if (isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
value = buffer[readIndex];
|
||||
advanceReadIndex();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Peek at the next element without removing it
|
||||
* @param value Reference to store the peeked value
|
||||
* @return true if an element was peeked, false if the buffer is empty
|
||||
*/
|
||||
bool peek(T& value) const {
|
||||
if (isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
value = buffer[readIndex];
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the element at a specific index relative to the read position
|
||||
* @param index The relative index from current read position
|
||||
* @param value Reference to store the value
|
||||
* @return true if the element exists, false otherwise
|
||||
*/
|
||||
bool peekAt(size_t index, T& value) const {
|
||||
if (isEmpty() || index >= available()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t actualIndex = (readIndex + index) % maxSize;
|
||||
value = buffer[actualIndex];
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Clear the buffer, removing all content
|
||||
*/
|
||||
void clear() {
|
||||
readIndex = 0;
|
||||
writeIndex = 0;
|
||||
full = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if the buffer is empty
|
||||
* @return true if the buffer is empty, false otherwise
|
||||
*/
|
||||
bool isEmpty() const {
|
||||
return !full && (readIndex == writeIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if the buffer is full
|
||||
* @return true if the buffer is full, false otherwise
|
||||
*/
|
||||
bool isFull() const {
|
||||
return full;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the number of elements in the buffer
|
||||
* @return Number of elements in the buffer
|
||||
*/
|
||||
size_t available() const {
|
||||
if (full) {
|
||||
return maxSize;
|
||||
}
|
||||
if (writeIndex >= readIndex) {
|
||||
return writeIndex - readIndex;
|
||||
} else {
|
||||
return maxSize - (readIndex - writeIndex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the number of empty slots in the buffer
|
||||
* @return Number of empty slots
|
||||
*/
|
||||
size_t availableForWrite() const {
|
||||
return maxSize - available();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the total capacity of the buffer
|
||||
* @return Total buffer capacity
|
||||
*/
|
||||
size_t capacity() const {
|
||||
return maxSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get direct access to the underlying vector
|
||||
* @return Reference to the underlying vector
|
||||
*/
|
||||
VectorType& getVector() {
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get const access to the underlying vector
|
||||
* @return Const reference to the underlying vector
|
||||
*/
|
||||
const VectorType& getVector() const {
|
||||
return buffer;
|
||||
}
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Advance the write index
|
||||
*/
|
||||
void advanceWriteIndex() {
|
||||
writeIndex = (writeIndex + 1) % maxSize;
|
||||
if (writeIndex == readIndex) {
|
||||
full = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Advance the read index
|
||||
*/
|
||||
void advanceReadIndex() {
|
||||
readIndex = (readIndex + 1) % maxSize;
|
||||
full = false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Type alias for a typed ring buffer that uses std::vector (in RAM)
|
||||
*/
|
||||
template<typename T>
|
||||
using TypedRingBufferRAM = TypedRingBuffer<T, std::vector<T>>;
|
||||
/**
|
||||
* @brief Type alias for a typed ring buffer that uses HIMEM-backed vector storage
|
||||
*/
|
||||
template<typename T>
|
||||
using TypedRingBufferHIMEM = TypedRingBuffer<T, VectorHIMEM<T>>;
|
||||
|
||||
/**
|
||||
* @brief Type alias for a typed ring buffer that uses PSRAM-backed vector storage
|
||||
*/
|
||||
template<typename T>
|
||||
using TypedRingBufferPSRAM = TypedRingBuffer<T, VectorPSRAM<T>>;
|
||||
|
||||
/**
|
||||
* @brief Type alias for a typed ring buffer that uses HIMEM-backed vector storage
|
||||
*/
|
||||
// template<typename T>
|
||||
// using TypedRingBufferHIMEM = TypedRingBuffer<T, VectorHIMEM<T>>;
|
||||
|
||||
} // namespace esp32_psram
|
||||
529
lib/esp32-psram/src/esp32-psram/VectorHIMEM.h
Normal file
529
lib/esp32-psram/src/esp32-psram/VectorHIMEM.h
Normal file
@@ -0,0 +1,529 @@
|
||||
#pragma once
|
||||
|
||||
#include "HimemBlock.h"
|
||||
|
||||
namespace esp32_psram {
|
||||
|
||||
|
||||
/**
|
||||
* @class VectorHIMEM
|
||||
* @brief Vector implementation that uses ESP32's high memory (himem) for
|
||||
* storage
|
||||
* @tparam T Type of elements stored in the vector
|
||||
*/
|
||||
template <typename T>
|
||||
class VectorHIMEM {
|
||||
public:
|
||||
// Type definitions
|
||||
using value_type = T;
|
||||
using reference = T&;
|
||||
using const_reference = const T&;
|
||||
using pointer = T*;
|
||||
using const_pointer = const T*;
|
||||
using size_type = size_t;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
|
||||
/**
|
||||
* @brief Default constructor - creates an empty vector
|
||||
*/
|
||||
VectorHIMEM() = default;
|
||||
|
||||
/**
|
||||
* @brief Constructs a vector with the given number of default-initialized
|
||||
* elements
|
||||
* @param count The size of the vector
|
||||
*/
|
||||
explicit VectorHIMEM(size_type count) { resize(count); }
|
||||
|
||||
/**
|
||||
* @brief Constructs a vector with the given number of copies of a value
|
||||
* @param count The size of the vector
|
||||
* @param value The value to initialize elements with
|
||||
*/
|
||||
VectorHIMEM(size_type count, const T& value) { resize(count, value); }
|
||||
|
||||
/**
|
||||
* @brief Copy constructor
|
||||
* @param other The vector to copy from
|
||||
*/
|
||||
VectorHIMEM(const VectorHIMEM& other) {
|
||||
if (other.element_count > 0) {
|
||||
if (reallocate(other.element_count)) {
|
||||
T temp;
|
||||
VectorHIMEM& other_ = const_cast<VectorHIMEM&>(other);
|
||||
for (size_t i = 0; i < other.element_count; ++i) {
|
||||
other_.memory.read((void*)&temp, i * sizeof(T), sizeof(T));
|
||||
memory.write((void*)&temp, i * sizeof(T), sizeof(T));
|
||||
}
|
||||
element_count = other.element_count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Move constructor
|
||||
* @param other The vector to move from
|
||||
*/
|
||||
VectorHIMEM(VectorHIMEM&& other) noexcept
|
||||
: memory(std::move(other.memory)),
|
||||
element_count(other.element_count),
|
||||
element_capacity(other.element_capacity) {
|
||||
other.element_count = 0;
|
||||
other.element_capacity = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Initializer list constructor
|
||||
* @param init The initializer list to copy from
|
||||
*/
|
||||
VectorHIMEM(std::initializer_list<T> init) {
|
||||
if (init.size() > 0) {
|
||||
if (reallocate(init.size())) {
|
||||
size_t i = 0;
|
||||
for (const auto& item : init) {
|
||||
memory.write(&item, i * sizeof(T), sizeof(T));
|
||||
++i;
|
||||
}
|
||||
element_count = init.size();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Destructor - ensures all elements are properly destroyed
|
||||
*/
|
||||
~VectorHIMEM() { clear(); }
|
||||
|
||||
/**
|
||||
* @brief Copy assignment operator
|
||||
* @param other The vector to copy from
|
||||
* @return Reference to this vector
|
||||
*/
|
||||
VectorHIMEM& operator=(const VectorHIMEM& other) {
|
||||
if (this != &other) {
|
||||
clear();
|
||||
if (other.element_count > 0) {
|
||||
if (reallocate(other.element_count)) {
|
||||
T temp;
|
||||
for (size_t i = 0; i < other.element_count; ++i) {
|
||||
const_cast<esp32_psram::HimemBlock*>(&other.memory)->read(&temp, i * sizeof(T), sizeof(T));
|
||||
memory.write(&temp, i * sizeof(T), sizeof(T));
|
||||
}
|
||||
element_count = other.element_count;
|
||||
}
|
||||
}
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Move assignment operator
|
||||
* @param other The vector to move from
|
||||
* @return Reference to this vector
|
||||
*/
|
||||
VectorHIMEM& operator=(VectorHIMEM&& other) noexcept {
|
||||
if (this != &other) {
|
||||
clear();
|
||||
memory = std::move(other.memory);
|
||||
element_count = other.element_count;
|
||||
element_capacity = other.element_capacity;
|
||||
other.element_count = 0;
|
||||
other.element_capacity = 0;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Initializer list assignment operator
|
||||
* @param ilist The initializer list to copy from
|
||||
* @return Reference to this vector
|
||||
*/
|
||||
VectorHIMEM& operator=(std::initializer_list<T> ilist) {
|
||||
clear();
|
||||
if (ilist.size() > 0) {
|
||||
if (reallocate(ilist.size())) {
|
||||
size_t i = 0;
|
||||
for (const auto& item : ilist) {
|
||||
memory.write(&item, i * sizeof(T), sizeof(T));
|
||||
++i;
|
||||
}
|
||||
element_count = ilist.size();
|
||||
}
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Access element with bounds checking
|
||||
* @param pos The position of the element
|
||||
* @return Reference to the element at position pos
|
||||
* @throws std::out_of_range if pos is not within the range of the vector
|
||||
*/
|
||||
reference at(size_type pos) {
|
||||
if (pos >= element_count) {
|
||||
throw std::out_of_range("VectorHIMEM: index out of range");
|
||||
}
|
||||
static T temp;
|
||||
memory.read(&temp, pos * sizeof(T), sizeof(T));
|
||||
return temp;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Access element with bounds checking (const version)
|
||||
* @param pos The position of the element
|
||||
* @return Const reference to the element at position pos
|
||||
* @throws std::out_of_range if pos is not within the range of the vector
|
||||
*/
|
||||
const_reference at(size_type pos) const {
|
||||
if (pos >= element_count) {
|
||||
throw std::out_of_range("VectorHIMEM: index out of range");
|
||||
}
|
||||
static T temp;
|
||||
memory.read(&temp, pos * sizeof(T), sizeof(T));
|
||||
return temp;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Access element without bounds checking
|
||||
* @param pos The position of the element
|
||||
* @return Reference to the element at position pos
|
||||
*/
|
||||
reference operator[](size_type pos) {
|
||||
static T temp;
|
||||
memory.read(&temp, pos * sizeof(T), sizeof(T));
|
||||
return temp;
|
||||
}
|
||||
|
||||
// Add const version of operator[]
|
||||
const_reference operator[](size_type pos) const {
|
||||
static T result;
|
||||
// Need to cast away const for the read operation since it doesn't modify content
|
||||
HimemBlock& non_const_memory = const_cast<HimemBlock&>(memory);
|
||||
non_const_memory.read(&result, pos * sizeof(T), sizeof(T));
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Access the first element
|
||||
* @return Reference to the first element
|
||||
*/
|
||||
reference front() {
|
||||
static T temp;
|
||||
memory.read(&temp, 0, sizeof(T));
|
||||
return temp;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Access the first element (const version)
|
||||
* @return Const reference to the first element
|
||||
*/
|
||||
const_reference front() const {
|
||||
static T temp;
|
||||
memory.read(&temp, 0, sizeof(T));
|
||||
return temp;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Access the last element
|
||||
* @return Reference to the last element
|
||||
*/
|
||||
reference back() {
|
||||
static T temp;
|
||||
memory.read(&temp, (element_count - 1) * sizeof(T), sizeof(T));
|
||||
return temp;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Access the last element (const version)
|
||||
* @return Const reference to the last element
|
||||
*/
|
||||
const_reference back() const {
|
||||
static T temp;
|
||||
memory.read(&temp, (element_count - 1) * sizeof(T), sizeof(T));
|
||||
return temp;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if the vector is empty
|
||||
* @return true if the vector is empty, false otherwise
|
||||
*/
|
||||
bool empty() const { return element_count == 0; }
|
||||
|
||||
/**
|
||||
* @brief Get the number of elements
|
||||
* @return The number of elements in the vector
|
||||
*/
|
||||
size_type size() const { return element_count; }
|
||||
|
||||
/**
|
||||
* @brief Get the maximum possible number of elements
|
||||
* @return The maximum possible number of elements the vector can hold
|
||||
*/
|
||||
size_type max_size() const {
|
||||
return std::numeric_limits<size_type>::max() / sizeof(T);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Reserve storage
|
||||
* @param new_cap The new capacity of the vector
|
||||
*/
|
||||
void reserve(size_type new_cap) {
|
||||
if (new_cap > element_capacity) {
|
||||
reallocate(new_cap);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the number of elements that can be held in current storage
|
||||
* @return The capacity of the vector
|
||||
*/
|
||||
size_type capacity() const { return element_capacity; }
|
||||
|
||||
/**
|
||||
* @brief Clear the contents
|
||||
*/
|
||||
void clear() {
|
||||
// We're using plain memory so no destructors to call
|
||||
element_count = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Add an element to the end
|
||||
* @param value The value to append
|
||||
*/
|
||||
void push_back(const T& value) {
|
||||
if (element_count >= element_capacity) {
|
||||
size_t new_capacity =
|
||||
element_capacity == 0 ? min_elements : element_capacity * 2;
|
||||
if (!reallocate(new_capacity)) {
|
||||
ESP_LOGE(TAG, "Failed to reallocate for push_back");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Write the new element at the end
|
||||
memory.write(&value, element_count * sizeof(T), sizeof(T));
|
||||
++element_count;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Add an element to the end by moving it
|
||||
* @param value The value to append
|
||||
*/
|
||||
void push_back(T&& value) {
|
||||
// For POD types, this is the same as the const& version
|
||||
push_back(static_cast<const T&>(value));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Remove the last element
|
||||
*/
|
||||
void pop_back() {
|
||||
if (element_count > 0) {
|
||||
--element_count;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Change the number of elements stored
|
||||
* @param count The new size of the vector
|
||||
*/
|
||||
void resize(size_type count) {
|
||||
if (count > element_capacity) {
|
||||
if (!reallocate(count)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
element_count = count;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Change the number of elements stored
|
||||
* @param count The new size of the vector
|
||||
* @param value The value to initialize new elements with
|
||||
*/
|
||||
void resize(size_type count, const value_type& value) {
|
||||
size_t old_size = element_count;
|
||||
|
||||
if (count > element_count) {
|
||||
// Need to add elements
|
||||
if (count > element_capacity) {
|
||||
if (!reallocate(count)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize new elements with value
|
||||
for (size_t i = old_size; i < count; ++i) {
|
||||
memory.write(&value, i * sizeof(T), sizeof(T));
|
||||
}
|
||||
}
|
||||
|
||||
element_count = count;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Erase an element
|
||||
* @param pos Index of the element to erase
|
||||
*/
|
||||
void erase(size_type pos) {
|
||||
if (pos >= element_count) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Move each element down by one, overwriting the erased element
|
||||
T temp;
|
||||
for (size_t i = pos + 1; i < element_count; ++i) {
|
||||
memory.read(&temp, i * sizeof(T), sizeof(T));
|
||||
memory.write(&temp, (i - 1) * sizeof(T), sizeof(T));
|
||||
}
|
||||
|
||||
--element_count;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Insert an element at a specific position
|
||||
* @param pos Position where the element should be inserted
|
||||
* @param value The value to insert
|
||||
*/
|
||||
void insert(size_type pos, const T& value) {
|
||||
if (pos > element_count) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (element_count >= element_capacity) {
|
||||
size_t new_capacity =
|
||||
element_capacity == 0 ? min_elements : element_capacity * 2;
|
||||
if (!reallocate(new_capacity)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Move elements up to make space
|
||||
T temp;
|
||||
for (size_t i = element_count; i > pos; --i) {
|
||||
memory.read(&temp, (i - 1) * sizeof(T), sizeof(T));
|
||||
memory.write(&temp, i * sizeof(T), sizeof(T));
|
||||
}
|
||||
|
||||
// Insert new element
|
||||
memory.write(&value, pos * sizeof(T), sizeof(T));
|
||||
++element_count;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Swap the contents of this vector with another
|
||||
* @param other Vector to swap with
|
||||
*/
|
||||
void swap(VectorHIMEM& other) noexcept {
|
||||
std::swap(memory, other.memory);
|
||||
std::swap(element_count, other.element_count);
|
||||
std::swap(element_capacity, other.element_capacity);
|
||||
}
|
||||
|
||||
protected:
|
||||
HimemBlock memory;
|
||||
size_t element_count = 0;
|
||||
size_t element_capacity = 0;
|
||||
static constexpr size_t min_elements = 16; // Minimum allocation size
|
||||
|
||||
/**
|
||||
* @brief Calculate required memory size in bytes for a given number of
|
||||
* elements
|
||||
* @param count Number of elements
|
||||
* @return Size in bytes
|
||||
*/
|
||||
static size_t calculate_size_bytes(size_t count) { return count * sizeof(T); }
|
||||
|
||||
/**
|
||||
* @brief Reallocate memory with a new capacity
|
||||
* @param new_capacity The new capacity to allocate
|
||||
* @return true if reallocation was successful, false otherwise
|
||||
*/
|
||||
bool reallocate(size_t new_capacity) {
|
||||
if (new_capacity <= element_capacity) {
|
||||
return true; // No need to reallocate
|
||||
}
|
||||
|
||||
// Calculate new size (at least min_elements)
|
||||
new_capacity = std::max(new_capacity, min_elements);
|
||||
|
||||
// Create a new memory block
|
||||
HimemBlock new_memory;
|
||||
element_capacity = new_memory.allocate(calculate_size_bytes(new_capacity)) / sizeof(T);
|
||||
if (element_capacity == 0) {
|
||||
ESP_LOGE(TAG, "Failed to allocate new memory block");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Copy existing elements if any
|
||||
if (element_count > 0) {
|
||||
T temp;
|
||||
for (size_t i = 0; i < element_count; ++i) {
|
||||
// Read from old memory
|
||||
memory.read(&temp, i * sizeof(T), sizeof(T));
|
||||
|
||||
// Write to new memory
|
||||
new_memory.write(&temp, i * sizeof(T), sizeof(T));
|
||||
}
|
||||
}
|
||||
|
||||
// Swap the memory blocks
|
||||
memory = std::move(new_memory);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Equality comparison operator
|
||||
* @tparam T Type of elements in the vectors
|
||||
* @param lhs First vector
|
||||
* @param rhs Second vector
|
||||
* @return true if the vectors are equal, false otherwise
|
||||
*/
|
||||
template <typename T>
|
||||
bool operator==(const VectorHIMEM<T>& lhs, const VectorHIMEM<T>& rhs) {
|
||||
if (lhs.size() != rhs.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
T lhs_val, rhs_val;
|
||||
for (size_t i = 0; i < lhs.size(); ++i) {
|
||||
lhs_val = lhs[i]; // Uses operator[] which calls read()
|
||||
rhs_val = rhs[i];
|
||||
if (!(lhs_val == rhs_val)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Inequality comparison operator
|
||||
* @tparam T Type of elements in the vectors
|
||||
* @param lhs First vector
|
||||
* @param rhs Second vector
|
||||
* @return true if the vectors are not equal, false otherwise
|
||||
*/
|
||||
template <typename T>
|
||||
bool operator!=(const VectorHIMEM<T>& lhs, const VectorHIMEM<T>& rhs) {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Swap the contents of two vectors
|
||||
* @tparam T Type of elements in the vectors
|
||||
* @param lhs First vector
|
||||
* @param rhs Second vector
|
||||
*/
|
||||
template <typename T>
|
||||
void swap(VectorHIMEM<T>& lhs, VectorHIMEM<T>& rhs) noexcept {
|
||||
lhs.swap(rhs);
|
||||
}
|
||||
|
||||
} // namespace esp32_psram
|
||||
521
lib/esp32-psram/src/esp32-psram/VectorPSRAM.h
Normal file
521
lib/esp32-psram/src/esp32-psram/VectorPSRAM.h
Normal file
@@ -0,0 +1,521 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <limits>
|
||||
#include <algorithm>
|
||||
#include "AllocatorPSRAM.h"
|
||||
|
||||
/**
|
||||
* @namespace esp32_psram
|
||||
* @brief Namespace containing ESP32 PSRAM-specific implementations
|
||||
*/
|
||||
namespace esp32_psram {
|
||||
|
||||
|
||||
/**
|
||||
* @brief Equality comparison operator for allocators
|
||||
* @tparam T Type of first allocator
|
||||
* @tparam U Type of second allocator
|
||||
* @return Always true since all instances are equivalent
|
||||
*/
|
||||
template <typename T, typename U>
|
||||
bool operator==(const AllocatorPSRAM<T>&, const AllocatorPSRAM<U>&) noexcept {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Inequality comparison operator for allocators
|
||||
* @tparam T Type of first allocator
|
||||
* @tparam U Type of second allocator
|
||||
* @return Always false since all instances are equivalent
|
||||
*/
|
||||
template <typename T, typename U>
|
||||
bool operator!=(const AllocatorPSRAM<T>&, const AllocatorPSRAM<U>&) noexcept {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @class VectorPSRAM
|
||||
* @brief Vector implementation that uses ESP32's PSRAM for storage
|
||||
* @tparam T Type of elements stored in the vector
|
||||
*
|
||||
* This class provides an interface identical to std::vector but allocates
|
||||
* all memory in ESP32's PSRAM, which helps preserve the limited internal RAM.
|
||||
* It wraps std::vector with a custom allocator that uses PSRAM.
|
||||
*/
|
||||
template <typename T>
|
||||
class VectorPSRAM {
|
||||
private:
|
||||
using vector_type = std::vector<T, AllocatorPSRAM<T>>;
|
||||
vector_type vec;
|
||||
|
||||
public:
|
||||
// Type definitions
|
||||
using value_type = typename vector_type::value_type;
|
||||
using allocator_type = typename vector_type::allocator_type;
|
||||
using size_type = typename vector_type::size_type;
|
||||
using difference_type = typename vector_type::difference_type;
|
||||
using reference = typename vector_type::reference;
|
||||
using const_reference = typename vector_type::const_reference;
|
||||
using pointer = typename vector_type::pointer;
|
||||
using const_pointer = typename vector_type::const_pointer;
|
||||
using iterator = typename vector_type::iterator;
|
||||
using const_iterator = typename vector_type::const_iterator;
|
||||
using reverse_iterator = typename vector_type::reverse_iterator;
|
||||
using const_reverse_iterator = typename vector_type::const_reverse_iterator;
|
||||
|
||||
/**
|
||||
* @brief Default constructor - creates an empty vector
|
||||
*/
|
||||
VectorPSRAM() : vec(AllocatorPSRAM<T>()) {}
|
||||
|
||||
/**
|
||||
* @brief Constructs a vector with the given number of default-initialized elements
|
||||
* @param count The size of the vector
|
||||
*/
|
||||
explicit VectorPSRAM(size_type count) : vec(count, AllocatorPSRAM<T>()) {}
|
||||
|
||||
/**
|
||||
* @brief Constructs a vector with the given number of copies of a value
|
||||
* @param count The size of the vector
|
||||
* @param value The value to initialize elements with
|
||||
*/
|
||||
VectorPSRAM(size_type count, const T& value) : vec(count, value, AllocatorPSRAM<T>()) {}
|
||||
|
||||
/**
|
||||
* @brief Constructs a vector with the contents of the range [first, last)
|
||||
* @tparam InputIt Input iterator type
|
||||
* @param first Iterator to the first element in the range
|
||||
* @param last Iterator to one past the last element in the range
|
||||
*/
|
||||
template <typename InputIt>
|
||||
VectorPSRAM(InputIt first, InputIt last) : vec(first, last, AllocatorPSRAM<T>()) {}
|
||||
|
||||
/**
|
||||
* @brief Copy constructor
|
||||
* @param other The vector to copy from
|
||||
*/
|
||||
VectorPSRAM(const VectorPSRAM& other) : vec(other.vec) {}
|
||||
|
||||
/**
|
||||
* @brief Move constructor
|
||||
* @param other The vector to move from
|
||||
*/
|
||||
VectorPSRAM(VectorPSRAM&& other) noexcept : vec(std::move(other.vec)) {}
|
||||
|
||||
/**
|
||||
* @brief Initializer list constructor
|
||||
* @param init The initializer list to copy from
|
||||
*/
|
||||
VectorPSRAM(std::initializer_list<T> init) : vec(init, AllocatorPSRAM<T>()) {}
|
||||
|
||||
/**
|
||||
* @brief Copy assignment operator
|
||||
* @param other The vector to copy from
|
||||
* @return Reference to this vector
|
||||
*/
|
||||
VectorPSRAM& operator=(const VectorPSRAM& other) {
|
||||
vec = other.vec;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Move assignment operator
|
||||
* @param other The vector to move from
|
||||
* @return Reference to this vector
|
||||
*/
|
||||
VectorPSRAM& operator=(VectorPSRAM&& other) noexcept {
|
||||
vec = std::move(other.vec);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Initializer list assignment operator
|
||||
* @param ilist The initializer list to copy from
|
||||
* @return Reference to this vector
|
||||
*/
|
||||
VectorPSRAM& operator=(std::initializer_list<T> ilist) {
|
||||
vec = ilist;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Access element with bounds checking
|
||||
* @param pos The position of the element
|
||||
* @return Reference to the element at position pos
|
||||
* @throws std::out_of_range if pos is not within the range of the vector
|
||||
*/
|
||||
reference at(size_type pos) { return vec.at(pos); }
|
||||
|
||||
/**
|
||||
* @brief Access element with bounds checking (const version)
|
||||
* @param pos The position of the element
|
||||
* @return Const reference to the element at position pos
|
||||
* @throws std::out_of_range if pos is not within the range of the vector
|
||||
*/
|
||||
const_reference at(size_type pos) const { return vec.at(pos); }
|
||||
|
||||
/**
|
||||
* @brief Access element without bounds checking
|
||||
* @param pos The position of the element
|
||||
* @return Reference to the element at position pos
|
||||
*/
|
||||
reference operator[](size_type pos) { return vec[pos]; }
|
||||
|
||||
/**
|
||||
* @brief Access element without bounds checking (const version)
|
||||
* @param pos The position of the element
|
||||
* @return Const reference to the element at position pos
|
||||
*/
|
||||
const_reference operator[](size_type pos) const { return vec[pos]; }
|
||||
|
||||
/**
|
||||
* @brief Access the first element
|
||||
* @return Reference to the first element
|
||||
*/
|
||||
reference front() { return vec.front(); }
|
||||
|
||||
/**
|
||||
* @brief Access the first element (const version)
|
||||
* @return Const reference to the first element
|
||||
*/
|
||||
const_reference front() const { return vec.front(); }
|
||||
|
||||
/**
|
||||
* @brief Access the last element
|
||||
* @return Reference to the last element
|
||||
*/
|
||||
reference back() { return vec.back(); }
|
||||
|
||||
/**
|
||||
* @brief Access the last element (const version)
|
||||
* @return Const reference to the last element
|
||||
*/
|
||||
const_reference back() const { return vec.back(); }
|
||||
|
||||
/**
|
||||
* @brief Get pointer to the underlying array
|
||||
* @return Pointer to the underlying array
|
||||
*/
|
||||
T* data() noexcept { return vec.data(); }
|
||||
|
||||
/**
|
||||
* @brief Get pointer to the underlying array (const version)
|
||||
* @return Const pointer to the underlying array
|
||||
*/
|
||||
const T* data() const noexcept { return vec.data(); }
|
||||
|
||||
/**
|
||||
* @brief Get iterator to the beginning
|
||||
* @return Iterator to the first element
|
||||
*/
|
||||
iterator begin() noexcept { return vec.begin(); }
|
||||
|
||||
/**
|
||||
* @brief Get const iterator to the beginning
|
||||
* @return Const iterator to the first element
|
||||
*/
|
||||
const_iterator begin() const noexcept { return vec.begin(); }
|
||||
|
||||
/**
|
||||
* @brief Get const iterator to the beginning
|
||||
* @return Const iterator to the first element
|
||||
*/
|
||||
const_iterator cbegin() const noexcept { return vec.cbegin(); }
|
||||
|
||||
/**
|
||||
* @brief Get iterator to the end
|
||||
* @return Iterator to one past the last element
|
||||
*/
|
||||
iterator end() noexcept { return vec.end(); }
|
||||
|
||||
/**
|
||||
* @brief Get const iterator to the end
|
||||
* @return Const iterator to one past the last element
|
||||
*/
|
||||
const_iterator end() const noexcept { return vec.end(); }
|
||||
|
||||
/**
|
||||
* @brief Get const iterator to the end
|
||||
* @return Const iterator to one past the last element
|
||||
*/
|
||||
const_iterator cend() const noexcept { return vec.cend(); }
|
||||
|
||||
/**
|
||||
* @brief Get reverse iterator to the beginning
|
||||
* @return Reverse iterator to the first element
|
||||
*/
|
||||
reverse_iterator rbegin() noexcept { return vec.rbegin(); }
|
||||
|
||||
/**
|
||||
* @brief Get const reverse iterator to the beginning
|
||||
* @return Const reverse iterator to the first element
|
||||
*/
|
||||
const_reverse_iterator rbegin() const noexcept { return vec.rbegin(); }
|
||||
|
||||
/**
|
||||
* @brief Get const reverse iterator to the beginning
|
||||
* @return Const reverse iterator to the first element
|
||||
*/
|
||||
const_reverse_iterator crbegin() const noexcept { return vec.crbegin(); }
|
||||
|
||||
/**
|
||||
* @brief Get reverse iterator to the end
|
||||
* @return Reverse iterator to one past the last element
|
||||
*/
|
||||
reverse_iterator rend() noexcept { return vec.rend(); }
|
||||
|
||||
/**
|
||||
* @brief Get const reverse iterator to the end
|
||||
* @return Const reverse iterator to one past the last element
|
||||
*/
|
||||
const_reverse_iterator rend() const noexcept { return vec.rend(); }
|
||||
|
||||
/**
|
||||
* @brief Get const reverse iterator to the end
|
||||
* @return Const reverse iterator to one past the last element
|
||||
*/
|
||||
const_reverse_iterator crend() const noexcept { return vec.crend(); }
|
||||
|
||||
/**
|
||||
* @brief Check if the vector is empty
|
||||
* @return true if the vector is empty, false otherwise
|
||||
*/
|
||||
bool empty() const noexcept { return vec.empty(); }
|
||||
|
||||
/**
|
||||
* @brief Get the number of elements
|
||||
* @return The number of elements in the vector
|
||||
*/
|
||||
size_type size() const noexcept { return vec.size(); }
|
||||
|
||||
/**
|
||||
* @brief Get the maximum possible number of elements
|
||||
* @return The maximum possible number of elements the vector can hold
|
||||
*/
|
||||
size_type max_size() const noexcept { return vec.max_size(); }
|
||||
|
||||
/**
|
||||
* @brief Reserve storage
|
||||
* @param new_cap The new capacity of the vector
|
||||
* @throws std::length_error if new_cap > max_size()
|
||||
*/
|
||||
void reserve(size_type new_cap) { vec.reserve(new_cap); }
|
||||
|
||||
/**
|
||||
* @brief Get the number of elements that can be held in current storage
|
||||
* @return The capacity of the vector
|
||||
*/
|
||||
size_type capacity() const noexcept { return vec.capacity(); }
|
||||
|
||||
/**
|
||||
* @brief Reduce memory usage by freeing unused memory
|
||||
*/
|
||||
void shrink_to_fit() { vec.shrink_to_fit(); }
|
||||
|
||||
/**
|
||||
* @brief Clear the contents
|
||||
*/
|
||||
void clear() noexcept { vec.clear(); }
|
||||
|
||||
/**
|
||||
* @brief Insert an element
|
||||
* @param pos Iterator to the position before which the element will be inserted
|
||||
* @param value The value to insert
|
||||
* @return Iterator to the inserted element
|
||||
*/
|
||||
iterator insert(const_iterator pos, const T& value) { return vec.insert(pos, value); }
|
||||
|
||||
/**
|
||||
* @brief Insert an element by moving it
|
||||
* @param pos Iterator to the position before which the element will be inserted
|
||||
* @param value The value to insert
|
||||
* @return Iterator to the inserted element
|
||||
*/
|
||||
iterator insert(const_iterator pos, T&& value) { return vec.insert(pos, std::move(value)); }
|
||||
|
||||
/**
|
||||
* @brief Insert multiple copies of an element
|
||||
* @param pos Iterator to the position before which the elements will be inserted
|
||||
* @param count Number of copies to insert
|
||||
* @param value The value to insert
|
||||
* @return Iterator to the first inserted element
|
||||
*/
|
||||
iterator insert(const_iterator pos, size_type count, const T& value) { return vec.insert(pos, count, value); }
|
||||
|
||||
/**
|
||||
* @brief Insert elements from a range
|
||||
* @tparam InputIt Input iterator type
|
||||
* @param pos Iterator to the position before which the elements will be inserted
|
||||
* @param first Iterator to the first element in the range
|
||||
* @param last Iterator to one past the last element in the range
|
||||
* @return Iterator to the first inserted element
|
||||
*/
|
||||
template <typename InputIt>
|
||||
iterator insert(const_iterator pos, InputIt first, InputIt last) { return vec.insert(pos, first, last); }
|
||||
|
||||
/**
|
||||
* @brief Insert elements from an initializer list
|
||||
* @param pos Iterator to the position before which the elements will be inserted
|
||||
* @param ilist The initializer list to insert from
|
||||
* @return Iterator to the first inserted element
|
||||
*/
|
||||
iterator insert(const_iterator pos, std::initializer_list<T> ilist) { return vec.insert(pos, ilist); }
|
||||
|
||||
/**
|
||||
* @brief Construct an element in-place
|
||||
* @tparam Args Types of arguments to forward to the constructor
|
||||
* @param pos Iterator to the position before which the element will be constructed
|
||||
* @param args Arguments to forward to the constructor
|
||||
* @return Iterator to the inserted element
|
||||
*/
|
||||
template <typename... Args>
|
||||
iterator emplace(const_iterator pos, Args&&... args) { return vec.emplace(pos, std::forward<Args>(args)...); }
|
||||
|
||||
/**
|
||||
* @brief Erase an element
|
||||
* @param pos Iterator to the element to erase
|
||||
* @return Iterator to the element after the erased element
|
||||
*/
|
||||
iterator erase(const_iterator pos) { return vec.erase(pos); }
|
||||
|
||||
/**
|
||||
* @brief Erase a range of elements
|
||||
* @param first Iterator to the first element to erase
|
||||
* @param last Iterator to one past the last element to erase
|
||||
* @return Iterator to the element after the erased range
|
||||
*/
|
||||
iterator erase(const_iterator first, const_iterator last) { return vec.erase(first, last); }
|
||||
|
||||
/**
|
||||
* @brief Add an element to the end
|
||||
* @param value The value to append
|
||||
*/
|
||||
void push_back(const T& value) { vec.push_back(value); }
|
||||
|
||||
/**
|
||||
* @brief Add an element to the end by moving it
|
||||
* @param value The value to append
|
||||
*/
|
||||
void push_back(T&& value) { vec.push_back(std::move(value)); }
|
||||
|
||||
/**
|
||||
* @brief Construct an element in-place at the end
|
||||
* @tparam Args Types of arguments to forward to the constructor
|
||||
* @param args Arguments to forward to the constructor
|
||||
* @return Reference to the inserted element
|
||||
*/
|
||||
template <typename... Args>
|
||||
reference emplace_back(Args&&... args) { return vec.emplace_back(std::forward<Args>(args)...); }
|
||||
|
||||
/**
|
||||
* @brief Remove the last element
|
||||
*/
|
||||
void pop_back() { vec.pop_back(); }
|
||||
|
||||
/**
|
||||
* @brief Change the number of elements stored
|
||||
* @param count The new size of the vector
|
||||
*/
|
||||
void resize(size_type count) { vec.resize(count); }
|
||||
|
||||
/**
|
||||
* @brief Change the number of elements stored
|
||||
* @param count The new size of the vector
|
||||
* @param value The value to initialize new elements with
|
||||
*/
|
||||
void resize(size_type count, const value_type& value) { vec.resize(count, value); }
|
||||
|
||||
/**
|
||||
* @brief Swap the contents
|
||||
* @param other Vector to swap with
|
||||
*/
|
||||
void swap(VectorPSRAM& other) noexcept { vec.swap(other.vec); }
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Equality comparison operator
|
||||
* @tparam T Type of elements in the vectors
|
||||
* @param lhs First vector
|
||||
* @param rhs Second vector
|
||||
* @return true if the vectors are equal, false otherwise
|
||||
*/
|
||||
template <typename T>
|
||||
bool operator==(const VectorPSRAM<T>& lhs, const VectorPSRAM<T>& rhs) {
|
||||
return std::equal(lhs.begin(), lhs.end(), rhs.begin(), rhs.end());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Inequality comparison operator
|
||||
* @tparam T Type of elements in the vectors
|
||||
* @param lhs First vector
|
||||
* @param rhs Second vector
|
||||
* @return true if the vectors are not equal, false otherwise
|
||||
*/
|
||||
template <typename T>
|
||||
bool operator!=(const VectorPSRAM<T>& lhs, const VectorPSRAM<T>& rhs) {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Less than comparison operator
|
||||
* @tparam T Type of elements in the vectors
|
||||
* @param lhs First vector
|
||||
* @param rhs Second vector
|
||||
* @return true if lhs is lexicographically less than rhs, false otherwise
|
||||
*/
|
||||
template <typename T>
|
||||
bool operator<(const VectorPSRAM<T>& lhs, const VectorPSRAM<T>& rhs) {
|
||||
return std::lexicographical_compare(lhs.begin(), lhs.end(), rhs.begin(), rhs.end());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Less than or equal comparison operator
|
||||
* @tparam T Type of elements in the vectors
|
||||
* @param lhs First vector
|
||||
* @param rhs Second vector
|
||||
* @return true if lhs is lexicographically less than or equal to rhs, false otherwise
|
||||
*/
|
||||
template <typename T>
|
||||
bool operator<=(const VectorPSRAM<T>& lhs, const VectorPSRAM<T>& rhs) {
|
||||
return !(rhs < lhs);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Greater than comparison operator
|
||||
* @tparam T Type of elements in the vectors
|
||||
* @param lhs First vector
|
||||
* @param rhs Second vector
|
||||
* @return true if lhs is lexicographically greater than rhs, false otherwise
|
||||
*/
|
||||
template <typename T>
|
||||
bool operator>(const VectorPSRAM<T>& lhs, const VectorPSRAM<T>& rhs) {
|
||||
return rhs < lhs;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Greater than or equal comparison operator
|
||||
* @tparam T Type of elements in the vectors
|
||||
* @param lhs First vector
|
||||
* @param rhs Second vector
|
||||
* @return true if lhs is lexicographically greater than or equal to rhs, false otherwise
|
||||
*/
|
||||
template <typename T>
|
||||
bool operator>=(const VectorPSRAM<T>& lhs, const VectorPSRAM<T>& rhs) {
|
||||
return !(lhs < rhs);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Swap the contents of two vectors
|
||||
* @tparam T Type of elements in the vectors
|
||||
* @param lhs First vector
|
||||
* @param rhs Second vector
|
||||
*/
|
||||
template <typename T>
|
||||
void swap(VectorPSRAM<T>& lhs, VectorPSRAM<T>& rhs) noexcept {
|
||||
lhs.swap(rhs);
|
||||
}
|
||||
|
||||
} // namespace esp32_psram
|
||||
|
||||
4
lib_standalone/esp32-psram.h
Normal file
4
lib_standalone/esp32-psram.h
Normal file
@@ -0,0 +1,4 @@
|
||||
#pragma once
|
||||
|
||||
#define stringPSRAM std::string
|
||||
#define AllocatorPSRAM std::allocator
|
||||
@@ -21,7 +21,8 @@
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
uuid::log::Logger AnalogSensor::logger_{F_(analogsensor), uuid::log::Facility::DAEMON};
|
||||
uuid::log::Logger AnalogSensor::logger_{F_(analogsensor), uuid::log::Facility::DAEMON};
|
||||
std::vector<uint8_t> AnalogSensor::exclude_types_;
|
||||
|
||||
#ifndef EMSESP_STANDALONE
|
||||
portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED;
|
||||
@@ -30,32 +31,37 @@ unsigned long AnalogSensor::edgecnt[] = {0, 0, 0};
|
||||
|
||||
void IRAM_ATTR AnalogSensor::freqIrq0() {
|
||||
portENTER_CRITICAL_ISR(&mux);
|
||||
edgecnt[0]++;
|
||||
edge[0] = micros();
|
||||
if (micros() - edge[0] > 10) { // limit to 100kHz
|
||||
edgecnt[0]++;
|
||||
edge[0] = micros();
|
||||
}
|
||||
portEXIT_CRITICAL_ISR(&mux);
|
||||
}
|
||||
void IRAM_ATTR AnalogSensor::freqIrq1() {
|
||||
portENTER_CRITICAL_ISR(&mux);
|
||||
edgecnt[1]++;
|
||||
edge[1] = micros();
|
||||
if (micros() - edge[1] > 10) { // limit to 100kHz
|
||||
edgecnt[1]++;
|
||||
edge[1] = micros();
|
||||
}
|
||||
portEXIT_CRITICAL_ISR(&mux);
|
||||
}
|
||||
void IRAM_ATTR AnalogSensor::freqIrq2() {
|
||||
portENTER_CRITICAL_ISR(&mux);
|
||||
edgecnt[2]++;
|
||||
edge[2] = micros();
|
||||
if (micros() - edge[2] > 10) { // limit to 100kHz
|
||||
edgecnt[2]++;
|
||||
edge[2] = micros();
|
||||
}
|
||||
portEXIT_CRITICAL_ISR(&mux);
|
||||
}
|
||||
#endif
|
||||
|
||||
void AnalogSensor::start(const bool factory_settings) {
|
||||
// if (factory_settings && EMSESP::nvs_.getString("boot").equals("E32V2_2") && EMSESP::nvs_.getString("hwrevision").equals("3.0")) {
|
||||
// if (factory_settings && EMSESP::nvs_.getString("boot").equals("E32V2_2")) {
|
||||
if (factory_settings && analogReadMilliVolts(39) > 700) { // core voltage > 2.6V
|
||||
EMSESP::webCustomizationService.update([&](WebCustomization & settings) {
|
||||
auto newSensor = AnalogCustomization();
|
||||
|
||||
strcpy(newSensor.name, "core_voltage");
|
||||
newSensor.gpio = 39;
|
||||
newSensor.name = "core_voltage";
|
||||
newSensor.offset = 0;
|
||||
newSensor.factor = 0.00377136; // Divider 24k - 8,66k
|
||||
newSensor.uom = DeviceValueUOM::VOLTS;
|
||||
@@ -63,8 +69,8 @@ void AnalogSensor::start(const bool factory_settings) {
|
||||
newSensor.is_system = true;
|
||||
settings.analogCustomizations.push_back(newSensor);
|
||||
|
||||
strcpy(newSensor.name, "supply_voltage");
|
||||
newSensor.gpio = 36;
|
||||
newSensor.name = "supply_voltage";
|
||||
newSensor.factor = 0.017; // Divider 24k - 1,5k
|
||||
newSensor.is_system = true;
|
||||
settings.analogCustomizations.push_back(newSensor);
|
||||
@@ -97,6 +103,7 @@ void AnalogSensor::start(const bool factory_settings) {
|
||||
|
||||
// load settings from the customization file, sorts them and initializes the GPIOs
|
||||
void AnalogSensor::reload(bool get_nvs) {
|
||||
exclude_types_.clear();
|
||||
EMSESP::webSettingsService.read([&](WebSettings & settings) { analog_enabled_ = settings.analog_enabled; });
|
||||
|
||||
#if defined(EMSESP_STANDALONE)
|
||||
@@ -122,13 +129,13 @@ void AnalogSensor::reload(bool get_nvs) {
|
||||
for (const auto & sensor : settings.analogCustomizations) { // search customlist
|
||||
if (sensor_.gpio() == sensor.gpio) {
|
||||
// for output sensors set value to new start-value
|
||||
if (sensor.type >= AnalogType::DIGITAL_OUT && sensor.type <= AnalogType::PWM_2
|
||||
if (((sensor.type >= AnalogType::DIGITAL_OUT && sensor.type <= AnalogType::PWM_2) || sensor.type >= AnalogType::RGB)
|
||||
&& (sensor_.type() != sensor.type || sensor_.offset() != sensor.offset || sensor_.factor() != sensor.factor)) {
|
||||
sensor_.set_value(sensor.offset);
|
||||
}
|
||||
if (sensor.type == AnalogType::COUNTER && sensor_.offset() != sensor.offset
|
||||
&& sensor.offset != EMSESP::nvs_.getDouble(sensor.name.c_str(), 0)) {
|
||||
EMSESP::nvs_.putDouble(sensor.name.c_str(), sensor.offset);
|
||||
if ((sensor.type == AnalogType::COUNTER || (sensor.type >= AnalogType::CNT_0 && sensor.type <= AnalogType::CNT_2))
|
||||
&& sensor_.offset() != sensor.offset && sensor.offset != EMSESP::nvs_.getDouble(sensor.name, 0)) {
|
||||
EMSESP::nvs_.putDouble(sensor.name, sensor.offset);
|
||||
sensor_.set_value(sensor.offset);
|
||||
}
|
||||
sensor_.set_name(sensor.name);
|
||||
@@ -156,11 +163,11 @@ void AnalogSensor::reload(bool get_nvs) {
|
||||
}
|
||||
if (!found) {
|
||||
// it's new, we assume it's valid
|
||||
AnalogType type = static_cast<AnalogType>(sensor.type);
|
||||
sensors_.emplace_back(sensor.gpio, sensor.name, sensor.offset, sensor.factor, sensor.uom, type, sensor.is_system);
|
||||
sensors_.emplace_back(sensor.gpio, sensor.name, sensor.offset, sensor.factor, sensor.uom, sensor.type, sensor.is_system);
|
||||
|
||||
sensors_.back().ha_registered = false; // this will trigger recreate of the HA config
|
||||
if (sensor.type == AnalogType::COUNTER || sensor.type >= AnalogType::DIGITAL_OUT) {
|
||||
if (sensor.type == AnalogType::COUNTER || (sensor.type >= AnalogType::DIGITAL_OUT && sensor.type <= AnalogType::PWM_2)
|
||||
|| sensor.type == AnalogType::RGB || (sensor.type >= AnalogType::CNT_0 && sensor.type <= AnalogType::CNT_2)) {
|
||||
sensors_.back().set_value(sensor.offset);
|
||||
} else {
|
||||
sensors_.back().set_value(0); // reset value only for new sensors
|
||||
@@ -169,16 +176,16 @@ void AnalogSensor::reload(bool get_nvs) {
|
||||
|
||||
// add the command to set the value of the sensor
|
||||
if (sensor.type == AnalogType::COUNTER || (sensor.type >= AnalogType::DIGITAL_OUT && sensor.type <= AnalogType::PWM_2)
|
||||
|| sensor.type == AnalogType::RGB || sensor.type == AnalogType::PULSE) {
|
||||
|| sensor.type == AnalogType::RGB || sensor.type == AnalogType::PULSE || (sensor.type >= AnalogType::CNT_0 && sensor.type <= AnalogType::CNT_2)) {
|
||||
Command::add(
|
||||
EMSdevice::DeviceType::ANALOGSENSOR,
|
||||
sensor.name.c_str(),
|
||||
sensor.name,
|
||||
[&](const char * value, const int8_t id) { return command_setvalue(value, sensor.gpio); },
|
||||
sensor.type == AnalogType::COUNTER ? FL_(counter)
|
||||
: sensor.type == AnalogType::DIGITAL_OUT ? FL_(digital_out)
|
||||
: sensor.type == AnalogType::RGB ? FL_(RGB)
|
||||
: sensor.type == AnalogType::PULSE ? FL_(pulse)
|
||||
: FL_(pwm),
|
||||
sensor.type == AnalogType::COUNTER || (sensor.type >= AnalogType::CNT_0 && sensor.type <= AnalogType::CNT_2) ? FL_(counter)
|
||||
: sensor.type == AnalogType::DIGITAL_OUT ? FL_(digital_out)
|
||||
: sensor.type == AnalogType::RGB ? FL_(RGB)
|
||||
: sensor.type == AnalogType::PULSE ? FL_(pulse)
|
||||
: FL_(pwm),
|
||||
CommandFlag::ADMIN_ONLY);
|
||||
}
|
||||
}
|
||||
@@ -191,9 +198,20 @@ void AnalogSensor::reload(bool get_nvs) {
|
||||
// activate each sensor
|
||||
for (auto & sensor : sensors_) {
|
||||
sensor.ha_registered = false; // force HA configs to be re-created
|
||||
if ((sensor.type() >= AnalogType::FREQ_0 && sensor.type() <= AnalogType::FREQ_2)) {
|
||||
exclude_types_.push_back(sensor.type());
|
||||
exclude_types_.push_back(sensor.type() + 3);
|
||||
}
|
||||
if ((sensor.type() >= AnalogType::CNT_0 && sensor.type() <= AnalogType::CNT_2)) {
|
||||
exclude_types_.push_back(sensor.type());
|
||||
exclude_types_.push_back(sensor.type() - 3);
|
||||
}
|
||||
if ((sensor.type() >= AnalogType::PWM_0 && sensor.type() <= AnalogType::PWM_2)) {
|
||||
exclude_types_.push_back(sensor.type());
|
||||
}
|
||||
|
||||
if (sensor.type() == AnalogType::COUNTER || sensor.type() == AnalogType::DIGITAL_IN || sensor.type() == AnalogType::RATE
|
||||
|| sensor.type() == AnalogType::TIMER) {
|
||||
|| sensor.type() == AnalogType::TIMER || (sensor.type() >= AnalogType::FREQ_0 && sensor.type() <= AnalogType::CNT_2)) {
|
||||
// pullup is mapped to DAC, so set to 3.3V
|
||||
#if CONFIG_IDF_TARGET_ESP32
|
||||
if (sensor.gpio() == 25 || sensor.gpio() == 26) {
|
||||
@@ -204,6 +222,7 @@ void AnalogSensor::reload(bool get_nvs) {
|
||||
dacWrite(sensor.gpio(), 255);
|
||||
}
|
||||
#endif
|
||||
pinMode(sensor.gpio(), INPUT_PULLUP);
|
||||
}
|
||||
if (sensor.type() == AnalogType::ADC) {
|
||||
LOG_DEBUG("ADC Sensor on GPIO %02d", sensor.gpio());
|
||||
@@ -216,16 +235,14 @@ void AnalogSensor::reload(bool get_nvs) {
|
||||
sensor.set_uom(DeviceValueUOM::DEGREES);
|
||||
} else if (sensor.type() == AnalogType::COUNTER) {
|
||||
LOG_DEBUG("I/O Counter on GPIO %02d", sensor.gpio());
|
||||
pinMode(sensor.gpio(), INPUT_PULLUP);
|
||||
sensor.polltime_ = 0;
|
||||
sensor.poll_ = digitalRead(sensor.gpio());
|
||||
if (double_t val = EMSESP::nvs_.getDouble(sensor.name().c_str(), 0)) {
|
||||
if (double_t val = EMSESP::nvs_.getDouble(sensor.name(), 0)) {
|
||||
sensor.set_value(val);
|
||||
}
|
||||
publish_sensor(sensor);
|
||||
} else if (sensor.type() == AnalogType::TIMER || sensor.type() == AnalogType::RATE) {
|
||||
LOG_DEBUG("Timer/Rate on GPIO %02d", sensor.gpio());
|
||||
pinMode(sensor.gpio(), INPUT_PULLUP);
|
||||
sensor.polltime_ = uuid::get_uptime();
|
||||
sensor.last_polltime_ = uuid::get_uptime();
|
||||
sensor.poll_ = digitalRead(sensor.gpio());
|
||||
@@ -234,25 +251,33 @@ void AnalogSensor::reload(bool get_nvs) {
|
||||
publish_sensor(sensor);
|
||||
#ifndef EMSESP_STANDALONE
|
||||
} else if (sensor.type() >= AnalogType::FREQ_0 && sensor.type() <= AnalogType::FREQ_2) {
|
||||
LOG_DEBUG("Frequency on GPIO %02d", sensor.gpio());
|
||||
pinMode(sensor.gpio(), INPUT_PULLUP);
|
||||
auto index = sensor.type() - AnalogType::FREQ_0;
|
||||
LOG_DEBUG("Frequency %d on GPIO %02d", index, sensor.gpio());
|
||||
sensor.set_offset(0);
|
||||
sensor.set_value(0);
|
||||
publish_sensor(sensor);
|
||||
auto index = sensor.type() - AnalogType::FREQ_0;
|
||||
attachInterrupt(sensor.gpio(), index == 0 ? freqIrq0 : index == 1 ? freqIrq1 : freqIrq2, FALLING);
|
||||
lastedge[index] = edge[index] = micros();
|
||||
edgecnt[index] = 0;
|
||||
} else if (sensor.type() >= AnalogType::CNT_0 && sensor.type() <= AnalogType::CNT_2) {
|
||||
auto index = sensor.type() - AnalogType::CNT_0;
|
||||
LOG_DEBUG("Counter %d on GPIO %02d", index, sensor.gpio());
|
||||
if (double_t val = EMSESP::nvs_.getDouble(sensor.name(), 0)) {
|
||||
sensor.set_value(val);
|
||||
}
|
||||
publish_sensor(sensor);
|
||||
attachInterrupt(sensor.gpio(), index == 0 ? freqIrq0 : index == 1 ? freqIrq1 : freqIrq2, FALLING);
|
||||
edgecnt[index] = 0;
|
||||
#endif
|
||||
} else if (sensor.type() == AnalogType::DIGITAL_IN) {
|
||||
LOG_DEBUG("Digital Read on GPIO %02d", sensor.gpio());
|
||||
pinMode(sensor.gpio(), INPUT_PULLUP);
|
||||
sensor.set_value(digitalRead(sensor.gpio())); // initial value
|
||||
sensor.set_uom(0); // no uom, just for safe measures
|
||||
sensor.polltime_ = 0;
|
||||
sensor.poll_ = digitalRead(sensor.gpio());
|
||||
publish_sensor(sensor);
|
||||
} else if (sensor.type() == AnalogType::RGB) {
|
||||
sensor.set_uom(0); // no uom
|
||||
LOG_DEBUG("RGB on GPIO %02d", sensor.gpio());
|
||||
uint32_t v = sensor.value();
|
||||
uint8_t r = v / 10000;
|
||||
@@ -290,12 +315,15 @@ void AnalogSensor::reload(bool get_nvs) {
|
||||
#endif
|
||||
{
|
||||
if (sensor.uom() == 0) { // set state from NVS
|
||||
if (!get_nvs || EMSESP::nvs_.getChar(sensor.name().c_str(), -1) == -1) {
|
||||
EMSESP::nvs_.putChar(sensor.name().c_str(), (int8_t)sensor.offset());
|
||||
if (!get_nvs || EMSESP::nvs_.getChar(sensor.name(), -1) == -1) {
|
||||
EMSESP::nvs_.putChar(sensor.name(), (int8_t)sensor.offset());
|
||||
} else {
|
||||
sensor.set_offset(EMSESP::nvs_.getChar(sensor.name().c_str()));
|
||||
sensor.set_offset(EMSESP::nvs_.getChar(sensor.name()));
|
||||
}
|
||||
} else if (sensor.uom() > 1) {
|
||||
sensor.set_uom(2);
|
||||
}
|
||||
sensor.set_offset(sensor.offset() > 0 ? 1 : 0);
|
||||
digitalWrite(sensor.gpio(), (sensor.offset() == 0) ^ (sensor.factor() > 0));
|
||||
sensor.set_value(sensor.offset());
|
||||
}
|
||||
@@ -344,10 +372,10 @@ void AnalogSensor::measure() {
|
||||
uint16_t a = analogReadMilliVolts(sensor.gpio()); // e.g. ADC1_CHANNEL_0_GPIO_NUM
|
||||
if (!sensor.analog_) { // init first time
|
||||
sensor.analog_ = a;
|
||||
sensor.sum_ = a * 512;
|
||||
sensor.sum_ = a * 128;
|
||||
} else { // simple moving average filter
|
||||
sensor.sum_ = (sensor.sum_ * 511) / 512 + a;
|
||||
sensor.analog_ = sensor.sum_ / 512;
|
||||
sensor.sum_ = (sensor.sum_ * 127) / 128 + a;
|
||||
sensor.analog_ = sensor.sum_ / 128;
|
||||
}
|
||||
|
||||
// detect change with little hysteresis on raw mV value
|
||||
@@ -393,6 +421,18 @@ void AnalogSensor::measure() {
|
||||
changed_ = true;
|
||||
publish_sensor(sensor);
|
||||
}
|
||||
} else if (sensor.type() >= AnalogType::CNT_0 && sensor.type() <= AnalogType::CNT_2) {
|
||||
auto index = sensor.type() - AnalogType::CNT_0;
|
||||
auto oldval = sensor.value();
|
||||
portENTER_CRITICAL_ISR(&mux);
|
||||
auto c = edgecnt[index];
|
||||
edgecnt[index] = 0;
|
||||
portEXIT_CRITICAL_ISR(&mux);
|
||||
sensor.set_value(oldval + sensor.factor() * c);
|
||||
if (sensor.value() != oldval) {
|
||||
changed_ = true;
|
||||
publish_sensor(sensor);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -417,7 +457,7 @@ void AnalogSensor::measure() {
|
||||
} else if (!sensor.poll_) { // falling edge
|
||||
if (sensor.type() == AnalogType::COUNTER) {
|
||||
sensor.set_value(old_value + sensor.factor());
|
||||
// EMSESP::nvs_.putDouble(sensor.name().c_str(), sensor.value());
|
||||
// EMSESP::nvs_.putDouble(sensor.name(), sensor.value());
|
||||
} else if (sensor.type() == AnalogType::RATE) { // default uom: Hz (1/sec) with factor 1
|
||||
sensor.set_value(sensor.factor() * 1000 / (sensor.polltime_ - sensor.last_polltime_));
|
||||
} else if (sensor.type() == AnalogType::TIMER) { // default seconds with factor 1
|
||||
@@ -453,9 +493,9 @@ void AnalogSensor::measure() {
|
||||
// store counters to NVS, called every hour, on restart and update
|
||||
void AnalogSensor::store_counters() {
|
||||
for (auto & sensor : sensors_) {
|
||||
if (sensor.type() == AnalogType::COUNTER) {
|
||||
if (sensor.value() != EMSESP::nvs_.getDouble(sensor.name().c_str())) {
|
||||
EMSESP::nvs_.putDouble(sensor.name().c_str(), sensor.value());
|
||||
if (sensor.type() == AnalogType::COUNTER || (sensor.type() >= AnalogType::CNT_0 && sensor.type() <= AnalogType::CNT_2)) {
|
||||
if (sensor.value() != EMSESP::nvs_.getDouble(sensor.name())) {
|
||||
EMSESP::nvs_.putDouble(sensor.name(), sensor.value());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -472,7 +512,13 @@ void AnalogSensor::loop() {
|
||||
// update analog information name, offset, factor, uom, type, deleted, is_system
|
||||
// a type value of -1 is used to delete the sensor
|
||||
// the gpio is the key
|
||||
bool AnalogSensor::update(uint8_t gpio, std::string & name, double offset, double factor, uint8_t uom, int8_t type, bool deleted, bool is_system) {
|
||||
bool AnalogSensor::update(uint8_t gpio, const char * org_name, double offset, double factor, uint8_t uom, int8_t type, bool deleted, bool is_system) {
|
||||
char name[20];
|
||||
if (org_name[0] == '\0') {
|
||||
snprintf(name, sizeof(name), "%s_%02d", FL_(list_sensortype)[type], gpio);
|
||||
} else {
|
||||
strlcpy(name, org_name, sizeof(name));
|
||||
}
|
||||
// first see if we can find the sensor in our customization list
|
||||
bool found_sensor = false;
|
||||
EMSESP::webCustomizationService.update([&](WebCustomization & settings) {
|
||||
@@ -480,12 +526,7 @@ bool AnalogSensor::update(uint8_t gpio, std::string & name, double offset, doubl
|
||||
if (AnalogCustomization.type == AnalogType::COUNTER
|
||||
|| (AnalogCustomization.type >= AnalogType::DIGITAL_OUT && AnalogCustomization.type <= AnalogType::PWM_2)
|
||||
|| AnalogCustomization.type == AnalogType::RGB || AnalogCustomization.type == AnalogType::PULSE) {
|
||||
Command::erase_command(EMSdevice::DeviceType::ANALOGSENSOR, AnalogCustomization.name.c_str());
|
||||
}
|
||||
if (name.empty()) {
|
||||
char n[20];
|
||||
snprintf(n, sizeof(n), "%s_%02d", FL_(list_sensortype)[type], gpio);
|
||||
name = n;
|
||||
Command::erase_command(EMSdevice::DeviceType::ANALOGSENSOR, AnalogCustomization.name);
|
||||
}
|
||||
if (AnalogCustomization.gpio == gpio) {
|
||||
found_sensor = true; // found the record
|
||||
@@ -493,14 +534,14 @@ bool AnalogSensor::update(uint8_t gpio, std::string & name, double offset, doubl
|
||||
if (deleted) {
|
||||
LOG_DEBUG("Removing analog sensor GPIO %02d", gpio);
|
||||
EMSESP::system_.remove_gpio(gpio); // remove from used list only
|
||||
EMSESP::nvs_.remove(AnalogCustomization.name.c_str());
|
||||
EMSESP::nvs_.remove(AnalogCustomization.name);
|
||||
settings.analogCustomizations.remove(AnalogCustomization);
|
||||
} else {
|
||||
// update existing record
|
||||
if (name != AnalogCustomization.name) {
|
||||
EMSESP::nvs_.remove(AnalogCustomization.name.c_str());
|
||||
if (!strcmp(name, AnalogCustomization.name)) {
|
||||
EMSESP::nvs_.remove(AnalogCustomization.name);
|
||||
}
|
||||
AnalogCustomization.name = name;
|
||||
strlcpy(AnalogCustomization.name, name, sizeof(AnalogCustomization.name));
|
||||
AnalogCustomization.offset = offset;
|
||||
AnalogCustomization.factor = factor;
|
||||
AnalogCustomization.uom = uom;
|
||||
@@ -523,9 +564,9 @@ bool AnalogSensor::update(uint8_t gpio, std::string & name, double offset, doubl
|
||||
if (!found_sensor) {
|
||||
found_sensor = true;
|
||||
EMSESP::webCustomizationService.update([&](WebCustomization & settings) {
|
||||
auto newSensor = AnalogCustomization();
|
||||
newSensor.gpio = gpio;
|
||||
newSensor.name = name;
|
||||
auto newSensor = AnalogCustomization();
|
||||
newSensor.gpio = gpio;
|
||||
strlcpy(newSensor.name, name, sizeof(newSensor.name));
|
||||
newSensor.offset = offset;
|
||||
newSensor.factor = factor;
|
||||
newSensor.uom = uom;
|
||||
@@ -565,9 +606,9 @@ void AnalogSensor::publish_sensor(const Sensor & sensor) const {
|
||||
if (Mqtt::publish_single()) {
|
||||
char topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
|
||||
if (Mqtt::publish_single2cmd()) {
|
||||
snprintf(topic, sizeof(topic), "%s/%s", F_(analogsensor), sensor.name().c_str());
|
||||
snprintf(topic, sizeof(topic), "%s/%s", F_(analogsensor), sensor.name());
|
||||
} else {
|
||||
snprintf(topic, sizeof(topic), "%s%s/%s", F_(analogsensor), "_data", sensor.name().c_str());
|
||||
snprintf(topic, sizeof(topic), "%s%s/%s", F_(analogsensor), "_data", sensor.name());
|
||||
}
|
||||
char result[12];
|
||||
if (sensor.type() == AnalogType::DIGITAL_IN || sensor.type() == AnalogType::DIGITAL_OUT) {
|
||||
@@ -578,7 +619,7 @@ void AnalogSensor::publish_sensor(const Sensor & sensor) const {
|
||||
Mqtt::queue_publish(topic, result); // always publish as doubles
|
||||
}
|
||||
char cmd[COMMAND_MAX_LENGTH];
|
||||
snprintf(cmd, sizeof(cmd), "%s/%s", F_(analogsensor), sensor.name().c_str());
|
||||
snprintf(cmd, sizeof(cmd), "%s/%s", F_(analogsensor), sensor.name());
|
||||
EMSESP::webSchedulerService.onChange(cmd);
|
||||
}
|
||||
|
||||
@@ -593,6 +634,8 @@ void AnalogSensor::remove_ha_topic(const int8_t type, const uint8_t gpio) const
|
||||
|
||||
#if CONFIG_IDF_TARGET_ESP32
|
||||
if (type == AnalogType::PULSE || (type == AnalogType::DIGITAL_OUT && gpio != 25 && gpio != 26)) {
|
||||
#elif CONFIG_IDF_TARGET_ESP32S2
|
||||
if (type == AnalogType::PULSE || (type == AnalogType::DIGITAL_OUT && gpio != 17 && gpio != 18)) {
|
||||
#else
|
||||
if (type == AnalogType::PULSE || type == AnalogType::DIGITAL_OUT) {
|
||||
#endif
|
||||
@@ -637,26 +680,12 @@ void AnalogSensor::publish_values(const bool force) {
|
||||
#else
|
||||
if (sensor.type() == AnalogType::PULSE || sensor.type() == AnalogType::DIGITAL_OUT) {
|
||||
#endif
|
||||
if (EMSESP::system_.bool_format() == BOOL_FORMAT_TRUEFALSE) {
|
||||
dataSensor["value"] = sensor.value() != 0;
|
||||
} else if (EMSESP::system_.bool_format() == BOOL_FORMAT_10) {
|
||||
dataSensor["value"] = sensor.value() != 0 ? 1 : 0;
|
||||
} else {
|
||||
char result[12];
|
||||
dataSensor["value"] = Helpers::render_boolean(result, sensor.value() != 0);
|
||||
}
|
||||
Mqtt::add_value_bool(doc.as<JsonObject>(), sensor.name(), sensor.value() != 0);
|
||||
} else {
|
||||
dataSensor["value"] = serialized(Helpers::render_value(s, sensor.value(), 2)); // double
|
||||
}
|
||||
} else if (sensor.type() == AnalogType::DIGITAL_IN || sensor.type() == AnalogType::DIGITAL_OUT || sensor.type() == AnalogType::PULSE) {
|
||||
if (EMSESP::system_.bool_format() == BOOL_FORMAT_TRUEFALSE) {
|
||||
doc[sensor.name()] = sensor.value() != 0;
|
||||
} else if (EMSESP::system_.bool_format() == BOOL_FORMAT_10) {
|
||||
doc[sensor.name()] = sensor.value() != 0 ? 1 : 0;
|
||||
} else {
|
||||
char result[12];
|
||||
doc[sensor.name()] = Helpers::render_boolean(result, sensor.value() != 0);
|
||||
}
|
||||
Mqtt::add_value_bool(doc.as<JsonObject>(), sensor.name(), sensor.value() != 0);
|
||||
} else {
|
||||
char s[10];
|
||||
doc[sensor.name()] = serialized(Helpers::render_value(s, sensor.value(), 2));
|
||||
@@ -667,7 +696,7 @@ void AnalogSensor::publish_values(const bool force) {
|
||||
LOG_DEBUG("Recreating HA config for analog sensor GPIO %02d", sensor.gpio());
|
||||
|
||||
JsonDocument config;
|
||||
config["~"] = Mqtt::base();
|
||||
config["~"] = Mqtt::base();
|
||||
|
||||
char stat_t[50];
|
||||
snprintf(stat_t, sizeof(stat_t), "~/%s_data", F_(analogsensor)); // use base path
|
||||
@@ -679,7 +708,7 @@ void AnalogSensor::publish_values(const bool force) {
|
||||
snprintf(val_obj, sizeof(val_obj), "value_json['%02d']['value']", sensor.gpio());
|
||||
snprintf(val_cond, sizeof(val_cond), "value_json['%02d'] is defined and %s is defined", sensor.gpio(), val_obj);
|
||||
} else {
|
||||
snprintf(val_obj, sizeof(val_obj), "value_json['%s']", sensor.name().c_str());
|
||||
snprintf(val_obj, sizeof(val_obj), "value_json['%s']", sensor.name());
|
||||
snprintf(val_cond, sizeof(val_cond), "%s is defined", val_obj);
|
||||
}
|
||||
char sample_val[12] = "0";
|
||||
@@ -700,12 +729,9 @@ void AnalogSensor::publish_values(const bool force) {
|
||||
snprintf(uniq_s, sizeof(uniq_s), "%s_%02d", F_(analogsensor), sensor.gpio());
|
||||
}
|
||||
|
||||
config["~"] = Mqtt::base();
|
||||
config["~"] = Mqtt::base();
|
||||
config["uniq_id"] = uniq_s;
|
||||
|
||||
char name[50];
|
||||
snprintf(name, sizeof(name), "%s", sensor.name().c_str());
|
||||
config["name"] = name;
|
||||
config["name"] = sensor.name();
|
||||
|
||||
if (sensor.uom() != DeviceValueUOM::NONE && sensor.type() != AnalogType::DIGITAL_OUT) {
|
||||
config["unit_of_meas"] = EMSdevice::uom_to_string(sensor.uom());
|
||||
@@ -717,16 +743,18 @@ void AnalogSensor::publish_values(const bool force) {
|
||||
char command_topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
|
||||
#if CONFIG_IDF_TARGET_ESP32
|
||||
if (sensor.type() == AnalogType::PULSE || (sensor.type() == AnalogType::DIGITAL_OUT && sensor.gpio() != 25 && sensor.gpio() != 26)) {
|
||||
#elif CONFIG_IDF_TARGET_ESP32S2
|
||||
if (sensor.type() == AnalogType::PULSE || (sensor.type() == AnalogType::DIGITAL_OUT && sensor.gpio() != 17 && sensor.gpio() != 18)) {
|
||||
#else
|
||||
if (sensor.type() == AnalogType::PULSE || sensor.type() == AnalogType::DIGITAL_OUT) {
|
||||
if (sensor.type() == AnalogType::PULSE || sensor.type() == AnalogType::DIGITAL_OUT) {
|
||||
#endif
|
||||
snprintf(topic, sizeof(topic), "switch/%s/%s_%02d/config", Mqtt::basename().c_str(), F_(analogsensor), sensor.gpio());
|
||||
snprintf(command_topic, sizeof(command_topic), "%s/%s/%s", Mqtt::base().c_str(), F_(analogsensor), sensor.name().c_str());
|
||||
snprintf(command_topic, sizeof(command_topic), "%s/%s/%s", Mqtt::base().c_str(), F_(analogsensor), sensor.name());
|
||||
config["cmd_t"] = command_topic;
|
||||
Mqtt::add_ha_bool(config.as<JsonObject>());
|
||||
} else if (sensor.type() == AnalogType::DIGITAL_OUT) { // DAC
|
||||
snprintf(topic, sizeof(topic), "number/%s/%s_%02d/config", Mqtt::basename().c_str(), F_(analogsensor), sensor.gpio());
|
||||
snprintf(command_topic, sizeof(command_topic), "%s/%s/%s", Mqtt::base().c_str(), F_(analogsensor), sensor.name().c_str());
|
||||
snprintf(command_topic, sizeof(command_topic), "%s/%s/%s", Mqtt::base().c_str(), F_(analogsensor), sensor.name());
|
||||
config["cmd_t"] = command_topic;
|
||||
config["min"] = 0;
|
||||
config["max"] = 255;
|
||||
@@ -734,7 +762,7 @@ void AnalogSensor::publish_values(const bool force) {
|
||||
config["step"] = 1;
|
||||
} else if (sensor.type() >= AnalogType::PWM_0 && sensor.type() <= AnalogType::PWM_2) {
|
||||
snprintf(topic, sizeof(topic), "number/%s/%s_%02d/config", Mqtt::basename().c_str(), F_(analogsensor), sensor.gpio());
|
||||
snprintf(command_topic, sizeof(command_topic), "%s/%s/%s", Mqtt::base().c_str(), F_(analogsensor), sensor.name().c_str());
|
||||
snprintf(command_topic, sizeof(command_topic), "%s/%s/%s", Mqtt::base().c_str(), F_(analogsensor), sensor.name());
|
||||
config["cmd_t"] = command_topic;
|
||||
config["min"] = 0;
|
||||
config["max"] = 100;
|
||||
@@ -742,15 +770,15 @@ void AnalogSensor::publish_values(const bool force) {
|
||||
config["step"] = 0.1;
|
||||
} else if (sensor.type() == AnalogType::RGB) {
|
||||
snprintf(topic, sizeof(topic), "number/%s/%s_%02d/config", Mqtt::basename().c_str(), F_(analogsensor), sensor.gpio());
|
||||
snprintf(command_topic, sizeof(command_topic), "%s/%s/%s", Mqtt::base().c_str(), F_(analogsensor), sensor.name().c_str());
|
||||
snprintf(command_topic, sizeof(command_topic), "%s/%s/%s", Mqtt::base().c_str(), F_(analogsensor), sensor.name());
|
||||
config["cmd_t"] = command_topic;
|
||||
config["min"] = 0;
|
||||
config["max"] = 999999;
|
||||
config["mode"] = "box"; // auto, slider or box
|
||||
config["step"] = 1;
|
||||
} else if (sensor.type() == AnalogType::COUNTER) {
|
||||
} else if (sensor.type() == AnalogType::COUNTER || (sensor.type() >= AnalogType::CNT_0 && sensor.type() <= AnalogType::CNT_2)) {
|
||||
snprintf(topic, sizeof(topic), "sensor/%s/%s_%02d/config", Mqtt::basename().c_str(), F_(analogsensor), sensor.gpio());
|
||||
snprintf(command_topic, sizeof(command_topic), "%s/%s/%s", Mqtt::base().c_str(), F_(analogsensor), sensor.name().c_str());
|
||||
snprintf(command_topic, sizeof(command_topic), "%s/%s/%s", Mqtt::base().c_str(), F_(analogsensor), sensor.name());
|
||||
config["cmd_t"] = command_topic;
|
||||
config["stat_cla"] = "total_increasing";
|
||||
// config["mode"] = "box"; // auto, slider or box
|
||||
@@ -835,10 +863,11 @@ void AnalogSensor::get_value_json(JsonObject output, const Sensor & sensor) {
|
||||
output["value"] = sensor.value();
|
||||
output["readable"] = true;
|
||||
output["writeable"] = sensor.type() == AnalogType::COUNTER || sensor.type() == AnalogType::RGB || sensor.type() == AnalogType::PULSE
|
||||
|| (sensor.type() >= AnalogType::DIGITAL_OUT && sensor.type() <= AnalogType::PWM_2);
|
||||
|| (sensor.type() >= AnalogType::DIGITAL_OUT && sensor.type() <= AnalogType::PWM_2)
|
||||
|| (sensor.type() >= AnalogType::CNT_0 && sensor.type() <= AnalogType::CNT_2);
|
||||
output["visible"] = true;
|
||||
output["is_system"] = sensor.is_system();
|
||||
if (sensor.type() == AnalogType::COUNTER) {
|
||||
if (sensor.type() == AnalogType::COUNTER || (sensor.type() >= AnalogType::CNT_0 && sensor.type() <= AnalogType::CNT_2)) {
|
||||
output["min"] = 0;
|
||||
output["max"] = 4000000;
|
||||
output["start_value"] = sensor.offset();
|
||||
@@ -859,6 +888,8 @@ void AnalogSensor::get_value_json(JsonObject output, const Sensor & sensor) {
|
||||
output["min"] = 0;
|
||||
#if CONFIG_IDF_TARGET_ESP32
|
||||
output["max"] = sensor.gpio() == 25 || sensor.gpio() == 26 ? 255 : 1;
|
||||
#elif CONFIG_IDF_TARGET_ESP32S2
|
||||
output["max"] = sensor.gpio() == 17 || sensor.gpio() == 18 ? 255 : 1;
|
||||
#else
|
||||
output["max"] = 1;
|
||||
#endif
|
||||
@@ -870,20 +901,14 @@ void AnalogSensor::get_value_json(JsonObject output, const Sensor & sensor) {
|
||||
}
|
||||
|
||||
// this creates the sensor, initializing everything
|
||||
AnalogSensor::Sensor::Sensor(const uint8_t gpio,
|
||||
const std::string & name,
|
||||
const double offset,
|
||||
const double factor,
|
||||
const uint8_t uom,
|
||||
const int8_t type,
|
||||
const bool is_system)
|
||||
AnalogSensor::Sensor::Sensor(const uint8_t gpio, const char * name, const double offset, const double factor, const uint8_t uom, const int8_t type, const bool is_system)
|
||||
: gpio_(gpio)
|
||||
, name_(name)
|
||||
, offset_(offset)
|
||||
, factor_(factor)
|
||||
, uom_(uom)
|
||||
, type_(type)
|
||||
, is_system_(is_system) {
|
||||
strlcpy(name_, name, sizeof(name_));
|
||||
value_ = 0; // init value to 0 always
|
||||
}
|
||||
|
||||
@@ -901,7 +926,7 @@ bool AnalogSensor::command_setvalue(const char * value, const int8_t gpio) {
|
||||
for (auto & sensor : sensors_) {
|
||||
if (sensor.gpio() == gpio) {
|
||||
double oldoffset = sensor.offset();
|
||||
if (sensor.type() == AnalogType::COUNTER) {
|
||||
if (sensor.type() == AnalogType::COUNTER || (sensor.type() >= AnalogType::CNT_0 && sensor.type() <= AnalogType::CNT_2)) {
|
||||
if (val < 0 || value[0] == '+') { // sign corrects values
|
||||
// sensor.set_offset(sensor.value() + val);
|
||||
sensor.set_value(sensor.value() + val);
|
||||
@@ -910,8 +935,8 @@ bool AnalogSensor::command_setvalue(const char * value, const int8_t gpio) {
|
||||
sensor.set_value(val);
|
||||
}
|
||||
sensor.set_offset(sensor.value());
|
||||
if (sensor.value() != EMSESP::nvs_.getDouble(sensor.name().c_str(), 0)) {
|
||||
EMSESP::nvs_.putDouble(sensor.name().c_str(), sensor.value());
|
||||
if (sensor.value() != EMSESP::nvs_.getDouble(sensor.name(), 0)) {
|
||||
EMSESP::nvs_.putDouble(sensor.name(), sensor.value());
|
||||
}
|
||||
} else if (sensor.type() == AnalogType::ADC) {
|
||||
sensor.set_offset(val);
|
||||
@@ -956,8 +981,8 @@ bool AnalogSensor::command_setvalue(const char * value, const int8_t gpio) {
|
||||
sensor.set_value(v);
|
||||
pinMode(sensor.gpio(), OUTPUT);
|
||||
digitalWrite(sensor.gpio(), (sensor.offset() == 0) ^ (sensor.factor() > 0));
|
||||
if (sensor.uom() == 0 && EMSESP::nvs_.getChar(sensor.name().c_str()) != (int8_t)sensor.offset()) {
|
||||
EMSESP::nvs_.putChar(sensor.name().c_str(), (int8_t)sensor.offset());
|
||||
if (sensor.uom() == 0 && EMSESP::nvs_.getChar(sensor.name()) != (int8_t)sensor.offset()) {
|
||||
EMSESP::nvs_.putChar(sensor.name(), (int8_t)sensor.offset());
|
||||
}
|
||||
}
|
||||
} else if (sensor.type() >= AnalogType::PWM_0 && sensor.type() <= AnalogType::PWM_2) {
|
||||
|
||||
@@ -25,27 +25,27 @@
|
||||
|
||||
#include <uuid/log.h>
|
||||
|
||||
namespace emsesp {
|
||||
#include <esp32-psram.h>
|
||||
|
||||
// names, same order as AnalogType, see list_sensortype in local_common.h
|
||||
// MAKE_ENUM_FIXED(AnalogTypeName, "disabled", "dig_in", "counter", "adc", "timer", "rate", "dig_out", "pwm0", "pwm1", "pwm2")
|
||||
namespace emsesp {
|
||||
|
||||
class AnalogSensor {
|
||||
public:
|
||||
class Sensor {
|
||||
public:
|
||||
Sensor(const uint8_t gpio, const std::string & name, const double offset, const double factor, const uint8_t uom, const int8_t type, const bool is_system);
|
||||
Sensor(const uint8_t gpio, const char * name, const double offset, const double factor, const uint8_t uom, const int8_t type, const bool is_system);
|
||||
~Sensor() = default;
|
||||
|
||||
void set_offset(const double offset) {
|
||||
offset_ = offset;
|
||||
}
|
||||
|
||||
std::string name() const {
|
||||
const char * name() const {
|
||||
return name_;
|
||||
}
|
||||
void set_name(const std::string & name) {
|
||||
name_ = name;
|
||||
|
||||
void set_name(const char * name) {
|
||||
strlcpy(name_, name, sizeof(name_));
|
||||
}
|
||||
|
||||
uint8_t gpio() const {
|
||||
@@ -104,14 +104,14 @@ class AnalogSensor {
|
||||
uint32_t last_polltime_ = 0; // for timer
|
||||
|
||||
private:
|
||||
uint8_t gpio_;
|
||||
std::string name_;
|
||||
double offset_;
|
||||
double factor_;
|
||||
uint8_t uom_;
|
||||
double value_; // double because of the factor is a double
|
||||
int8_t type_; // one of the AnalogType enum
|
||||
bool is_system_; // if true, the sensor is a system sensor
|
||||
uint8_t gpio_;
|
||||
char name_[20];
|
||||
double offset_;
|
||||
double factor_;
|
||||
uint8_t uom_;
|
||||
double value_; // double because of the factor is a double
|
||||
int8_t type_; // one of the AnalogType enum
|
||||
bool is_system_; // if true, the sensor is a system sensor
|
||||
};
|
||||
|
||||
AnalogSensor() = default;
|
||||
@@ -132,7 +132,10 @@ class AnalogSensor {
|
||||
PULSE = 12,
|
||||
FREQ_0 = 13,
|
||||
FREQ_1 = 14,
|
||||
FREQ_2 = 15
|
||||
FREQ_2 = 15,
|
||||
CNT_0 = 16,
|
||||
CNT_1 = 17,
|
||||
CNT_2 = 18
|
||||
};
|
||||
|
||||
void start(const bool factory_settings = false);
|
||||
@@ -143,7 +146,7 @@ class AnalogSensor {
|
||||
bool updated_values();
|
||||
|
||||
// return back reference to the sensor list, used by other classes
|
||||
std::vector<Sensor> sensors() const {
|
||||
std::vector<Sensor, AllocatorPSRAM<Sensor>> sensors() const {
|
||||
return sensors_;
|
||||
}
|
||||
|
||||
@@ -171,9 +174,12 @@ class AnalogSensor {
|
||||
return sensors_.size();
|
||||
}
|
||||
|
||||
bool update(uint8_t gpio, std::string & name, double offset, double factor, uint8_t uom, int8_t type, bool deleted, bool is_system);
|
||||
bool get_value_info(JsonObject output, const char * cmd, const int8_t id = -1);
|
||||
void store_counters();
|
||||
bool update(uint8_t gpio, const char * name, double offset, double factor, uint8_t uom, int8_t type, bool deleted, bool is_system);
|
||||
bool get_value_info(JsonObject output, const char * cmd, const int8_t id = -1);
|
||||
void store_counters();
|
||||
static std::vector<uint8_t> exclude_types() {
|
||||
return exclude_types_;
|
||||
}
|
||||
|
||||
private:
|
||||
static constexpr double Beta = 4260;
|
||||
@@ -185,14 +191,14 @@ class AnalogSensor {
|
||||
static constexpr uint32_t MEASURE_ANALOG_INTERVAL = 500;
|
||||
|
||||
static uuid::log::Logger logger_;
|
||||
void remove_ha_topic(const int8_t type, const uint8_t id) const;
|
||||
bool command_setvalue(const char * value, const int8_t gpio);
|
||||
void measure();
|
||||
void addSensorJson(JsonObject output, const Sensor & sensor);
|
||||
void get_value_json(JsonObject output, const Sensor & sensor);
|
||||
|
||||
void remove_ha_topic(const int8_t type, const uint8_t id) const;
|
||||
bool command_setvalue(const char * value, const int8_t gpio);
|
||||
void measure();
|
||||
void addSensorJson(JsonObject output, const Sensor & sensor);
|
||||
void get_value_json(JsonObject output, const Sensor & sensor);
|
||||
|
||||
std::vector<Sensor> sensors_; // our list of sensors
|
||||
std::vector<Sensor, AllocatorPSRAM<Sensor>> sensors_; // our list of sensors
|
||||
static std::vector<uint8_t> exclude_types_;
|
||||
|
||||
bool analog_enabled_;
|
||||
bool changed_ = true; // this will force a publish of all sensors when initialising
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace emsesp {
|
||||
|
||||
uuid::log::Logger Command::logger_{F_(command), uuid::log::Facility::DAEMON};
|
||||
|
||||
std::vector<Command::CmdFunction> Command::cmdfunctions_;
|
||||
std::vector<Command::CmdFunction, AllocatorPSRAM<Command::CmdFunction>> Command::cmdfunctions_;
|
||||
|
||||
// takes a URI path and a json body, parses the data and calls the command
|
||||
// the path is leading so if duplicate keys are in the input JSON it will be ignored
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include <unordered_map>
|
||||
|
||||
#include "console.h"
|
||||
#include <esp32-psram.h>
|
||||
|
||||
using uuid::console::Shell;
|
||||
|
||||
@@ -97,7 +98,7 @@ class Command {
|
||||
}
|
||||
};
|
||||
|
||||
static std::vector<CmdFunction> commands() {
|
||||
static std::vector<CmdFunction, AllocatorPSRAM<CmdFunction>> commands() {
|
||||
return cmdfunctions_;
|
||||
}
|
||||
|
||||
@@ -145,7 +146,7 @@ class Command {
|
||||
private:
|
||||
static uuid::log::Logger logger_;
|
||||
|
||||
static std::vector<CmdFunction> cmdfunctions_; // the list of commands
|
||||
static std::vector<CmdFunction, AllocatorPSRAM<CmdFunction>> cmdfunctions_; // the list of commands
|
||||
|
||||
static uint8_t json_message(uint8_t error_code, const char * message, JsonObject output, const char * object = nullptr);
|
||||
};
|
||||
|
||||
@@ -1637,14 +1637,7 @@ void EMSdevice::get_value_json(JsonObject json, DeviceValue & dv) {
|
||||
auto value_b = (bool)*(uint8_t *)(dv.value_p);
|
||||
json["bool"] = value_b;
|
||||
json["index"] = value_b ? 1 : 0;
|
||||
if (EMSESP::system_.bool_format() == BOOL_FORMAT_TRUEFALSE) {
|
||||
json[value] = value_b;
|
||||
} else if (EMSESP::system_.bool_format() == BOOL_FORMAT_10) {
|
||||
json[value] = value_b ? 1 : 0;
|
||||
} else {
|
||||
char s[12];
|
||||
json[value] = Helpers::render_boolean(s, value_b);
|
||||
}
|
||||
Mqtt::add_value_bool(json, value, value_b);
|
||||
}
|
||||
json[type] = ("boolean");
|
||||
break;
|
||||
@@ -1953,13 +1946,8 @@ bool EMSdevice::generate_values(JsonObject output, const int8_t tag_filter, cons
|
||||
if (output_target == OUTPUT_TARGET::CONSOLE) {
|
||||
char s[12];
|
||||
json[name] = Helpers::render_boolean(s, value_b, true); // console use web settings
|
||||
} else if (EMSESP::system_.bool_format() == BOOL_FORMAT_TRUEFALSE) {
|
||||
json[name] = value_b;
|
||||
} else if (EMSESP::system_.bool_format() == BOOL_FORMAT_10) {
|
||||
json[name] = value_b ? 1 : 0;
|
||||
} else {
|
||||
char s[12];
|
||||
json[name] = Helpers::render_boolean(s, value_b);
|
||||
Mqtt::add_value_bool(json, name, value_b);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2006,12 +1994,12 @@ bool EMSdevice::generate_values(JsonObject output, const int8_t tag_filter, cons
|
||||
char time_s[60];
|
||||
snprintf(time_s,
|
||||
sizeof(time_s),
|
||||
"%lu %s %lu %s %lu %s",
|
||||
(time_value / 1440),
|
||||
"%d %s %d %s %d %s",
|
||||
(int)(time_value / 1440),
|
||||
Helpers::translated_word(FL_(days)),
|
||||
((time_value % 1440) / 60),
|
||||
(int)((time_value % 1440) / 60),
|
||||
Helpers::translated_word(FL_(hours)),
|
||||
(time_value % 60),
|
||||
(int)(time_value % 60),
|
||||
Helpers::translated_word(FL_(minutes)));
|
||||
json[name] = time_s;
|
||||
} else {
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include "helpers.h"
|
||||
#include "emsdevicevalue.h"
|
||||
|
||||
#include <esp32-psram.h>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace emsesp {
|
||||
@@ -549,13 +550,12 @@ class EMSdevice {
|
||||
}
|
||||
};
|
||||
|
||||
std::vector<uint16_t> handlers_ignored_;
|
||||
|
||||
std::vector<uint16_t, AllocatorPSRAM<uint16_t>> handlers_ignored_, handlers_broadcasted_, handlers_config_;
|
||||
#if defined(EMSESP_STANDALONE) || defined(EMSESP_TEST)
|
||||
public: // so we can call it from WebCustomizationService::load_test_data() and EMSESP::dump_all_entities()
|
||||
#endif
|
||||
std::vector<TelegramFunction> telegram_functions_; // each EMS device has its own set of registered telegram types
|
||||
std::vector<DeviceValue> devicevalues_; // all the device values
|
||||
std::vector<TelegramFunction, AllocatorPSRAM<TelegramFunction>> telegram_functions_; // each EMS device has its own set of registered telegram types
|
||||
std::vector<DeviceValue, AllocatorPSRAM<DeviceValue>> devicevalues_; // all the device values
|
||||
};
|
||||
|
||||
} // namespace emsesp
|
||||
|
||||
@@ -29,22 +29,28 @@ static_assert(uuid::console::thread_safe, "uuid-console must be thread-safe");
|
||||
namespace emsesp {
|
||||
|
||||
// Static member definitions
|
||||
#ifndef EMSESP_STANDALONE
|
||||
std::vector<std::unique_ptr<EMSdevice>, AllocatorPSRAM<std::unique_ptr<EMSdevice>>> EMSESP::emsdevices{};
|
||||
std::vector<EMSESP::Device_record, AllocatorPSRAM<EMSESP::Device_record>> EMSESP::device_library_;
|
||||
#else
|
||||
std::vector<std::unique_ptr<EMSdevice>> EMSESP::emsdevices{};
|
||||
std::vector<EMSESP::Device_record> EMSESP::device_library_;
|
||||
uuid::log::Logger EMSESP::logger_{F_(emsesp), uuid::log::Facility::KERN};
|
||||
uint16_t EMSESP::watch_id_ = WATCH_ID_NONE;
|
||||
uint8_t EMSESP::watch_ = 0;
|
||||
uint16_t EMSESP::read_id_ = WATCH_ID_NONE;
|
||||
bool EMSESP::read_next_ = false;
|
||||
uint16_t EMSESP::publish_id_ = 0;
|
||||
uint16_t EMSESP::response_id_ = 0;
|
||||
bool EMSESP::tap_water_active_ = false;
|
||||
uint8_t EMSESP::publish_all_idx_ = 0;
|
||||
uint8_t EMSESP::unique_id_count_ = 0;
|
||||
bool EMSESP::trace_raw_ = false;
|
||||
uint16_t EMSESP::wait_validate_ = 0;
|
||||
bool EMSESP::wait_km_ = false;
|
||||
uint32_t EMSESP::last_fetch_ = 0;
|
||||
#endif
|
||||
|
||||
uuid::log::Logger EMSESP::logger_{F_(emsesp), uuid::log::Facility::KERN};
|
||||
uint16_t EMSESP::watch_id_ = WATCH_ID_NONE;
|
||||
uint8_t EMSESP::watch_ = 0;
|
||||
uint16_t EMSESP::read_id_ = WATCH_ID_NONE;
|
||||
bool EMSESP::read_next_ = false;
|
||||
uint16_t EMSESP::publish_id_ = 0;
|
||||
uint16_t EMSESP::response_id_ = 0;
|
||||
bool EMSESP::tap_water_active_ = false;
|
||||
uint8_t EMSESP::publish_all_idx_ = 0;
|
||||
uint8_t EMSESP::unique_id_count_ = 0;
|
||||
bool EMSESP::trace_raw_ = false;
|
||||
uint16_t EMSESP::wait_validate_ = 0;
|
||||
bool EMSESP::wait_km_ = false;
|
||||
uint32_t EMSESP::last_fetch_ = 0;
|
||||
|
||||
AsyncWebServer webServer(80);
|
||||
|
||||
@@ -78,10 +84,6 @@ uuid::log::Logger EMSESP::logger() {
|
||||
return logger_;
|
||||
}
|
||||
|
||||
#ifndef EMSESP_STANDALONE
|
||||
uuid::syslog::SyslogService System::syslog_;
|
||||
#endif
|
||||
|
||||
// The services
|
||||
RxService EMSESP::rxservice_; // incoming Telegram Rx handler
|
||||
TxService EMSESP::txservice_; // outgoing Telegram Tx handler
|
||||
@@ -516,19 +518,19 @@ void EMSESP::show_sensor_values(uuid::console::Shell & shell) {
|
||||
for (const auto & sensor : temperaturesensor_.sensors()) {
|
||||
if (Helpers::hasValue(sensor.temperature_c)) {
|
||||
shell.printfln(" %s: %s%s °%c%s (Offset: %s, ID: %s, System: %s)",
|
||||
sensor.name().c_str(),
|
||||
sensor.name(),
|
||||
COLOR_BRIGHT_GREEN,
|
||||
Helpers::render_value(s, sensor.temperature_c, 10, fahrenheit),
|
||||
(fahrenheit == 0) ? 'C' : 'F',
|
||||
COLOR_RESET,
|
||||
Helpers::render_value(s2, sensor.offset(), 10, fahrenheit),
|
||||
sensor.id().c_str(),
|
||||
sensor.id(),
|
||||
sensor.is_system() ? "Yes" : "No");
|
||||
} else {
|
||||
shell.printfln(" %s (Offset: %s, ID: %s, System: %s)",
|
||||
sensor.name().c_str(),
|
||||
sensor.name(),
|
||||
Helpers::render_value(s, sensor.offset(), 10, fahrenheit),
|
||||
sensor.id().c_str(),
|
||||
sensor.id(),
|
||||
sensor.is_system() ? "Yes" : "No");
|
||||
}
|
||||
}
|
||||
@@ -544,7 +546,7 @@ void EMSESP::show_sensor_values(uuid::console::Shell & shell) {
|
||||
switch (sensor.type()) {
|
||||
case AnalogSensor::AnalogType::ADC:
|
||||
shell.printfln(" %s: %s%s %s%s (Type: ADC, Factor: %s, Offset: %s, System: %s)",
|
||||
sensor.name().c_str(),
|
||||
sensor.name(),
|
||||
COLOR_BRIGHT_GREEN,
|
||||
Helpers::render_value(s, sensor.value(), 2),
|
||||
EMSdevice::uom_to_string(sensor.uom()),
|
||||
@@ -557,7 +559,7 @@ void EMSESP::show_sensor_values(uuid::console::Shell & shell) {
|
||||
// case AnalogSensor::AnalogType::DIGITAL_IN:
|
||||
// case AnalogSensor::AnalogType::COUNTER:
|
||||
shell.printfln(" %s: %s%d%s (Type: %s)",
|
||||
sensor.name().c_str(),
|
||||
sensor.name(),
|
||||
COLOR_BRIGHT_GREEN,
|
||||
(uint16_t)sensor.value(), // as int
|
||||
COLOR_RESET,
|
||||
|
||||
@@ -72,6 +72,7 @@
|
||||
#include "command.h"
|
||||
|
||||
#include "../emsesp_version.h"
|
||||
#include <esp32-psram.h>
|
||||
|
||||
// Load external modules
|
||||
class Module {}; // forward declaration
|
||||
@@ -220,8 +221,7 @@ class EMSESP {
|
||||
static void scan_devices();
|
||||
static void clear_all_devices();
|
||||
|
||||
static std::vector<std::unique_ptr<EMSdevice>> emsdevices;
|
||||
|
||||
static std::vector<std::unique_ptr<EMSdevice>, AllocatorPSRAM<std::unique_ptr<EMSdevice>>> emsdevices;
|
||||
// services
|
||||
static Mqtt mqtt_;
|
||||
static Modbus * modbus_;
|
||||
@@ -266,7 +266,7 @@ class EMSESP {
|
||||
const char * default_name;
|
||||
uint8_t flags;
|
||||
};
|
||||
static std::vector<Device_record> device_library_;
|
||||
static std::vector<Device_record, AllocatorPSRAM<Device_record>> device_library_;
|
||||
|
||||
static uint16_t watch_id_;
|
||||
static uint8_t watch_;
|
||||
|
||||
@@ -470,26 +470,25 @@ char * Helpers::utf8tolatin1(char * result, const char * c, const uint8_t len) {
|
||||
*p = '\0'; // terminate result
|
||||
return result;
|
||||
}
|
||||
|
||||
// creates string of hex values from an array of bytes
|
||||
std::string Helpers::data_to_hex(const uint8_t * data, const uint8_t length) {
|
||||
if (length == 0) {
|
||||
return "<empty>";
|
||||
}
|
||||
|
||||
char str[length * 3];
|
||||
memset(str, 0, sizeof(str));
|
||||
std::string str;
|
||||
str.reserve(length * 3 + 1);
|
||||
|
||||
char buffer[4];
|
||||
char * p = &str[0];
|
||||
char buffer[4];
|
||||
for (uint8_t i = 0; i < length; i++) {
|
||||
Helpers::hextoa(buffer, data[i]);
|
||||
*p++ = buffer[0];
|
||||
*p++ = buffer[1];
|
||||
*p++ = ' '; // space
|
||||
str.append(Helpers::hextoa(buffer, data[i]));
|
||||
str.push_back(' ');
|
||||
}
|
||||
*--p = '\0'; // null terminate just in case, loosing the trailing space
|
||||
|
||||
return std::string(str);
|
||||
if (!str.empty()) {
|
||||
str.pop_back();
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
// takes a hex string and convert it to an unsigned 32bit number (max 8 hex digits)
|
||||
|
||||
@@ -896,6 +896,8 @@ MAKE_TRANSLATION(cyl3BottomTemp, "cyl3bottomtemp", "third cylinder bottom temper
|
||||
MAKE_TRANSLATION(cylTopTemp, "cyltoptemp", "cylinder top temperature (TS10)", "Speichertemperatur oben (TS10)", "", "Cylindertemperatur Toppen (TS10)", "", "", "", "", "", "horná teplota valca (TS10)", "") // TODO translate
|
||||
MAKE_TRANSLATION(transferPumpMod, "transferpumpmod", "transfer pump modulation", "Transferpumpenmodulation", "", "Överföringspumpmodulering", "", "", "", "", "", "modulácia prenosového čerpadla", "") // TODO translate
|
||||
MAKE_TRANSLATION(transferPump, "transferpump", "transfer pump", "Transferpumpe", "", "Överföringspump", "", "", "", "", "", "prenosové čerpadlo", "") // TODO translate
|
||||
MAKE_TRANSLATION(heatAssistOn, "heatassiston", "heat assistance on", "Einschaltdiff. Rücklaufanh.")
|
||||
MAKE_TRANSLATION(heatAssistOff, "heatassistoff", "heat assistance off", "Ausschaltdiff. Rücklaufanh.")
|
||||
|
||||
// solar dhw
|
||||
MAKE_TRANSLATION(wwColdTemp, "coldtemp", "cold water", "Kaltwasser", "", "kallvatten", "zimna woda", "", "", "", "", "studená voda", "studená voda") // TODO translate
|
||||
|
||||
@@ -486,6 +486,8 @@ const std::initializer_list<Modbus::EntityModbusInfo> Modbus::modbus_register_ma
|
||||
REGISTER_MAPPING(dt::SOLAR, TAG_TYPE_DEVICE_DATA, FL_(heatCnt), 67, 1), // heatcnt
|
||||
REGISTER_MAPPING(dt::SOLAR, TAG_TYPE_DEVICE_DATA, FL_(swapFlowTemp), 68, 1), // swapflowtemp
|
||||
REGISTER_MAPPING(dt::SOLAR, TAG_TYPE_DEVICE_DATA, FL_(swapRetTemp), 69, 1), // swaprettemp
|
||||
REGISTER_MAPPING(dt::SOLAR, TAG_TYPE_DEVICE_DATA, FL_(heatAssistOn), 70, 1), // heatassiston
|
||||
REGISTER_MAPPING(dt::SOLAR, TAG_TYPE_DEVICE_DATA, FL_(heatAssistOff), 71, 1), // heatassistoff
|
||||
REGISTER_MAPPING(dt::SOLAR, TAG_TYPE_DHW, FL_(wwMinTemp), 0, 1), // mintemp
|
||||
REGISTER_MAPPING(dt::HEATPUMP, TAG_TYPE_DEVICE_DATA, FL_(airHumidity), 0, 1), // airhumidity
|
||||
REGISTER_MAPPING(dt::HEATPUMP, TAG_TYPE_DEVICE_DATA, FL_(dewTemperature), 1, 1), // dewtemperature
|
||||
|
||||
@@ -47,7 +47,7 @@ bool Mqtt::send_response_;
|
||||
bool Mqtt::publish_single_;
|
||||
bool Mqtt::publish_single2cmd_;
|
||||
|
||||
std::vector<Mqtt::MQTTSubFunction> Mqtt::mqtt_subfunctions_;
|
||||
std::vector<Mqtt::MQTTSubFunction, AllocatorPSRAM<Mqtt::MQTTSubFunction>> Mqtt::mqtt_subfunctions_;
|
||||
|
||||
uint32_t Mqtt::mqtt_publish_fails_ = 0;
|
||||
bool Mqtt::connecting_ = false;
|
||||
@@ -119,7 +119,7 @@ void Mqtt::resubscribe() {
|
||||
}
|
||||
|
||||
for (const auto & mqtt_subfunction : mqtt_subfunctions_) {
|
||||
queue_subscribe_message(mqtt_subfunction.topic_);
|
||||
queue_subscribe_message(mqtt_subfunction.topic_.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1511,4 +1511,16 @@ void Mqtt::add_ha_bool(JsonObject doc) {
|
||||
}
|
||||
}
|
||||
|
||||
// adds the bool depending on bool setting
|
||||
void Mqtt::add_value_bool(JsonObject doc, const char * name, bool value) {
|
||||
if (EMSESP::system_.bool_format() == BOOL_FORMAT_TRUEFALSE) {
|
||||
doc[name] = value;
|
||||
} else if (EMSESP::system_.bool_format() == BOOL_FORMAT_10) {
|
||||
doc[name] = value ? 1 : 0;
|
||||
} else {
|
||||
char result[12];
|
||||
doc[name] = Helpers::render_boolean(result, value);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace emsesp
|
||||
@@ -26,6 +26,7 @@
|
||||
#include "console.h"
|
||||
#include "command.h"
|
||||
#include "emsdevicevalue.h"
|
||||
#include <esp32-psram.h>
|
||||
|
||||
using uuid::console::Shell;
|
||||
|
||||
@@ -262,6 +263,7 @@ class Mqtt {
|
||||
const char * cond2 = nullptr,
|
||||
const char * negcond = nullptr);
|
||||
static void add_ha_bool(JsonObject doc);
|
||||
static void add_value_bool(JsonObject doc, const char * name, bool value);
|
||||
|
||||
private:
|
||||
static uuid::log::Logger logger_;
|
||||
@@ -279,18 +281,18 @@ class Mqtt {
|
||||
// function handlers for MQTT subscriptions
|
||||
struct MQTTSubFunction {
|
||||
uint8_t device_type_; // which device type, from DeviceType::
|
||||
const std::string topic_; // short topic name
|
||||
const stringPSRAM topic_; // short topic name
|
||||
mqtt_sub_function_p mqtt_subfunction_; // can be empty
|
||||
|
||||
// replaced &&topic with &topic in 3.7.0-dev.43, so we prevent the std:move later
|
||||
MQTTSubFunction(uint8_t device_type, const std::string & topic, mqtt_sub_function_p mqtt_subfunction)
|
||||
: device_type_(device_type)
|
||||
, topic_(topic)
|
||||
, topic_(topic.c_str())
|
||||
, mqtt_subfunction_(mqtt_subfunction) {
|
||||
}
|
||||
};
|
||||
|
||||
static std::vector<MQTTSubFunction> mqtt_subfunctions_; // list of mqtt subscribe callbacks for all devices
|
||||
static std::vector<MQTTSubFunction, AllocatorPSRAM<MQTTSubFunction>> mqtt_subfunctions_; // list of mqtt subscribe callbacks for all devices
|
||||
|
||||
uint32_t last_publish_boiler_ = 0;
|
||||
uint32_t last_publish_thermostat_ = 0;
|
||||
|
||||
@@ -195,9 +195,8 @@ void Shower::create_ha_discovery() {
|
||||
JsonDocument doc;
|
||||
char topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
|
||||
char str[70];
|
||||
char stat_t[50];
|
||||
|
||||
doc["~"] = Mqtt::base();
|
||||
doc["~"] = Mqtt::base();
|
||||
|
||||
// shower active
|
||||
doc["name"] = "Shower Active";
|
||||
@@ -209,11 +208,11 @@ void Shower::create_ha_discovery() {
|
||||
}
|
||||
doc["uniq_id"] = str;
|
||||
doc["def_ent_id"] = (std::string) "binary_sensor." + str;
|
||||
doc["stat_t"] = "~/shower_active";
|
||||
doc["stat_t"] = "~/shower_active";
|
||||
|
||||
Mqtt::add_ha_bool(doc.as<JsonObject>());
|
||||
Mqtt::add_ha_dev_section(doc.as<JsonObject>(), "Shower Sensor", nullptr, nullptr, nullptr, false);
|
||||
Mqtt::add_ha_avail_section(doc.as<JsonObject>(), stat_t, true); // no conditions
|
||||
Mqtt::add_ha_avail_section(doc.as<JsonObject>(), "~/shower_active", true); // no conditions
|
||||
|
||||
snprintf(topic, sizeof(topic), "binary_sensor/%s/shower_active/config", Mqtt::basename().c_str());
|
||||
ha_configdone_ = Mqtt::queue_ha(topic, doc.as<JsonObject>()); // publish the config payload with retain flag
|
||||
@@ -225,8 +224,8 @@ void Shower::create_ha_discovery() {
|
||||
|
||||
doc["uniq_id"] = str;
|
||||
doc["def_ent_id"] = (std::string) "sensor." + str;
|
||||
doc["stat_t"] = "~/shower_data",
|
||||
doc["name"] = "Shower Duration";
|
||||
doc["stat_t"] = "~/shower_data";
|
||||
doc["name"] = "Shower Duration";
|
||||
|
||||
// don't bother with value template conditions if using Domoticz which doesn't fully support MQTT Discovery
|
||||
if (Mqtt::discovery_type() == Mqtt::discoveryType::HOMEASSISTANT) {
|
||||
@@ -241,7 +240,7 @@ void Shower::create_ha_discovery() {
|
||||
// doc["ent_cat"] = "diagnostic";
|
||||
|
||||
Mqtt::add_ha_dev_section(doc.as<JsonObject>(), "Shower Sensor", nullptr, nullptr, nullptr, false);
|
||||
Mqtt::add_ha_avail_section(doc.as<JsonObject>(), stat_t, false, "value_json.duration is defined");
|
||||
Mqtt::add_ha_avail_section(doc.as<JsonObject>(), "~/shower_data", false, "value_json.duration is defined");
|
||||
|
||||
snprintf(topic, sizeof(topic), "sensor/%s/shower_duration/config", Mqtt::basename().c_str());
|
||||
Mqtt::queue_ha(topic, doc.as<JsonObject>()); // publish the config payload with retain flag
|
||||
|
||||
@@ -83,15 +83,19 @@ const char * const languages[] = {EMSESP_LOCALE_EN,
|
||||
|
||||
static constexpr uint8_t NUM_LANGUAGES = sizeof(languages) / sizeof(const char *);
|
||||
|
||||
#ifndef EMSESP_STANDALONE
|
||||
uuid::syslog::SyslogService System::syslog_;
|
||||
#endif
|
||||
|
||||
uuid::log::Logger System::logger_{F_(system), uuid::log::Facility::KERN};
|
||||
|
||||
// init statics
|
||||
PButton System::myPButton_;
|
||||
bool System::test_set_all_active_ = false;
|
||||
uint32_t System::max_alloc_mem_;
|
||||
uint32_t System::heap_mem_;
|
||||
std::vector<uint8_t> System::valid_system_gpios_;
|
||||
std::vector<uint8_t> System::used_gpios_;
|
||||
PButton System::myPButton_;
|
||||
bool System::test_set_all_active_ = false;
|
||||
uint32_t System::max_alloc_mem_;
|
||||
uint32_t System::heap_mem_;
|
||||
std::vector<uint8_t, AllocatorPSRAM<uint8_t>> System::valid_system_gpios_;
|
||||
std::vector<uint8_t, AllocatorPSRAM<uint8_t>> System::used_gpios_;
|
||||
|
||||
// find the index of the language
|
||||
// 0 = EN, 1 = DE, etc...
|
||||
@@ -366,21 +370,20 @@ void System::syslog_init() {
|
||||
#ifndef EMSESP_STANDALONE
|
||||
if (syslog_enabled_) {
|
||||
// start & configure syslog
|
||||
EMSESP::logger().info("Starting Syslog service");
|
||||
syslog_.start();
|
||||
|
||||
syslog_.maximum_log_messages(10);
|
||||
syslog_.log_level((uuid::log::Level)syslog_level_);
|
||||
syslog_.mark_interval(syslog_mark_interval_);
|
||||
syslog_.destination(syslog_host_.c_str(), syslog_port_);
|
||||
syslog_.hostname(hostname().c_str());
|
||||
|
||||
syslog_.hostname(hostname());
|
||||
EMSESP::logger().info("Starting Syslog service");
|
||||
} else if (syslog_.started()) {
|
||||
// in case service is still running, this flushes the queue
|
||||
// https://github.com/emsesp/EMS-ESP/issues/496
|
||||
EMSESP::logger().info("Stopping Syslog");
|
||||
syslog_.log_level((uuid::log::Level)-1); // stop server
|
||||
syslog_.loop();
|
||||
syslog_.log_level(uuid::log::Level::OFF); // stop server
|
||||
syslog_.mark_interval(0);
|
||||
syslog_.destination("");
|
||||
// syslog_.destination("");
|
||||
}
|
||||
if (Mqtt::publish_single()) {
|
||||
if (Mqtt::publish_single2cmd()) {
|
||||
@@ -758,7 +761,9 @@ void System::network_init() {
|
||||
int mdio = 18; // Pin# of the I²C IO signal for the Ethernet PHY - hardcoded
|
||||
uint8_t phy_addr = eth_phy_addr_; // I²C-address of Ethernet PHY (0 or 1 for LAN8720, 31 for TLK110)
|
||||
int8_t power = eth_power_; // Pin# of the enable signal for the external crystal oscillator (-1 to disable for internal APLL source)
|
||||
eth_phy_type_t type = (phy_type_ == PHY_type::PHY_TYPE_LAN8720) ? ETH_PHY_LAN8720 : ETH_PHY_TLK110; // Type of the Ethernet PHY (LAN8720 or TLK110)
|
||||
eth_phy_type_t type = (phy_type_ == PHY_type::PHY_TYPE_LAN8720) ? ETH_PHY_LAN8720
|
||||
: (phy_type_ == PHY_type::PHY_TYPE_TLK110) ? ETH_PHY_TLK110
|
||||
: ETH_PHY_RTL8201; // Type of the Ethernet PHY (LAN8720 or TLK110)
|
||||
// clock mode:
|
||||
// ETH_CLOCK_GPIO0_IN = 0 RMII clock input to GPIO0
|
||||
// ETH_CLOCK_GPIO0_OUT = 1 RMII clock output from GPIO0
|
||||
@@ -1875,7 +1880,8 @@ bool System::command_info(const char * value, const int8_t id, JsonObject output
|
||||
});
|
||||
|
||||
// NTP status
|
||||
node = output["ntp"].to<JsonObject>();
|
||||
node = output["ntp"].to<JsonObject>();
|
||||
node["NTPstatus"] = EMSESP::system_.ntp_connected() ? "connected" : "disconnected";
|
||||
EMSESP::esp32React.getNTPSettingsService()->read([&](const NTPSettings & settings) {
|
||||
#ifndef EMSESP_STANDALONE
|
||||
node["enabled"] = settings.enabled;
|
||||
@@ -2507,10 +2513,10 @@ uint8_t System::systemStatus() {
|
||||
}
|
||||
|
||||
// takes a string range like "6-11, 1, 23, 24-48" which has optional ranges and single values and converts to a vector of ints
|
||||
std::vector<uint8_t> System::string_range_to_vector(const std::string & range) {
|
||||
std::vector<uint8_t> gpios;
|
||||
std::string::size_type pos = 0;
|
||||
std::string::size_type prev = 0;
|
||||
std::vector<uint8_t, AllocatorPSRAM<uint8_t>> System::string_range_to_vector(const std::string & range) {
|
||||
std::vector<uint8_t, AllocatorPSRAM<uint8_t>> gpios;
|
||||
std::string::size_type pos = 0;
|
||||
std::string::size_type prev = 0;
|
||||
|
||||
auto process_part = [&gpios](std::string part) {
|
||||
// trim whitespace
|
||||
@@ -2562,19 +2568,17 @@ void System::set_valid_system_gpios() {
|
||||
// 38 and 39 are input only
|
||||
// 45 and 36 are strapping pins, input only
|
||||
valid_system_gpios_ = string_range_to_vector("0-14, 17, 18, 21, 33-39, 45, 46");
|
||||
#elif CONFIG_IDF_TARGET_ESP32 || defined(EMSESP_STANDALONE)
|
||||
// 1 and 3 are UART0 pins
|
||||
#elif CONFIG_IDF_TARGET_ESP32
|
||||
// 1 and 3 are UART0 pins, but used for some eth-boards (BBQKees-E32, OlimexPOE)
|
||||
// 32-39 is ADC1, input only
|
||||
valid_system_gpios_ = string_range_to_vector("0, 2, 4, 5, 12-19, 23, 25-27, 32-39");
|
||||
#else
|
||||
#endif
|
||||
|
||||
// if psram is enabled remove pins 16 and 17 from the list, if set
|
||||
#if CONFIG_IDF_TARGET_ESP32
|
||||
if (ESP.getPsramSize() > 0) {
|
||||
valid_system_gpios_.erase(std::remove(valid_system_gpios_.begin(), valid_system_gpios_.end(), 16), valid_system_gpios_.end());
|
||||
valid_system_gpios_.erase(std::remove(valid_system_gpios_.begin(), valid_system_gpios_.end(), 17), valid_system_gpios_.end());
|
||||
// if psram is enabled remove pins 16 and 17 from the list
|
||||
valid_system_gpios_ = string_range_to_vector("0-5, 12-15, 18-19, 23, 25-27, 32-39");
|
||||
} else {
|
||||
valid_system_gpios_ = string_range_to_vector("0-5, 12-19, 23, 25-27, 32-39");
|
||||
}
|
||||
#elif defined(EMSESP_STANDALONE)
|
||||
valid_system_gpios_ = string_range_to_vector("0-5, 12-19, 23, 25-27, 32-39");
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
#include <uuid/syslog.h>
|
||||
#endif
|
||||
|
||||
#include <esp32-psram.h>
|
||||
#include <uuid/log.h>
|
||||
#include <PButton.h>
|
||||
|
||||
@@ -59,7 +60,7 @@ using uuid::console::Shell;
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
enum PHY_type : uint8_t { PHY_TYPE_NONE = 0, PHY_TYPE_LAN8720, PHY_TYPE_TLK110 };
|
||||
enum PHY_type : uint8_t { PHY_TYPE_NONE = 0, PHY_TYPE_LAN8720, PHY_TYPE_TLK110, PHY_TYPE_RTL8201 };
|
||||
|
||||
enum SYSTEM_STATUS : uint8_t {
|
||||
SYSTEM_STATUS_NORMAL = 0,
|
||||
@@ -395,10 +396,10 @@ class System {
|
||||
void led_monitor();
|
||||
void system_check();
|
||||
|
||||
static std::vector<uint8_t> string_range_to_vector(const std::string & range);
|
||||
static std::vector<uint8_t, AllocatorPSRAM<uint8_t>> string_range_to_vector(const std::string & range);
|
||||
|
||||
static std::vector<uint8_t> valid_system_gpios_; // list of valid GPIOs for the ESP32 board that can be used
|
||||
static std::vector<uint8_t> used_gpios_; // list of GPIOs used by the application
|
||||
static std::vector<uint8_t, AllocatorPSRAM<uint8_t>> valid_system_gpios_; // list of valid GPIOs for the ESP32 board that can be used
|
||||
static std::vector<uint8_t, AllocatorPSRAM<uint8_t>> used_gpios_; // list of GPIOs used by the application
|
||||
|
||||
int8_t wifi_quality(int8_t dBm);
|
||||
|
||||
|
||||
@@ -633,7 +633,8 @@ void TxService::retry_tx(const uint8_t operation, const uint8_t * data, const ui
|
||||
EMSESP::wait_validate(0); // do not wait for validation
|
||||
return;
|
||||
}
|
||||
|
||||
// for the last try wait 2 sec before sending.
|
||||
delayed_send_ = (retry_count_ < MAXIMUM_TX_RETRIES) ? 0 : (uuid::get_uptime() + POST_SEND_DELAY);
|
||||
tx_telegrams_.emplace_front(tx_telegram_id_++, std::move(telegram_last_), true, get_post_send_query());
|
||||
}
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
#endif
|
||||
|
||||
#include "helpers.h"
|
||||
#include <esp32-psram.h>
|
||||
|
||||
#define MAX_RX_TELEGRAMS 100 // size of Rx queue
|
||||
#define MAX_TX_TELEGRAMS 160 // size of Tx queue
|
||||
@@ -287,7 +288,7 @@ class RxService : public EMSbus {
|
||||
}
|
||||
};
|
||||
|
||||
std::deque<QueuedRxTelegram> queue() const {
|
||||
std::deque<QueuedRxTelegram, AllocatorPSRAM<QueuedRxTelegram>> queue() const {
|
||||
return rx_telegrams_;
|
||||
}
|
||||
|
||||
@@ -298,7 +299,8 @@ class RxService : public EMSbus {
|
||||
uint32_t telegram_count_ = 0; // # Rx received
|
||||
uint32_t telegram_error_count_ = 0; // # Rx CRC errors
|
||||
std::shared_ptr<const Telegram> rx_telegram; // the incoming Rx telegram
|
||||
std::deque<QueuedRxTelegram> rx_telegrams_; // the Rx Queue
|
||||
|
||||
std::deque<QueuedRxTelegram, AllocatorPSRAM<QueuedRxTelegram>> rx_telegrams_; // the Rx Queue
|
||||
};
|
||||
|
||||
class TxService : public EMSbus {
|
||||
@@ -419,7 +421,7 @@ class TxService : public EMSbus {
|
||||
}
|
||||
};
|
||||
|
||||
std::deque<QueuedTxTelegram> queue() const {
|
||||
std::deque<QueuedTxTelegram, AllocatorPSRAM<QueuedTxTelegram>> queue() const {
|
||||
return tx_telegrams_;
|
||||
}
|
||||
|
||||
@@ -431,7 +433,7 @@ class TxService : public EMSbus {
|
||||
static constexpr uint32_t POST_SEND_DELAY = 2000;
|
||||
|
||||
private:
|
||||
std::deque<QueuedTxTelegram> tx_telegrams_; // the Tx queue
|
||||
std::deque<QueuedTxTelegram, AllocatorPSRAM<QueuedTxTelegram>> tx_telegrams_; // the Tx queue
|
||||
|
||||
uint32_t telegram_read_count_ = 0; // # Tx successful reads
|
||||
uint32_t telegram_write_count_ = 0; // # Tx successful writes
|
||||
|
||||
@@ -108,7 +108,7 @@ void TemperatureSensor::loop() {
|
||||
last_activity_ = time_now;
|
||||
}
|
||||
} else if (state_ == State::READING) {
|
||||
if (temperature_convert_complete() && (time_now - last_activity_ > CONVERSION_MS)) {
|
||||
if (temperature_convert_complete(time_now - last_activity_)) {
|
||||
#ifdef EMSESP_DEBUG_SENSOR
|
||||
LOG_DEBUG("Scanning for temperature sensors");
|
||||
#endif
|
||||
@@ -181,12 +181,12 @@ void TemperatureSensor::loop() {
|
||||
|
||||
default:
|
||||
sensorfails_++;
|
||||
LOG_ERROR("Unknown sensor %s", Sensor(addr).id().c_str());
|
||||
LOG_ERROR("Unknown sensor %s", Sensor(addr).id());
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
sensorfails_++;
|
||||
LOG_ERROR("Invalid sensor %s", Sensor(addr).id().c_str());
|
||||
LOG_ERROR("Invalid sensor %s", Sensor(addr).id());
|
||||
}
|
||||
} else {
|
||||
if (!parasite_) {
|
||||
@@ -219,13 +219,13 @@ void TemperatureSensor::loop() {
|
||||
s->set_name("gateway_temperature");
|
||||
s->set_is_system(true); // mark as internal system temperature sensor
|
||||
if (!EMSESP::nvs_.isKey("intTemp")) {
|
||||
EMSESP::nvs_.putString("intTemp", s->id().c_str());
|
||||
EMSESP::nvs_.putString("intTemp", s->id());
|
||||
}
|
||||
// LOG_NOTICE("Adding system sensor for gateway temperature %s", s->id().c_str());
|
||||
EMSESP::webCustomizationService.update([&](WebCustomization & settings) {
|
||||
auto newSensor = SensorCustomization();
|
||||
newSensor.id = s->id();
|
||||
newSensor.name = s->name();
|
||||
auto newSensor = SensorCustomization();
|
||||
strlcpy(newSensor.id, s->id(), sizeof(newSensor.id));
|
||||
strlcpy(newSensor.name, s->name(), sizeof(newSensor.name));
|
||||
newSensor.offset = 0;
|
||||
newSensor.is_system = s->is_system(); // always true
|
||||
settings.sensorCustomizations.push_back(newSensor);
|
||||
@@ -244,21 +244,21 @@ void TemperatureSensor::loop() {
|
||||
#endif
|
||||
}
|
||||
|
||||
bool TemperatureSensor::temperature_convert_complete() {
|
||||
bool TemperatureSensor::temperature_convert_complete(const uint32_t time) {
|
||||
#ifndef EMSESP_STANDALONE
|
||||
if (parasite_) {
|
||||
return true; // don't care, use the minimum time in loop
|
||||
return time > CONVERSION_MS; // don't care, use the datasheet time
|
||||
}
|
||||
return bus_.read_bit() == 1;
|
||||
#else
|
||||
return true;
|
||||
return time > CONVERSION_MS;
|
||||
#endif
|
||||
}
|
||||
|
||||
int16_t TemperatureSensor::get_temperature_c(const uint8_t addr[]) {
|
||||
#ifndef EMSESP_STANDALONE
|
||||
if (!bus_.reset()) {
|
||||
LOG_ERROR("Bus reset failed before reading scratchpad from %s", Sensor(addr).id().c_str());
|
||||
LOG_ERROR("Bus reset failed before reading scratchpad from %s", Sensor(addr).id());
|
||||
return EMS_VALUE_INT16_NOTSET;
|
||||
}
|
||||
YIELD;
|
||||
@@ -270,7 +270,7 @@ int16_t TemperatureSensor::get_temperature_c(const uint8_t addr[]) {
|
||||
YIELD;
|
||||
|
||||
if (!bus_.reset()) {
|
||||
LOG_ERROR("Bus reset failed after reading scratchpad from %s", Sensor(addr).id().c_str());
|
||||
LOG_ERROR("Bus reset failed after reading scratchpad from %s", Sensor(addr).id());
|
||||
return EMS_VALUE_INT16_NOTSET;
|
||||
}
|
||||
YIELD;
|
||||
@@ -286,7 +286,7 @@ int16_t TemperatureSensor::get_temperature_c(const uint8_t addr[]) {
|
||||
scratchpad[6],
|
||||
scratchpad[7],
|
||||
scratchpad[8],
|
||||
Sensor(addr).id().c_str());
|
||||
Sensor(addr).id());
|
||||
return EMS_VALUE_INT16_NOTSET;
|
||||
}
|
||||
|
||||
@@ -319,10 +319,10 @@ int16_t TemperatureSensor::get_temperature_c(const uint8_t addr[]) {
|
||||
}
|
||||
|
||||
// update temperature sensor information name and offset
|
||||
bool TemperatureSensor::update(const std::string & id, const std::string & name, int16_t offset, bool is_system) {
|
||||
bool TemperatureSensor::update(const char * id, const char * name, int16_t offset, bool hide, bool is_system) {
|
||||
// find the sensor
|
||||
for (auto & sensor : sensors_) {
|
||||
if (sensor.id() == id) {
|
||||
if (!strcmp(id, sensor.id())) {
|
||||
// found a match, update the sensor object
|
||||
|
||||
// if HA is enabled then delete the old record
|
||||
@@ -336,27 +336,27 @@ bool TemperatureSensor::update(const std::string & id, const std::string & name,
|
||||
sensor.set_is_system(is_system);
|
||||
|
||||
// store the new name and offset in our configuration
|
||||
EMSESP::webCustomizationService.update([&id, &name, &offset, &is_system, &sensor](WebCustomization & settings) {
|
||||
EMSESP::webCustomizationService.update([&id, &name, &offset, &sensor, &hide, &is_system](WebCustomization & settings) {
|
||||
// look it up to see if it exists
|
||||
bool found = false;
|
||||
for (auto & SensorCustomization : settings.sensorCustomizations) {
|
||||
if (SensorCustomization.id == id) {
|
||||
SensorCustomization.name = name;
|
||||
if (!strcmp(id, SensorCustomization.id)) {
|
||||
strlcpy(SensorCustomization.name, name, sizeof(SensorCustomization.name));
|
||||
SensorCustomization.offset = offset;
|
||||
SensorCustomization.is_system = is_system;
|
||||
found = true;
|
||||
LOG_DEBUG("Customizing existing sensor ID %s", id.c_str());
|
||||
LOG_DEBUG("Customizing existing sensor ID %s", id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
auto newSensor = SensorCustomization();
|
||||
newSensor.id = id;
|
||||
newSensor.name = name;
|
||||
auto newSensor = SensorCustomization();
|
||||
strlcpy(newSensor.id, id, sizeof(newSensor.id));
|
||||
strlcpy(newSensor.name, name, sizeof(newSensor.name));
|
||||
newSensor.offset = offset;
|
||||
newSensor.is_system = is_system; // is user defined, not system
|
||||
settings.sensorCustomizations.push_back(newSensor);
|
||||
LOG_DEBUG("Adding new customization for sensor ID %s", id.c_str());
|
||||
LOG_DEBUG("Adding new customization for sensor ID %s", id);
|
||||
}
|
||||
sensor.ha_registered = false; // it's changed so we may need to recreate the HA config
|
||||
return StateUpdateResult::CHANGED;
|
||||
@@ -438,30 +438,27 @@ void TemperatureSensor::publish_sensor(const Sensor & sensor) {
|
||||
if (Mqtt::enabled() && Mqtt::publish_single()) {
|
||||
char topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
|
||||
if (Mqtt::publish_single2cmd()) {
|
||||
snprintf(topic, sizeof(topic), "%s/%s", F_(temperaturesensor), sensor.name().c_str());
|
||||
snprintf(topic, sizeof(topic), "%s/%s", F_(temperaturesensor), sensor.name());
|
||||
} else {
|
||||
snprintf(topic, sizeof(topic), "%s%s/%s", F_(temperaturesensor), "_data", sensor.name().c_str());
|
||||
snprintf(topic, sizeof(topic), "%s%s/%s", F_(temperaturesensor), "_data", sensor.name());
|
||||
}
|
||||
char payload[10];
|
||||
Mqtt::queue_publish(topic, Helpers::render_value(payload, sensor.temperature_c, 10, EMSESP::system_.fahrenheit() ? 2 : 0));
|
||||
}
|
||||
char cmd[COMMAND_MAX_LENGTH];
|
||||
snprintf(cmd, sizeof(cmd), "%s/%s", F_(temperaturesensor), sensor.name().c_str());
|
||||
snprintf(cmd, sizeof(cmd), "%s/%s", F_(temperaturesensor), sensor.name());
|
||||
EMSESP::webSchedulerService.onChange(cmd);
|
||||
}
|
||||
|
||||
// send empty config topic to remove the entry from HA
|
||||
void TemperatureSensor::remove_ha_topic(const std::string & id) {
|
||||
void TemperatureSensor::remove_ha_topic(const char * id) {
|
||||
if (!Mqtt::ha_enabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_DEBUG("Removing HA config for temperature sensor ID %s", id.c_str());
|
||||
// use '_' as HA doesn't like '-' in the topic name
|
||||
std::string sensorid = id;
|
||||
std::replace(sensorid.begin(), sensorid.end(), '-', '_');
|
||||
LOG_DEBUG("Removing HA config for temperature sensor ID %s", id);
|
||||
char topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
|
||||
snprintf(topic, sizeof(topic), "sensor/%s/temperaturesensor_%s/config", Mqtt::basename().c_str(), sensorid.c_str());
|
||||
snprintf(topic, sizeof(topic), "sensor/%s/temperaturesensor_%s/config", Mqtt::basename().c_str(), id);
|
||||
Mqtt::queue_remove_topic(topic);
|
||||
}
|
||||
|
||||
@@ -504,10 +501,10 @@ void TemperatureSensor::publish_values(const bool force) {
|
||||
remove_ha_topic(sensor.id());
|
||||
sensor.ha_registered = false;
|
||||
} else if (!sensor.ha_registered || force) {
|
||||
LOG_DEBUG("Recreating HA config for sensor ID %s", sensor.id().c_str());
|
||||
LOG_DEBUG("Recreating HA config for sensor ID %s", sensor.id());
|
||||
|
||||
JsonDocument config;
|
||||
config["~"] = Mqtt::base();
|
||||
config["~"] = Mqtt::base();
|
||||
config["dev_cla"] = "temperature";
|
||||
config["stat_cla"] = "measurement";
|
||||
|
||||
@@ -520,10 +517,10 @@ void TemperatureSensor::publish_values(const bool force) {
|
||||
char val_obj[70];
|
||||
char val_cond[170];
|
||||
if (Mqtt::is_nested()) {
|
||||
snprintf(val_obj, sizeof(val_obj), "value_json['%s']['temp']", sensor.id().c_str());
|
||||
snprintf(val_cond, sizeof(val_cond), "value_json['%s'] is defined and %s is defined", sensor.id().c_str(), val_obj);
|
||||
snprintf(val_obj, sizeof(val_obj), "value_json['%s']['temp']", sensor.id());
|
||||
snprintf(val_cond, sizeof(val_cond), "value_json['%s'] is defined and %s is defined", sensor.id(), val_obj);
|
||||
} else {
|
||||
snprintf(val_obj, sizeof(val_obj), "value_json['%s']", sensor.name().c_str());
|
||||
snprintf(val_obj, sizeof(val_obj), "value_json['%s']", sensor.name());
|
||||
snprintf(val_cond, sizeof(val_cond), "%s is defined", val_obj);
|
||||
}
|
||||
|
||||
@@ -537,17 +534,14 @@ void TemperatureSensor::publish_values(const bool force) {
|
||||
|
||||
char uniq_s[70];
|
||||
if (Mqtt::entity_format() == Mqtt::entityFormat::MULTI_SHORT) {
|
||||
snprintf(uniq_s, sizeof(uniq_s), "%s_%s_%s", Mqtt::basename().c_str(), F_(temperaturesensor), sensor.id().c_str());
|
||||
snprintf(uniq_s, sizeof(uniq_s), "%s_%s_%s", Mqtt::basename().c_str(), F_(temperaturesensor), sensor.id());
|
||||
} else {
|
||||
snprintf(uniq_s, sizeof(uniq_s), "%s_%s", F_(temperaturesensor), sensor.id().c_str());
|
||||
snprintf(uniq_s, sizeof(uniq_s), "%s_%s", F_(temperaturesensor), sensor.id());
|
||||
}
|
||||
|
||||
config["uniq_id"] = uniq_s;
|
||||
config["def_ent_id"] = (std::string) "sensor." + uniq_s;
|
||||
|
||||
char name[50];
|
||||
snprintf(name, sizeof(name), "%s", sensor.name().c_str());
|
||||
config["name"] = name;
|
||||
config["name"] = sensor.name();
|
||||
|
||||
// see if we need to create the [devs] discovery section, as this needs only to be done once for all sensors
|
||||
bool is_ha_device_created = false;
|
||||
@@ -562,7 +556,7 @@ void TemperatureSensor::publish_values(const bool force) {
|
||||
Mqtt::add_ha_avail_section(config.as<JsonObject>(), stat_t, !is_ha_device_created, val_cond);
|
||||
|
||||
char topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
|
||||
snprintf(topic, sizeof(topic), "sensor/%s/%s_%s/config", Mqtt::basename().c_str(), F_(temperaturesensor), sensor.id().c_str());
|
||||
snprintf(topic, sizeof(topic), "sensor/%s/%s_%s/config", Mqtt::basename().c_str(), F_(temperaturesensor), sensor.id());
|
||||
|
||||
sensor.ha_registered = Mqtt::queue_ha(topic, config.as<JsonObject>());
|
||||
}
|
||||
@@ -579,17 +573,15 @@ TemperatureSensor::Sensor::Sensor(const uint8_t addr[])
|
||||
: internal_id_(((uint64_t)addr[0] << 48) | ((uint64_t)addr[1] << 40) | ((uint64_t)addr[2] << 32) | ((uint64_t)addr[3] << 24) | ((uint64_t)addr[4] << 16)
|
||||
| ((uint64_t)addr[5] << 8) | ((uint64_t)addr[6])) {
|
||||
// create ID string
|
||||
char id_s[20];
|
||||
snprintf(id_s,
|
||||
sizeof(id_s),
|
||||
snprintf(id_,
|
||||
sizeof(id_),
|
||||
"%02X_%04X_%04X_%04X",
|
||||
(unsigned int)(internal_id_ >> 48) & 0xFF,
|
||||
(unsigned int)(internal_id_ >> 32) & 0xFFFF,
|
||||
(unsigned int)(internal_id_ >> 16) & 0xFFFF,
|
||||
(unsigned int)(internal_id_) & 0xFFFF);
|
||||
id_ = std::string(id_s);
|
||||
name_ = std::string{}; // name (alias) is empty
|
||||
offset_ = 0; // 0 degrees offset
|
||||
name_[0] = '\0';
|
||||
offset_ = 0; // 0 degrees offset
|
||||
}
|
||||
|
||||
uint64_t TemperatureSensor::get_id(const uint8_t addr[]) {
|
||||
@@ -599,8 +591,8 @@ uint64_t TemperatureSensor::get_id(const uint8_t addr[]) {
|
||||
|
||||
// find the name from the customization service
|
||||
// if empty, return the ID as a string
|
||||
std::string TemperatureSensor::Sensor::name() const {
|
||||
if (name_.empty()) {
|
||||
const char * TemperatureSensor::Sensor::name() const {
|
||||
if (name_[0] == '\0') {
|
||||
return id_;
|
||||
}
|
||||
return name_;
|
||||
@@ -613,8 +605,8 @@ bool TemperatureSensor::Sensor::apply_customization() {
|
||||
auto const & sensors = settings.sensorCustomizations;
|
||||
if (!sensors.empty()) {
|
||||
for (const auto & sensor : sensors) {
|
||||
if (id_ == sensor.id) {
|
||||
LOG_DEBUG("Loading customization for temperature sensor %s", sensor.id.c_str());
|
||||
if (!strcmp(sensor.id, id_)) {
|
||||
LOG_DEBUG("Loading customization for temperature sensor %s", id_);
|
||||
set_name(sensor.name);
|
||||
set_offset(sensor.offset);
|
||||
set_is_system(sensor.is_system);
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
#ifndef EMSESP_STANDALONE
|
||||
#include <OneWire.h>
|
||||
#endif
|
||||
#include <esp32-psram.h>
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
@@ -51,7 +52,7 @@ class TemperatureSensor {
|
||||
is_system_ = is_system;
|
||||
}
|
||||
|
||||
std::string id() const {
|
||||
const char * id() const {
|
||||
return id_;
|
||||
}
|
||||
|
||||
@@ -62,9 +63,10 @@ class TemperatureSensor {
|
||||
offset_ = offset;
|
||||
}
|
||||
|
||||
std::string name() const;
|
||||
void set_name(const std::string & name) {
|
||||
name_ = name;
|
||||
const char * name() const;
|
||||
|
||||
void set_name(const char * name) {
|
||||
strlcpy(name_, name, sizeof(name_));
|
||||
}
|
||||
|
||||
bool apply_customization();
|
||||
@@ -75,11 +77,12 @@ class TemperatureSensor {
|
||||
bool ha_registered = false;
|
||||
|
||||
private:
|
||||
uint64_t internal_id_;
|
||||
std::string id_;
|
||||
std::string name_;
|
||||
int16_t offset_;
|
||||
bool is_system_;
|
||||
uint64_t internal_id_;
|
||||
char id_[18];
|
||||
char name_[20];
|
||||
int16_t offset_;
|
||||
|
||||
bool is_system_;
|
||||
};
|
||||
|
||||
TemperatureSensor() = default;
|
||||
@@ -94,7 +97,7 @@ class TemperatureSensor {
|
||||
bool get_value_info(JsonObject output, const char * cmd, const int8_t id = -1);
|
||||
|
||||
// return back reference to the sensor list, used by other classes
|
||||
std::vector<Sensor> sensors() const {
|
||||
std::vector<Sensor, AllocatorPSRAM<Sensor>> sensors() const {
|
||||
return sensors_;
|
||||
}
|
||||
|
||||
@@ -121,7 +124,7 @@ class TemperatureSensor {
|
||||
return sensors_.size();
|
||||
}
|
||||
|
||||
bool update(const std::string & id, const std::string & name, int16_t offset, bool is_system);
|
||||
bool update(const char* id, const char* name, int16_t offset, bool hide = false, bool is_system = false);
|
||||
|
||||
#if defined(EMSESP_TEST)
|
||||
void load_test_data();
|
||||
@@ -159,13 +162,13 @@ class TemperatureSensor {
|
||||
|
||||
static uuid::log::Logger logger_;
|
||||
|
||||
bool temperature_convert_complete();
|
||||
bool temperature_convert_complete(const uint32_t time);
|
||||
int16_t get_temperature_c(const uint8_t addr[]);
|
||||
uint64_t get_id(const uint8_t addr[]);
|
||||
void get_value_json(JsonObject output, const Sensor & sensor);
|
||||
void remove_ha_topic(const std::string & id);
|
||||
void remove_ha_topic(const char* id);
|
||||
|
||||
std::vector<Sensor> sensors_; // our list of active sensors
|
||||
std::vector<Sensor, AllocatorPSRAM<Sensor>> sensors_; // our list of active sensors
|
||||
|
||||
#ifndef EMSESP_STANDALONE
|
||||
OneWire bus_;
|
||||
|
||||
@@ -417,6 +417,20 @@ Solar::Solar(uint8_t device_type, uint8_t device_id, uint8_t product_id, const c
|
||||
DeviceValueNumOp::DV_NUMOP_DIV10,
|
||||
FL_(swapRetTemp),
|
||||
DeviceValueUOM::DEGREES);
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
|
||||
&heatAssistOn_,
|
||||
DeviceValueType::INT8,
|
||||
DeviceValueNumOp::DV_NUMOP_DIV10,
|
||||
FL_(heatAssistOn),
|
||||
DeviceValueUOM::K,
|
||||
MAKE_CF_CB(set_solarHeatAssistOn));
|
||||
register_device_value(DeviceValueTAG::TAG_DEVICE_DATA,
|
||||
&heatAssistOff_,
|
||||
DeviceValueType::INT8,
|
||||
DeviceValueNumOp::DV_NUMOP_DIV10,
|
||||
FL_(heatAssistOff),
|
||||
DeviceValueUOM::K,
|
||||
MAKE_CF_CB(set_solarHeatAssistOn));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -540,7 +554,8 @@ void Solar::process_SM100Circuit2Config(std::shared_ptr<const Telegram> telegram
|
||||
|
||||
// type 0x35C Heat assistance
|
||||
void Solar::process_SM100HeatAssist(std::shared_ptr<const Telegram> telegram) {
|
||||
has_update(telegram, solarHeatAssist_, 0); // is *10
|
||||
has_update(telegram, heatAssistOn_, 0); // is *10
|
||||
has_update(telegram, heatAssistOff_, 1); // is *10
|
||||
}
|
||||
|
||||
// type 0x361 differential control
|
||||
@@ -1103,4 +1118,22 @@ bool Solar::set_diffControl(const char * value, const int8_t id) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Solar::set_solarHeatAssistOn(const char * value, const int8_t id) {
|
||||
float t;
|
||||
if (!Helpers::value2float(value, t)) {
|
||||
return false;
|
||||
}
|
||||
write_command(0x35C, 0, (uint8_t)(t * 10), 0x35C);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Solar::set_solarHeatAssistOff(const char * value, const int8_t id) {
|
||||
float t;
|
||||
if (!Helpers::value2float(value, t)) {
|
||||
return false;
|
||||
}
|
||||
write_command(0x35C, 1, (uint8_t)(t * 10), 0x35C);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace emsesp
|
||||
|
||||
@@ -105,7 +105,8 @@ class Solar : public EMSdevice {
|
||||
uint8_t solarPump2Mode_; // 00=off, 01=PWM, 02=10V
|
||||
|
||||
// telegram 0x35C Heat assistance
|
||||
uint8_t solarHeatAssist_; // is *10
|
||||
int8_t heatAssistOn_; // is *10
|
||||
int8_t heatAssistOff_; // is *10
|
||||
|
||||
// telegram 0x035F
|
||||
uint8_t cylPriority_; // 0 or 1
|
||||
@@ -195,6 +196,8 @@ class Solar : public EMSdevice {
|
||||
bool set_cylPriority(const char * value, const int8_t id);
|
||||
bool set_heatAssist(const char * value, const int8_t id);
|
||||
bool set_diffControl(const char * value, const int8_t id);
|
||||
bool set_solarHeatAssistOn(const char * value, const int8_t id);
|
||||
bool set_solarHeatAssistOff(const char * value, const int8_t id);
|
||||
};
|
||||
|
||||
} // namespace emsesp
|
||||
|
||||
@@ -1 +1 @@
|
||||
#define EMSESP_APP_VERSION "3.7.3-dev.34"
|
||||
#define EMSESP_APP_VERSION "3.7.3-dev.35"
|
||||
|
||||
@@ -106,6 +106,7 @@ void EMSuart::start(const uint8_t tx_mode, const uint8_t rx_gpio, const uint8_t
|
||||
xTaskCreatePinnedToCore(uart_event_task, "uart_event_task", EMSESP_UART_STACKSIZE, NULL, EMSESP_UART_PRIORITY, &xHandle, EMSESP_UART_RUNNING_CORE);
|
||||
#endif
|
||||
} else {
|
||||
uart_set_pin(EMSUART_NUM, tx_gpio, rx_gpio, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
|
||||
vTaskResume(xHandle);
|
||||
}
|
||||
tx_mode_ = tx_mode;
|
||||
|
||||
@@ -159,7 +159,7 @@ void WebAPIService::parse(AsyncWebServerRequest * request, JsonObject input) {
|
||||
response->setLength();
|
||||
response->setContentType("application/json; charset=utf-8");
|
||||
request->send(response);
|
||||
|
||||
api_count_++;
|
||||
// serialize JSON to string to ensure correct content-length and avoid HTTP parsing errors (issue #2752)
|
||||
// std::string output_str;
|
||||
// serializeJson(output, output_str);
|
||||
|
||||
@@ -88,9 +88,7 @@ void WebActivityService::webActivityService(AsyncWebServerRequest * request) {
|
||||
statJson["id"] = 7;
|
||||
statJson["s"] = EMSESP::system_.syslog_count();
|
||||
statJson["f"] = EMSESP::system_.syslog_fails();
|
||||
statJson["q"] = (EMSESP::system_.syslog_count() + EMSESP::system_.syslog_fails()) == 0
|
||||
? 100
|
||||
: 100 - (uint8_t)((100 * EMSESP::system_.syslog_fails()) / (EMSESP::system_.syslog_count() + EMSESP::system_.syslog_fails()));
|
||||
statJson["q"] = EMSESP::system_.syslog_count() == 0 ? 100 : 100 - (uint8_t)((100 * EMSESP::system_.syslog_fails()) / EMSESP::system_.syslog_count());
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ void WebCustomEntity::read(WebCustomEntity & webEntity, JsonObject root) {
|
||||
ei["type_id"] = entityItem.type_id;
|
||||
ei["offset"] = entityItem.offset;
|
||||
ei["factor"] = entityItem.factor;
|
||||
ei["name"] = entityItem.name;
|
||||
ei["name"] = (const char *)entityItem.name;
|
||||
ei["uom"] = entityItem.value_type == DeviceValueType::BOOL ? 0 : entityItem.uom;
|
||||
ei["value_type"] = entityItem.value_type;
|
||||
ei["writeable"] = entityItem.writeable;
|
||||
@@ -91,12 +91,12 @@ StateUpdateResult WebCustomEntity::update(JsonObject root, WebCustomEntity & web
|
||||
entityItem.type_id = ei["type_id"];
|
||||
entityItem.offset = ei["offset"];
|
||||
entityItem.factor = ei["factor"];
|
||||
entityItem.name = ei["name"].as<std::string>();
|
||||
entityItem.uom = ei["uom"];
|
||||
entityItem.value_type = ei["value_type"];
|
||||
entityItem.writeable = ei["writeable"];
|
||||
entityItem.hide = ei["hide"] | false;
|
||||
entityItem.data = ei["value"].as<std::string>();
|
||||
strlcpy(entityItem.name, ei["name"].as<const char *>(), sizeof(entityItem.name));
|
||||
if (entityItem.ram == 1) {
|
||||
entityItem.device_id = 0;
|
||||
entityItem.type_id = 0;
|
||||
@@ -130,12 +130,12 @@ StateUpdateResult WebCustomEntity::update(JsonObject root, WebCustomEntity & web
|
||||
|
||||
webCustomEntity.customEntityItems.push_back(entityItem); // add to list
|
||||
|
||||
if (entityItem.writeable && !entityItem.name.empty()) {
|
||||
if (entityItem.writeable && entityItem.name[0] != '\0') {
|
||||
Command::add(
|
||||
EMSdevice::DeviceType::CUSTOM,
|
||||
webCustomEntity.customEntityItems.back().name.c_str(),
|
||||
webCustomEntity.customEntityItems.back().name,
|
||||
[webCustomEntity](const char * value, const int8_t id) {
|
||||
return EMSESP::webCustomEntityService.command_setvalue(value, id, webCustomEntity.customEntityItems.back().name.c_str());
|
||||
return EMSESP::webCustomEntityService.command_setvalue(value, id, webCustomEntity.customEntityItems.back().name);
|
||||
},
|
||||
FL_(entity_cmd),
|
||||
CommandFlag::ADMIN_ONLY);
|
||||
@@ -143,7 +143,7 @@ StateUpdateResult WebCustomEntity::update(JsonObject root, WebCustomEntity & web
|
||||
|
||||
if (entityItem.ram && doc[entityItem.name].is<JsonVariantConst>() && doc[entityItem.name] != entityItem.value) {
|
||||
char cmd[COMMAND_MAX_LENGTH];
|
||||
snprintf(cmd, sizeof(cmd), "%s/%s", F_(custom), entityItem.name.c_str());
|
||||
snprintf(cmd, sizeof(cmd), "%s/%s", F_(custom), entityItem.name);
|
||||
EMSESP::webSchedulerService.onChange(cmd);
|
||||
}
|
||||
}
|
||||
@@ -196,7 +196,7 @@ bool WebCustomEntityService::command_setvalue(const char * value, const int8_t i
|
||||
if (!Helpers::value2float(value, f)) {
|
||||
return false;
|
||||
}
|
||||
int v = f / entityItem.factor;
|
||||
int v = (f / entityItem.factor + 0.5);
|
||||
if (entityItem.value_type == DeviceValueType::UINT8 || entityItem.value_type == DeviceValueType::INT8) {
|
||||
EMSESP::send_write_request(entityItem.type_id, entityItem.device_id, entityItem.offset, v, 0);
|
||||
} else if (entityItem.value_type == DeviceValueType::UINT16 || entityItem.value_type == DeviceValueType::INT16) {
|
||||
@@ -213,7 +213,7 @@ bool WebCustomEntityService::command_setvalue(const char * value, const int8_t i
|
||||
publish();
|
||||
}
|
||||
char cmd[COMMAND_MAX_LENGTH];
|
||||
snprintf(cmd, sizeof(cmd), "%s/%s", F_(custom), entityItem.name.c_str());
|
||||
snprintf(cmd, sizeof(cmd), "%s/%s", F_(custom), entityItem.name);
|
||||
EMSESP::webSchedulerService.onChange(cmd);
|
||||
return true;
|
||||
}
|
||||
@@ -224,19 +224,16 @@ bool WebCustomEntityService::command_setvalue(const char * value, const int8_t i
|
||||
// output of a single value
|
||||
// if add_uom is true it will add the UOM string to the value
|
||||
void WebCustomEntityService::render_value(JsonObject output, CustomEntityItem const & entity, const bool useVal, const bool web, const bool add_uom) {
|
||||
char payload[20];
|
||||
std::string name = useVal ? "value" : entity.name;
|
||||
char payload[20];
|
||||
const char * name = useVal ? "value" : entity.name;
|
||||
|
||||
switch (entity.value_type) {
|
||||
case DeviceValueType::BOOL:
|
||||
if ((uint8_t)entity.value != EMS_VALUE_BOOL_NOTSET) {
|
||||
if (web) {
|
||||
output[name] = Helpers::render_boolean(payload, (uint8_t)entity.value, true);
|
||||
} else if (EMSESP::system_.bool_format() == BOOL_FORMAT_TRUEFALSE) {
|
||||
output[name] = (uint8_t)entity.value ? true : false;
|
||||
} else if (EMSESP::system_.bool_format() == BOOL_FORMAT_10) {
|
||||
output[name] = (uint8_t)entity.value ? 1 : 0;
|
||||
} else {
|
||||
output[name] = Helpers::render_boolean(payload, (uint8_t)entity.value);
|
||||
Mqtt::add_value_bool(output, name, (uint8_t)entity.value != 0);
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -248,7 +245,7 @@ void WebCustomEntityService::render_value(JsonObject output, CustomEntityItem co
|
||||
break;
|
||||
case DeviceValueType::UINT8:
|
||||
if ((uint8_t)entity.value != EMS_VALUE_UINT8_NOTSET) {
|
||||
std::string v = Helpers::render_value(payload, entity.factor * (uint8_t)entity.value, 2);
|
||||
std::string v = Helpers::render_value(payload, entity.factor * (int8_t)entity.value, 2);
|
||||
output[name] = add_uom ? serialized(v + ' ' + EMSdevice::uom_to_string(entity.uom)) : serialized(v);
|
||||
}
|
||||
break;
|
||||
@@ -336,8 +333,8 @@ bool WebCustomEntityService::get_value_info(JsonObject output, const char * cmd)
|
||||
|
||||
// build the json for specific entity
|
||||
void WebCustomEntityService::get_value_json(JsonObject output, CustomEntityItem const & entity) {
|
||||
output["name"] = entity.name;
|
||||
output["fullname"] = entity.name;
|
||||
output["name"] = (const char *)entity.name;
|
||||
output["fullname"] = (const char *)entity.name;
|
||||
output["storage"] = entity.ram ? "ram" : "ems";
|
||||
output["type"] = entity.value_type == DeviceValueType::BOOL ? "boolean" : entity.value_type == DeviceValueType::STRING ? "string" : F_(number);
|
||||
output["readable"] = true;
|
||||
@@ -369,9 +366,9 @@ void WebCustomEntityService::publish_single(CustomEntityItem & entity) {
|
||||
|
||||
char topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
|
||||
if (Mqtt::publish_single2cmd()) {
|
||||
snprintf(topic, sizeof(topic), "%s/%s", F_(custom), entity.name.c_str());
|
||||
snprintf(topic, sizeof(topic), "%s/%s", F_(custom), entity.name);
|
||||
} else {
|
||||
snprintf(topic, sizeof(topic), "%s_data/%s", F_(custom), entity.name.c_str());
|
||||
snprintf(topic, sizeof(topic), "%s_data/%s", F_(custom), entity.name);
|
||||
}
|
||||
|
||||
JsonDocument doc;
|
||||
@@ -411,15 +408,15 @@ void WebCustomEntityService::publish(const bool force) {
|
||||
// create HA config
|
||||
if (Mqtt::ha_enabled() && !ha_registered_) {
|
||||
JsonDocument config;
|
||||
config["~"] = Mqtt::base();
|
||||
config["~"] = Mqtt::base();
|
||||
|
||||
char stat_t[50];
|
||||
char stat_t[50];
|
||||
snprintf(stat_t, sizeof(stat_t), "~/%s_data", F_(custom));
|
||||
config["stat_t"] = stat_t;
|
||||
|
||||
char val_obj[50];
|
||||
char val_cond[65];
|
||||
snprintf(val_obj, sizeof(val_obj), "value_json['%s']", entityItem.name.c_str());
|
||||
snprintf(val_obj, sizeof(val_obj), "value_json['%s']", entityItem.name);
|
||||
snprintf(val_cond, sizeof(val_cond), "%s is defined", val_obj);
|
||||
// don't bother with value template conditions if using Domoticz which doesn't fully support MQTT Discovery
|
||||
if (Mqtt::discovery_type() == Mqtt::discoveryType::HOMEASSISTANT) {
|
||||
@@ -429,31 +426,31 @@ void WebCustomEntityService::publish(const bool force) {
|
||||
}
|
||||
|
||||
char uniq_s[70];
|
||||
snprintf(uniq_s, sizeof(uniq_s), "%s_%s", F_(custom), entityItem.name.c_str());
|
||||
snprintf(uniq_s, sizeof(uniq_s), "%s_%s", F_(custom), entityItem.name);
|
||||
|
||||
config["uniq_id"] = uniq_s;
|
||||
config["name"] = entityItem.name.c_str();
|
||||
config["name"] = (const char *)entityItem.name;
|
||||
|
||||
char topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
|
||||
|
||||
if (entityItem.writeable) {
|
||||
if (entityItem.value_type == DeviceValueType::BOOL) {
|
||||
snprintf(topic, sizeof(topic), "switch/%s/%s_%s/config", Mqtt::basename().c_str(), F_(custom), entityItem.name.c_str());
|
||||
snprintf(topic, sizeof(topic), "switch/%s/%s_%s/config", Mqtt::basename().c_str(), F_(custom), entityItem.name);
|
||||
} else if (entityItem.value_type == DeviceValueType::STRING) {
|
||||
snprintf(topic, sizeof(topic), "sensor/%s/%s_%s/config", Mqtt::basename().c_str(), F_(custom), entityItem.name.c_str());
|
||||
snprintf(topic, sizeof(topic), "sensor/%s/%s_%s/config", Mqtt::basename().c_str(), F_(custom), entityItem.name);
|
||||
} else if (Mqtt::discovery_type() == Mqtt::discoveryType::HOMEASSISTANT || Mqtt::discovery_type() == Mqtt::discoveryType::DOMOTICZ_LATEST) {
|
||||
snprintf(topic, sizeof(topic), "number/%s/%s_%s/config", Mqtt::basename().c_str(), F_(custom), entityItem.name.c_str());
|
||||
snprintf(topic, sizeof(topic), "number/%s/%s_%s/config", Mqtt::basename().c_str(), F_(custom), entityItem.name);
|
||||
} else {
|
||||
snprintf(topic, sizeof(topic), "sensor/%s/%s_%s/config", Mqtt::basename().c_str(), F_(custom), entityItem.name.c_str());
|
||||
snprintf(topic, sizeof(topic), "sensor/%s/%s_%s/config", Mqtt::basename().c_str(), F_(custom), entityItem.name);
|
||||
}
|
||||
char command_topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
|
||||
snprintf(command_topic, sizeof(command_topic), "~/%s/%s", F_(custom), entityItem.name.c_str());
|
||||
snprintf(command_topic, sizeof(command_topic), "~/%s/%s", F_(custom), entityItem.name);
|
||||
config["cmd_t"] = command_topic;
|
||||
} else {
|
||||
if (entityItem.value_type == DeviceValueType::BOOL) {
|
||||
snprintf(topic, sizeof(topic), "binary_sensor/%s/%s_%s/config", Mqtt::basename().c_str(), F_(custom), entityItem.name.c_str());
|
||||
snprintf(topic, sizeof(topic), "binary_sensor/%s/%s_%s/config", Mqtt::basename().c_str(), F_(custom), entityItem.name);
|
||||
} else {
|
||||
snprintf(topic, sizeof(topic), "sensor/%s/%s_%s/config", Mqtt::basename().c_str(), F_(custom), entityItem.name.c_str());
|
||||
snprintf(topic, sizeof(topic), "sensor/%s/%s_%s/config", Mqtt::basename().c_str(), F_(custom), entityItem.name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -523,7 +520,7 @@ void WebCustomEntityService::generate_value_web(JsonObject output, const bool is
|
||||
obj = root_obj;
|
||||
}
|
||||
|
||||
obj["id"] = "00" + entity.name;
|
||||
obj["id"] = std::string("00") + entity.name;
|
||||
if (entity.value_type != DeviceValueType::BOOL) {
|
||||
obj["u"] = entity.uom;
|
||||
}
|
||||
@@ -648,15 +645,15 @@ bool WebCustomEntityService::get_value(std::shared_ptr<const Telegram> telegram)
|
||||
if (rest > 0) {
|
||||
memcpy(&entity.raw[offset], message_data, rest);
|
||||
auto data = Helpers::data_to_hex(entity.raw, (uint8_t)length);
|
||||
if (entity.data != data && length == (int)entity.factor) {
|
||||
entity.data = data;
|
||||
if (entity.data != data.c_str() && length == (int)entity.factor) {
|
||||
entity.data = data.c_str();
|
||||
if (Mqtt::publish_single()) {
|
||||
publish_single(entity);
|
||||
} else if (EMSESP::mqtt_.get_publish_onchange(0)) {
|
||||
has_change = true;
|
||||
}
|
||||
char cmd[COMMAND_MAX_LENGTH];
|
||||
snprintf(cmd, sizeof(cmd), "%s/%s", F_(custom), entity.name.c_str());
|
||||
snprintf(cmd, sizeof(cmd), "%s/%s", F_(custom), entity.name);
|
||||
EMSESP::webSchedulerService.onChange(cmd);
|
||||
}
|
||||
}
|
||||
@@ -678,10 +675,10 @@ bool WebCustomEntityService::get_value(std::shared_ptr<const Telegram> telegram)
|
||||
has_change = true;
|
||||
}
|
||||
char cmd[COMMAND_MAX_LENGTH];
|
||||
snprintf(cmd, sizeof(cmd), "%s/%s", F_(custom), entity.name.c_str());
|
||||
snprintf(cmd, sizeof(cmd), "%s/%s", F_(custom), entity.name);
|
||||
EMSESP::webSchedulerService.onChange(cmd);
|
||||
}
|
||||
// EMSESP::logger().debug("custom entity %s received with value %d", entity.name.c_str(), (int)entity.val);
|
||||
// EMSESP::logger().debug("custom entity %s received with value %d", entity.name, (int)entity.val);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -709,7 +706,7 @@ void WebCustomEntityService::load_test_data() {
|
||||
entityItem.type_id = 24;
|
||||
entityItem.offset = 0;
|
||||
entityItem.factor = 1;
|
||||
entityItem.name = "test_custom";
|
||||
strcpy(entityItem.name,"test_custom");
|
||||
entityItem.uom = 1;
|
||||
entityItem.value_type = 1;
|
||||
entityItem.writeable = true;
|
||||
@@ -718,9 +715,9 @@ void WebCustomEntityService::load_test_data() {
|
||||
webCustomEntity.customEntityItems.push_back(entityItem);
|
||||
Command::add(
|
||||
EMSdevice::DeviceType::CUSTOM,
|
||||
webCustomEntity.customEntityItems.back().name.c_str(),
|
||||
webCustomEntity.customEntityItems.back().name,
|
||||
[webCustomEntity](const char * value, const int8_t id) {
|
||||
return EMSESP::webCustomEntityService.command_setvalue(value, id, webCustomEntity.customEntityItems.back().name.c_str());
|
||||
return EMSESP::webCustomEntityService.command_setvalue(value, id, webCustomEntity.customEntityItems.back().name);
|
||||
},
|
||||
FL_(entity_cmd),
|
||||
CommandFlag::ADMIN_ONLY);
|
||||
@@ -732,7 +729,7 @@ void WebCustomEntityService::load_test_data() {
|
||||
entityItem.type_id = 677;
|
||||
entityItem.offset = 3;
|
||||
entityItem.factor = 1;
|
||||
entityItem.name = "test_read_only";
|
||||
strcpy(entityItem.name, "test_read_only");
|
||||
entityItem.uom = 0;
|
||||
entityItem.value_type = 2;
|
||||
entityItem.writeable = false;
|
||||
@@ -746,7 +743,7 @@ void WebCustomEntityService::load_test_data() {
|
||||
entityItem.type_id = 0;
|
||||
entityItem.offset = 0;
|
||||
entityItem.factor = 1;
|
||||
entityItem.name = "test_ram";
|
||||
strcpy(entityItem.name, "test_ram");
|
||||
entityItem.uom = 0;
|
||||
entityItem.value_type = 8;
|
||||
entityItem.writeable = true;
|
||||
@@ -754,9 +751,9 @@ void WebCustomEntityService::load_test_data() {
|
||||
webCustomEntity.customEntityItems.push_back(entityItem);
|
||||
Command::add(
|
||||
EMSdevice::DeviceType::CUSTOM,
|
||||
webCustomEntity.customEntityItems.back().name.c_str(),
|
||||
webCustomEntity.customEntityItems.back().name,
|
||||
[webCustomEntity](const char * value, const int8_t id) {
|
||||
return EMSESP::webCustomEntityService.command_setvalue(value, id, webCustomEntity.customEntityItems.back().name.c_str());
|
||||
return EMSESP::webCustomEntityService.command_setvalue(value, id, webCustomEntity.customEntityItems.back().name);
|
||||
},
|
||||
FL_(entity_cmd),
|
||||
CommandFlag::ADMIN_ONLY);
|
||||
@@ -768,7 +765,7 @@ void WebCustomEntityService::load_test_data() {
|
||||
entityItem.type_id = 0;
|
||||
entityItem.offset = 0;
|
||||
entityItem.factor = 1;
|
||||
entityItem.name = "test_seltemp";
|
||||
strcpy(entityItem.name, "test_seltemp");
|
||||
entityItem.uom = 0;
|
||||
entityItem.value_type = 8;
|
||||
entityItem.writeable = true;
|
||||
@@ -777,9 +774,9 @@ void WebCustomEntityService::load_test_data() {
|
||||
webCustomEntity.customEntityItems.push_back(entityItem);
|
||||
Command::add(
|
||||
EMSdevice::DeviceType::CUSTOM,
|
||||
webCustomEntity.customEntityItems.back().name.c_str(),
|
||||
webCustomEntity.customEntityItems.back().name,
|
||||
[webCustomEntity](const char * value, const int8_t id) {
|
||||
return EMSESP::webCustomEntityService.command_setvalue(value, id, webCustomEntity.customEntityItems.back().name.c_str());
|
||||
return EMSESP::webCustomEntityService.command_setvalue(value, id, webCustomEntity.customEntityItems.back().name);
|
||||
},
|
||||
FL_(entity_cmd),
|
||||
CommandFlag::ADMIN_ONLY);
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include "../core/telegram.h"
|
||||
#include <esp32-psram.h>
|
||||
|
||||
#ifndef WebCustomEntityService_h
|
||||
#define WebCustomEntityService_h
|
||||
@@ -33,11 +34,11 @@ class CustomEntityItem {
|
||||
uint8_t offset;
|
||||
int8_t value_type;
|
||||
uint8_t uom; // DeviceValueUOM
|
||||
std::string name;
|
||||
char name[20];
|
||||
double factor;
|
||||
bool writeable;
|
||||
uint32_t value;
|
||||
std::string data;
|
||||
stringPSRAM data;
|
||||
uint8_t ram;
|
||||
uint8_t * raw;
|
||||
bool hide;
|
||||
@@ -45,7 +46,7 @@ class CustomEntityItem {
|
||||
|
||||
class WebCustomEntity {
|
||||
public:
|
||||
std::list<CustomEntityItem> customEntityItems;
|
||||
std::list<CustomEntityItem, AllocatorPSRAM<CustomEntityItem>> customEntityItems;
|
||||
|
||||
static void read(WebCustomEntity & webEntity, JsonObject root);
|
||||
static StateUpdateResult update(JsonObject root, WebCustomEntity & webEntity);
|
||||
@@ -82,7 +83,7 @@ class WebCustomEntityService : public StatefulService<WebCustomEntity> {
|
||||
|
||||
void getEntities(AsyncWebServerRequest * request);
|
||||
|
||||
std::list<CustomEntityItem> * customEntityItems_; // pointer to the list of entity items
|
||||
std::list<CustomEntityItem, AllocatorPSRAM<CustomEntityItem>> * customEntityItems_; // pointer to the list of entity items
|
||||
|
||||
bool ha_registered_ = false;
|
||||
};
|
||||
|
||||
@@ -54,23 +54,23 @@ void WebCustomization::read(WebCustomization & customizations, JsonObject root)
|
||||
JsonArray sensorsJson = root["ts"].to<JsonArray>();
|
||||
for (const SensorCustomization & sensor : customizations.sensorCustomizations) {
|
||||
JsonObject sensorJson = sensorsJson.add<JsonObject>();
|
||||
sensorJson["id"] = sensor.id; // ID of chip
|
||||
sensorJson["name"] = sensor.name; // n
|
||||
sensorJson["offset"] = sensor.offset; // o
|
||||
sensorJson["is_system"] = sensor.is_system; // s for core_voltage, supply_voltage
|
||||
sensorJson["id"] = (const char *)sensor.id; // ID of chip
|
||||
sensorJson["name"] = (const char *)sensor.name; // n
|
||||
sensorJson["offset"] = sensor.offset; // o
|
||||
sensorJson["is_system"] = sensor.is_system; // s for core_voltage, supply_voltage
|
||||
}
|
||||
|
||||
// Analog Sensor customization
|
||||
JsonArray analogJson = root["as"].to<JsonArray>();
|
||||
for (const AnalogCustomization & sensor : customizations.analogCustomizations) {
|
||||
JsonObject sensorJson = analogJson.add<JsonObject>();
|
||||
sensorJson["gpio"] = sensor.gpio; // g
|
||||
sensorJson["name"] = sensor.name; // n
|
||||
sensorJson["offset"] = sensor.offset; // o
|
||||
sensorJson["factor"] = sensor.factor; // f
|
||||
sensorJson["uom"] = sensor.uom; // u
|
||||
sensorJson["type"] = sensor.type; // t
|
||||
sensorJson["is_system"] = sensor.is_system; // s for core_voltage, supply_voltage
|
||||
sensorJson["gpio"] = sensor.gpio; // g
|
||||
sensorJson["name"] = (const char *)sensor.name; // n
|
||||
sensorJson["offset"] = sensor.offset; // o
|
||||
sensorJson["factor"] = sensor.factor; // f
|
||||
sensorJson["uom"] = sensor.uom; // u
|
||||
sensorJson["type"] = sensor.type; // t
|
||||
sensorJson["is_system"] = sensor.is_system; // s for core_voltage, supply_voltage
|
||||
}
|
||||
|
||||
// Masked entities customization and custom device name (optional)
|
||||
@@ -83,8 +83,8 @@ void WebCustomization::read(WebCustomization & customizations, JsonObject root)
|
||||
|
||||
// entries are in the form <XX><shortname>[optional customname] e.g "08heatingactive|heating is on"
|
||||
JsonArray masked_entityJson = entityJson["entity_ids"].to<JsonArray>();
|
||||
for (const std::string & entity_id : entityCustomization.entity_ids) {
|
||||
masked_entityJson.add(entity_id);
|
||||
for (const auto & entity_id : entityCustomization.entity_ids) {
|
||||
masked_entityJson.add(entity_id.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -98,16 +98,18 @@ StateUpdateResult WebCustomization::update(JsonObject root, WebCustomization & c
|
||||
auto sensorsJsons = root["ts"].as<JsonArray>();
|
||||
for (const JsonObject sensorJson : sensorsJsons) {
|
||||
// create each of the sensor, overwriting any previous settings
|
||||
auto sensor = SensorCustomization();
|
||||
sensor.id = sensorJson["id"].as<std::string>();
|
||||
sensor.name = sensorJson["name"].as<std::string>();
|
||||
sensor.offset = sensorJson["offset"];
|
||||
if (sensor.id == sensor.name) {
|
||||
sensor.name = ""; // no need to store id as name
|
||||
}
|
||||
auto sensor = SensorCustomization();
|
||||
strlcpy(sensor.id, sensorJson["id"].as<const char *>(), sizeof(sensor.id));
|
||||
strlcpy(sensor.name, sensorJson["name"].as<const char *>(), sizeof(sensor.name));
|
||||
sensor.offset = sensorJson["offset"];
|
||||
sensor.is_system = sensorJson["is_system"] | false;
|
||||
std::replace(sensor.id.begin(), sensor.id.end(), '-', '_'); // change old ids to v3.7 style
|
||||
customizations.sensorCustomizations.push_back(sensor); // add to list
|
||||
// change old ids to v3.7 style
|
||||
for (char * p = sensor.id; *p != '\0'; p++) {
|
||||
if (*p == '-') {
|
||||
*p = '_';
|
||||
}
|
||||
}
|
||||
customizations.sensorCustomizations.push_back(sensor); // add to list
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,12 +123,12 @@ StateUpdateResult WebCustomization::update(JsonObject root, WebCustomization & c
|
||||
if (!EMSESP::system_.add_gpio(analogJson["gpio"].as<uint8_t>(), "Analog Sensor")) {
|
||||
EMSESP::logger().warning("Analog sensor: Invalid GPIO %d for %s. Skipping.",
|
||||
analogJson["gpio"].as<uint8_t>(),
|
||||
analogJson["name"].as<std::string>().c_str());
|
||||
analogJson["name"].as<const char *>());
|
||||
continue;
|
||||
}
|
||||
auto analog = AnalogCustomization();
|
||||
auto analog = AnalogCustomization();
|
||||
strlcpy(analog.name, analogJson["name"].as<const char *>(), sizeof(analog.name));
|
||||
analog.gpio = analogJson["gpio"];
|
||||
analog.name = analogJson["name"].as<std::string>();
|
||||
analog.offset = analogJson["offset"];
|
||||
analog.factor = analogJson["factor"];
|
||||
analog.uom = analogJson["uom"];
|
||||
@@ -153,7 +155,7 @@ StateUpdateResult WebCustomization::update(JsonObject root, WebCustomization & c
|
||||
auto masked_entity_ids = masked_entity["entity_ids"].as<JsonArray>();
|
||||
for (const JsonVariant masked_entity_id : masked_entity_ids) {
|
||||
if (masked_entity_id.is<std::string>()) {
|
||||
emsEntity.entity_ids.push_back(masked_entity_id.as<std::string>()); // add entity list
|
||||
emsEntity.entity_ids.push_back(masked_entity_id.as<std::string>().c_str()); // add entity list
|
||||
}
|
||||
}
|
||||
|
||||
@@ -312,9 +314,9 @@ void WebCustomizationService::customization_entities(AsyncWebServerRequest * req
|
||||
read([&](WebCustomization & settings) {
|
||||
for (EntityCustomization entityCustomization : settings.entityCustomizations) {
|
||||
if (entityCustomization.device_id == device_id) {
|
||||
for (const std::string & entity_id : entityCustomization.entity_ids) {
|
||||
for (const auto & entity_id : entityCustomization.entity_ids) {
|
||||
uint8_t mask = Helpers::hextoint(entity_id.substr(0, 2).c_str());
|
||||
std::string name = DeviceValue::get_name(entity_id);
|
||||
std::string name = DeviceValue::get_name(entity_id.c_str());
|
||||
if (mask & 0x80) {
|
||||
bool is_set = false;
|
||||
for (const JsonVariant id : entity_ids_json) {
|
||||
@@ -382,23 +384,23 @@ void WebCustomizationService::load_test_data() {
|
||||
// Temperature sensors
|
||||
webCustomization.sensorCustomizations.clear(); // delete all existing sensors
|
||||
|
||||
auto sensor1 = SensorCustomization();
|
||||
sensor1.id = "01_0203_0405_0607";
|
||||
sensor1.name = "test_tempsensor1";
|
||||
auto sensor1 = SensorCustomization();
|
||||
strcpy(sensor1.id, "01_0203_0405_0607");
|
||||
strcpy(sensor1.name, "test_tempsensor1");
|
||||
sensor1.offset = 0;
|
||||
sensor1.is_system = false;
|
||||
webCustomization.sensorCustomizations.push_back(sensor1);
|
||||
|
||||
auto sensor2 = SensorCustomization();
|
||||
sensor2.id = "0B_0C0D_0E0F_1011";
|
||||
sensor2.name = "test_tempsensor2";
|
||||
auto sensor2 = SensorCustomization();
|
||||
strcpy(sensor2.id, "0B_0C0D_0E0F_1011");
|
||||
strcpy(sensor2.name, "test_tempsensor2");
|
||||
sensor2.offset = 4;
|
||||
sensor2.is_system = false;
|
||||
webCustomization.sensorCustomizations.push_back(sensor2);
|
||||
|
||||
auto sensor3 = SensorCustomization();
|
||||
sensor3.id = "28_1767_7B13_2502";
|
||||
sensor3.name = "gateway_temperature";
|
||||
auto sensor3 = SensorCustomization();
|
||||
strcpy(sensor3.id, "28_1767_7B13_2502");
|
||||
strcpy(sensor3.name, "gateway_temperature");
|
||||
sensor3.offset = 0;
|
||||
sensor3.is_system = true;
|
||||
webCustomization.sensorCustomizations.push_back(sensor3);
|
||||
@@ -406,9 +408,9 @@ void WebCustomizationService::load_test_data() {
|
||||
// Analog sensors
|
||||
// This actually adds the sensors as we use customizations to store them
|
||||
webCustomization.analogCustomizations.clear();
|
||||
auto analog = AnalogCustomization();
|
||||
analog.gpio = 36;
|
||||
analog.name = "test_analogsensor1";
|
||||
auto analog = AnalogCustomization();
|
||||
analog.gpio = 36;
|
||||
strcpy(analog.name, "test_analogsensor1");
|
||||
analog.offset = 0;
|
||||
analog.factor = 0.2;
|
||||
analog.uom = 17;
|
||||
@@ -416,9 +418,9 @@ void WebCustomizationService::load_test_data() {
|
||||
analog.is_system = false;
|
||||
webCustomization.analogCustomizations.push_back(analog);
|
||||
|
||||
analog = AnalogCustomization();
|
||||
analog.gpio = 37;
|
||||
analog.name = "test_analogsensor2";
|
||||
analog = AnalogCustomization();
|
||||
analog.gpio = 37;
|
||||
strcpy(analog.name, "test_analogsensor2");
|
||||
analog.offset = 0;
|
||||
analog.factor = 1;
|
||||
analog.uom = 0;
|
||||
@@ -426,9 +428,9 @@ void WebCustomizationService::load_test_data() {
|
||||
analog.is_system = false;
|
||||
webCustomization.analogCustomizations.push_back(analog);
|
||||
|
||||
analog = AnalogCustomization();
|
||||
analog.gpio = 38;
|
||||
analog.name = "test_analogsensor3";
|
||||
analog = AnalogCustomization();
|
||||
analog.gpio = 38;
|
||||
strcpy(analog.name, "test_analogsensor3");
|
||||
analog.offset = 0;
|
||||
analog.factor = 1;
|
||||
analog.uom = 0;
|
||||
@@ -436,9 +438,9 @@ void WebCustomizationService::load_test_data() {
|
||||
analog.is_system = false;
|
||||
webCustomization.analogCustomizations.push_back(analog);
|
||||
|
||||
analog = AnalogCustomization();
|
||||
analog.gpio = 33;
|
||||
analog.name = "test_analogsensor4";
|
||||
analog = AnalogCustomization();
|
||||
analog.gpio = 33;
|
||||
strcpy(analog.name, "test_analogsensor4");
|
||||
analog.offset = 0;
|
||||
analog.factor = 1;
|
||||
analog.uom = 0;
|
||||
@@ -446,9 +448,9 @@ void WebCustomizationService::load_test_data() {
|
||||
analog.is_system = false;
|
||||
webCustomization.analogCustomizations.push_back(analog);
|
||||
|
||||
analog = AnalogCustomization();
|
||||
analog.gpio = 39;
|
||||
analog.name = "test_analogsensor5"; // core_voltage
|
||||
analog = AnalogCustomization();
|
||||
analog.gpio = 39;
|
||||
strcpy(analog.name, "test_analogsensor5"); // core_voltage
|
||||
analog.offset = 0;
|
||||
analog.factor = 0.003771;
|
||||
analog.uom = 23;
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
#ifndef WebCustomizationService_h
|
||||
#define WebCustomizationService_h
|
||||
#include <esp32-psram.h>
|
||||
|
||||
#define EMSESP_CUSTOMIZATION_FILE "/config/emsespCustomization.json"
|
||||
|
||||
@@ -34,21 +35,21 @@ namespace emsesp {
|
||||
// Customization for temperature sensor
|
||||
class SensorCustomization {
|
||||
public:
|
||||
std::string id;
|
||||
std::string name;
|
||||
uint16_t offset;
|
||||
bool is_system; // if true, the customization is a system customization
|
||||
char id[18];
|
||||
char name[20];
|
||||
uint16_t offset;
|
||||
bool is_system; // if true, the customization is a system customization
|
||||
};
|
||||
|
||||
class AnalogCustomization {
|
||||
public:
|
||||
uint8_t gpio;
|
||||
std::string name;
|
||||
double offset;
|
||||
double factor;
|
||||
uint8_t uom; // 0 is none
|
||||
int8_t type; // -1 is for deletion
|
||||
bool is_system; // if true, the customization is a system customization
|
||||
uint8_t gpio;
|
||||
char name[20];
|
||||
double offset;
|
||||
double factor;
|
||||
uint8_t uom; // 0 is none
|
||||
int8_t type; // -1 is for deletion
|
||||
bool is_system; // if true, the customization is a system customization
|
||||
|
||||
// used for removing from a list
|
||||
bool operator==(const AnalogCustomization & a) const {
|
||||
@@ -70,11 +71,12 @@ class EntityCustomization {
|
||||
|
||||
class WebCustomization {
|
||||
public:
|
||||
std::list<SensorCustomization> sensorCustomizations; // for sensor names and offsets
|
||||
std::list<AnalogCustomization> analogCustomizations; // for analog sensors
|
||||
std::list<EntityCustomization> entityCustomizations; // for a list of entities that have a special mask set
|
||||
static void read(WebCustomization & customizations, JsonObject root);
|
||||
static StateUpdateResult update(JsonObject root, WebCustomization & customizations);
|
||||
std::list<SensorCustomization, AllocatorPSRAM<SensorCustomization>> sensorCustomizations; // for sensor names and offsets
|
||||
std::list<AnalogCustomization, AllocatorPSRAM<AnalogCustomization>> analogCustomizations; // for analog sensors
|
||||
std::list<EntityCustomization, AllocatorPSRAM<EntityCustomization>> entityCustomizations; // for a list of entities that have a special mask set
|
||||
|
||||
static void read(WebCustomization & customizations, JsonObject root);
|
||||
static StateUpdateResult update(JsonObject root, WebCustomization & customizations);
|
||||
|
||||
private:
|
||||
static bool _start;
|
||||
|
||||
@@ -158,6 +158,12 @@ void WebDataService::sensor_data(AsyncWebServerRequest * request) {
|
||||
available_gpios.add(gpio);
|
||||
}
|
||||
|
||||
// disable types that can only be used once
|
||||
JsonArray exclude_types = root["exclude_types"].to<JsonArray>();
|
||||
for (uint8_t type : EMSESP::analogsensor_.exclude_types()) {
|
||||
exclude_types.add(type);
|
||||
}
|
||||
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
}
|
||||
@@ -315,8 +321,8 @@ void WebDataService::write_temperature_sensor(AsyncWebServerRequest * request, J
|
||||
if (json.is<JsonObject>()) {
|
||||
JsonObject sensor = json;
|
||||
|
||||
std::string id = sensor["id"]; // this is the key
|
||||
std::string name = sensor["name"];
|
||||
const char * id = sensor["id"]; // this is the key
|
||||
const char * name = sensor["name"];
|
||||
|
||||
// calculate offset. We'll convert it to an int and * 10
|
||||
float offset = sensor["offset"];
|
||||
@@ -325,7 +331,7 @@ void WebDataService::write_temperature_sensor(AsyncWebServerRequest * request, J
|
||||
offset10 = offset / 0.18;
|
||||
}
|
||||
|
||||
bool is_system = sensor["is_system"];
|
||||
bool is_system = sensor["is_system"] | false;
|
||||
|
||||
ok = EMSESP::temperaturesensor_.update(id, name, offset10, is_system);
|
||||
}
|
||||
@@ -340,15 +346,15 @@ void WebDataService::write_analog_sensor(AsyncWebServerRequest * request, JsonVa
|
||||
if (json.is<JsonObject>()) {
|
||||
JsonObject analog = json;
|
||||
|
||||
uint8_t gpio = analog["gpio"];
|
||||
std::string name = analog["name"];
|
||||
double factor = analog["factor"];
|
||||
double offset = analog["offset"];
|
||||
uint8_t uom = analog["uom"];
|
||||
int8_t type = analog["type"];
|
||||
bool deleted = analog["deleted"];
|
||||
bool is_system = analog["is_system"];
|
||||
ok = EMSESP::analogsensor_.update(gpio, name, offset, factor, uom, type, deleted, is_system);
|
||||
uint8_t gpio = analog["gpio"];
|
||||
const char * name = analog["name"];
|
||||
double factor = analog["factor"];
|
||||
double offset = analog["offset"];
|
||||
uint8_t uom = analog["uom"];
|
||||
int8_t type = analog["type"];
|
||||
bool deleted = analog["deleted"];
|
||||
bool is_system = analog["is_system"] | false;
|
||||
ok = EMSESP::analogsensor_.update(gpio, name, offset, factor, uom, type, deleted, is_system);
|
||||
}
|
||||
|
||||
AsyncWebServerResponse * response = request->beginResponse(ok ? 200 : 400); // ok or bad request
|
||||
@@ -408,7 +414,7 @@ void WebDataService::dashboard_data(AsyncWebServerRequest * request) {
|
||||
node["id"] = (EMSdevice::DeviceTypeUniqueID::TEMPERATURESENSOR_UID * 100) + count++;
|
||||
|
||||
JsonObject dv = node["dv"].to<JsonObject>();
|
||||
dv["id"] = "00" + sensor.name();
|
||||
dv["id"] = std::string("00") + sensor.name();
|
||||
if (EMSESP::system_.fahrenheit()) {
|
||||
if (Helpers::hasValue(sensor.temperature_c)) {
|
||||
dv["v"] = (float)sensor.temperature_c * 0.18 + 32;
|
||||
@@ -439,7 +445,7 @@ void WebDataService::dashboard_data(AsyncWebServerRequest * request) {
|
||||
node["id"] = (EMSdevice::DeviceTypeUniqueID::ANALOGSENSOR_UID * 100) + count++;
|
||||
|
||||
JsonObject dv = node["dv"].to<JsonObject>();
|
||||
dv["id"] = "00" + sensor.name();
|
||||
dv["id"] = std::string("00") + sensor.name();
|
||||
#if CONFIG_IDF_TARGET_ESP32
|
||||
if (sensor.type() == AnalogSensor::AnalogType::DIGITAL_OUT && (sensor.gpio() == 25 || sensor.gpio() == 26)) {
|
||||
obj["v"] = Helpers::transformNumFloat(sensor.value());
|
||||
@@ -462,7 +468,8 @@ void WebDataService::dashboard_data(AsyncWebServerRequest * request) {
|
||||
}
|
||||
if (sensor.type() == AnalogSensor::AnalogType::COUNTER
|
||||
|| (sensor.type() >= AnalogSensor::AnalogType::DIGITAL_OUT && sensor.type() <= AnalogSensor::AnalogType::PWM_2)
|
||||
|| sensor.type() == AnalogSensor::AnalogType::RGB || sensor.type() == AnalogSensor::AnalogType::PULSE) {
|
||||
|| sensor.type() == AnalogSensor::AnalogType::RGB || sensor.type() == AnalogSensor::AnalogType::PULSE
|
||||
|| (sensor.type() >= AnalogSensor::AnalogType::CNT_0 && sensor.type() <= AnalogSensor::AnalogType::CNT_2)) {
|
||||
dv["c"] = sensor.name();
|
||||
}
|
||||
}
|
||||
@@ -479,12 +486,12 @@ void WebDataService::dashboard_data(AsyncWebServerRequest * request) {
|
||||
EMSESP::webSchedulerService.read([&](const WebScheduler & webScheduler) {
|
||||
for (const ScheduleItem & scheduleItem : webScheduler.scheduleItems) {
|
||||
// only add if we have a name - we don't need a u (UOM) for this
|
||||
if (!scheduleItem.name.empty()) {
|
||||
if (scheduleItem.name[0] != '\0') {
|
||||
JsonObject node = nodes.add<JsonObject>();
|
||||
node["id"] = (EMSdevice::DeviceTypeUniqueID::SCHEDULER_UID * 100) + count++;
|
||||
|
||||
JsonObject dv = node["dv"].to<JsonObject>();
|
||||
dv["id"] = "00" + scheduleItem.name;
|
||||
dv["id"] = std::string("00") + scheduleItem.name;
|
||||
dv["c"] = scheduleItem.name;
|
||||
char s[12];
|
||||
dv["v"] = Helpers::render_boolean(s, scheduleItem.active, true);
|
||||
|
||||
@@ -40,7 +40,8 @@ WebLogService::WebLogService(AsyncWebServer * server, SecurityManager * security
|
||||
|
||||
// start the log service with INFO level
|
||||
void WebLogService::begin() {
|
||||
uuid::log::Logger::register_handler(this, uuid::log::Level::INFO);
|
||||
level_ = uuid::log::Level::INFO;
|
||||
uuid::log::Logger::register_handler(this, level_);
|
||||
}
|
||||
|
||||
// apply the user settings
|
||||
@@ -49,24 +50,10 @@ void WebLogService::start() {
|
||||
maximum_log_messages_ = settings.weblog_buffer;
|
||||
limit_log_messages_ = maximum_log_messages_;
|
||||
compact_ = settings.weblog_compact;
|
||||
uuid::log::Logger::register_handler(this, (uuid::log::Level)settings.weblog_level);
|
||||
if ((uuid::log::Level)settings.weblog_level == uuid::log::Level::OFF) {
|
||||
log_messages_.clear();
|
||||
}
|
||||
level_ = (uuid::log::Level)settings.weblog_level;
|
||||
});
|
||||
}
|
||||
|
||||
uuid::log::Level WebLogService::log_level() const {
|
||||
return uuid::log::Logger::get_log_level(this);
|
||||
}
|
||||
|
||||
void WebLogService::log_level(uuid::log::Level level) {
|
||||
EMSESP::webSettingsService.update([&](WebSettings & settings) {
|
||||
settings.weblog_level = level;
|
||||
return StateUpdateResult::CHANGED;
|
||||
});
|
||||
uuid::log::Logger::register_handler(this, level);
|
||||
if (level == uuid::log::Level::OFF) {
|
||||
uuid::log::Logger::register_handler(this, level_);
|
||||
if (level_ == uuid::log::Level::OFF) {
|
||||
log_messages_.clear();
|
||||
}
|
||||
}
|
||||
@@ -75,58 +62,32 @@ size_t WebLogService::num_log_messages() const {
|
||||
return log_messages_.size();
|
||||
}
|
||||
|
||||
size_t WebLogService::maximum_log_messages() const {
|
||||
return maximum_log_messages_;
|
||||
}
|
||||
|
||||
void WebLogService::maximum_log_messages(size_t count) {
|
||||
maximum_log_messages_ = std::max((size_t)1, count);
|
||||
|
||||
if (limit_log_messages_ > maximum_log_messages_) {
|
||||
limit_log_messages_ = maximum_log_messages_;
|
||||
}
|
||||
|
||||
while (log_messages_.size() > maximum_log_messages_) {
|
||||
log_messages_.pop_front();
|
||||
}
|
||||
|
||||
EMSESP::webSettingsService.update([&](WebSettings & settings) {
|
||||
settings.weblog_buffer = count;
|
||||
return StateUpdateResult::CHANGED;
|
||||
});
|
||||
}
|
||||
|
||||
bool WebLogService::compact() const {
|
||||
return compact_;
|
||||
}
|
||||
|
||||
void WebLogService::compact(bool compact) {
|
||||
compact_ = compact;
|
||||
EMSESP::webSettingsService.update([&](WebSettings & settings) {
|
||||
settings.weblog_compact = compact;
|
||||
return StateUpdateResult::CHANGED;
|
||||
});
|
||||
}
|
||||
|
||||
WebLogService::QueuedLogMessage::QueuedLogMessage(unsigned long id, std::shared_ptr<uuid::log::Message> && content)
|
||||
WebLogService::QueuedLogMessage::QueuedLogMessage(unsigned long id, uint64_t uptime, uuid::log::Level level, const char * name, std::string text)
|
||||
: id_(id)
|
||||
, content_(std::move(content)) {
|
||||
, uptime_(uptime)
|
||||
, level_(level)
|
||||
, name_(name)
|
||||
, text_(text.c_str()) {
|
||||
}
|
||||
|
||||
void WebLogService::operator<<(std::shared_ptr<uuid::log::Message> message) {
|
||||
#ifndef EMSESP_STANDALONE
|
||||
size_t maxAlloc = ESP.getMaxAllocHeap();
|
||||
if (limit_log_messages_ > 5 && maxAlloc < 46080) {
|
||||
--limit_log_messages_;
|
||||
} else if (limit_log_messages_ < maximum_log_messages_ && maxAlloc > 51200) {
|
||||
++limit_log_messages_;
|
||||
if (emsesp::EMSESP::system_.PSram()) {
|
||||
limit_log_messages_ = maximum_log_messages_;
|
||||
} else {
|
||||
uint32_t maxAlloc = ESP.getMaxAllocHeap();
|
||||
if (limit_log_messages_ > 5 && maxAlloc < (50 * 1024)) { // 50k
|
||||
--limit_log_messages_;
|
||||
} else if (limit_log_messages_ < maximum_log_messages_ && maxAlloc > (60 * 1024)) { // 60k
|
||||
++limit_log_messages_;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
while (log_messages_.size() >= limit_log_messages_) {
|
||||
log_messages_.pop_front();
|
||||
}
|
||||
|
||||
log_messages_.emplace_back(++log_message_id_, std::move(message));
|
||||
log_messages_.emplace_back(++log_message_id_, message->uptime_ms, message->level, message->name, message->text.c_str());
|
||||
}
|
||||
|
||||
// dumps out the contents of log buffer to shell console
|
||||
@@ -136,30 +97,28 @@ void WebLogService::show(Shell & shell) {
|
||||
}
|
||||
|
||||
shell.println();
|
||||
shell.printfln("Recent Log (level %s, max %d messages):", format_level_uppercase(log_level()), maximum_log_messages());
|
||||
shell.printfln("Recent Log (level %s, max %d messages):", uuid::log::format_level_uppercase(level_), maximum_log_messages_);
|
||||
shell.println();
|
||||
|
||||
for (const auto & message : log_messages_) {
|
||||
log_message_id_tail_ = message.id_;
|
||||
|
||||
char time_string[26];
|
||||
shell.print(messagetime(time_string, message.content_->uptime_ms, sizeof(time_string)));
|
||||
shell.printf(" %c %lu: [%s] ", uuid::log::format_level_char(message.content_->level), message.id_, message.content_->name);
|
||||
shell.print(messagetime(time_string, message.uptime_, sizeof(time_string)));
|
||||
shell.printf(" %c %lu: [%s] ", uuid::log::format_level_char(message.level_), message.id_, message.name_);
|
||||
|
||||
if ((message.content_->level == uuid::log::Level::ERR) || (message.content_->level == uuid::log::Level::WARNING)) {
|
||||
if ((message.level_ == uuid::log::Level::ERR) || (message.level_ == uuid::log::Level::WARNING)) {
|
||||
shell.print(COLOR_RED);
|
||||
shell.println(message.content_->text);
|
||||
shell.println(message.text_.c_str());
|
||||
shell.print(COLOR_RESET);
|
||||
} else if (message.content_->level == uuid::log::Level::INFO) {
|
||||
} else if (message.level_ == uuid::log::Level::INFO) {
|
||||
shell.print(COLOR_YELLOW);
|
||||
shell.println(message.content_->text);
|
||||
shell.println(message.text_.c_str());
|
||||
shell.print(COLOR_RESET);
|
||||
} else if (message.content_->level == uuid::log::Level::DEBUG) {
|
||||
} else if (message.level_ == uuid::log::Level::DEBUG) {
|
||||
shell.print(COLOR_CYAN);
|
||||
shell.println(message.content_->text);
|
||||
shell.println(message.text_.c_str());
|
||||
shell.print(COLOR_RESET);
|
||||
} else {
|
||||
shell.println(message.content_->text);
|
||||
shell.println(message.text_.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,7 +146,6 @@ void WebLogService::loop() {
|
||||
// flush
|
||||
for (const auto & message : log_messages_) {
|
||||
if (message.id_ > log_message_id_tail_) {
|
||||
log_message_id_tail_ = message.id_;
|
||||
transmit(message);
|
||||
return;
|
||||
}
|
||||
@@ -201,8 +159,8 @@ char * WebLogService::messagetime(char * out, const uint64_t t, const size_t buf
|
||||
strlcpy(out, uuid::log::format_timestamp_ms(t, 3).c_str(), bufsize);
|
||||
} else {
|
||||
time_t t1 = offset + (time_t)(t / 1000);
|
||||
char timestr[bufsize];
|
||||
strftime(timestr, bufsize, "%FT%T", localtime(&t1));
|
||||
char timestr[25];
|
||||
strftime(timestr, 25, "%FT%T", localtime(&t1));
|
||||
snprintf(out, bufsize, "%s.%03d", timestr, (uint16_t)(t % 1000));
|
||||
}
|
||||
return out;
|
||||
@@ -215,17 +173,18 @@ void WebLogService::transmit(const QueuedLogMessage & message) {
|
||||
JsonObject logEvent = jsonDocument.to<JsonObject>();
|
||||
char time_string[25];
|
||||
|
||||
logEvent["t"] = messagetime(time_string, message.content_->uptime_ms, sizeof(time_string));
|
||||
logEvent["l"] = message.content_->level;
|
||||
logEvent["t"] = messagetime(time_string, message.uptime_, sizeof(time_string));
|
||||
logEvent["l"] = message.level_; // .content_->level;
|
||||
logEvent["i"] = message.id_;
|
||||
logEvent["n"] = message.content_->name;
|
||||
logEvent["m"] = message.content_->text;
|
||||
logEvent["n"] = message.name_; // content_->name;
|
||||
logEvent["m"] = message.text_; // content_->text;
|
||||
|
||||
size_t len = measureJson(jsonDocument);
|
||||
char * buffer = new char[len + 1];
|
||||
size_t len = measureJson(jsonDocument) + 1;
|
||||
char * buffer = new char[len];
|
||||
if (buffer) {
|
||||
serializeJson(jsonDocument, buffer, len + 1);
|
||||
serializeJson(jsonDocument, buffer, len);
|
||||
events_.send(buffer, "message", message.id_);
|
||||
log_message_id_tail_ = message.id_;
|
||||
}
|
||||
delete[] buffer;
|
||||
}
|
||||
@@ -236,9 +195,9 @@ void WebLogService::getSetValues(AsyncWebServerRequest * request, JsonVariant js
|
||||
// GET - return the values
|
||||
auto * response = new AsyncJsonResponse(false);
|
||||
JsonObject root = response->getRoot();
|
||||
root["level"] = log_level();
|
||||
root["max_messages"] = maximum_log_messages();
|
||||
root["compact"] = compact();
|
||||
root["level"] = level_;
|
||||
root["max_messages"] = maximum_log_messages_;
|
||||
root["compact"] = compact_;
|
||||
root["psram"] = (EMSESP::system_.PSram() > 0);
|
||||
root["developer_mode"] = EMSESP::system_.developer_mode();
|
||||
|
||||
@@ -250,19 +209,29 @@ void WebLogService::getSetValues(AsyncWebServerRequest * request, JsonVariant js
|
||||
|
||||
return;
|
||||
}
|
||||
// POST - write the settings
|
||||
level_ = json["level"];
|
||||
maximum_log_messages_ = json["max_messages"];
|
||||
compact_ = json["compact"];
|
||||
|
||||
// POST - set the values
|
||||
auto && body = json.as<JsonObject>();
|
||||
uuid::log::Logger::register_handler(this, level_);
|
||||
if (level_ == uuid::log::Level::OFF) {
|
||||
log_messages_.clear();
|
||||
}
|
||||
|
||||
uuid::log::Level level = body["level"];
|
||||
log_level(level);
|
||||
|
||||
uint8_t max_messages = body["max_messages"];
|
||||
maximum_log_messages(max_messages);
|
||||
|
||||
bool comp = body["compact"];
|
||||
compact(comp);
|
||||
if (limit_log_messages_ > maximum_log_messages_) {
|
||||
limit_log_messages_ = maximum_log_messages_;
|
||||
while (log_messages_.size() > limit_log_messages_) {
|
||||
log_messages_.pop_front();
|
||||
}
|
||||
}
|
||||
|
||||
EMSESP::webSettingsService.update([&](WebSettings & settings) {
|
||||
settings.weblog_compact = compact_;
|
||||
settings.weblog_level = level_;
|
||||
settings.weblog_buffer = maximum_log_messages_;
|
||||
return StateUpdateResult::CHANGED;
|
||||
});
|
||||
request->send(200); // OK
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
|
||||
#define EMSESP_EVENT_SOURCE_LOG_PATH "/es/log"
|
||||
#define EMSESP_LOG_SETTINGS_PATH "/rest/logSettings"
|
||||
#include <esp32-psram.h>
|
||||
|
||||
using ::uuid::console::Shell;
|
||||
|
||||
@@ -33,17 +34,11 @@ class WebLogService : public uuid::log::Handler {
|
||||
|
||||
WebLogService(AsyncWebServer * server, SecurityManager * securityManager);
|
||||
|
||||
void begin();
|
||||
void start();
|
||||
uuid::log::Level log_level() const;
|
||||
void log_level(uuid::log::Level level);
|
||||
size_t maximum_log_messages() const;
|
||||
size_t num_log_messages() const;
|
||||
void maximum_log_messages(size_t count);
|
||||
bool compact() const;
|
||||
void compact(bool compact);
|
||||
void loop();
|
||||
void show(Shell & shell);
|
||||
void begin();
|
||||
void start();
|
||||
size_t num_log_messages() const;
|
||||
void loop();
|
||||
void show(Shell & shell);
|
||||
|
||||
virtual void operator<<(std::shared_ptr<uuid::log::Message> message);
|
||||
|
||||
@@ -52,12 +47,16 @@ class WebLogService : public uuid::log::Handler {
|
||||
|
||||
class QueuedLogMessage {
|
||||
public:
|
||||
QueuedLogMessage(unsigned long id, std::shared_ptr<uuid::log::Message> && content);
|
||||
QueuedLogMessage(unsigned long id, uint64_t uptime, uuid::log::Level level, const char * name, std::string text);
|
||||
|
||||
~QueuedLogMessage() = default;
|
||||
|
||||
unsigned long id_; // Sequential identifier for this log message
|
||||
struct timeval time_; // Time message was received
|
||||
const std::shared_ptr<const uuid::log::Message> content_; // Log message content
|
||||
unsigned long id_; // Sequential identifier for this log message
|
||||
struct timeval time_; // Time message was received
|
||||
uint64_t uptime_;
|
||||
uuid::log::Level level_;
|
||||
const char * name_;
|
||||
stringPSRAM text_;
|
||||
};
|
||||
|
||||
void transmit(const QueuedLogMessage & message);
|
||||
@@ -65,12 +64,13 @@ class WebLogService : public uuid::log::Handler {
|
||||
|
||||
char * messagetime(char * out, const uint64_t t, const size_t bufsize);
|
||||
|
||||
size_t maximum_log_messages_ = MAX_LOG_MESSAGES; // Maximum number of log messages to buffer before they are output
|
||||
size_t limit_log_messages_ = 1; // dynamic limit
|
||||
unsigned long log_message_id_ = 0; // The next identifier to use for queued log messages
|
||||
unsigned long log_message_id_tail_ = 0; // last event shown on the screen after fetch
|
||||
std::deque<QueuedLogMessage> log_messages_; // Queued log messages, in the order they were received
|
||||
bool compact_ = true;
|
||||
std::deque<QueuedLogMessage, AllocatorPSRAM<QueuedLogMessage>> log_messages_; // Queued log messages, in the order they were received
|
||||
size_t maximum_log_messages_ = MAX_LOG_MESSAGES; // Maximum number of log messages to buffer before they are output
|
||||
size_t limit_log_messages_ = 1; // dynamic limit
|
||||
unsigned long log_message_id_ = 0; // The next identifier to use for queued log messages
|
||||
unsigned long log_message_id_tail_ = 0; // last event shown on the screen after fetch
|
||||
bool compact_ = true;
|
||||
uuid::log::Level level_ = uuid::log::Level::INFO;
|
||||
};
|
||||
|
||||
} // namespace emsesp
|
||||
|
||||
@@ -87,19 +87,19 @@ StateUpdateResult WebScheduler::update(JsonObject root, WebScheduler & webSchedu
|
||||
si.time = si.flags == SCHEDULEFLAG_SCHEDULE_IMMEDIATE ? "" : schedule["time"].as<std::string>();
|
||||
si.cmd = schedule["cmd"].as<std::string>();
|
||||
si.value = schedule["value"].as<std::string>();
|
||||
si.name = schedule["name"].as<std::string>();
|
||||
strlcpy(si.name, schedule["name"].as<const char *>(), sizeof(si.name));
|
||||
|
||||
// calculated elapsed minutes
|
||||
si.elapsed_min = Helpers::string2minutes(si.time);
|
||||
si.elapsed_min = Helpers::string2minutes(si.time.c_str());
|
||||
si.retry_cnt = 0xFF; // no startup retries
|
||||
|
||||
webScheduler.scheduleItems.push_back(si); // add to list
|
||||
if (!webScheduler.scheduleItems.back().name.empty()) {
|
||||
if (webScheduler.scheduleItems.back().name[0] != '\0') {
|
||||
Command::add(
|
||||
EMSdevice::DeviceType::SCHEDULER,
|
||||
webScheduler.scheduleItems.back().name.c_str(),
|
||||
webScheduler.scheduleItems.back().name,
|
||||
[webScheduler](const char * value, const int8_t id) {
|
||||
return EMSESP::webSchedulerService.command_setvalue(value, id, webScheduler.scheduleItems.back().name.c_str());
|
||||
return EMSESP::webSchedulerService.command_setvalue(value, id, webScheduler.scheduleItems.back().name);
|
||||
},
|
||||
FL_(schedule_cmd),
|
||||
CommandFlag::ADMIN_ONLY);
|
||||
@@ -146,15 +146,8 @@ bool WebSchedulerService::get_value_info(JsonObject output, const char * cmd) {
|
||||
if (!strlen(cmd) || !strcmp(cmd, F_(values)) || !strcmp(cmd, F_(info))) {
|
||||
// list all names
|
||||
for (const ScheduleItem & scheduleItem : *scheduleItems_) {
|
||||
if (!scheduleItem.name.empty()) {
|
||||
if (EMSESP::system_.bool_format() == BOOL_FORMAT_TRUEFALSE) {
|
||||
output[scheduleItem.name] = scheduleItem.active;
|
||||
} else if (EMSESP::system_.bool_format() == BOOL_FORMAT_10) {
|
||||
output[scheduleItem.name] = scheduleItem.active ? 1 : 0;
|
||||
} else {
|
||||
char result[12];
|
||||
output[scheduleItem.name] = Helpers::render_boolean(result, scheduleItem.active);
|
||||
}
|
||||
if (scheduleItem.name[0] != '\0') {
|
||||
Mqtt::add_value_bool(output, scheduleItem.name, scheduleItem.active);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
@@ -162,9 +155,9 @@ bool WebSchedulerService::get_value_info(JsonObject output, const char * cmd) {
|
||||
|
||||
if (!strcmp(cmd, F_(entities))) {
|
||||
uint8_t i = 0;
|
||||
char name[30];
|
||||
char name[20];
|
||||
for (const ScheduleItem & scheduleItem : *scheduleItems_) {
|
||||
strlcpy(name, scheduleItem.name == "" ? Helpers::smallitoa(name, i++) : scheduleItem.name.c_str(), sizeof(name));
|
||||
strlcpy(name, scheduleItem.name[0] == '\0' ? Helpers::smallitoa(name, i++) : scheduleItem.name, sizeof(name));
|
||||
get_value_json(output[name].to<JsonObject>(), scheduleItem);
|
||||
}
|
||||
return true;
|
||||
@@ -183,17 +176,10 @@ bool WebSchedulerService::get_value_info(JsonObject output, const char * cmd) {
|
||||
|
||||
// build the json for specific entity
|
||||
void WebSchedulerService::get_value_json(JsonObject output, const ScheduleItem & scheduleItem) {
|
||||
output["name"] = scheduleItem.name;
|
||||
output["fullname"] = scheduleItem.name;
|
||||
output["name"] = (const char *)scheduleItem.name;
|
||||
output["fullname"] = (const char *)scheduleItem.name;
|
||||
output["type"] = "boolean";
|
||||
if (EMSESP::system_.bool_format() == BOOL_FORMAT_TRUEFALSE) {
|
||||
output["value"] = scheduleItem.active;
|
||||
} else if (EMSESP::system_.bool_format() == BOOL_FORMAT_10) {
|
||||
output["value"] = scheduleItem.active ? 1 : 0;
|
||||
} else {
|
||||
char result[12];
|
||||
output["value"] = Helpers::render_boolean(result, scheduleItem.active);
|
||||
}
|
||||
Mqtt::add_value_bool(output, "value", scheduleItem.active);
|
||||
if (scheduleItem.flags == SCHEDULEFLAG_SCHEDULE_CONDITION) {
|
||||
output["condition"] = scheduleItem.time;
|
||||
} else if (scheduleItem.flags == SCHEDULEFLAG_SCHEDULE_ONCHANGE) {
|
||||
@@ -205,7 +191,7 @@ void WebSchedulerService::get_value_json(JsonObject output, const ScheduleItem &
|
||||
}
|
||||
output["command"] = scheduleItem.cmd;
|
||||
output["cmd_data"] = scheduleItem.value;
|
||||
bool hasName = scheduleItem.name != "";
|
||||
bool hasName = scheduleItem.name[0] != '\0';
|
||||
output["readable"] = hasName;
|
||||
output["writeable"] = hasName;
|
||||
output["visible"] = hasName;
|
||||
@@ -221,7 +207,7 @@ void WebSchedulerService::publish_single(const char * name, const bool state) {
|
||||
if (Mqtt::publish_single2cmd()) {
|
||||
snprintf(topic, sizeof(topic), "%s/%s", F_(scheduler), name);
|
||||
} else {
|
||||
snprintf(topic, sizeof(topic), "%s%s/%s", F_(scheduler), "_data", name);
|
||||
snprintf(topic, sizeof(topic), "%s_data/%s", F_(scheduler), name);
|
||||
}
|
||||
|
||||
char payload[12];
|
||||
@@ -244,36 +230,28 @@ void WebSchedulerService::publish(const bool force) {
|
||||
|
||||
if (Mqtt::publish_single() && force) {
|
||||
for (const ScheduleItem & scheduleItem : *scheduleItems_) {
|
||||
publish_single(scheduleItem.name.c_str(), scheduleItem.active);
|
||||
publish_single(scheduleItem.name, scheduleItem.active);
|
||||
}
|
||||
}
|
||||
|
||||
JsonDocument doc;
|
||||
bool ha_created = ha_registered_;
|
||||
for (const ScheduleItem & scheduleItem : *scheduleItems_) {
|
||||
if (!scheduleItem.name.empty() && !doc[scheduleItem.name].is<JsonVariantConst>()) {
|
||||
if (EMSESP::system_.bool_format() == BOOL_FORMAT_TRUEFALSE) {
|
||||
doc[scheduleItem.name] = scheduleItem.active;
|
||||
} else if (EMSESP::system_.bool_format() == BOOL_FORMAT_10) {
|
||||
doc[scheduleItem.name] = scheduleItem.active ? 1 : 0;
|
||||
} else {
|
||||
char result[12];
|
||||
doc[scheduleItem.name] = Helpers::render_boolean(result, scheduleItem.active);
|
||||
}
|
||||
if (scheduleItem.name[0] != '\0' && !doc[scheduleItem.name].is<JsonVariantConst>()) {
|
||||
Mqtt::add_value_bool(doc.as<JsonObject>(), scheduleItem.name, scheduleItem.active);
|
||||
|
||||
// create HA config
|
||||
if (Mqtt::ha_enabled() && !ha_registered_) {
|
||||
|
||||
JsonDocument config;
|
||||
config["~"] = Mqtt::base();
|
||||
config["~"] = Mqtt::base();
|
||||
|
||||
char stat_t[50];
|
||||
char stat_t[50];
|
||||
snprintf(stat_t, sizeof(stat_t), "~/%s_data", F_(scheduler));
|
||||
config["stat_t"] = stat_t;
|
||||
|
||||
char val_obj[50];
|
||||
char val_cond[65];
|
||||
snprintf(val_obj, sizeof(val_obj), "value_json['%s']", scheduleItem.name.c_str());
|
||||
snprintf(val_obj, sizeof(val_obj), "value_json['%s']", scheduleItem.name);
|
||||
snprintf(val_cond, sizeof(val_cond), "%s is defined", val_obj);
|
||||
|
||||
if (Mqtt::discovery_type() == Mqtt::discoveryType::HOMEASSISTANT) {
|
||||
@@ -283,17 +261,17 @@ void WebSchedulerService::publish(const bool force) {
|
||||
}
|
||||
|
||||
char uniq_s[70];
|
||||
snprintf(uniq_s, sizeof(uniq_s), "%s_%s", F_(scheduler), scheduleItem.name.c_str());
|
||||
snprintf(uniq_s, sizeof(uniq_s), "%s_%s", F_(scheduler), scheduleItem.name);
|
||||
|
||||
config["uniq_id"] = uniq_s;
|
||||
config["name"] = scheduleItem.name.c_str();
|
||||
config["def_ent_id"] = (std::string) "switch." + uniq_s;
|
||||
config["name"] = scheduleItem.name;
|
||||
config["def_ent_id"] = std::string("switch.") + uniq_s;
|
||||
|
||||
char topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
|
||||
char command_topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
|
||||
|
||||
snprintf(topic, sizeof(topic), "switch/%s/%s_%s/config", Mqtt::basename().c_str(), F_(scheduler), scheduleItem.name.c_str());
|
||||
snprintf(command_topic, sizeof(command_topic), "~/%s/%s", F_(scheduler), scheduleItem.name.c_str());
|
||||
snprintf(topic, sizeof(topic), "switch/%s/%s_%s/config", Mqtt::basename().c_str(), F_(scheduler), scheduleItem.name);
|
||||
snprintf(command_topic, sizeof(command_topic), "~/%s/%s", F_(scheduler), scheduleItem.name);
|
||||
config["cmd_t"] = command_topic;
|
||||
|
||||
Mqtt::add_ha_bool(config.as<JsonObject>());
|
||||
@@ -318,15 +296,13 @@ void WebSchedulerService::publish(const bool force) {
|
||||
uint8_t WebSchedulerService::count_entities(bool cmd_only) {
|
||||
uint8_t count = 0;
|
||||
for (const ScheduleItem & scheduleItem : *scheduleItems_) {
|
||||
if (!scheduleItem.name.empty() || !cmd_only) {
|
||||
if (scheduleItem.name[0] != '\0' || !cmd_only) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// execute scheduled command
|
||||
bool WebSchedulerService::command(const char * name, const std::string & command, const std::string & data) {
|
||||
std::string cmd = Helpers::toLower(command);
|
||||
@@ -428,7 +404,7 @@ bool WebSchedulerService::command(const char * name, const std::string & command
|
||||
bool WebSchedulerService::onChange(const char * cmd) {
|
||||
for (ScheduleItem & scheduleItem : *scheduleItems_) {
|
||||
if (scheduleItem.active && scheduleItem.flags == SCHEDULEFLAG_SCHEDULE_ONCHANGE
|
||||
&& Helpers::toLower(scheduleItem.time).find(Helpers::toLower(cmd)) != std::string::npos) {
|
||||
&& Helpers::toLower(scheduleItem.time.c_str()).find(Helpers::toLower(cmd)) != std::string::npos) {
|
||||
cmd_changed_.push_back(&scheduleItem);
|
||||
return true;
|
||||
}
|
||||
@@ -440,12 +416,12 @@ bool WebSchedulerService::onChange(const char * cmd) {
|
||||
void WebSchedulerService::condition() {
|
||||
for (ScheduleItem & scheduleItem : *scheduleItems_) {
|
||||
if (scheduleItem.active && scheduleItem.flags == SCHEDULEFLAG_SCHEDULE_CONDITION) {
|
||||
auto match = compute(scheduleItem.time);
|
||||
auto match = compute(scheduleItem.time.c_str());
|
||||
#ifdef EMESESP_DEBUG
|
||||
// EMSESP::logger().debug("condition match: %s", match.c_str());
|
||||
#endif
|
||||
if (match.length() == 1 && match[0] == '1' && scheduleItem.retry_cnt == 0xFF) {
|
||||
scheduleItem.retry_cnt = command(scheduleItem.name.c_str(), scheduleItem.cmd, compute(scheduleItem.value)) ? 1 : 0xFF;
|
||||
scheduleItem.retry_cnt = command(scheduleItem.name, scheduleItem.cmd.c_str(), compute(scheduleItem.value.c_str())) ? 1 : 0xFF;
|
||||
} else if (match.length() == 1 && match[0] == '0' && scheduleItem.retry_cnt == 1) {
|
||||
scheduleItem.retry_cnt = 0xFF;
|
||||
} else if (match.length() != 1) { // the match is not boolean
|
||||
@@ -475,13 +451,13 @@ void WebSchedulerService::loop() {
|
||||
// check if we have onChange events
|
||||
while (!cmd_changed_.empty()) {
|
||||
ScheduleItem si = *cmd_changed_.front();
|
||||
command(si.name.c_str(), si.cmd, compute(si.value));
|
||||
command(si.name, si.cmd.c_str(), compute(si.value.c_str()));
|
||||
cmd_changed_.pop_front();
|
||||
}
|
||||
|
||||
for (ScheduleItem & scheduleItem : *scheduleItems_) {
|
||||
if (scheduleItem.active && scheduleItem.flags == SCHEDULEFLAG_SCHEDULE_IMMEDIATE) {
|
||||
command(scheduleItem.name.c_str(), scheduleItem.cmd, compute(scheduleItem.value));
|
||||
command(scheduleItem.name, scheduleItem.cmd.c_str(), compute(scheduleItem.value.c_str()));
|
||||
scheduleItem.active = false;
|
||||
}
|
||||
}
|
||||
@@ -497,7 +473,7 @@ void WebSchedulerService::loop() {
|
||||
if (last_tm_min == -2) {
|
||||
for (ScheduleItem & scheduleItem : *scheduleItems_) {
|
||||
if (scheduleItem.active && scheduleItem.flags == SCHEDULEFLAG_SCHEDULE_TIMER && scheduleItem.elapsed_min == 0) {
|
||||
scheduleItem.retry_cnt = command(scheduleItem.name.c_str(), scheduleItem.cmd, compute(scheduleItem.value)) ? 0xFF : 0;
|
||||
scheduleItem.retry_cnt = command(scheduleItem.name, scheduleItem.cmd.c_str(), compute(scheduleItem.value.c_str())) ? 0xFF : 0;
|
||||
}
|
||||
}
|
||||
last_tm_min = -1; // startup done, now use for RTC
|
||||
@@ -510,12 +486,12 @@ void WebSchedulerService::loop() {
|
||||
// retry startup commands not yet executed
|
||||
if (scheduleItem.active && scheduleItem.flags == SCHEDULEFLAG_SCHEDULE_TIMER && scheduleItem.elapsed_min == 0
|
||||
&& scheduleItem.retry_cnt < MAX_STARTUP_RETRIES) {
|
||||
scheduleItem.retry_cnt = command(scheduleItem.name.c_str(), scheduleItem.cmd, scheduleItem.value) ? 0xFF : scheduleItem.retry_cnt + 1;
|
||||
scheduleItem.retry_cnt = command(scheduleItem.name, scheduleItem.cmd.c_str(), scheduleItem.value.c_str()) ? 0xFF : scheduleItem.retry_cnt + 1;
|
||||
}
|
||||
// scheduled timer commands
|
||||
if (scheduleItem.active && scheduleItem.flags == SCHEDULEFLAG_SCHEDULE_TIMER && scheduleItem.elapsed_min > 0
|
||||
&& (uptime_min % scheduleItem.elapsed_min == 0)) {
|
||||
command(scheduleItem.name.c_str(), scheduleItem.cmd, compute(scheduleItem.value));
|
||||
command(scheduleItem.name, scheduleItem.cmd.c_str(), compute(scheduleItem.value.c_str()));
|
||||
}
|
||||
}
|
||||
last_uptime_min = uptime_min;
|
||||
@@ -532,7 +508,7 @@ void WebSchedulerService::loop() {
|
||||
for (const ScheduleItem & scheduleItem : *scheduleItems_) {
|
||||
uint8_t dow = scheduleItem.flags & SCHEDULEFLAG_SCHEDULE_TIMER ? 0 : scheduleItem.flags;
|
||||
if (scheduleItem.active && (real_dow & dow) && real_min == scheduleItem.elapsed_min) {
|
||||
command(scheduleItem.name.c_str(), scheduleItem.cmd, compute(scheduleItem.value));
|
||||
command(scheduleItem.name, scheduleItem.cmd.c_str(), compute(scheduleItem.value.c_str()));
|
||||
}
|
||||
}
|
||||
last_tm_min = tm->tm_min;
|
||||
@@ -559,26 +535,26 @@ void WebSchedulerService::load_test_data() {
|
||||
webScheduler.scheduleItems.clear(); // delete all existing schedules
|
||||
|
||||
// test 1
|
||||
auto si = ScheduleItem();
|
||||
si.active = true;
|
||||
si.flags = 1;
|
||||
si.time = "12:00";
|
||||
si.cmd = "system/fetch";
|
||||
si.value = "10";
|
||||
si.name = "test_scheduler";
|
||||
auto si = ScheduleItem();
|
||||
si.active = true;
|
||||
si.flags = 1;
|
||||
si.time = "12:00";
|
||||
si.cmd = "system/fetch";
|
||||
si.value = "10";
|
||||
strcpy(si.name, "test_scheduler");
|
||||
si.elapsed_min = 0;
|
||||
si.retry_cnt = 0xFF; // no startup retries
|
||||
|
||||
webScheduler.scheduleItems.push_back(si);
|
||||
|
||||
// test 2
|
||||
si = ScheduleItem();
|
||||
si.active = false;
|
||||
si.flags = 1;
|
||||
si.time = "13:00";
|
||||
si.cmd = "system/message";
|
||||
si.value = "20";
|
||||
si.name = ""; // to make sure its excluded from Dashboard
|
||||
si = ScheduleItem();
|
||||
si.active = false;
|
||||
si.flags = 1;
|
||||
si.time = "13:00";
|
||||
si.cmd = "system/message";
|
||||
si.value = "20";
|
||||
strcpy(si.name, ""); // to make sure its excluded from Dashboard
|
||||
si.elapsed_min = 0;
|
||||
si.retry_cnt = 0xFF; // no startup retries
|
||||
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <esp32-psram.h>
|
||||
|
||||
#ifndef WebSchedulerService_h
|
||||
#define WebSchedulerService_h
|
||||
|
||||
@@ -54,16 +56,16 @@ class ScheduleItem {
|
||||
boolean active;
|
||||
uint8_t flags;
|
||||
uint16_t elapsed_min; // total mins from 00:00
|
||||
std::string time; // HH:MM
|
||||
std::string cmd;
|
||||
std::string value;
|
||||
std::string name;
|
||||
stringPSRAM time; // HH:MM
|
||||
stringPSRAM cmd;
|
||||
stringPSRAM value;
|
||||
char name[20];
|
||||
uint8_t retry_cnt;
|
||||
};
|
||||
|
||||
class WebScheduler {
|
||||
public:
|
||||
std::list<ScheduleItem> scheduleItems;
|
||||
std::list<ScheduleItem, AllocatorPSRAM<ScheduleItem>> scheduleItems;
|
||||
|
||||
static void read(WebScheduler & webScheduler, JsonObject root);
|
||||
static StateUpdateResult update(JsonObject root, WebScheduler & webScheduler);
|
||||
@@ -104,10 +106,10 @@ class WebSchedulerService : public StatefulService<WebScheduler> {
|
||||
|
||||
HttpEndpoint<WebScheduler> _httpEndpoint;
|
||||
FSPersistence<WebScheduler> _fsPersistence;
|
||||
bool ha_registered_ = false;
|
||||
|
||||
std::list<ScheduleItem> * scheduleItems_; // pointer to the list of schedule events
|
||||
bool ha_registered_ = false;
|
||||
std::deque<ScheduleItem *> cmd_changed_;
|
||||
std::list<ScheduleItem, AllocatorPSRAM<ScheduleItem>> * scheduleItems_; // pointer to the list of schedule events
|
||||
std::list<ScheduleItem *, AllocatorPSRAM<ScheduleItem *>> cmd_changed_; // pointer to commands in list that are triggert by change
|
||||
};
|
||||
|
||||
} // namespace emsesp
|
||||
|
||||
@@ -123,50 +123,61 @@ StateUpdateResult WebSettings::update(JsonObject root, WebSettings & settings) {
|
||||
add_flags(ChangeFlags::RESTART);
|
||||
}
|
||||
|
||||
check_flag(original_settings.phy_type, settings.phy_type, ChangeFlags::RESTART);
|
||||
// ETH has changed, so we need to check the ethernet pins. Only if ETH is being used.
|
||||
if (settings.phy_type != PHY_type::PHY_TYPE_NONE) {
|
||||
check_flag(original_settings.eth_power, settings.eth_power, ChangeFlags::RESTART);
|
||||
check_flag(original_settings.eth_clock_mode, settings.eth_clock_mode, ChangeFlags::RESTART);
|
||||
if (settings.eth_power != -1) { // Ethernet Power -1 means disabled
|
||||
EMSESP::system_.remove_gpio(settings.eth_power, true);
|
||||
}
|
||||
// remove the ethernet pins from valid list, regardless of whether the GPIOs are valid or not
|
||||
EMSESP::system_.remove_gpio(23, true); // MDC
|
||||
EMSESP::system_.remove_gpio(18, true); // MDIO
|
||||
if (settings.eth_clock_mode < 2) {
|
||||
EMSESP::system_.remove_gpio(0, true); // ETH.clock input
|
||||
} else if (settings.eth_clock_mode == 2) {
|
||||
EMSESP::system_.remove_gpio(16, true); // ETH.clock output
|
||||
} else if (settings.eth_clock_mode == 3) {
|
||||
EMSESP::system_.remove_gpio(17, true); // ETH.clock output
|
||||
}
|
||||
}
|
||||
#if CONFIG_IDF_TARGET_ESP32
|
||||
// Uart0 pins not allowed for all other gpio
|
||||
EMSESP::system_.remove_gpio(1, true);
|
||||
EMSESP::system_.remove_gpio(3, true);
|
||||
#endif
|
||||
// if any of the GPIOs have changed and re-validate them
|
||||
bool have_valid_gpios = true;
|
||||
|
||||
if (check_flag(original_settings.led_gpio, settings.led_gpio, ChangeFlags::LED)) {
|
||||
if (settings.led_gpio != 0) { // 0 means disabled
|
||||
have_valid_gpios = have_valid_gpios && EMSESP::system_.add_gpio(settings.led_gpio, "LED");
|
||||
}
|
||||
// free old gpios from used list to allow remapping
|
||||
EMSESP::system_.remove_gpio(original_settings.led_gpio);
|
||||
EMSESP::system_.remove_gpio(original_settings.dallas_gpio);
|
||||
EMSESP::system_.remove_gpio(original_settings.pbutton_gpio);
|
||||
EMSESP::system_.remove_gpio(original_settings.rx_gpio);
|
||||
EMSESP::system_.remove_gpio(original_settings.tx_gpio);
|
||||
|
||||
// now add new gpio assignment, start with rx/tx
|
||||
check_flag(original_settings.rx_gpio, settings.rx_gpio, ChangeFlags::UART);
|
||||
have_valid_gpios = have_valid_gpios && EMSESP::system_.add_gpio(settings.rx_gpio, "UART Rx");
|
||||
|
||||
check_flag(original_settings.tx_gpio, settings.tx_gpio, ChangeFlags::UART);
|
||||
have_valid_gpios = have_valid_gpios && EMSESP::system_.add_gpio(settings.tx_gpio, "UART Tx");
|
||||
|
||||
check_flag(original_settings.led_gpio, settings.led_gpio, ChangeFlags::LED);
|
||||
if (settings.led_gpio != 0 && !EMSESP::system_.add_gpio(settings.led_gpio, "LED")) {
|
||||
settings.led_gpio = 0; // 0 means disabled
|
||||
have_valid_gpios = false;
|
||||
}
|
||||
|
||||
if (check_flag(original_settings.dallas_gpio, settings.dallas_gpio, ChangeFlags::TEMPERATURE_SENSOR)) {
|
||||
if (settings.dallas_gpio != 0) { // 0 means disabled
|
||||
have_valid_gpios = have_valid_gpios && EMSESP::system_.add_gpio(settings.dallas_gpio, "Dallas");
|
||||
}
|
||||
check_flag(original_settings.dallas_gpio, settings.dallas_gpio, ChangeFlags::TEMPERATURE_SENSOR);
|
||||
if (settings.dallas_gpio != 0 && !EMSESP::system_.add_gpio(settings.dallas_gpio, "Dallas")) {
|
||||
settings.dallas_gpio = 0; // 0 means disabled
|
||||
have_valid_gpios = false;
|
||||
}
|
||||
|
||||
if (check_flag(original_settings.rx_gpio, settings.rx_gpio, ChangeFlags::RESTART)) {
|
||||
have_valid_gpios = have_valid_gpios && EMSESP::system_.add_gpio(settings.rx_gpio, "UART Rx");
|
||||
}
|
||||
|
||||
if (check_flag(original_settings.tx_gpio, settings.tx_gpio, ChangeFlags::RESTART)) {
|
||||
have_valid_gpios = have_valid_gpios && EMSESP::system_.add_gpio(settings.tx_gpio, "UART Tx");
|
||||
}
|
||||
|
||||
if (check_flag(original_settings.pbutton_gpio, settings.pbutton_gpio, ChangeFlags::BUTTON)) {
|
||||
have_valid_gpios = have_valid_gpios && EMSESP::system_.add_gpio(settings.pbutton_gpio, "Button");
|
||||
}
|
||||
|
||||
if (check_flag(original_settings.phy_type, settings.phy_type, ChangeFlags::RESTART)) {
|
||||
// ETH has changed, so we need to check the ethernet pins. Only if ETH is being used.
|
||||
if (settings.phy_type != PHY_type::PHY_TYPE_NONE) {
|
||||
if (settings.eth_power != -1) {
|
||||
// Always remove Ethernet Power gpio unless disabled (-1)
|
||||
EMSESP::system_.remove_gpio(settings.eth_power, true);
|
||||
}
|
||||
// all valid so far, now remove the ethernet pins from valid list, regardless of whether the GPIOs are valid or not
|
||||
EMSESP::system_.remove_gpio(23, true); // MDC
|
||||
EMSESP::system_.remove_gpio(18, true); // MDIO
|
||||
EMSESP::system_.remove_gpio(0, true); // ETH.clock input
|
||||
EMSESP::system_.remove_gpio(16, true); // ETH.clock output
|
||||
EMSESP::system_.remove_gpio(17, true); // ETH.clock output
|
||||
EMSESP::system_.remove_gpio(21, true); // I2C SDA
|
||||
EMSESP::system_.remove_gpio(22, true); // I2C SCL
|
||||
}
|
||||
}
|
||||
check_flag(original_settings.pbutton_gpio, settings.pbutton_gpio, ChangeFlags::BUTTON);
|
||||
have_valid_gpios = have_valid_gpios && EMSESP::system_.add_gpio(settings.pbutton_gpio, "Button");
|
||||
|
||||
// check if the LED type, eth_phy_addr or eth_clock_mode have changed
|
||||
check_flag(original_settings.led_type, settings.led_type, ChangeFlags::LED);
|
||||
|
||||
@@ -164,7 +164,8 @@ void test_21() {
|
||||
auto expected_response =
|
||||
"[{\"system\":{\"version\":\"dev\",\"uptime\":\"000+00:00:00.000\",\"uptimeSec\":0,\"resetReason\":\"Unknown / "
|
||||
"Unknown\"},\"network\":{\"network\":\"WiFi\",\"hostname\":\"ems-esp\",\"RSSI\":-23,\"TxPowerSetting\":0,\"staticIP\":false,\"lowBandwidth\":false,"
|
||||
"\"disableSleep\":true,\"enableMDNS\":true,\"enableCORS\":false},\"ntp\":{\"enabled\":true,\"server\":\"pool.ntp.org\",\"tzLabel\":\"Europe/"
|
||||
"\"disableSleep\":true,\"enableMDNS\":true,\"enableCORS\":false},\"ntp\":{\"NTPstatus\":\"disconnected\",\"enabled\":true,\"server\":\"pool.ntp.org\","
|
||||
"\"tzLabel\":\"Europe/"
|
||||
"London\",\"NTPStatus\":\"disconnected\"},\"ap\":{\"provisionMode\":\"always\",\"ssid\":\"ems-esp\"},\"mqtt\":{\"MQTTStatus\":\"disconnected\","
|
||||
"\"MQTTPublishes\":0,\"MQTTQueued\":0,\"MQTTPublishFails\":0,\"MQTTReconnects\":0,\"enabled\":true,\"clientID\":\"ems-esp\",\"keepAlive\":60,"
|
||||
"\"cleanSession\":false,\"entityFormat\":1,\"base\":\"ems-esp\",\"discoveryPrefix\":\"homeassistant\",\"discoveryType\":0,\"nestedFormat\":1,"
|
||||
@@ -192,7 +193,8 @@ void test_22() {
|
||||
auto expected_response =
|
||||
"[{\"system\":{\"version\":\"dev\",\"uptime\":\"000+00:00:00.000\",\"uptimeSec\":0,\"resetReason\":\"Unknown / "
|
||||
"Unknown\"},\"network\":{\"network\":\"WiFi\",\"hostname\":\"ems-esp\",\"RSSI\":-23,\"TxPowerSetting\":0,\"staticIP\":false,\"lowBandwidth\":false,"
|
||||
"\"disableSleep\":true,\"enableMDNS\":true,\"enableCORS\":false},\"ntp\":{\"enabled\":true,\"server\":\"pool.ntp.org\",\"tzLabel\":\"Europe/"
|
||||
"\"disableSleep\":true,\"enableMDNS\":true,\"enableCORS\":false},\"ntp\":{\"NTPstatus\":\"disconnected\",\"enabled\":true,\"server\":\"pool.ntp.org\","
|
||||
"\"tzLabel\":\"Europe/"
|
||||
"London\",\"NTPStatus\":\"disconnected\"},\"ap\":{\"provisionMode\":\"always\",\"ssid\":\"ems-esp\"},\"mqtt\":{\"MQTTStatus\":\"disconnected\","
|
||||
"\"MQTTPublishes\":0,\"MQTTQueued\":0,\"MQTTPublishFails\":0,\"MQTTReconnects\":0,\"enabled\":true,\"clientID\":\"ems-esp\",\"keepAlive\":60,"
|
||||
"\"cleanSession\":false,\"entityFormat\":1,\"base\":\"ems-esp\",\"discoveryPrefix\":\"homeassistant\",\"discoveryType\":0,\"nestedFormat\":1,"
|
||||
|
||||
Reference in New Issue
Block a user