'''
To Do:
- Implement filament cycling (Main Display)
> Button A : Short Press --> Change Filament
> Button A : Long Press --> Set Filament
> Button B : Short Press --> Start|Pause (cycles)
> Button B : Long Press --> End
> Button B : Longer Press --> Change Mode (between Humidity | Time)
- Implement Modes:
> Humidity: Runs in perpetuity, managing the temperature in the box to maintain humidity between a range.
> Time: Runs until the time is reached OR until the desired humidity is reached.
- Settings View:
> Button A : Short Press --> Select Setting
--> A: Cycle Setting
--> A: Set Setting
--> B: Back
> Button B : Short Press --> Back (to Main View)
> Button B : Long Press --> Back (to Main View)
- User Settings:
> Offset Temp
> Offset Humidity
> Cooldown Time
- View Manager:
> View is preconfigured to fit 8x16 (row * column)
> Changes in data get pushed to the a controlling Class and then refreshed.
- Add Generic Filament Temp Config:
> "CUSTOM"
> 5 deg increments 30deg to 100deg
> Time in hour, 0-48hrs
Interrupts can't be on GPIO 6 or GPIO 11
- https://randomnerdtutorials.com/micropython-interrupts-esp32-esp8266/
- https://docs.espressif.com/projects/esp-idf/en/v4.3/esp32/api-reference/peripherals/timer.html
- https://www.upesy.com/blogs/tutorials/timer-esp32-with-micro-python-scripts
Timers:
- Buttons
NOTE:
ESP32-C3 has NeoPixel on-board at GPIO8
TO DO:
- Refactor Imports to just the necessities
'''
from machine import Pin, I2C, Timer
import neopixel
import ssd1306
import dht
import time
# Interrupt Handler
def handle_interrupt(pin):
# Process interruption
# PLACEHOLDER: If state = running, do something different
print("Button IRQ: ", pin)
time.sleep(0.01)
def test_print(*args):
print("Timer 0 ...", *args)
#pin_builtin_led.value(not pin_builtin_led.value())
'''
# Miscellaneous Init
timer_0 = Timer(0) # Between 0-3 for ESP32
timer_0.init(mode=Timer.PERIODIC, period=3000, callback=test_print)
# GPIO: ESP32 Pin assignment
i2c = I2C(0, scl=Pin(22), sda=Pin(21))
button_1 = Pin(12, Pin.IN, Pin.PULL_UP)
button_1.irq(trigger=Pin.IRQ_FALLING, handler=handle_interrupt)
button_2 = Pin(14, Pin.IN, Pin.PULL_UP)
button_2.irq(trigger=Pin.IRQ_FALLING, handler=handle_interrupt)
pin_builtin_led = Pin(2, mode=Pin.OUT)
relay = Pin(4, Pin.OUT)
dht = dht.DHT22(Pin(26))
np = neopixel.NeoPixel(Pin(15), 1)
relay.value(0) # Temporary initial state since Wowki doesn't always reset.
'''
# NeoPixel Colors
class Colors(dict):
violet = (148,0,211)
purple = (75, 0, 130)
blue = (0,0,255)
green = (0,255,0)
yellow = (255,255,0)
orange = (255,127,0)
red = (255,0,0)
white = (255,255,255)
off = (0,0,0)
# Temp Testing Functions
def testbtns(sec=5):
import time
time.sleep(sec)
print(button_1.value(), button_2.value())
if(button_1.value):
# RELAY ON
relay.value(1)
"""
| Filament: PLA
| Desired Temp: 105c|
| Desired Humidity: 10% |
| ------ CURRENT ------ |
| Temp: 105c @ 12% |
| Ranges: 100-110
"""
class Display():
def __init__(self, oled_display, oled_width = 128, oled_height = 64):
self.oled_width = oled_width
self.oled_height = oled_height
self.oled = oled_display
self.oled_default_row_offset = 9;
self.oled_default_column_offset = 8;
self.state = dict()
self.reset()
def set_values(self, **kwargs):
# Given a set of kwargs, update all that it was handed.
for key, value in kwargs.items():
self.state[key] = value
def refresh(self):
# Refresh the screen with the new values
self.clear()
self.oled.text(self.state['filament_active'][0:5].upper(), self._column(0), self._row(0))
self.oled.text(f"{int(self.state['filament_humidity'])}% {int(self.state['filament_temp'])}c", self._column(6), self._row(0))
self.oled.text("--TARGETS-------", self._column(0), self._row(1))
self.oled.text(f" temp: {int(self.state['filament_temp']) - \
int(self.state['hysteresis_temp'])}-{int(self.state['filament_temp']) + \
int(self.state['hysteresis_temp'])}c", self._column(0), self._row(2))
self.oled.text(f" humid: {int(self.state['filament_humidity']) - \
int(self.state['hysteresis_humidity'])}-{int(self.state['filament_humidity']) + \
int(self.state['hysteresis_humidity'])}%", self._column(0), self._row(3))
self.oled.text("--CURRENT-------", self._column(0), self._row(4))
self.oled.text("h: 30% | t: 105c", self._column(0), self._row(5))
self.oled.text("status: standby", self._column(0), self._row(6))
self.oled.show()
def reset(self):
# Resets the state to the default values.
self.state = {
"current_humidity": 0,
"current_temp": 0,
"hysteresis_temp": 3,
"hysteresis_humidity": 2,
"filament_humidity": 0,
"filament_temp": 0,
"filament_active": None,
"status": None,
"mode": None,
"status_heater": "off"
}
def clear(self):
self.oled.fill(0)
def _column(self, column=0, offset=None):
# Given a column number, return a px starting position.
if not offset:
offset = self.oled_default_column_offset
return column*offset
def _row(self, row=0, offset=None):
# Given a row number, return a px starting position.
if not offset:
offset = self.oled_default_row_offset
return row*offset
# Logic for Filament
class FilamentPresets():
def __init__(self):
self.temp_hysteresis = 3.0 # Degrees Celsius
self.humidity_hysteresis = 3.0 # Percent in 100 scale
self.cooldown_time = 10 # Minutes
self.filaments = dict()
def add_filament(self, name, temp, time):
name = name.lower()
self.filaments[name] = self.Filament(name, temp, time)
def list_filament(self, name = None):
try:
print(f'----\n {name.upper()}\n \
Temperature: {self.filaments[name.lower()].temp}°C \n \
Time: {self.filaments[name.lower()].time} hours\n----\n')
except Exception as e:
print("!! -- ERROR occurred, msg: ", e)
except:
print("!! -- ERROR occurred ...")
def list_filaments(self):
for name, settings in self.filaments.items():
self.list_filament(name)
"""
def __repr__(self):
print(" Temperature Hysteresis {temp_hysteresis} = ", self.temp_hysteresis, "°C \n",
"Humidity Hysteresis {humidity_hysteresis} = ", self.humidity_hysteresis, "% \n",
"Minimum Cooldown Time {cooldown_time} = ", self.humidity_hysteresis, "minutes \n",
"Filaments: \n ")
for filament in self.filaments:
print("Filament: ", filament)
#for setting in self.filaments[filament]:
for setting in self.filaments:
print(setting, ":", self.filaments[setting])
print("---- \n")
"""
class Filament(object):
def __init__(self, name, temp, time):
self.name = name
self.temp = temp # in celsius
self.time = time # in hours
#TEMP HELPERS
def debounce(pin):
prev = None
for _ in range(32):
current_value = pin.value()
if prev != None and prev != current_value:
return None
prev = current_value
return prev
def button_callback(pin):
d = debounce(pin)
if d == None:
return
elif not d:
#led.value(not led.value())
print("Not D triggered")
'''
Button Debouncing:
--> Button Standard State = unpressed (pressed=false)
-> if last_state = not_pressed:
curr_state = pressed
last_state = curr_state
----> IRQ Callback: Button is pressed, state = pressed
----> ---->
'''
class EibosController(object):
''' Controller class to handle the command logic for the Eibos Easdry Box '''
def __init__(self, filament_presets):
self._presets = filament_presets
self._state = self.ControlStates().boot
self._active_filament_idx = 0
self.update_interval = 60 # seconds
self._mode = "humidity"
# Initialize GPIO
self.__init_gpio()
# Button Flags
self._button_a_last_ms = None
self._button_b_state = False
self.last_state_pressed = False
self.curr_state_pressed = False
def __init_gpio(self):
# Sets up the ESP32 on initial boot
self._timer_0 = Timer(0) # Between 0-3 for ESP32
self._timer_3 = Timer(3) # Button
#self._timer_3.init(mode=Timer.ONE_SHOT, callback=self._handle_interrupt_btn_a)
#self._timer_0.init(mode=Timer.PERIODIC, period=3000, callback=test_print)
# GPIO: ESP32 Pin assignment
self._i2c = I2C(0, scl=Pin(22), sda=Pin(21))
self._button_a = Pin(12, Pin.IN, Pin.PULL_UP)
self._button_a.irq(trigger=Pin.IRQ_FALLING, handler=button_callback)
self._button_b = Pin(14, Pin.IN, Pin.PULL_UP)
self._button_b.irq(trigger=Pin.IRQ_FALLING, handler=self._handle_interrupt_btn_a)
self._heater_relay = Pin(4, Pin.OUT)
self._heater_relay.value(0) # Temporary initial state since Wowki doesn't always reset.
self._sensor_temp_humid = dht.DHT22(Pin(26))
self._read_sensor()
self._pixel = neopixel.NeoPixel(Pin(15), 1)
# Initialize the Display
oled_w = 128
oled_h = 64
self._oled = ssd1306.SSD1306_I2C(oled_w, oled_h, self._i2c)
self._display = Display(self._oled);
def run(self):
# Runs the currently active program
pass
def stop(self):
# Stops the currently running program.
pass
def reset(self):
# Resets the active program
pass
def set_mode_time(self):
# Sets the mode to timer based, future use for timer based mode
pass
def set_mode_humidity(self):
# Sets the mode to humidity based.
self._mode = "humidity"
@property
def humidity_last(self):
# Returns the last known humidity state.
return self._sensor_temp_humid.humidity()
@property
def temperature_last(self):
# Returns the last known temperature state.
return self._sensor_temp_humid.temperature()
def _handle_interrupt(self, pin):
#REMOVE
print("--- IRQ: pin->",pin)
def _handle_interrupt_btn_a(self, pin):
print("--- IRQ: A pin->", pin, "Active IDX ", self._active_filament_idx, self._button_a_last_ms)
if not self._button_a_last_ms:
# Button is none or not set
self._button_a_last_ms = self._time_ms()
elif self._time_ms() - self._button_a_last_ms >= 250:
print("BTN ACTION Triggered", self._time_ms() - self._button_a_last_ms)
self._active_filament_idx += 1
self._button_a_last_ms = None
print("Button Status - Diff:", self._time_ms() - self._button_a_last_ms, " Last_MS: ", self._button_a_last_ms)
def _handle_interrupt_btn_b(self, pin):
print("--- IRQ: B pin->", pin)
if not self.last_state_pressed:
self.curr_state_pressed = True
def _read_sensor(self):
# Queries temperature/humidity sensor, stores them in sensor state
self._sensor_temp_humid.measure()
def _update_display(self):
# Changes the display
pass
def _update_setting(self):
# Changes settings
pass
def _update_led(self):
# Changes the LED state
pass
def _read_buttons(self):
# Checks the button(s) state
pass
def _heater_relay(self):
# Controls the heater relay
pass
def _set_status(self):
# Set the device status using the controller state
pass
def _time_ms(self):
# Return the time in milliseconds
return time.time_ns() // 1_000_000
class ControlStates:
# Basic holder class for states
boot = "boot"
standby = "standby"
idle = "idle"
running = "running"
'''
dht.measure()
print(dht.humidity(), dht.temperature())
# Set NeoPixel (test)
colors = Colors()
np[0] = colors.violet # First Pixel = 0
np.write()
time.sleep(1)
np[0] = colors.orange # First Pixel = 0
np.write()
time.sleep(1)
np[0] = colors.white # First Pixel = 0
np.write()
Status Modes:
- Standby
-> Not Running
-> Heater is not active (relay is off)
-> if state != running & relay = off
-> LED Color: Blue
- Idle ^ Temp || Idle ^ Hum.
-> Running
-> Heater is not active (relay is off)
-> if state = running & relay = off
-> LED Color: Orange
- Target Reached
-> Running
-> Heater is not active (relay is off)
-> if state = running & relay = off & current_humid <= target_humid
-> LED Color: Green
- Running
-> Running
-> Heater is active (relay is on)
-> if state = running & relay = on
-> LED Color: Red
'''
# Presets for Filaments
presets = FilamentPresets()
presets.add_filament('PLA', 45, 24)
presets.add_filament('PETG', 65, 6)
presets.add_filament('PVA', 45, 24)
presets.add_filament('PVB', 45, 8)
presets.add_filament('ABS', 60, 6)
presets.add_filament('ASA', 60, 24)
presets.add_filament('PC', 70, 8)
presets.add_filament('PCCF', 70, 12)
presets.add_filament('PA11CF', 70, 24)
presets.add_filament('NYLON', 70, 24)
presets.add_filament('TPU', 55, 24)
# Controller Setup
controller = EibosController(filament_presets = presets)
print("------ DEBUG ------ \n")
#display.refresh({"filament_active":"abs"})
controller._display.set_values(status = "Idle", filament_active = "Abs", filament_humidity=11.6, filament_temp=102.23)
controller._display.refresh()
def test_wait():
count = 0
while True:
count += 1
if(count % 100 == 0):
print("Still running", time.time())
count = 0
def main():
# The main loop
pass