Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 52 additions & 6 deletions helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,47 @@
OpenJBOD Helper Functions
"""

import os
import json
import machine
import time
import binascii
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)
Expand All @@ -25,25 +54,38 @@ 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():
return True
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")
Expand Down Expand Up @@ -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 = {}
Expand Down
59 changes: 36 additions & 23 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand All @@ -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,
Expand Down Expand Up @@ -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.
Expand All @@ -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()
Expand All @@ -107,18 +128,23 @@ 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()
else:
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)
Expand All @@ -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.
Expand All @@ -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)


Expand Down Expand Up @@ -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
Expand Down
15 changes: 10 additions & 5 deletions templates/settings_power.html
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,15 @@
</div>
<div class="row">
<div class="grid-2">
<b>Turn on ATX on Boot</b>
<b>On Power Restore</b>
</div>
<div class="grid-2">
<input type="checkbox" name="on_boot" {% if config['power']['on_boot'] %}checked{% endif %} value=True />
<select name="on_power_restore" id="on_power_restore">
<option value="power_off" {% if config['power'].get('on_power_restore', 'hardware_control') == 'power_off' %}selected{% endif %}>Power Off</option>
<option value="power_on" {% if config['power'].get('on_power_restore', 'hardware_control') == 'power_on' %}selected{% endif %}>Power On</option>
<option value="last_state" {% if config['power'].get('on_power_restore', 'hardware_control') == 'last_state' %}selected{% endif %}>Last State</option>
<option value="hardware_control" {% if config['power'].get('on_power_restore', 'hardware_control') == 'hardware_control' %}selected{% endif %}>Hardware Control</option>
</select>
</div>
</div>
<div class="row">
Expand Down Expand Up @@ -55,13 +60,13 @@
<p class="heading">Legend</p>
<fieldset>
<p><b>Ignore Power Switch</b> - This causes the controller to ignore the power button input.</p>
<p><b>Turn on ATX on Boot</b> - The controller will start the power supply as part of the boot process. This is
mutually exclusive with <b>Follow USB Power</b></b></p>
<p><b>On Power Restore</b> - 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 <b>Follow USB Power</b></b></p>
<p><b>Boot Delay</b> - This is a timer in seconds that will elapse before the power supply is turned on.</p>
<p><b>Follow USB Power</b> - 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 <b>Turn on ATX on Boot</b></p>
<p><b>Boot Delay</b> - This is a timer in seconds that will elapse before the power supply is turned on.</p>
</fieldset>
</div>
</div>
{% include "footer.html" %}
{% include "footer.html" %}