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 @@
- Turn on ATX on Boot + On Power Restore
- +
@@ -55,8 +60,8 @@

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. + 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

@@ -64,4 +69,4 @@
-{% include "footer.html" %} \ No newline at end of file +{% include "footer.html" %}