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 = {} diff --git a/main.py b/main.py index 605144d..6a293c7 100644 --- a/main.py +++ b/main.py @@ -17,6 +17,12 @@ uart0.init(tx=16, rx=17) os.dupterm(uart0) +# Define PSU latch early to use in on-boot checks. +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.3.0-DEV" 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": "hardware_control", }, "monitoring": { "use_ds18x20": True, @@ -71,11 +77,25 @@ helpers.write_config(DEFAULT_CONFIG) CONFIG = helpers.read_config() + +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", "hardware_control") == "power_off": + psu.off() +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) + 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"): (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 +107,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 +128,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 +141,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) @@ -133,24 +159,14 @@ 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) 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) -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. @@ -166,7 +182,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) @@ -282,10 +298,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 30e0367..56c6d11 100644 --- a/templates/settings_power.html +++ b/templates/settings_power.html @@ -14,10 +14,15 @@
Legend