From a7f46fda45e617783abbd68655a2763ece45715f Mon Sep 17 00:00:00 2001 From: Natan Keddem Date: Tue, 24 Dec 2024 12:22:09 -0500 Subject: [PATCH 1/4] black --- main.py | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/main.py b/main.py index bd9df6b..142c2e7 100644 --- a/main.py +++ b/main.py @@ -75,7 +75,7 @@ # be rendered even if they are no longer accurate to the HTML for i in os.ilistdir("templates"): (name, entry_type, inode, size) = i - if '_html.py' in name: + if "_html.py" in name: os.remove(f"templates/{name}") # SSL has been removed. @@ -87,17 +87,18 @@ # Set up fan curve. FAN_TEMPS = [] FAN_SPEEDS = [] -for i in CONFIG['fan_curve']: - FAN_TEMPS.append(CONFIG['fan_curve'][i]['temp']) - FAN_SPEEDS.append(CONFIG['fan_curve'][i]['fan_p']) +for i in CONFIG["fan_curve"]: + FAN_TEMPS.append(CONFIG["fan_curve"][i]["temp"]) + FAN_SPEEDS.append(CONFIG["fan_curve"][i]["fan_p"]) FAN_TEMPS.sort() FAN_SPEEDS.sort() + def usb_pin_check(pin): pin.irq(handler=None) print("Triggered usb_pin_check") - if CONFIG['power']['follow_usb_delay']: - time.sleep(CONFIG['power']['follow_usb_delay']) + if CONFIG["power"]["follow_usb_delay"]: + time.sleep(CONFIG["power"]["follow_usb_delay"]) time.sleep(1) if pin.value(): psu.on() @@ -107,10 +108,12 @@ def usb_pin_check(pin): print("Turning off") pin.irq(handler=usb_pin_check) + def fan_fail_handler(pin): # TODO: See https://github.com/OpenJBOD/software/issues/3 FAN_FAILED = True + def power_btn_handler(pin): if psu.state(): psu.off() @@ -118,7 +121,10 @@ def power_btn_handler(pin): psu.on() power_btn.irq(handler=power_debounce) + pwr_timer = Timer() + + def power_debounce(pin): power_btn.irq(handler=None) pwr_timer.init(mode=Timer.ONE_SHOT, period=200, callback=power_btn_handler) @@ -142,7 +148,7 @@ def power_debounce(pin): # Interrupts power_btn.irq(trigger=Pin.IRQ_FALLING, handler=power_debounce) fan_fail.irq(trigger=Pin.IRQ_FALLING, handler=fan_fail_handler) -if CONFIG['power']['follow_usb']: +if CONFIG["power"]["follow_usb"]: usb_sense.irq(trigger=Pin.IRQ_RISING, handler=usb_pin_check) usb_sense.irq(trigger=Pin.IRQ_FALLING, handler=usb_pin_check) @@ -168,7 +174,7 @@ def power_debounce(pin): else: ds_rom = ds_roms[0] # Set temperature resolution to 9 bits. - config = b'\x00\x00\x1f' + config = b"\x00\x00\x1f" ds_sensor.write_scratch(ds_rom, config) From 10dace767fc0a77bb229710b04dd727a3ef77a7c Mon Sep 17 00:00:00 2001 From: Natan Keddem Date: Tue, 24 Dec 2024 14:44:27 -0500 Subject: [PATCH 2/4] added srlatch state storage --- helpers.py | 58 ++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 52 insertions(+), 6 deletions(-) diff --git a/helpers.py b/helpers.py index cc39777..a1ed9ca 100644 --- a/helpers.py +++ b/helpers.py @@ -2,6 +2,7 @@ OpenJBOD Helper Functions """ +import os import json import machine import time @@ -9,11 +10,39 @@ from hashlib import sha1 CONFIG_FILE = "config.json" +STATE_FILE = "state.json" +STATE = {} + + +def read_state(): + with open(STATE_FILE, "r") as f: + return json.load(f) + + +def write_state(state): + with open(STATE_FILE, "w") as f: + json.dump(state, f) + + return "State written!" + + +try: + os.stat(STATE_FILE) + print("[INIT] Reading state from file.") + STATE = read_state() +except OSError: + print("[INIT] State not found, writing and assuming defaults!") + write_state(STATE) + STATE = read_state() class SRLatch: def __init__( - self, set_pin: machine.Pin, reset_pin: machine.Pin, sense_pin: machine.Pin + self, + set_pin: machine.Pin, + reset_pin: machine.Pin, + sense_pin: machine.Pin, + name: str = None, ): if ( not isinstance(set_pin, machine.Pin) @@ -25,18 +54,25 @@ def __init__( self.set_pin = set_pin self.reset_pin = reset_pin self.sense_pin = sense_pin + self.name = name - def on(self): + def on(self, store=True): self.reset_pin.off() self.set_pin.on() time.sleep_ms(250) self.set_pin.off() + if self.name and store: + STATE[f"{self.name}_srlatch_state"] = True + write_state(STATE) - def off(self): + def off(self, store=True): self.set_pin.off() self.reset_pin.on() time.sleep_ms(250) self.reset_pin.off() + if self.name and store: + STATE[f"{self.name}_srlatch_state"] = False + write_state(STATE) def state(self): if self.sense_pin.value(): @@ -44,6 +80,12 @@ def state(self): else: return False + def stored_state(self): + if self.name and STATE.get(f"{self.name}_srlatch_state", False): + return True + else: + return False + def create_hash(password): return binascii.hexlify(sha1(password).digest()).decode("utf-8") @@ -109,21 +151,25 @@ def check_temp(temp, fan_curve): break return fan_speed + def linear_interpolation(x_values, y_values, x): if len(x_values) != 5 or len(y_values) != 5: - raise ValueError("x_values and y_values must each contain exactly five elements.") + raise ValueError( + "x_values and y_values must each contain exactly five elements." + ) if sorted(x_values) != x_values: raise ValueError("x_values must be sorted in ascending order.") - + for i in range(4): if x_values[i] <= x <= x_values[i + 1]: x1, x2 = x_values[i], x_values[i + 1] y1, y2 = y_values[i], y_values[i + 1] y = y1 + (y2 - y1) * (x - x1) / (x2 - x1) return y - + raise ValueError("x is out of the range of the provided x_values.") + def get_network_info(ifconfig): # Store the W5500 ifconfig tuple in a more readable format. network_info = {} From 625aff799672bf12ae0502a16e84c8e6dec702f7 Mon Sep 17 00:00:00 2001 From: Natan Keddem Date: Tue, 24 Dec 2024 14:45:06 -0500 Subject: [PATCH 3/4] improved power restore options --- main.py | 37 +++++++++++++++++++++-------------- templates/settings_power.html | 12 ++++++++---- 2 files changed, 30 insertions(+), 19 deletions(-) diff --git a/main.py b/main.py index 142c2e7..94f4fae 100644 --- a/main.py +++ b/main.py @@ -17,6 +17,12 @@ uart0.init(tx=16, rx=17) os.dupterm(uart0) +psu_set = Pin(14, Pin.OUT) +psu_sense = Pin(15, Pin.IN) +psu_reset = Pin(13, Pin.OUT) +psu = helpers.SRLatch(psu_set, psu_reset, psu_sense, name="psu") + + VERSION = "1.2.0" DEFAULT_CONFIG = { "network": { @@ -28,11 +34,11 @@ "dns": "", }, "power": { - "on_boot": False, "on_boot_delay": 0, "follow_usb": False, "follow_usb_delay": 0, "ignore_power_switch": False, + "on_power_restore": "power_on", }, "monitoring": { "use_ds18x20": True, @@ -71,6 +77,20 @@ helpers.write_config(DEFAULT_CONFIG) CONFIG = helpers.read_config() + +if CONFIG["power"].get("on_power_restore", "power_off") == "power_on": + if not psu.state(): + time.sleep(CONFIG["power"]["on_boot_delay"]) + psu.on() +elif CONFIG["power"].get("on_power_restore", "power_off") == "power_off": + psu.off() +elif CONFIG["power"].get("on_power_restore", "power_off") == "last_state": + if psu.stored_state(): + time.sleep(CONFIG["power"]["on_boot_delay"]) + psu.on(store=False) + else: + psu.off(store=False) + # Remove generated template files as otherwise these will # be rendered even if they are no longer accurate to the HTML for i in os.ilistdir("templates"): @@ -141,9 +161,6 @@ def power_debounce(pin): led = Pin(6, Pin.OUT) fan_fail = Pin(10, Pin.IN, Pin.PULL_UP) power_btn = Pin(12, Pin.IN, Pin.PULL_UP) -psu_reset = Pin(13, Pin.OUT) -psu_set = Pin(14, Pin.OUT) -psu_sense = Pin(15, Pin.IN) usb_sense = Pin(25, Pin.IN) # Interrupts power_btn.irq(trigger=Pin.IRQ_FALLING, handler=power_debounce) @@ -152,13 +169,6 @@ def power_debounce(pin): usb_sense.irq(trigger=Pin.IRQ_RISING, handler=usb_pin_check) usb_sense.irq(trigger=Pin.IRQ_FALLING, handler=usb_pin_check) -psu = helpers.SRLatch(psu_set, psu_reset, psu_sense) - -if CONFIG["power"]["on_boot"]: - time.sleep(CONFIG["power"]["on_boot_delay"]) - if not psu.state(): - psu.on() - led.on() emc2301 = EMC2301(i2c) # This shouldn't need to be hardcoded. @@ -290,10 +300,7 @@ async def settings_network(req): @auth async def settings_power(req): if req.method == "POST": - if req.form.get("on_boot"): - CONFIG["power"]["on_boot"] = True - else: - CONFIG["power"]["on_boot"] = False + CONFIG["power"]["on_power_restore"] = req.form["on_power_restore"] CONFIG["power"]["on_boot_delay"] = int(req.form["on_boot_delay"]) if req.form.get("follow_usb"): CONFIG["power"]["follow_usb"] = True diff --git a/templates/settings_power.html b/templates/settings_power.html index 41a8633..cc04476 100644 --- a/templates/settings_power.html +++ b/templates/settings_power.html @@ -9,8 +9,12 @@

