wip min
This commit is contained in:
		
							
								
								
									
										
											BIN
										
									
								
								board/Body1.3mf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								board/Body1.3mf
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								board/modules/MPPT/battery-charging.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								board/modules/MPPT/battery-charging.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 2.1 KiB | 
							
								
								
									
										6
									
								
								board/modules/MPPT/battery-charging.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								board/modules/MPPT/battery-charging.svg
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-battery-charging" viewBox="0 0 16 16"> | ||||
|   <path d="M9.585 2.568a.5.5 0 0 1 .226.58L8.677 6.832h1.99a.5.5 0 0 1 .364.843l-5.334 5.667a.5.5 0 0 1-.842-.49L5.99 9.167H4a.5.5 0 0 1-.364-.843l5.333-5.667a.5.5 0 0 1 .616-.09z"/> | ||||
|   <path d="M2 4h4.332l-.94 1H2a1 1 0 0 0-1 1v4a1 1 0 0 0 1 1h2.38l-.308 1H2a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2"/> | ||||
|   <path d="M2 6h2.45L2.908 7.639A1.5 1.5 0 0 0 3.313 10H2zm8.595-2-.308 1H12a1 1 0 0 1 1 1v4a1 1 0 0 1-1 1H9.276l-.942 1H12a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2z"/> | ||||
|   <path d="M12 10h-1.783l1.542-1.639q.146-.156.241-.34zm0-3.354V6h-.646a1.5 1.5 0 0 1 .646.646M16 8a1.5 1.5 0 0 1-1.5 1.5v-3A1.5 1.5 0 0 1 16 8"/> | ||||
| </svg> | ||||
| After Width: | Height: | Size: 738 B | 
							
								
								
									
										4
									
								
								board/modules/Sensors_can/Sensors/sym-lib-table
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								board/modules/Sensors_can/Sensors/sym-lib-table
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | ||||
| (sym_lib_table | ||||
|   (version 7) | ||||
|   (lib (name "Modules")(type "KiCad")(uri "/home/empire/workspace/PlantCtrl/board/modules/Modules.kicad_sym")(options "")(descr "")) | ||||
| ) | ||||
							
								
								
									
										14
									
								
								board/modules/Sensors_can/ch32-sensor/.cargo/config.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								board/modules/Sensors_can/ch32-sensor/.cargo/config.toml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| [build] | ||||
| target = "riscv32imc-unknown-none-elf" | ||||
|  | ||||
| [target."riscv32imc-unknown-none-elf"] | ||||
| rustflags = [ | ||||
| #  "-C", "link-arg=-Tlink.x", | ||||
| ] | ||||
| # runner = "riscv64-unknown-elf-gdb -q -x openocd.gdb" | ||||
| # runner = "riscv-none-embed-gdb -q -x openocd.gdb" | ||||
| # runner = "gdb -q -x openocd.gdb" | ||||
| # runner = "wlink -v flash" | ||||
|  | ||||
| runner = "wlink -v flash --enable-sdi-print --watch-serial --erase" | ||||
| # runner = "wlink -v flash" | ||||
							
								
								
									
										7
									
								
								board/modules/Sensors_can/ch32-sensor/.gdbinit
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								board/modules/Sensors_can/ch32-sensor/.gdbinit
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | ||||
| target extended-remote :3333 | ||||
| set remotetimeout 2000 | ||||
|  | ||||
| #symbol-file target/riscv32imc-unknown-none-elf/release/ch32v203-examples | ||||
| file target/riscv32imc-unknown-none-elf/release/bms | ||||
|  | ||||
| monitor reset halt | ||||
							
								
								
									
										1
									
								
								board/modules/Sensors_can/ch32-sensor/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								board/modules/Sensors_can/ch32-sensor/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| target | ||||
							
								
								
									
										43
									
								
								board/modules/Sensors_can/ch32-sensor/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								board/modules/Sensors_can/ch32-sensor/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,43 @@ | ||||
| [package] | ||||
| name = "bms" | ||||
| version = "0.1.0" | ||||
| edition = "2021" | ||||
|  | ||||
| [dependencies] | ||||
| ch32-hal = { git = "https://github.com/ch32-rs/ch32-hal", features = [ | ||||
|     "ch32v203c8t6", | ||||
|     "memory-x", | ||||
|     "embassy", | ||||
|     "rt", | ||||
|     "time-driver-tim2", | ||||
| ], default-features = false } | ||||
|  | ||||
| embassy-executor = { version = "0.7.0", features = [ | ||||
|     "arch-riscv32", | ||||
|     "executor-thread", | ||||
| ] } | ||||
|  | ||||
| embassy-time = { version = "0.4.0" } | ||||
| embassy-usb = { version = "0.3.0" } | ||||
| embassy-futures = { version = "0.1.0" } | ||||
|  | ||||
| # This is okay because we should automatically use whatever ch32-hal uses | ||||
| qingke-rt = "*" | ||||
| qingke = "*" | ||||
|  | ||||
| panic-halt = "1.0" | ||||
|  | ||||
| embedded-hal = "1.0.0" | ||||
| heapless = "0.8.0" | ||||
| micromath = { version = "2.1.0", features = ["num-traits"] } | ||||
| embedded-can = "0.4.1" | ||||
|  | ||||
| [profile.dev] | ||||
| #lto = true | ||||
| opt-level = 1 | ||||
|  | ||||
| [profile.release] | ||||
| strip = false   # symbols are not flashed to the microcontroller, so don't strip them. | ||||
| #lto = true | ||||
| debug = true | ||||
| opt-level = "z" # Optimize for size. | ||||
							
								
								
									
										72
									
								
								board/modules/Sensors_can/ch32-sensor/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								board/modules/Sensors_can/ch32-sensor/README.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,72 @@ | ||||
|  | ||||
| # ch32v203-bms | ||||
|  | ||||
| A simple battery management controller software. | ||||
|  | ||||
| ## CAN bus and hardware address | ||||
|  | ||||
| This firmware exposes a CAN interface on the CH32V203 and uses 4 hardware address pins to allow up to 16 sensors on the same bus. | ||||
|  | ||||
| - CAN pins (default mapping): | ||||
|   - CAN RX: PA11 | ||||
|   - CAN TX: PA12 | ||||
| - Address select pins (with internal pull-ups): | ||||
|   - A0: PA0 | ||||
|   - A1: PA1 | ||||
|   - A2: PA2 | ||||
|   - A3: PA3 | ||||
|  | ||||
| Wire each address pin to GND to set its corresponding bit to 1. The 4-bit address range is 0..15. The node’s CAN Standard ID is `0x100 | addr`, i.e. 0x100..0x10F. The CAN acceptance filter is configured to only accept frames with the node’s own ID. | ||||
|  | ||||
| Adjust the pins above if your PCB routes CAN or address lines to different pads. | ||||
|  | ||||
| ## 555 timer (software) emulation mode | ||||
|  | ||||
| To save the BOM cost of a classic NE555 in simple oscillator applications, this firmware implements a minimal 555-like Schmitt trigger using the MCU’s ADC and a GPIO, approximating the behavior when the capacitor is charged/discharged via Q through a resistor, and the combined Trigger/Threshold senses the capacitor node. | ||||
|  | ||||
| - Pins used: | ||||
|   - Q output: PB2 | ||||
|   - Combined Trigger/Threshold (ADC input): PA0 | ||||
| - Wiring: | ||||
|   - PB2 (Q) -> series resistor R -> capacitor node | ||||
|   - Capacitor node -> capacitor to GND | ||||
|   - Capacitor node -> PA0 (ADC input) | ||||
| - Behavior: | ||||
|   - When ADC(PA0) <= ~1/3 Vref, PB2 is driven High. | ||||
|   - When ADC(PA0) >= ~2/3 Vref, PB2 is driven Low. | ||||
|   - Hysteresis avoids chatter; the actual charge/discharge dynamics follow your chosen R and C. | ||||
| - Notes: | ||||
|   - Use an appropriate resistor from PB2 to the capacitor to set oscillation frequency. Start with 10k..100k and adjust with C. | ||||
|   - Ensure PA0 is routed to the capacitor node and left high impedance (no strong pull-ups/downs) so the ADC can sense the analog voltage. | ||||
|   - PB2 drives the on-board LED (if present), so the LED might blink at the oscillation frequency. | ||||
|  | ||||
| This mode is implemented in `src/main.rs` using `hal::adc::Adc::convert(&mut pin, SampleTime::...)` to take periodic samples and a simple state machine to toggle the Q output based on ~1/3 and ~2/3 Vref thresholds. | ||||
|  | ||||
| ## Building | ||||
|  | ||||
| ``` sh | ||||
| cargo build --release | ||||
| ``` | ||||
|  | ||||
| ## Flash | ||||
|  | ||||
| ``` sh | ||||
| wchisp config reset | ||||
| wchip wchisp flash target/riscv32imc-unknown-none-elf/release/bms | ||||
| ``` | ||||
|  | ||||
| ## Debugging | ||||
|  | ||||
| For debugging purposes a container file is provided together with wrapper scripts to start the containerized `openocd` and `riscv-gdb` transparently. The wrapper scripts assume that `podman` is setup. | ||||
|  | ||||
| Starting Debug server  | ||||
|  | ||||
| ``` | ||||
| ./bin/openocd | ||||
| ``` | ||||
|  | ||||
| Connecting with gdb for interactive debugging | ||||
|  | ||||
| ``` | ||||
| ./bin/gdb -f target/riscv32imc-unknown-none-elf/release/bms | ||||
| ``` | ||||
							
								
								
									
										10
									
								
								board/modules/Sensors_can/ch32-sensor/bin/build-wch-tools-container.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										10
									
								
								board/modules/Sensors_can/ch32-sensor/bin/build-wch-tools-container.sh
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,10 @@ | ||||
| #!/usr/bin/env bash | ||||
|  | ||||
| set -euo pipefail | ||||
|  | ||||
| CONTAINER_NAME="localhost/wch-dev-tools:latest" | ||||
| CONTAINER_TOOLS_BASEDIR="$(dirname "$(readlink -f "$0")")" | ||||
|  | ||||
| pushd "$CONTAINER_TOOLS_BASEDIR" | ||||
| podman build -t "$CONTAINER_NAME" -f "../wch-tools.Containerfile" . | ||||
| popd | ||||
							
								
								
									
										29
									
								
								board/modules/Sensors_can/ch32-sensor/bin/gdb
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										29
									
								
								board/modules/Sensors_can/ch32-sensor/bin/gdb
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,29 @@ | ||||
| #!/usr/bin/env bash | ||||
|  | ||||
| set -euo pipefail | ||||
|  | ||||
| CONTAINER_IMAGE="localhost/wch-dev-tools:latest" | ||||
| CONTAINER_TOOLS_BASEDIR="$(dirname "$(readlink -f "$0")")" | ||||
|  | ||||
| function _fatal { | ||||
| 	echo -e "\e[31mERROR\e[0m   $(</dev/stdin)$*" 1>&2 | ||||
| 	exit 1 | ||||
| } | ||||
|  | ||||
| declare -a PODMAN_ARGS=( | ||||
| 	"--rm" "-i" "--log-driver=none" | ||||
|     "--network=host" | ||||
|     "--pid=host" | ||||
| 	"-v" "$PWD:$PWD:rw" | ||||
| 	"-w" "$PWD" | ||||
| ) | ||||
|  | ||||
| [[ -t 1 ]] && PODMAN_ARGS+=("-t") | ||||
|  | ||||
| if ! podman image exists "$CONTAINER_IMAGE"; then | ||||
|     #attempt to build container | ||||
|     "$CONTAINER_TOOLS_BASEDIR/build-wch-tools-container.sh" 1>&2 || | ||||
|         _fatal "faild to build local image, cannot continue! … please ensure you have an internet connection" | ||||
| fi | ||||
|  | ||||
| podman run "${PODMAN_ARGS[@]}" --entrypoint riscv-none-elf-gdb-py3 "$CONTAINER_IMAGE" "$@" | ||||
							
								
								
									
										44
									
								
								board/modules/Sensors_can/ch32-sensor/bin/openocd
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										44
									
								
								board/modules/Sensors_can/ch32-sensor/bin/openocd
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,44 @@ | ||||
| #!/usr/bin/env bash | ||||
|  | ||||
| set -euo pipefail | ||||
|  | ||||
| CONTAINER_IMAGE="localhost/wch-dev-tools:latest" | ||||
| CONTAINER_TOOLS_BASEDIR="$(dirname "$(readlink -f "$0")")" | ||||
|  | ||||
| function _fatal { | ||||
| 	echo -e "\e[31mERROR\e[0m   $(</dev/stdin)$*" 1>&2 | ||||
| 	exit 1 | ||||
| } | ||||
|  | ||||
| declare -a PODMAN_ARGS=( | ||||
| 	"--rm" "-i" "--log-driver=none" | ||||
|     "--network=host" | ||||
| 	"-v" "$PWD:$PWD:rw" | ||||
| 	"-w" "$PWD" | ||||
| ) | ||||
|  | ||||
| for device in /dev/bus/usb/*/*; do | ||||
|     if udevadm info "$device" | grep -q "ID_VENDOR=wch.cn" && \ | ||||
|        udevadm info "$device" | grep -q "ID_MODEL=WCH-Link"; then | ||||
|         DEBUGGER_DEV_PATH="$device" | ||||
|         break | ||||
|     fi | ||||
| done | ||||
|  | ||||
| if [[ -z "${DEBUGGER_DEV_PATH:-}" ]]; then | ||||
|     echo "Could not find hardware debugger … Exiting!" 1>&2 | ||||
|     exit 1 | ||||
| else | ||||
|     # add jlink to podman device | ||||
|     PODMAN_ARGS+=("--device=$DEBUGGER_DEV_PATH") | ||||
| fi | ||||
|  | ||||
| [[ -t 1 ]] && PODMAN_ARGS+=("-t") | ||||
|  | ||||
| if ! podman image exists "$CONTAINER_IMAGE"; then | ||||
|     #attempt to build container | ||||
|     "$CONTAINER_TOOLS_BASEDIR/build-wch-tools-container.sh" 1>&2 || | ||||
|         _fatal "faild to build local image, cannot continue! … please ensure you have an internet connection" | ||||
| fi | ||||
|  | ||||
| podman run "${PODMAN_ARGS[@]}" --entrypoint openocd "$CONTAINER_IMAGE" "$@" | ||||
							
								
								
									
										11
									
								
								board/modules/Sensors_can/ch32-sensor/build.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								board/modules/Sensors_can/ch32-sensor/build.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | ||||
| fn main() { | ||||
|     // println!("cargo:rustc-link-arg-bins=--nmagic"); | ||||
|     println!("cargo:rustc-link-arg-bins=-Tlink.x"); | ||||
|     // println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); | ||||
|  | ||||
|     let out_dir = std::env::var("OUT_DIR").unwrap(); | ||||
|     let out_dir = std::path::PathBuf::from(out_dir); | ||||
|     std::fs::write(out_dir.join("memory.x"), include_bytes!("memory.x")).unwrap(); | ||||
|     println!("cargo:rustc-link-search={}", out_dir.display()); | ||||
|     println!("cargo:rerun-if-changed=memory.x"); | ||||
| } | ||||
							
								
								
									
										125
									
								
								board/modules/Sensors_can/ch32-sensor/memory.x
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										125
									
								
								board/modules/Sensors_can/ch32-sensor/memory.x
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,125 @@ | ||||
| /* CH32V203c8t6 */ | ||||
| MEMORY | ||||
| { | ||||
|     FLASH : ORIGIN = 0x00000000, LENGTH =   64K /* BANK_1 */ | ||||
|     RAM   : ORIGIN = 0x20000000, LENGTH =   20K | ||||
| } | ||||
| REGION_ALIAS("REGION_TEXT", FLASH); | ||||
| REGION_ALIAS("REGION_RODATA", FLASH); | ||||
| REGION_ALIAS("REGION_DATA", RAM); | ||||
| REGION_ALIAS("REGION_BSS", RAM); | ||||
| REGION_ALIAS("REGION_HEAP", RAM); | ||||
| REGION_ALIAS("REGION_STACK", RAM); | ||||
|  | ||||
| /* fault handlers */ | ||||
|  | ||||
| PROVIDE(InstructionMisaligned = ExceptionHandler); | ||||
| PROVIDE(InstructionFault = ExceptionHandler); | ||||
| PROVIDE(IllegalInstruction = ExceptionHandler); | ||||
| PROVIDE(Breakpoint = ExceptionHandler); | ||||
| PROVIDE(LoadMisaligned = ExceptionHandler); | ||||
| PROVIDE(LoadFault = ExceptionHandler); | ||||
| PROVIDE(StoreMisaligned = ExceptionHandler); | ||||
| PROVIDE(StoreFault = ExceptionHandler);; | ||||
| PROVIDE(UserEnvCall = ExceptionHandler); | ||||
| PROVIDE(SupervisorEnvCall = ExceptionHandler); | ||||
| PROVIDE(MachineEnvCall = ExceptionHandler); | ||||
| PROVIDE(InstructionPageFault = ExceptionHandler); | ||||
| PROVIDE(LoadPageFault = ExceptionHandler); | ||||
| PROVIDE(StorePageFault = ExceptionHandler); | ||||
|  | ||||
| /* core interrupt handlers */ | ||||
|  | ||||
| PROVIDE(NonMaskableInt = DefaultHandler); | ||||
| PROVIDE(Software = DefaultHandler); | ||||
|  | ||||
| /* external interrupt handlers */ | ||||
|  | ||||
| PROVIDE(WWDG = DefaultHandler); | ||||
| PROVIDE(PVD = DefaultHandler); | ||||
| PROVIDE(TAMPER = DefaultHandler); | ||||
| PROVIDE(RTC = DefaultHandler); | ||||
| PROVIDE(FLASH = DefaultHandler); | ||||
| PROVIDE(RCC = DefaultHandler); | ||||
| PROVIDE(EXTI0 = DefaultHandler); | ||||
| PROVIDE(EXTI1 = DefaultHandler); | ||||
| PROVIDE(EXTI2 = DefaultHandler); | ||||
| PROVIDE(EXTI3 = DefaultHandler); | ||||
| PROVIDE(EXTI4 = DefaultHandler); | ||||
| PROVIDE(DMA1_CHANNEL1 = DefaultHandler); | ||||
| PROVIDE(DMA1_CHANNEL2 = DefaultHandler); | ||||
| PROVIDE(DMA1_CHANNEL3 = DefaultHandler); | ||||
| PROVIDE(DMA1_CHANNEL4 = DefaultHandler); | ||||
| PROVIDE(DMA1_CHANNEL5 = DefaultHandler); | ||||
| PROVIDE(DMA1_CHANNEL6 = DefaultHandler); | ||||
| PROVIDE(DMA1_CHANNEL7 = DefaultHandler); | ||||
| PROVIDE(ADC = DefaultHandler); | ||||
| PROVIDE(USB_HP_CAN1_TX = DefaultHandler); | ||||
| /*PROVIDE(USB_LP_CAN1_RX0 = DefaultHandler);*/ | ||||
| PROVIDE(CAN1_RX1 = DefaultHandler); | ||||
| PROVIDE(CAN1_SCE = DefaultHandler); | ||||
| PROVIDE(EXTI9_5 = DefaultHandler); | ||||
| PROVIDE(TIM1_BRK = DefaultHandler); | ||||
| PROVIDE(TIM1_UP_ = DefaultHandler); | ||||
| PROVIDE(TIM1_TRG_COM = DefaultHandler); | ||||
| PROVIDE(TIM1_CC = DefaultHandler); | ||||
| PROVIDE(TIM2 = DefaultHandler); | ||||
| PROVIDE(TIM3 = DefaultHandler); | ||||
| PROVIDE(TIM4 = DefaultHandler); | ||||
| PROVIDE(I2C1_EV = DefaultHandler); | ||||
| PROVIDE(I2C1_ER = DefaultHandler); | ||||
| PROVIDE(I2C2_EV = DefaultHandler); | ||||
| PROVIDE(I2C2_ER = DefaultHandler); | ||||
| PROVIDE(SPI1 = DefaultHandler); | ||||
| PROVIDE(SPI2 = DefaultHandler); | ||||
| PROVIDE(USART1 = DefaultHandler); | ||||
| PROVIDE(USART2 = DefaultHandler); | ||||
| PROVIDE(USART3 = DefaultHandler); | ||||
| PROVIDE(EXTI15_10 = DefaultHandler); | ||||
| PROVIDE(RTCALARM = DefaultHandler); | ||||
| PROVIDE(USBWAKE_UP = DefaultHandler); | ||||
| PROVIDE(TIM8_BRK = DefaultHandler); | ||||
| PROVIDE(TIM8_UP_ = DefaultHandler); | ||||
| PROVIDE(TIM8_TRG_COM = DefaultHandler); | ||||
| PROVIDE(TIM8_CC = DefaultHandler); | ||||
| PROVIDE(RNG = DefaultHandler); | ||||
| PROVIDE(FSMC = DefaultHandler); | ||||
| PROVIDE(SDIO = DefaultHandler); | ||||
| PROVIDE(TIM5 = DefaultHandler); | ||||
| PROVIDE(SPI3 = DefaultHandler); | ||||
| PROVIDE(UART4 = DefaultHandler); | ||||
| PROVIDE(UART5 = DefaultHandler); | ||||
| PROVIDE(TIM6 = DefaultHandler); | ||||
| PROVIDE(TIM7 = DefaultHandler); | ||||
| PROVIDE(DMA2_CHANNEL1 = DefaultHandler); | ||||
| PROVIDE(DMA2_CHANNEL2 = DefaultHandler); | ||||
| PROVIDE(DMA2_CHANNEL3 = DefaultHandler); | ||||
| PROVIDE(DMA2_CHANNEL4 = DefaultHandler); | ||||
| PROVIDE(DMA2_CHANNEL5 = DefaultHandler); | ||||
| PROVIDE(ETH = DefaultHandler); | ||||
| PROVIDE(ETH_WKUP = DefaultHandler); | ||||
| PROVIDE(CAN2_TX = DefaultHandler); | ||||
| PROVIDE(CAN2_RX0 = DefaultHandler); | ||||
| PROVIDE(CAN2_RX1 = DefaultHandler); | ||||
| PROVIDE(CAN2_SCE = DefaultHandler); | ||||
| PROVIDE(OTG_FS = DefaultHandler); | ||||
| PROVIDE(USBHSWAKEUP = DefaultHandler); | ||||
| PROVIDE(USBHS = DefaultHandler); | ||||
| PROVIDE(DVP = DefaultHandler); | ||||
| PROVIDE(UART6 = DefaultHandler); | ||||
| PROVIDE(UART7 = DefaultHandler); | ||||
| PROVIDE(UART8 = DefaultHandler); | ||||
| PROVIDE(TIM9_BRK = DefaultHandler); | ||||
| PROVIDE(TIM9_UP_ = DefaultHandler); | ||||
| PROVIDE(TIM9_TRG_COM = DefaultHandler); | ||||
| PROVIDE(TIM9_CC = DefaultHandler); | ||||
| PROVIDE(TIM10_BRK = DefaultHandler); | ||||
| PROVIDE(TIM10_UP_ = DefaultHandler); | ||||
| PROVIDE(TIM10_TRG_COM = DefaultHandler); | ||||
| PROVIDE(TIM10_CC = DefaultHandler); | ||||
| PROVIDE(DMA2_CHANNEL6 = DefaultHandler); | ||||
| PROVIDE(DMA2_CHANNEL7 = DefaultHandler); | ||||
| PROVIDE(DMA2_CHANNEL8 = DefaultHandler); | ||||
| PROVIDE(DMA2_CHANNEL9 = DefaultHandler); | ||||
| PROVIDE(DMA2_CHANNEL10 = DefaultHandler); | ||||
| PROVIDE(DMA2_CHANNEL11 = DefaultHandler); | ||||
							
								
								
									
										17
									
								
								board/modules/Sensors_can/ch32-sensor/openocd.cfg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								board/modules/Sensors_can/ch32-sensor/openocd.cfg
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | ||||
| set _CHIPNAME ch32v203 | ||||
| set _TARGETNAME $_CHIPNAME.cpu | ||||
|  | ||||
| #bindto 0.0.0.0 | ||||
|  | ||||
| adapter driver wlinke | ||||
| adapter speed 6000 | ||||
| transport select sdi | ||||
|  | ||||
| sdi newtap $_CHIPNAME cpu -irlen 5 --expected-id 0x00001 | ||||
| target create $_TARGETNAME.0 wch_riscv -chain-position $_TARGETNAME | ||||
| $_TARGETNAME.0 configure -work-area-phys 0x20000000 -work-area-size 10000 -work-area-backup 1 | ||||
| set _FLASHNAME $_CHIPNAME.flash | ||||
|  | ||||
| flash bank $_FLASHNAME wch_rsicv 0x00000000 0 0 0 $_TARGETNAME.0 | ||||
|  | ||||
| init | ||||
| @@ -0,0 +1,2 @@ | ||||
| [toolchain] | ||||
| channel = "nightly" | ||||
							
								
								
									
										61
									
								
								board/modules/Sensors_can/ch32-sensor/src/main.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								board/modules/Sensors_can/ch32-sensor/src/main.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,61 @@ | ||||