Power

- - + +
@@ -33,10 +37,10 @@

Power

Legend

Ignore Power Switch - This causes the controller to ignore the power button input.

-

Turn on ATX on Boot - The controller will start the power supply as part of the boot process. This is mutually exclusive with Follow USB Power

+

On Power Restore - When AC power is applied the controller will automatically power the system on, off, or in the last state recorded. This is mutually exclusive with Follow USB Power

Boot Delay - This is a timer in seconds that will elapse before the power supply is turned on.

Follow USB Power - The controller will start and stop the ATX power supply when it receives or stops receiving voltage from the microUSB port. This is mutually exclusive with Turn on ATX on Boot

Boot Delay - This is a timer in seconds that will elapse before the power supply is turned on.

-{% include "footer.html" %} \ No newline at end of file +{% include "footer.html" %} From 7222d81a0ab06bbe8a36c9507232e9efe942cc1c Mon Sep 17 00:00:00 2001 From: Natan Keddem Date: Thu, 20 Feb 2025 19:01:41 -0500 Subject: [PATCH 4/4] added hardware control and made it default --- main.py | 8 ++++---- templates/settings_power.html | 9 +++++---- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/main.py b/main.py index 94f4fae..9d25b52 100644 --- a/main.py +++ b/main.py @@ -38,7 +38,7 @@ "follow_usb": False, "follow_usb_delay": 0, "ignore_power_switch": False, - "on_power_restore": "power_on", + "on_power_restore": "hardware_control", }, "monitoring": { "use_ds18x20": True, @@ -78,13 +78,13 @@ CONFIG = helpers.read_config() -if CONFIG["power"].get("on_power_restore", "power_off") == "power_on": +if CONFIG["power"].get("on_power_restore", "hardware_control") == "power_on": if not psu.state(): time.sleep(CONFIG["power"]["on_boot_delay"]) psu.on() -elif CONFIG["power"].get("on_power_restore", "power_off") == "power_off": +elif CONFIG["power"].get("on_power_restore", "hardware_control") == "power_off": psu.off() -elif CONFIG["power"].get("on_power_restore", "power_off") == "last_state": +elif CONFIG["power"].get("on_power_restore", "hardware_control") == "last_state": if psu.stored_state(): time.sleep(CONFIG["power"]["on_boot_delay"]) psu.on(store=False) diff --git a/templates/settings_power.html b/templates/settings_power.html index cc04476..2541d3b 100644 --- a/templates/settings_power.html +++ b/templates/settings_power.html @@ -11,9 +11,10 @@

Power

@@ -37,7 +38,7 @@

Power

Legend

Ignore Power Switch - This causes the controller to ignore the power button input.

-

On Power Restore - When AC power is applied the controller will automatically power the system on, off, or in the last state recorded. This is mutually exclusive with Follow USB Power

+

On Power Restore - When AC power is applied the controller will automatically power the system on, off, or in the last state recorded. Hardware Control will not alter the state via software. This is mutually exclusive with Follow USB Power

Boot Delay - This is a timer in seconds that will elapse before the power supply is turned on.

Follow USB Power - The controller will start and stop the ATX power supply when it receives or stops receiving voltage from the microUSB port. This is mutually exclusive with Turn on ATX on Boot

Boot Delay - This is a timer in seconds that will elapse before the power supply is turned on.