| #![no_std] | ||||
| #![no_main] | ||||
| #![feature(type_alias_impl_trait)] | ||||
| #![feature(impl_trait_in_assoc_type)] | ||||
|  | ||||
| // Simple 555-like oscillator implemented in firmware. | ||||
| // - Q output: PB2 (also drives the on-board LED if present) | ||||
| // - Combined Trigger/Threshold analog input: PA0 (capacitor node) | ||||
| // Wiring suggestion: | ||||
| //   Q (PB2) --[R]--+-- C -- GND | ||||
| //                   | | ||||
| //                 PA0 (ADC input) | ||||
| // The firmware toggles Q high when PA0 <= 1/3 Vref and low when PA0 >= 2/3 Vref. | ||||
|  | ||||
| use embassy_executor::Spawner; | ||||
| use embassy_time::Timer; | ||||
| use hal::gpio::{Level, Output}; | ||||
| use {ch32_hal as hal, panic_halt as _}; | ||||
|  | ||||
| use hal::adc::{Adc, SampleTime}; | ||||
|  | ||||
| #[embassy_executor::main(entry = "qingke_rt::entry")] | ||||
| async fn main(_spawner: Spawner) -> ! { | ||||
|     let p = hal::init(Default::default()); | ||||
|  | ||||
|     // Q output on PB2 | ||||
|     let mut q = Output::new(p.PB2, Level::Low, Default::default()); | ||||
|  | ||||
|     // ADC on PA0 for combined Trigger/Threshold input | ||||
|     let mut adc = Adc::new(p.ADC1, Default::default()); | ||||
|     let mut trig_thres = p.PA0; // analog-capable pin used as ADC channel | ||||
|  | ||||
|     // ADC characteristics: assume 12-bit if HAL doesn't expose it. | ||||
|     // If the HAL provides a method to query resolution, prefer that. | ||||
|     let full_scale: u16 = 4095; // 12-bit default | ||||
|     let thr_low: u16 = (full_scale as u32 / 3) as u16;        // ~1/3 Vref | ||||
|     let thr_high: u16 = ((full_scale as u32 * 2) / 3) as u16; // ~2/3 Vref | ||||
|  | ||||
|     // Start with Q low. State variable to avoid redundant toggles. | ||||
|     let mut q_high = false; | ||||
|     q.set_low(); | ||||
|  | ||||
|     loop { | ||||
|         // Read capacitor node voltage via ADC | ||||
|         let sample: u16 = adc.convert(&mut trig_thres, SampleTime::CYCLES239_5); | ||||
|  | ||||
|         // Implement Schmitt trigger behavior like NE555 using thresholds | ||||
|         if !q_high && sample <= thr_low { | ||||
|             // Trigger: voltage fell below 1/3 Vref -> set output high | ||||
|             q.set_high(); | ||||
|             q_high = true; | ||||
|         } else if q_high && sample >= thr_high { | ||||
|             // Threshold: voltage rose above 2/3 Vref -> set output low | ||||
|             q.set_low(); | ||||
|             q_high = false; | ||||
|         } | ||||
|  | ||||
|         // Small delay to reduce CPU usage; adjust for responsiveness/noise | ||||
|         Timer::after_micros(200).await; | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,27 @@ | ||||
| FROM debian:bookworm | ||||
|  | ||||
| RUN apt update -y && apt upgrade -y && apt install git libjaylink-dev libusb-1.0-0 unzip curl libhidapi-hidraw0 xz-utils -y | ||||
|  | ||||
| RUN cd /root && \ | ||||
|     curl -L -o mrs-toolchain.tar.xz "https://github.com/ch32-riscv-ug/MounRiver_Studio_Community_miror/releases/download/1.92-toolchain/MRS_Toolchain_Linux_x64_V1.92.tar.xz" && \ | ||||
|     mkdir mrs-toolchain && \ | ||||
|     tar -xvf mrs-toolchain.tar.xz -C mrs-toolchain --strip-components=1 && \ | ||||
|     mv mrs-toolchain/OpenOCD/bin/openocd /usr/local/bin && \ | ||||
|     mv mrs-toolchain/OpenOCD/share/openocd /usr/local/share && \ | ||||
|     # mv mrs-toolchain/RISC-V_Embedded_GCC12/bin/riscv-none-elf-gdb /usr/local/bin && \                     # both toolchains in MRS are to old to work with emacs dape | ||||
|     # mv mrs-toolchain/RISC-V_Embedded_GCC12/libexec /usr/local && \                                        # both toolchains in MRS are to old to work with emacs dape | ||||
|     rm -rf mrs-toolchain mrs-toolchain.tar.xz && \ | ||||
|     # Use up to date xpack toolchains for gdb | ||||
|     curl -L -o xpack-riscv-toolchain.tar.gz "https://github.com/xpack-dev-tools/riscv-none-elf-gcc-xpack/releases/download/v14.2.0-3/xpack-riscv-none-elf-gcc-14.2.0-3-linux-x64.tar.gz" && \ | ||||
|     mkdir xpack-toolchain && \ | ||||
|     tar -xvf xpack-riscv-toolchain.tar.gz -C xpack-toolchain --strip-components=1 && \ | ||||
|     mv xpack-toolchain/bin/* /usr/local/bin && \ | ||||
|     mv xpack-toolchain/lib/ /usr/local && \ | ||||
|     mv xpack-toolchain/lib64/ /usr/local && \ | ||||
|     mv xpack-toolchain/libexec /usr/local && \ | ||||
|     mv xpack-toolchain/riscv-none-elf /usr/local && \ | ||||
|     rm -rf xpack-toolchain xpack-riscv-toolchain.tar.gz | ||||
|  | ||||
| RUN mkdir -p /root/.config/gdb && echo "set auto-load safe-path /" >> /root/.config/gdb/gdbinit | ||||
|  | ||||
| ENTRYPOINT [ "/usr/bin/bash" ] | ||||
							
								
								
									
										4
									
								
								board/modules/Sensors_simplified/Sensors/fp-lib-table
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								board/modules/Sensors_simplified/Sensors/fp-lib-table
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | ||||
| (fp_lib_table | ||||
|   (version 7) | ||||
|   (lib (name "Sensor")(type "KiCad")(uri "${KIPRJMOD}/Sensor.pretty")(options "")(descr "")) | ||||
| ) | ||||
| @@ -58,7 +58,7 @@ pub struct TankConfig { | ||||
|     pub tank_warn_percent: u8, | ||||
|     pub tank_empty_percent: u8, | ||||
|     pub tank_full_percent: u8, | ||||
|     pub ml_per_pulse: f32 | ||||
|     pub ml_per_pulse: f32, | ||||
| } | ||||
| impl Default for TankConfig { | ||||
|     fn default() -> Self { | ||||
| @@ -111,6 +111,7 @@ pub struct PlantControllerConfig { | ||||
| pub struct PlantConfig { | ||||
|     pub mode: PlantWateringMode, | ||||
|     pub target_moisture: f32, | ||||
|     pub min_moisture: f32, | ||||
|     pub pump_time_s: u16, | ||||
|     pub pump_limit_ml: u16, | ||||
|     pub pump_cooldown_min: u16, | ||||
| @@ -131,6 +132,7 @@ impl Default for PlantConfig { | ||||
|         Self { | ||||
|             mode: PlantWateringMode::OFF, | ||||
|             target_moisture: 40., | ||||
|             min_moisture: 30., | ||||
|             pump_time_s: 30, | ||||
|             pump_limit_ml: 5000, | ||||
|             pump_cooldown_min: 60, | ||||
|   | ||||
| @@ -22,6 +22,7 @@ use bq34z100::Bq34z100g1Driver; | ||||
| use ds323x::{DateTimeAccess, Ds323x}; | ||||
| use eeprom24x::{Eeprom24x, SlaveAddr, Storage}; | ||||
| use embedded_hal_bus::i2c::MutexDevice; | ||||
| use esp_idf_hal::pcnt::PCNT1; | ||||
| use esp_idf_hal::{ | ||||
|     adc::ADC1, | ||||
|     delay::Delay, | ||||
| @@ -47,7 +48,6 @@ use once_cell::sync::Lazy; | ||||
| use std::result::Result::Ok as OkStd; | ||||
| use std::sync::Mutex; | ||||
| use std::time::Duration; | ||||
| use esp_idf_hal::pcnt::PCNT1; | ||||
|  | ||||
| //Only support for 8 right now! | ||||
| pub const PLANT_COUNT: usize = 8; | ||||
|   | ||||
| @@ -132,7 +132,7 @@ pub(crate) fn create_v3( | ||||
|         peripherals.gpio5, | ||||
|         tank_power_pin, | ||||
|         flow_sensor_pin, | ||||
|         peripherals.pcnt1 | ||||
|         peripherals.pcnt1, | ||||
|     )?; | ||||
|  | ||||
|     let mut signal_counter = PcntDriver::new( | ||||
|   | ||||
| @@ -160,7 +160,7 @@ pub(crate) fn create_v4( | ||||
|         peripherals.gpio5, | ||||
|         tank_power_pin, | ||||
|         flow_sensor_pin, | ||||
|         peripherals.pcnt1 | ||||
|         peripherals.pcnt1, | ||||
|     )?; | ||||
|  | ||||
|     let mut signal_counter = PcntDriver::new( | ||||
|   | ||||
| @@ -6,7 +6,9 @@ use esp_idf_hal::adc::oneshot::{AdcChannelDriver, AdcDriver}; | ||||
| use esp_idf_hal::adc::{attenuation, Resolution, ADC1}; | ||||
| use esp_idf_hal::delay::Delay; | ||||
| use esp_idf_hal::gpio::{AnyIOPin, AnyInputPin, Gpio5, InputOutput, PinDriver, Pull}; | ||||
| use esp_idf_hal::pcnt::{PcntChannel, PcntChannelConfig, PcntControlMode, PcntCountMode, PcntDriver, PinIndex, PCNT1}; | ||||
| use esp_idf_hal::pcnt::{ | ||||
|     PcntChannel, PcntChannelConfig, PcntControlMode, PcntCountMode, PcntDriver, PinIndex, PCNT1, | ||||
| }; | ||||
| use esp_idf_sys::EspError; | ||||
| use one_wire_bus::OneWire; | ||||
|  | ||||
| @@ -25,7 +27,7 @@ impl<'a> TankSensor<'a> { | ||||
|         gpio5: Gpio5, | ||||
|         tank_power_pin: AnyIOPin, | ||||
|         flow_sensor_pin: AnyIOPin, | ||||
|         pcnt1: PCNT1 | ||||
|         pcnt1: PCNT1, | ||||
|     ) -> anyhow::Result<TankSensor<'a>> { | ||||
|         let mut one_wire_pin = | ||||
|             PinDriver::input_output_od(one_wire_pin).expect("Failed to configure pin"); | ||||
| @@ -73,7 +75,6 @@ impl<'a> TankSensor<'a> { | ||||
|             }, | ||||
|         )?; | ||||
|  | ||||
|  | ||||
|         Ok(TankSensor { | ||||
|             one_wire_bus, | ||||
|             tank_channel, | ||||
|   | ||||
| @@ -591,16 +591,31 @@ pub fn do_secure_pump( | ||||
|     let mut first_error = true; | ||||
|     let mut pump_time_s = 0; | ||||
|     if !dry_run { | ||||
|         board.board_hal.get_tank_sensor().unwrap().reset_flow_meter(); | ||||
|         board.board_hal.get_tank_sensor().unwrap().start_flow_meter(); | ||||
|         board | ||||
|             .board_hal | ||||
|             .get_tank_sensor() | ||||
|             .unwrap() | ||||
|             .reset_flow_meter(); | ||||
|         board | ||||
|             .board_hal | ||||
|             .get_tank_sensor() | ||||
|             .unwrap() | ||||
|             .start_flow_meter(); | ||||
|         board.board_hal.pump(plant_id, true)?; | ||||
|         Delay::new_default().delay_ms(10); | ||||
|         for step in 0..plant_config.pump_time_s as usize { | ||||
|             let flow_value = board.board_hal.get_tank_sensor().unwrap().get_flow_meter_value(); | ||||
|             let flow_value = board | ||||
|                 .board_hal | ||||
|                 .get_tank_sensor() | ||||
|                 .unwrap() | ||||
|                 .get_flow_meter_value(); | ||||
|             flow_collector[step] = flow_value; | ||||
|             let flow_value_ml = flow_value as f32 * board.board_hal.get_config().tank.ml_per_pulse; | ||||
|  | ||||
|             println!("Flow value is {} ml, limit is {} ml raw sensor {}", flow_value_ml, plant_config.pump_limit_ml, flow_value); | ||||
|             println!( | ||||
|                 "Flow value is {} ml, limit is {} ml raw sensor {}", | ||||
|                 flow_value_ml, plant_config.pump_limit_ml, flow_value | ||||
|             ); | ||||
|             if flow_value_ml > plant_config.pump_limit_ml as f32 { | ||||
|                 println!("Flow value is reached, stopping"); | ||||
|                 break; | ||||
| @@ -672,9 +687,16 @@ pub fn do_secure_pump( | ||||
|         } | ||||
|     } | ||||
|     board.board_hal.get_tank_sensor().unwrap().stop_flow_meter(); | ||||
|     let final_flow_value = board.board_hal.get_tank_sensor().unwrap().get_flow_meter_value(); | ||||
|     let final_flow_value = board | ||||
|         .board_hal | ||||
|         .get_tank_sensor() | ||||
|         .unwrap() | ||||
|         .get_flow_meter_value(); | ||||
|     let flow_value_ml = final_flow_value as f32 * board.board_hal.get_config().tank.ml_per_pulse; | ||||
|     println!("Final flow value is {} with {} ml", final_flow_value, flow_value_ml); | ||||
|     println!( | ||||
|         "Final flow value is {} with {} ml", | ||||
|         final_flow_value, flow_value_ml | ||||
|     ); | ||||
|     current_collector.sort(); | ||||
|     Ok(PumpResult { | ||||
|         median_current_ma: current_collector[current_collector.len() / 2], | ||||
|   | ||||
| @@ -78,6 +78,7 @@ impl PumpState { | ||||
| pub enum PlantWateringMode { | ||||
|     OFF, | ||||
|     TargetMoisture, | ||||
|     MinMoisture, | ||||
|     TimerOnly, | ||||
| } | ||||
|  | ||||
| @@ -235,6 +236,30 @@ impl PlantState { | ||||
|                     false | ||||
|                 } | ||||
|             } | ||||
|             PlantWateringMode::MinMoisture => { | ||||
|                 let (moisture_percent, _) = self.plant_moisture(); | ||||
|                 if let Some(moisture_percent) = moisture_percent { | ||||
|                     if self.pump_in_timeout(plant_conf, current_time) { | ||||
|                         false | ||||
|                     } else if !in_time_range( | ||||
|                         current_time, | ||||
|                         plant_conf.pump_hour_start, | ||||
|                         plant_conf.pump_hour_end, | ||||
|                     ) { | ||||
|                         false | ||||
|                     } else if (true) { | ||||
|                         //if not cooldown min and below max | ||||
|                         true | ||||
|                     } else if (true) { | ||||
|                         //if below min disable cooldown min | ||||
|                         true | ||||
|                     } else { | ||||
|                         false | ||||
|                     } | ||||
|                 } else { | ||||
|                     false | ||||
|                 } | ||||
|             } | ||||
|             PlantWateringMode::TimerOnly => !self.pump_in_timeout(plant_conf, current_time), | ||||
|         } | ||||
|     } | ||||
| @@ -268,7 +293,9 @@ impl PlantState { | ||||
|                 .map(|t| t.with_timezone(¤t_time.timezone())), | ||||
|             next_pump: if matches!( | ||||
|                 plant_conf.mode, | ||||
|                 PlantWateringMode::TimerOnly | PlantWateringMode::TargetMoisture | ||||
|                 PlantWateringMode::TimerOnly | ||||
|                     | PlantWateringMode::TargetMoisture | ||||
|                     | PlantWateringMode::MinMoisture | ||||
|             ) { | ||||
|                 self.pump.previous_pump.and_then(|last_pump| { | ||||
|                     last_pump | ||||
|   | ||||
| @@ -105,6 +105,7 @@ export interface PlantControllerConfig { | ||||
| export interface PlantConfig { | ||||
|     mode: string, | ||||
|     target_moisture: number, | ||||
|     min_moisture: number, | ||||
|     pump_time_s: number, | ||||
|     pump_cooldown_min: number, | ||||
|     pump_hour_start: number, | ||||
|   | ||||
| @@ -55,6 +55,7 @@ | ||||
|         <select class="plantvalue" id="plant_${plantId}_mode"> | ||||
|             <option value="OFF">Off</option> | ||||
|             <option value="TargetMoisture">Target</option> | ||||
|             <option value="MinMoisture">Min Moisture</option> | ||||
|             <option value="TimerOnly">Timer</option> | ||||
|         </select> | ||||
|  | ||||
| @@ -63,6 +64,10 @@ | ||||
|         <div class="plantkey">Target Moisture:</div> | ||||
|         <input class="plantvalue" id="plant_${plantId}_target_moisture" type="number" min="0" max="100" placeholder="0"> | ||||
|     </div> | ||||
|     <div class="flexcontainer plantMinEnabledOnly_${plantId}"> | ||||
|         <div class="plantkey">Minimum Moisture:</div> | ||||
|         <input class="plantvalue" id="plant_${plantId}_min_moisture" type="number" min="0" max="100" placeholder="0"> | ||||
|     </div> | ||||
|     <div class="flexcontainer plantPumpEnabledOnly_${plantId}"> | ||||
|         <div class="plantkey">Pump Time (s):</div> | ||||
|         <input class="plantvalue" id="plant_${plantId}_pump_time_s" type="number" min="0" max="600" placeholder="30"> | ||||
|   | ||||
| @@ -59,6 +59,7 @@ export class PlantView { | ||||
|     private readonly header: HTMLElement; | ||||
|     private readonly testButton: HTMLButtonElement; | ||||
|     private readonly targetMoisture: HTMLInputElement; | ||||
|     private readonly minMoisture: HTMLInputElement; | ||||
|     private readonly pumpTimeS: HTMLInputElement; | ||||
|     private readonly pumpCooldown: HTMLInputElement; | ||||
|     private readonly pumpHourStart: HTMLSelectElement; | ||||
| @@ -118,6 +119,11 @@ export class PlantView { | ||||
|             controller.configChanged() | ||||
|         } | ||||
|  | ||||
|         this.minMoisture = document.getElementById("plant_" + plantId + "_min_moisture")! as HTMLInputElement; | ||||
|         this.minMoisture.onchange = function () { | ||||
|             controller.configChanged() | ||||
|         } | ||||
|  | ||||
|         this.pumpTimeS = document.getElementById("plant_" + plantId + "_pump_time_s") as HTMLInputElement; | ||||
|         this.pumpTimeS.onchange = function () { | ||||
|             controller.configChanged() | ||||
| @@ -203,13 +209,15 @@ export class PlantView { | ||||
|         let sensorOnly = document.getElementsByClassName("plantSensorEnabledOnly_"+ this.plantId) | ||||
|         let pumpOnly = document.getElementsByClassName("plantPumpEnabledOnly_"+ this.plantId) | ||||
|         let targetOnly = document.getElementsByClassName("plantTargetEnabledOnly_"+ this.plantId) | ||||
|         let minOnly = document.getElementsByClassName("plantMinEnabledOnly_"+ this.plantId) | ||||
|  | ||||
|         console.log("updateVisibility plantConfig: " + plantConfig.mode) | ||||
|         let showSensor = plantConfig.sensor_a || plantConfig.sensor_b | ||||
|         let showPump = plantConfig.mode !== "OFF" | ||||
|         let showTarget = plantConfig.mode === "TargetMoisture" | ||||
|         let showMin = plantConfig.mode === "MinMoisture" | ||||
|  | ||||
|         console.log("updateVisibility showsensor: " + showSensor + " pump " + showPump + " target " +showTarget) | ||||
|         console.log("updateVisibility showsensor: " + showSensor + " pump " + showPump + " target " +showTarget + " min " + showMin) | ||||
|  | ||||
|         for (const element of Array.from(sensorOnly)) { | ||||
|             if (showSensor) { | ||||
| @@ -234,6 +242,14 @@ export class PlantView { | ||||
|                 element.classList.add("plantHidden_" + this.plantId) | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         for (const element of Array.from(minOnly)) { | ||||
|             if (showMin) { | ||||
|                 element.classList.remove("plantHidden_" + this.plantId) | ||||
|             } else { | ||||
|                 element.classList.add("plantHidden_" + this.plantId) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     setTestResult(result: PumpTestResult) { | ||||
| @@ -255,6 +271,7 @@ export class PlantView { | ||||
|     setConfig(plantConfig: PlantConfig) { | ||||
|         this.mode.value = plantConfig.mode; | ||||
|         this.targetMoisture.value = plantConfig.target_moisture.toString(); | ||||
|         this.minMoisture.value = plantConfig.min_moisture?.toString() || ""; | ||||
|         this.pumpTimeS.value = plantConfig.pump_time_s.toString(); | ||||
|         this.pumpCooldown.value = plantConfig.pump_cooldown_min.toString(); | ||||
|         this.pumpHourStart.value = plantConfig.pump_hour_start.toString(); | ||||
| @@ -280,6 +297,7 @@ export class PlantView { | ||||
|         let conv: PlantConfig =  { | ||||
|             mode: this.mode.value, | ||||
|             target_moisture: this.targetMoisture.valueAsNumber, | ||||
|             min_moisture: this.minMoisture.valueAsNumber, | ||||
|             pump_time_s: this.pumpTimeS.valueAsNumber, | ||||
|             pump_cooldown_min: this.pumpCooldown.valueAsNumber, | ||||
|             pump_hour_start: +this.pumpHourStart.value, | ||||
|   | ||||
		Reference in New Issue
	
	Block a user