commit 1
This commit is contained in:
48
Maker_Pico_Box/3buttons.py
Normal file
48
Maker_Pico_Box/3buttons.py
Normal file
@@ -0,0 +1,48 @@
|
||||
import machine
|
||||
import time
|
||||
import neopixel
|
||||
|
||||
# NeoPixel setup
|
||||
NUM_LEDS = 3
|
||||
np = neopixel.NeoPixel(machine.Pin(17), NUM_LEDS)
|
||||
|
||||
# Buttons
|
||||
button_pins = [20, 21, 22]
|
||||
buttons = [machine.Pin(pin, machine.Pin.IN, machine.Pin.PULL_UP) for pin in button_pins]
|
||||
|
||||
# Colors to cycle through
|
||||
colors = [
|
||||
(255, 0, 0), # Red
|
||||
(0, 255, 0), # Green
|
||||
(0, 0, 255), # Blue
|
||||
(255, 255, 0), # Yellow
|
||||
(255, 0, 255), # Magenta
|
||||
(0, 255, 255), # Cyan
|
||||
(255, 255, 255) # White
|
||||
]
|
||||
|
||||
# Individual color indexes for each pixel
|
||||
indexes = [0, 0, 0]
|
||||
last_state = [1, 1, 1] # buttons start unpressed
|
||||
|
||||
def show_pixels():
|
||||
for i in range(NUM_LEDS):
|
||||
np[i] = colors[indexes[i]]
|
||||
np.write()
|
||||
|
||||
# Initial display
|
||||
show_pixels()
|
||||
|
||||
while True:
|
||||
for i in range(3):
|
||||
state = buttons[i].value()
|
||||
|
||||
# Detect press (1 → 0)
|
||||
if last_state[i] == 1 and state == 0:
|
||||
indexes[i] = (indexes[i] + 1) % len(colors)
|
||||
show_pixels()
|
||||
time.sleep_ms(200) # debounce
|
||||
|
||||
last_state[i] = state
|
||||
|
||||
time.sleep_ms(10)
|
||||
10
Maker_Pico_Box/button.py
Normal file
10
Maker_Pico_Box/button.py
Normal file
@@ -0,0 +1,10 @@
|
||||
import machine
|
||||
import time
|
||||
|
||||
while True:
|
||||
if machine.bootsel_button():
|
||||
print("BOOTSEL button pressed!")
|
||||
else:
|
||||
print("Not pressed.")
|
||||
time.sleep(0.2)
|
||||
|
||||
44
Maker_Pico_Box/color_button.py
Normal file
44
Maker_Pico_Box/color_button.py
Normal file
@@ -0,0 +1,44 @@
|
||||
import machine
|
||||
import neopixel
|
||||
import time
|
||||
|
||||
# NeoPixel setup
|
||||
NUM_LEDS = 8
|
||||
np = neopixel.NeoPixel(machine.Pin(28), NUM_LEDS)
|
||||
|
||||
# Button on GP22 (active LOW)
|
||||
button = machine.Pin(22, machine.Pin.IN, machine.Pin.PULL_UP)
|
||||
|
||||
# Color list (R, G, B)
|
||||
colors = [
|
||||
(255, 0, 0), # Red
|
||||
(0, 255, 0), # Green
|
||||
(0, 0, 255), # Blue
|
||||
(255, 255, 0), # Yellow
|
||||
(255, 0, 255), # Magenta
|
||||
(0, 255, 255), # Cyan
|
||||
(255, 255, 255) # White
|
||||
]
|
||||
|
||||
current_color = 0
|
||||
last_state = 1 # button not pressed
|
||||
|
||||
def show_color(color):
|
||||
for i in range(NUM_LEDS):
|
||||
np[i] = color
|
||||
np.write()
|
||||
|
||||
# Initial color
|
||||
show_color(colors[current_color])
|
||||
|
||||
while True:
|
||||
state = button.value()
|
||||
|
||||
# Detect button press (HIGH → LOW)
|
||||
if last_state == 1 and state == 0:
|
||||
current_color = (current_color + 1) % len(colors)
|
||||
show_color(colors[current_color])
|
||||
time.sleep_ms(200) # Debounce delay
|
||||
|
||||
last_state = state
|
||||
time.sleep_ms(10)
|
||||
32
Maker_Pico_Box/color_off.py
Normal file
32
Maker_Pico_Box/color_off.py
Normal file
@@ -0,0 +1,32 @@
|
||||
import machine
|
||||
import neopixel
|
||||
import time
|
||||
|
||||
# NeoPixel setup
|
||||
NUM_LEDS = 8
|
||||
np = neopixel.NeoPixel(machine.Pin(28), NUM_LEDS)
|
||||
|
||||
# Button on GP22 (active LOW)
|
||||
button = machine.Pin(22, machine.Pin.IN, machine.Pin.PULL_UP)
|
||||
|
||||
# Color list (R, G, B)
|
||||
colors = [
|
||||
(0, 0, 0),
|
||||
(255, 0, 0), # Red
|
||||
(0, 255, 0), # Green
|
||||
(0, 0, 255), # Blue
|
||||
(255, 255, 0), # Yellow
|
||||
(255, 0, 255), # Magenta
|
||||
(0, 255, 255), # Cyan
|
||||
(255, 255, 255) # White
|
||||
]
|
||||
|
||||
current_color = 0
|
||||
last_state = 1 # button not pressed
|
||||
|
||||
def show_color(color):
|
||||
for i in range(NUM_LEDS):
|
||||
np[i] = color
|
||||
np.write()
|
||||
|
||||
show_color(colors[current_color])
|
||||
172
Maker_Pico_Box/main.py
Normal file
172
Maker_Pico_Box/main.py
Normal file
@@ -0,0 +1,172 @@
|
||||
import machine
|
||||
import time
|
||||
import neopixel
|
||||
|
||||
# ============================================================
|
||||
# HEARTBEAT CLASS — smooth fade with MIN + MAX brightness
|
||||
# ============================================================
|
||||
|
||||
class HeartbeatFadeNeoPixel:
|
||||
def __init__(self, neopixel_obj, pixel_index=0, color=(255, 0, 0),
|
||||
min_brightness=5, max_brightness=100,
|
||||
step=2, interval_ms=20):
|
||||
|
||||
self.np = neopixel_obj
|
||||
self.index = pixel_index
|
||||
self.color = color
|
||||
|
||||
self.min_brightness = min_brightness
|
||||
self.max_brightness = max_brightness
|
||||
self.step = step
|
||||
self.interval = interval_ms
|
||||
|
||||
self.brightness = min_brightness
|
||||
self.direction = 1 # 1 = fade up, -1 = fade down
|
||||
self.last_time = time.ticks_ms()
|
||||
|
||||
def apply_brightness(self, c, level):
|
||||
r, g, b = c
|
||||
factor = level / 100
|
||||
return (int(r * factor), int(g * factor), int(b * factor))
|
||||
|
||||
def update(self):
|
||||
now = time.ticks_ms()
|
||||
if time.ticks_diff(now, self.last_time) >= self.interval:
|
||||
self.last_time = now
|
||||
|
||||
# Update brightness
|
||||
self.brightness += self.direction * self.step
|
||||
|
||||
# Bounce at limits
|
||||
if self.brightness >= self.max_brightness:
|
||||
self.brightness = self.max_brightness
|
||||
self.direction = -1
|
||||
|
||||
if self.brightness <= self.min_brightness:
|
||||
self.brightness = self.min_brightness
|
||||
self.direction = 1
|
||||
|
||||
# Apply brightness
|
||||
self.np[self.index] = self.apply_brightness(self.color, self.brightness)
|
||||
self.np.write()
|
||||
|
||||
|
||||
# ============================================================
|
||||
# NEOPIXEL SETUP
|
||||
# ============================================================
|
||||
|
||||
STRIP_PIN = 17
|
||||
STRIP_LEDS = 3
|
||||
strip = neopixel.NeoPixel(machine.Pin(STRIP_PIN), STRIP_LEDS)
|
||||
|
||||
SINGLE_PIN = 28
|
||||
single = neopixel.NeoPixel(machine.Pin(SINGLE_PIN), 1)
|
||||
|
||||
# ============================================================
|
||||
# BUTTONS
|
||||
# ============================================================
|
||||
|
||||
button_pins = [20, 21, 22]
|
||||
buttons = [machine.Pin(pin, machine.Pin.IN, machine.Pin.PULL_UP) for pin in button_pins]
|
||||
|
||||
# ============================================================
|
||||
# COLOR PALETTE
|
||||
# ============================================================
|
||||
|
||||
colors = [
|
||||
(0, 0, 0), # Off
|
||||
(255, 0, 0), # Red
|
||||
(0, 255, 0), # Green
|
||||
(0, 0, 255), # Blue
|
||||
# (255, 255, 0), # Yellow
|
||||
# (0, 255, 255), # Cyan
|
||||
# (255, 0, 255), # Magenta
|
||||
# (255, 128, 0), # Orange
|
||||
# (128, 0, 255), # Purple
|
||||
# (255, 20, 147), # Pink
|
||||
# (128, 255, 0), # Lime
|
||||
(255, 255, 255), # White
|
||||
]
|
||||
|
||||
# ============================================================
|
||||
# STRIP STATE
|
||||
# ============================================================
|
||||
|
||||
color_index = [0, 0, 0]
|
||||
brightness = [50, 50, 50]
|
||||
last_state = [1, 1, 1]
|
||||
press_time = [0, 0, 0]
|
||||
|
||||
# ============================================================
|
||||
# HEARTBEAT CONFIG (NOW HAS MIN + MAX)
|
||||
# ============================================================
|
||||
|
||||
FLASH_COLOR_INDEX = 1 # Palette color
|
||||
FLASH_MIN = 1 # << lowest brightness (0–100)
|
||||
FLASH_MAX = 10 # << highest brightness
|
||||
FLASH_STEP = 0.2 # fade speed
|
||||
FLASH_INTERVAL_MS = 20 # smoothness
|
||||
|
||||
heartbeat = HeartbeatFadeNeoPixel(
|
||||
neopixel_obj=single,
|
||||
pixel_index=0,
|
||||
color=colors[FLASH_COLOR_INDEX],
|
||||
min_brightness=FLASH_MIN,
|
||||
max_brightness=FLASH_MAX,
|
||||
step=FLASH_STEP,
|
||||
interval_ms=FLASH_INTERVAL_MS
|
||||
)
|
||||
|
||||
# ============================================================
|
||||
# HELPERS
|
||||
# ============================================================
|
||||
|
||||
def apply_brightness(color, level):
|
||||
r, g, b = color
|
||||
factor = level / 100
|
||||
return (int(r * factor), int(g * factor), int(b * factor))
|
||||
|
||||
def update_strip():
|
||||
for i in range(STRIP_LEDS):
|
||||
strip[i] = apply_brightness(colors[color_index[i]], brightness[i])
|
||||
strip.write()
|
||||
|
||||
# ============================================================
|
||||
# INITIAL
|
||||
# ============================================================
|
||||
|
||||
update_strip()
|
||||
|
||||
# ============================================================
|
||||
# MAIN LOOP
|
||||
# ============================================================
|
||||
|
||||
while True:
|
||||
now = time.ticks_ms()
|
||||
|
||||
# BUTTON CONTROLLED PIXELS (0–2)
|
||||
for i in range(3):
|
||||
state = buttons[i].value()
|
||||
|
||||
if last_state[i] == 1 and state == 0:
|
||||
press_time[i] = now
|
||||
|
||||
if last_state[i] == 0 and state == 1:
|
||||
duration = time.ticks_diff(now, press_time[i])
|
||||
|
||||
if duration > 500:
|
||||
brightness[i] += 10
|
||||
if brightness[i] > 100:
|
||||
brightness[i] = 10
|
||||
else:
|
||||
color_index[i] = (color_index[i] + 1) % len(colors)
|
||||
|
||||
update_strip()
|
||||
time.sleep_ms(200)
|
||||
|
||||
last_state[i] = state
|
||||
|
||||
# HEARTBEAT ON GP28
|
||||
heartbeat.update()
|
||||
|
||||
time.sleep_ms(10)
|
||||
111
Maker_Pico_Box/main3.py
Normal file
111
Maker_Pico_Box/main3.py
Normal file
@@ -0,0 +1,111 @@
|
||||
import machine
|
||||
import time
|
||||
import neopixel
|
||||
|
||||
# ----------------------------
|
||||
# NeoPixel SETUP
|
||||
# ----------------------------
|
||||
|
||||
# Strip on GP17 (3 LEDs)
|
||||
STRIP_PIN = 17
|
||||
STRIP_LEDS = 3
|
||||
strip = neopixel.NeoPixel(machine.Pin(STRIP_PIN), STRIP_LEDS)
|
||||
|
||||
# Single Pixel on GP28 (1 LED)
|
||||
SINGLE_PIN = 28
|
||||
single = neopixel.NeoPixel(machine.Pin(SINGLE_PIN), 1)
|
||||
|
||||
# ----------------------------
|
||||
# Buttons on GP20, GP21, GP22
|
||||
# ----------------------------
|
||||
button_pins = [20, 21, 22]
|
||||
buttons = [machine.Pin(pin, machine.Pin.IN, machine.Pin.PULL_UP) for pin in button_pins]
|
||||
|
||||
# ----------------------------
|
||||
# 12-color palette
|
||||
# ----------------------------
|
||||
colors = [
|
||||
(0, 0, 0), # Off
|
||||
(255, 0, 0), # Red
|
||||
(0, 255, 0), # Green
|
||||
(0, 0, 255), # Blue
|
||||
(255, 255, 0), # Yellow
|
||||
(0, 255, 255), # Cyan
|
||||
(255, 0, 255), # Magenta
|
||||
(255, 128, 0), # Orange
|
||||
(128, 0, 255), # Purple
|
||||
(255, 20, 147), # Pink
|
||||
(128, 255, 0), # Lime
|
||||
(255, 255, 255), # White
|
||||
]
|
||||
|
||||
# State for strip pixels (0–2)
|
||||
color_index = [0, 0, 0]
|
||||
brightness = [5, 5, 5]
|
||||
last_state = [1, 1, 1]
|
||||
press_time = [0, 0, 0]
|
||||
|
||||
# State for single pixel (pixel #3)
|
||||
single_color_index = 1
|
||||
single_brightness = 5
|
||||
|
||||
# ----------------------------
|
||||
# Helpers
|
||||
# ----------------------------
|
||||
def apply_brightness(color, level):
|
||||
r, g, b = color
|
||||
factor = level / 100
|
||||
return (int(r * factor), int(g * factor), int(b * factor))
|
||||
|
||||
def update_strip():
|
||||
for i in range(STRIP_LEDS):
|
||||
strip[i] = apply_brightness(colors[color_index[i]], brightness[i])
|
||||
strip.write()
|
||||
|
||||
def update_single():
|
||||
single[0] = apply_brightness(colors[single_color_index], single_brightness)
|
||||
single.write()
|
||||
|
||||
# Initial setup
|
||||
update_strip()
|
||||
update_single()
|
||||
|
||||
# ----------------------------
|
||||
# Main loop
|
||||
# ----------------------------
|
||||
while True:
|
||||
now = time.ticks_ms()
|
||||
|
||||
# Handle buttons for pixels 0,1,2
|
||||
for i in range(3):
|
||||
state = buttons[i].value()
|
||||
|
||||
# Button pressed
|
||||
if last_state[i] == 1 and state == 0:
|
||||
press_time[i] = now
|
||||
|
||||
# Button released
|
||||
if last_state[i] == 0 and state == 1:
|
||||
duration = time.ticks_diff(now, press_time[i])
|
||||
|
||||
if duration > 500:
|
||||
# LONG PRESS → brightness
|
||||
brightness[i] += 10
|
||||
if brightness[i] > 100:
|
||||
brightness[i] = 10
|
||||
else:
|
||||
# SHORT PRESS → color change
|
||||
color_index[i] = (color_index[i] + 1) % len(colors)
|
||||
|
||||
update_strip()
|
||||
time.sleep_ms(200) # debounce
|
||||
|
||||
last_state[i] = state
|
||||
|
||||
# (Optional) Example automatic color change for pixel 3
|
||||
# single_color_index = (single_color_index + 1) % len(colors)
|
||||
# update_single()
|
||||
# time.sleep(0.5)
|
||||
|
||||
time.sleep_ms(10)
|
||||
|
||||
342
Maker_Pico_Box/main4.py
Normal file
342
Maker_Pico_Box/main4.py
Normal file
@@ -0,0 +1,342 @@
|
||||
import machine
|
||||
import time
|
||||
import neopixel
|
||||
|
||||
# ============================================================
|
||||
# NEOPIXELS
|
||||
# ============================================================
|
||||
|
||||
# 3-LED strip on GP17
|
||||
STRIP_PIN = 17
|
||||
STRIP_LEDS = 3
|
||||
strip = neopixel.NeoPixel(machine.Pin(STRIP_PIN), STRIP_LEDS)
|
||||
|
||||
# Single flashing LED on GP28
|
||||
SINGLE_PIN = 28
|
||||
single = neopixel.NeoPixel(machine.Pin(SINGLE_PIN), 1)
|
||||
|
||||
# ============================================================
|
||||
# BUTTONS (GP20, GP21, GP22)
|
||||
# ============================================================
|
||||
|
||||
button_pins = [20, 21, 22]
|
||||
buttons = [machine.Pin(pin, machine.Pin.IN, machine.Pin.PULL_UP) for pin in button_pins]
|
||||
|
||||
# ============================================================
|
||||
# COLOR PALETTE (index-based)
|
||||
# ============================================================
|
||||
|
||||
colors = [
|
||||
(0, 0, 0), # 0 Off
|
||||
(255, 0, 0), # 1 Red
|
||||
(0, 255, 0), # 2 Green
|
||||
(0, 0, 255), # 3 Blue
|
||||
(255, 255, 0), # 4 Yellow
|
||||
(0, 255, 255), # 5 Cyan
|
||||
(255, 0, 255), # 6 Magenta
|
||||
(255, 128, 0), # 7 Orange
|
||||
(128, 0, 255), # 8 Purple
|
||||
(255, 20, 147), # 9 Pink
|
||||
(128, 255, 0), # 10 Lime
|
||||
(255, 255, 255) # 11 White
|
||||
]
|
||||
|
||||
# ============================================================
|
||||
# STATE FOR PIXELS 0-2 (buttons)
|
||||
# ============================================================
|
||||
|
||||
color_index = [0, 0, 0] # Color for each strip LED
|
||||
brightness = [5, 5, 5] # Brightness 0–100%
|
||||
last_state = [1, 1, 1]
|
||||
press_time = [0, 0, 0]
|
||||
|
||||
# ============================================================
|
||||
# FLASHING SINGLE PIXEL (GP28)
|
||||
# ============================================================
|
||||
|
||||
FLASH_COLOR_INDEX = 1 # Choose color from palette (0–10)
|
||||
FLASH_BRIGHTNESS = 5 # 0–100%
|
||||
FLASH_INTERVAL_MS = 500 # Flash speed
|
||||
|
||||
flash_on = False
|
||||
last_flash_time = time.ticks_ms()
|
||||
|
||||
# ============================================================
|
||||
# HELPER FUNCTIONS
|
||||
# ============================================================
|
||||
|
||||
def apply_brightness(color, level):
|
||||
r, g, b = color
|
||||
factor = level / 100
|
||||
return (int(r * factor), int(g * factor), int(b * factor))
|
||||
|
||||
def update_strip():
|
||||
for i in range(STRIP_LEDS):
|
||||
strip[i] = apply_brightness(colors[color_index[i]], brightness[i])
|
||||
strip.write()
|
||||
|
||||
def single_on():
|
||||
c = apply_brightness(colors[FLASH_COLOR_INDEX], FLASH_BRIGHTNESS)
|
||||
single[0] = c
|
||||
single.write()
|
||||
|
||||
def single_off():
|
||||
single[0] = (0, 0, 0)
|
||||
single.write()
|
||||
|
||||
# ============================================================
|
||||
# INITIAL STATE
|
||||
# ============================================================
|
||||
|
||||
update_strip()
|
||||
single_off()
|
||||
|
||||
class HeartbeatNeoPixel:
|
||||
def __init__(self, neopixel_obj, pixel_index=0, color=(255, 0, 0), brightness=50, interval_ms=500):
|
||||
self.np = neopixel_obj
|
||||
self.index = pixel_index
|
||||
self.color = color
|
||||
self.brightness = brightness
|
||||
self.interval = interval_ms
|
||||
|
||||
self.state = False
|
||||
self.last_time = time.ticks_ms()
|
||||
|
||||
def apply_brightness(self, c):
|
||||
r, g, b = c
|
||||
factor = self.brightness / 100
|
||||
return (int(r * factor), int(g * factor), int(b * factor))
|
||||
|
||||
def update(self):
|
||||
now = time.ticks_ms()
|
||||
if time.ticks_diff(now, self.last_time) >= self.interval:
|
||||
self.state = not self.state
|
||||
self.last_time = now
|
||||
|
||||
if self.state:
|
||||
self.np[self.index] = self.apply_brightness(self.color)
|
||||
else:
|
||||
self.np[self.index] = (0, 0, 0)
|
||||
|
||||
self.np.write()
|
||||
|
||||
# heartbeat = HeartbeatNeoPixel(
|
||||
# neopixel_obj=single,
|
||||
# pixel_index=0,
|
||||
# color=colors[FLASH_COLOR_INDEX],
|
||||
# brightness=FLASH_BRIGHTNESS,
|
||||
# interval_ms=FLASH_INTERVAL_MS
|
||||
# )
|
||||
# ============================================================
|
||||
# MAIN LOOP
|
||||
# ============================================================
|
||||
|
||||
while True:
|
||||
now = time.ticks_ms()
|
||||
|
||||
# -----------------------------
|
||||
# BUTTONS → control pixels 0–2
|
||||
# -----------------------------
|
||||
heartbeat.update()
|
||||
|
||||
for i in range(3):
|
||||
state = buttons[i].value()
|
||||
|
||||
if last_state[i] == 1 and state == 0:
|
||||
press_time[i] = now
|
||||
|
||||
if last_state[i] == 0 and state == 1:
|
||||
duration = time.ticks_diff(now, press_time[i])
|
||||
|
||||
if duration > 500:
|
||||
# LONG PRESS → adjust brightness
|
||||
brightness[i] += 10
|
||||
if brightness[i] > 100:
|
||||
brightness[i] = 10
|
||||
else:
|
||||
# SHORT PRESS → next color
|
||||
color_indimport machine
|
||||
import time
|
||||
import neopixel
|
||||
|
||||
# ============================================================
|
||||
# HEARTBEAT CLASS (NeoPixel version)
|
||||
# ============================================================
|
||||
|
||||
class HeartbeatNeoPixel:
|
||||
def __init__(self, neopixel_obj, pixel_index=0, color=(255, 0, 0),
|
||||
brightness=50, interval_ms=500):
|
||||
self.np = neopixel_obj
|
||||
self.index = pixel_index
|
||||
self.color = color
|
||||
self.brightness = brightness
|
||||
self.interval = interval_ms
|
||||
|
||||
self.state = False
|
||||
self.last_time = time.ticks_ms()
|
||||
|
||||
def apply_brightness(self, c):
|
||||
r, g, b = c
|
||||
factor = self.brightness / 100
|
||||
return (int(r * factor), int(g * factor), int(b * factor))
|
||||
|
||||
def update(self):
|
||||
now = time.ticks_ms()
|
||||
if time.ticks_diff(now, self.last_time) >= self.interval:
|
||||
self.state = not self.state
|
||||
self.last_time = now
|
||||
|
||||
if self.state:
|
||||
self.np[self.index] = self.apply_brightness(self.color)
|
||||
else:
|
||||
self.np[self.index] = (0, 0, 0)
|
||||
|
||||
self.np.write()
|
||||
|
||||
|
||||
# ============================================================
|
||||
# NEOPIXEL SETUP
|
||||
# ============================================================
|
||||
|
||||
# 3-LED strip on GP17
|
||||
STRIP_PIN = 17
|
||||
STRIP_LEDS = 3
|
||||
strip = neopixel.NeoPixel(machine.Pin(STRIP_PIN), STRIP_LEDS)
|
||||
|
||||
# Single heartbeat pixel on GP28
|
||||
SINGLE_PIN = 28
|
||||
single = neopixel.NeoPixel(machine.Pin(SINGLE_PIN), 1)
|
||||
|
||||
# ============================================================
|
||||
# BUTTONS (GP20, GP21, GP22)
|
||||
# ============================================================
|
||||
|
||||
button_pins = [20, 21, 22]
|
||||
buttons = [machine.Pin(pin, machine.Pin.IN, machine.Pin.PULL_UP)
|
||||
for pin in button_pins]
|
||||
|
||||
# ============================================================
|
||||
# COLOR PALETTE
|
||||
# ============================================================
|
||||
|
||||
colors = [
|
||||
(255, 0, 0), # 0 Red
|
||||
(0, 255, 0), # 1 Green
|
||||
(0, 0, 255), # 2 Blue
|
||||
(255, 255, 0), # 3 Yellow
|
||||
(0, 255, 255), # 4 Cyan
|
||||
(255, 0, 255), # 5 Magenta
|
||||
(255, 128, 0), # 6 Orange
|
||||
(128, 0, 255), # 7 Purple
|
||||
(255, 20, 147), # 8 Pink
|
||||
(128, 255, 0), # 9 Lime
|
||||
(255, 255, 255) # 10 White
|
||||
]
|
||||
|
||||
# ============================================================
|
||||
# STATE FOR PIXELS 0–2 (BUTTON CONTROLLED)
|
||||
# ============================================================
|
||||
|
||||
color_index = [0, 0, 0]
|
||||
brightness = [50, 50, 50]
|
||||
last_state = [1, 1, 1]
|
||||
press_time = [0, 0, 0]
|
||||
|
||||
# ============================================================
|
||||
# HEARTBEAT PIXEL CONFIG
|
||||
# ============================================================
|
||||
|
||||
FLASH_COLOR_INDEX = 7 # choose from palette (0–10)
|
||||
FLASH_BRIGHTNESS = 100
|
||||
FLASH_INTERVAL_MS = 500 # speed of heartbeat blink
|
||||
|
||||
heartbeat = HeartbeatNeoPixel(
|
||||
neopixel_obj=single,
|
||||
pixel_index=0,
|
||||
color=colors[FLASH_COLOR_INDEX],
|
||||
brightness=FLASH_BRIGHTNESS,
|
||||
interval_ms=FLASH_INTERVAL_MS
|
||||
)
|
||||
|
||||
# ============================================================
|
||||
# HELPER FUNCTIONS
|
||||
# ============================================================
|
||||
|
||||
def apply_brightness(color, level):
|
||||
r, g, b = color
|
||||
factor = level / 100
|
||||
return (int(r * factor), int(g * factor), int(b * factor))
|
||||
|
||||
def update_strip():
|
||||
for i in range(STRIP_LEDS):
|
||||
strip[i] = apply_brightness(colors[color_index[i]], brightness[i])
|
||||
strip.write()
|
||||
|
||||
# ============================================================
|
||||
# INITIAL STATE
|
||||
# ============================================================
|
||||
|
||||
update_strip()
|
||||
|
||||
# ============================================================
|
||||
# MAIN LOOP
|
||||
# ============================================================
|
||||
|
||||
while True:
|
||||
now = time.ticks_ms()
|
||||
|
||||
# --------------------------------------------
|
||||
# BUTTON HANDLING FOR PIXELS 0, 1, 2
|
||||
# --------------------------------------------
|
||||
for i in range(3):
|
||||
state = buttons[i].value()
|
||||
|
||||
if last_state[i] == 1 and state == 0:
|
||||
press_time[i] = now
|
||||
|
||||
if last_state[i] == 0 and state == 1:
|
||||
duration = time.ticks_diff(now, press_time[i])
|
||||
|
||||
if duration > 500:
|
||||
# LONG PRESS → brightness
|
||||
brightness[i] += 10
|
||||
if brightness[i] > 100:
|
||||
brightness[i] = 10
|
||||
else:
|
||||
# SHORT PRESS → next color
|
||||
color_index[i] = (color_index[i] + 1) % len(colors)
|
||||
|
||||
update_strip()
|
||||
time.sleep_ms(200) # debounce
|
||||
|
||||
last_state[i] = state
|
||||
|
||||
# --------------------------------------------
|
||||
# HEARTBEAT PIXEL UPDATE (GP28)
|
||||
# --------------------------------------------
|
||||
heartbeat.update()
|
||||
|
||||
time.sleep_ms(10)
|
||||
|
||||
ex[i] = (color_index[i] + 1) % len(colors)
|
||||
|
||||
update_strip()
|
||||
time.sleep_ms(200) # debounce
|
||||
|
||||
last_state[i] = state
|
||||
|
||||
# -----------------------------
|
||||
# FLASHING SINGLE PIXEL (GP28)
|
||||
# -----------------------------
|
||||
# if time.ticks_diff(now, last_flash_time) >= FLASH_INTERVAL_MS:
|
||||
# flash_on = not flash_on
|
||||
# last_flash_time = now
|
||||
#
|
||||
# if flash_on:
|
||||
# single_on()
|
||||
# else:
|
||||
# single_off()
|
||||
|
||||
|
||||
time.sleep_ms(10)
|
||||
|
||||
174
Maker_Pico_Box/main5.py
Normal file
174
Maker_Pico_Box/main5.py
Normal file
@@ -0,0 +1,174 @@
|
||||
import machine
|
||||
import time
|
||||
import neopixel
|
||||
|
||||
# ============================================================
|
||||
# HEARTBEAT CLASS (smooth fading version)
|
||||
# ============================================================
|
||||
|
||||
class HeartbeatFadeNeoPixel:
|
||||
def __init__(self, neopixel_obj, pixel_index=0, color=(255, 0, 0),
|
||||
max_brightness=100, step=2, interval_ms=20):
|
||||
self.np = neopixel_obj
|
||||
self.index = pixel_index
|
||||
self.color = color
|
||||
self.max_brightness = max_brightness
|
||||
self.step = step # how fast brightness changes
|
||||
self.interval = interval_ms
|
||||
|
||||
self.brightness = 0
|
||||
self.direction = 1 # 1 = going up, -1 = going down
|
||||
self.last_time = time.ticks_ms()
|
||||
|
||||
def apply_brightness(self, c, level):
|
||||
r, g, b = c
|
||||
factor = level / 100
|
||||
return (int(r * factor), int(g * factor), int(b * factor))
|
||||
|
||||
def update(self):
|
||||
now = time.ticks_ms()
|
||||
if time.ticks_diff(now, self.last_time) >= self.interval:
|
||||
self.last_time = now
|
||||
|
||||
# Update brightness
|
||||
self.brightness += self.direction * self.step
|
||||
|
||||
# Bounce at limits
|
||||
if self.brightness >= self.max_brightness:
|
||||
self.brightness = self.max_brightness
|
||||
self.direction = -1
|
||||
|
||||
if self.brightness <= 0:
|
||||
self.brightness = 0
|
||||
self.direction = 1
|
||||
|
||||
# Apply brightness
|
||||
self.np[self.index] = self.apply_brightness(self.color, self.brightness)
|
||||
self.np.write()
|
||||
|
||||
|
||||
# ============================================================
|
||||
# NEOPIXEL SETUP
|
||||
# ============================================================
|
||||
|
||||
# 3-LED strip on GP17
|
||||
STRIP_PIN = 17
|
||||
STRIP_LEDS = 3
|
||||
strip = neopixel.NeoPixel(machine.Pin(STRIP_PIN), STRIP_LEDS)
|
||||
|
||||
# Single heartbeat pixel on GP28
|
||||
SINGLE_PIN = 28
|
||||
single = neopixel.NeoPixel(machine.Pin(SINGLE_PIN), 1)
|
||||
|
||||
# ============================================================
|
||||
# BUTTONS (GP20, GP21, GP22)
|
||||
# ============================================================
|
||||
|
||||
button_pins = [20, 21, 22]
|
||||
buttons = [machine.Pin(pin, machine.Pin.IN, machine.Pin.PULL_UP)
|
||||
for pin in button_pins]
|
||||
|
||||
# ============================================================
|
||||
# COLOR PALETTE
|
||||
# ============================================================
|
||||
|
||||
colors = [
|
||||
(0, 0, 0), # Off
|
||||
(255, 0, 0), # Red
|
||||
(0, 255, 0), # Green
|
||||
(0, 0, 255), # Blue
|
||||
(255, 255, 0), # Yellow
|
||||
(0, 255, 255), # Cyan
|
||||
(255, 0, 255), # Magenta
|
||||
(255, 128, 0), # Orange
|
||||
(128, 0, 255), # Purple
|
||||
(255, 20, 147), # Pink
|
||||
(128, 255, 0), # Lime
|
||||
(255, 255, 255), # White
|
||||
]
|
||||
|
||||
# ============================================================
|
||||
# STATE FOR PIXELS 0–2 (BUTTON-CONTROLLED)
|
||||
# ============================================================
|
||||
|
||||
color_index = [0, 0, 0]
|
||||
brightness = [50, 50, 50]
|
||||
last_state = [1, 1, 1]
|
||||
press_time = [0, 0, 0]
|
||||
|
||||
# ============================================================
|
||||
# HEARTBEAT PIXEL CONFIG (smooth fade)
|
||||
# ============================================================
|
||||
|
||||
FLASH_COLOR_INDEX = 1 # Choose from palette
|
||||
FLASH_MAX_BRIGHTNESS = 20 # Peak brightness for fade
|
||||
FLASH_STEP = 0.5 # Speed of fade
|
||||
FLASH_INTERVAL_MS = 30 # Lower = smoother animation
|
||||
|
||||
heartbeat = HeartbeatFadeNeoPixel(
|
||||
neopixel_obj=single,
|
||||
pixel_index=0,
|
||||
color=colors[FLASH_COLOR_INDEX],
|
||||
max_brightness=FLASH_MAX_BRIGHTNESS,
|
||||
step=FLASH_STEP,
|
||||
interval_ms=FLASH_INTERVAL_MS
|
||||
)
|
||||
|
||||
# ============================================================
|
||||
# HELPERS
|
||||
# ============================================================
|
||||
|
||||
def apply_brightness(color, level):
|
||||
r, g, b = color
|
||||
factor = level / 100
|
||||
return (int(r * factor), int(g * factor), int(b * factor))
|
||||
|
||||
def update_strip():
|
||||
for i in range(STRIP_LEDS):
|
||||
strip[i] = apply_brightness(colors[color_index[i]], brightness[i])
|
||||
strip.write()
|
||||
|
||||
# ============================================================
|
||||
# INITIAL STATE
|
||||
# ============================================================
|
||||
|
||||
update_strip()
|
||||
|
||||
# ============================================================
|
||||
# MAIN LOOP
|
||||
# ============================================================
|
||||
|
||||
while True:
|
||||
now = time.ticks_ms()
|
||||
|
||||
# --------------------------------
|
||||
# BUTTON HANDLING FOR 3-LED STRIP
|
||||
# --------------------------------
|
||||
for i in range(3):
|
||||
state = buttons[i].value()
|
||||
|
||||
if last_state[i] == 1 and state == 0:
|
||||
press_time[i] = now
|
||||
|
||||
if last_state[i] == 0 and state == 1:
|
||||
duration = time.ticks_diff(now, press_time[i])
|
||||
|
||||
if duration > 500:
|
||||
brightness[i] += 10
|
||||
if brightness[i] > 100:
|
||||
brightness[i] = 10
|
||||
else:
|
||||
color_index[i] = (color_index[i] + 1) % len(colors)
|
||||
|
||||
update_strip()
|
||||
time.sleep_ms(200)
|
||||
|
||||
last_state[i] = state
|
||||
|
||||
# ----------------------------
|
||||
# SMOOTH HEARTBEAT UPDATE
|
||||
# ----------------------------
|
||||
heartbeat.update()
|
||||
|
||||
time.sleep_ms(10)
|
||||
|
||||
46
Maker_Pico_Box/main_old.py
Normal file
46
Maker_Pico_Box/main_old.py
Normal file
@@ -0,0 +1,46 @@
|
||||
import machine
|
||||
import neopixel
|
||||
import time
|
||||
|
||||
# NeoPixel setup
|
||||
NUM_LEDS = 8
|
||||
np = neopixel.NeoPixel(machine.Pin(17), NUM_LEDS) # 28
|
||||
|
||||
# Button on GP22 (active LOW)
|
||||
button = machine.Pin(22, machine.Pin.IN, machine.Pin.PULL_UP)
|
||||
|
||||
# Color list (R, G, B)
|
||||
colors = [
|
||||
(255, 0, 0), # Red
|
||||
(0, 255, 0), # Green
|
||||
(0, 0, 255), # Blue
|
||||
(255, 255, 0), # Yellow
|
||||
(255, 0, 255), # Magenta
|
||||
(0, 255, 255), # Cyan
|
||||
(255, 255, 255) # White
|
||||
]
|
||||
|
||||
current_color = 0
|
||||
last_state = 1 # button not pressed
|
||||
|
||||
def show_color(color):
|
||||
for i in range(NUM_LEDS):
|
||||
np[i] = color
|
||||
np.write()
|
||||
|
||||
# Initial color
|
||||
show_color(colors[current_color])
|
||||
|
||||
while True:
|
||||
state = button.value()
|
||||
|
||||
# Detect button press (HIGH → LOW)
|
||||
if last_state == 1 and state == 0:
|
||||
current_color = (current_color + 1) % len(colors)
|
||||
show_color(colors[current_color])
|
||||
time.sleep_ms(200) # Debounce delay
|
||||
|
||||
last_state = state
|
||||
time.sleep_ms(10)
|
||||
|
||||
|
||||
29
Maker_Pico_Box/rainbow.py
Normal file
29
Maker_Pico_Box/rainbow.py
Normal file
@@ -0,0 +1,29 @@
|
||||
import machine
|
||||
import neopixel
|
||||
import time
|
||||
|
||||
# GP28 pin and number of LEDs
|
||||
pin = 28
|
||||
num_leds = 8 # change to how many LEDs you have
|
||||
|
||||
np = neopixel.NeoPixel(machine.Pin(pin), num_leds)
|
||||
|
||||
# Simple color helper
|
||||
def set_color(r, g, b):
|
||||
for i in range(num_leds):
|
||||
np[i] = (r, g, b)
|
||||
np.write()
|
||||
|
||||
# Demo loop
|
||||
while True:
|
||||
set_color(255, 0, 0) # Red
|
||||
time.sleep(1)
|
||||
|
||||
set_color(0, 255, 0) # Green
|
||||
time.sleep(1)
|
||||
|
||||
set_color(0, 0, 255) # Blue
|
||||
time.sleep(1)
|
||||
|
||||
set_color(0, 0, 0) # Off
|
||||
time.sleep(1)
|
||||
107
calibrate.py
Normal file
107
calibrate.py
Normal file
@@ -0,0 +1,107 @@
|
||||
import time
|
||||
import ntptime
|
||||
import json
|
||||
import os
|
||||
from hx710b import HX710B # Correct driver
|
||||
from config import *
|
||||
|
||||
CALIB_FILE = "calibration.cfg"
|
||||
|
||||
tz_offset = 0
|
||||
utc = time.localtime()
|
||||
local = time.localtime(time.mktime(utc) + tz_offset * 3600)
|
||||
ctime = f"{local[0]:02}-{local[1]:02}-{local[2]:02} {local[3]:02}:{local[4]:02}:{local[5]:02}"
|
||||
#print("Time =", local)
|
||||
print("Time =", ctime)
|
||||
|
||||
# ---------------------------------------------------------
|
||||
# USER INPUT
|
||||
# ---------------------------------------------------------
|
||||
# Water depth (meters)
|
||||
# KNOWN_DEPTH_M = 1.680
|
||||
|
||||
# Pressure created by water column (kPa)
|
||||
KNOWN_PRESSURE_KPA = KNOWN_DEPTH_M * 9.81
|
||||
|
||||
print(
|
||||
"\nUsing depth {:.3f} m → expected pressure {:.3f} kPa".format(
|
||||
KNOWN_DEPTH_M, KNOWN_PRESSURE_KPA
|
||||
)
|
||||
)
|
||||
|
||||
# ---------------------------------------------------------
|
||||
# SAVE CALIBRATION
|
||||
# ---------------------------------------------------------
|
||||
def save_calibration(ctime, zero_offset, raw_at_pressure, scale_factor):
|
||||
data = {
|
||||
"zero_offset": zero_offset,
|
||||
"raw_at_pressure": raw_at_pressure,
|
||||
"scale_factor": scale_factor,
|
||||
"Date_Time": ctime
|
||||
}
|
||||
|
||||
with open(CALIB_FILE, "w") as f:
|
||||
json.dump(data, f)
|
||||
|
||||
print("\n✔ Saved calibration:")
|
||||
print(" Date_Time =", ctime)
|
||||
print(" zero_offset =", zero_offset)
|
||||
print(" raw_at_pressure =", raw_at_pressure)
|
||||
print(" scale_factor =", scale_factor)
|
||||
|
||||
|
||||
# ---------------------------------------------------------
|
||||
# RAW AVERAGING
|
||||
# ---------------------------------------------------------
|
||||
def read_average(hx, samples=30):
|
||||
total = 0
|
||||
for _ in range(samples):
|
||||
total += hx.read_raw()
|
||||
time.sleep_ms(5)
|
||||
return total / samples
|
||||
|
||||
|
||||
# ---------------------------------------------------------
|
||||
# CALIBRATION PROCESS
|
||||
# ---------------------------------------------------------
|
||||
print("\n=== HX710B Depth-Based Calibration ===")
|
||||
|
||||
hx = HX710B(dout_pin=0, sck_pin=1)
|
||||
|
||||
# Step 1 — ZERO PRESSURE
|
||||
print("\n➡ Ensure tank is OPEN TO AIR (0 kPa)...")
|
||||
time.sleep(3)
|
||||
|
||||
zero_offset = read_average(hx)
|
||||
print("Zero offset captured:", zero_offset)
|
||||
|
||||
# Step 2 — APPLY KNOWN PRESSURE
|
||||
print(
|
||||
"\n➡ Now apply EXACTLY {:.3f} kPa (depth {:.3f} m)...".format(
|
||||
KNOWN_PRESSURE_KPA, KNOWN_DEPTH_M
|
||||
)
|
||||
)
|
||||
time.sleep(10)
|
||||
|
||||
raw_at_pressure = read_average(hx)
|
||||
print("Raw at known depth:", raw_at_pressure)
|
||||
|
||||
# Compute scale factor
|
||||
scale_factor = KNOWN_PRESSURE_KPA / (raw_at_pressure - zero_offset)
|
||||
|
||||
# Save calibration (INCLUDING raw value)
|
||||
save_calibration(ctime, zero_offset, raw_at_pressure, scale_factor)
|
||||
|
||||
print("\n=== Calibration Complete ===")
|
||||
|
||||
# ---------------------------------------------------------
|
||||
# VERIFY
|
||||
# ---------------------------------------------------------
|
||||
hx.zero_offset = zero_offset
|
||||
hx.scale_factor = scale_factor
|
||||
|
||||
test = hx.read_kpa()
|
||||
|
||||
print("\nVerification reading:", test, "kPa")
|
||||
print("Expected:", KNOWN_PRESSURE_KPA, "kPa")
|
||||
print("\nDone.\n")
|
||||
1
calibration.cfg
Normal file
1
calibration.cfg
Normal file
@@ -0,0 +1 @@
|
||||
{"Date_Time": "2026-01-18 15:17:40", "zero_offset": -2477692.0, "raw_at_pressure": 7415596.0, "scale_factor": 1.427877e-06}
|
||||
21
config.py
Normal file
21
config.py
Normal file
@@ -0,0 +1,21 @@
|
||||
SSID = "WORKSHOP"
|
||||
PASSWORD = "hellothere"
|
||||
HOSTNAME = "TANK-MONITOR"
|
||||
DISPLAY_ON = False
|
||||
HEARTBEAT = True
|
||||
AVERAGE = True
|
||||
VERSION = "20260123B"
|
||||
CPU_FREQ = 250_000_000
|
||||
TZ_OFFSET = 2
|
||||
|
||||
MAX_CONNECTIONS = 5
|
||||
|
||||
MIN_PRESSURE_KPA = 0.0
|
||||
#MAX_PRESSURE_KPA = 13.72
|
||||
#MAX_PRESSURE_KPA = 50.13
|
||||
MAX_DEPTH_M = 1.44
|
||||
|
||||
#KNOWN_DEPTH_M = 1.680
|
||||
KNOWN_DEPTH_M = MAX_DEPTH_M
|
||||
MAX_PRESSURE_KPA = KNOWN_DEPTH_M * 9.81
|
||||
TANK_CAPACITY_L = 1800.00
|
||||
53
display.py
Normal file
53
display.py
Normal file
@@ -0,0 +1,53 @@
|
||||
from machine import I2C, Pin
|
||||
import ssd1306
|
||||
import framebuf
|
||||
|
||||
class OLED:
|
||||
def __init__(self):
|
||||
i2c = I2C(1, scl=Pin(3), sda=Pin(2), freq=400_000)
|
||||
|
||||
self.width = 128
|
||||
self.height = 64
|
||||
|
||||
# Real OLED
|
||||
self.oled = ssd1306.SSD1306_I2C(self.width, self.height, i2c)
|
||||
|
||||
# Off-screen buffer (normal orientation)
|
||||
self.buf = bytearray(self.width * self.height // 8)
|
||||
self.fb = framebuf.FrameBuffer(self.buf, self.width, self.height, framebuf.MONO_VLSB)
|
||||
|
||||
# --------------------------------------------------
|
||||
# Rotate framebuffer 180°
|
||||
# --------------------------------------------------
|
||||
def rotate_180(self):
|
||||
self.oled.fill(0)
|
||||
|
||||
for y in range(self.height):
|
||||
for x in range(self.width):
|
||||
if self.fb.pixel(x, y):
|
||||
self.oled.pixel(
|
||||
self.width - 1 - x,
|
||||
self.height - 1 - y,
|
||||
1
|
||||
)
|
||||
|
||||
# --------------------------------------------------
|
||||
# Display content
|
||||
# --------------------------------------------------
|
||||
def show(self, rtc, pressure, percent, cpu_temp, rssi_percent, depth_mm):
|
||||
dt = rtc.datetime()
|
||||
ctime = f"{dt[4]:02}:{dt[5]:02}:{dt[6]:02}"
|
||||
|
||||
# Draw NORMAL orientation into framebuffer
|
||||
self.fb.fill(0)
|
||||
|
||||
self.fb.text(ctime, 30, 0)
|
||||
self.fb.text(f"P: {pressure:.2f} kPa", 0, 10)
|
||||
self.fb.text(f"D: {depth_mm:.0f} mm", 0, 20)
|
||||
self.fb.text(f"L: {percent:.2f} %", 0, 30)
|
||||
self.fb.text(f"CPU: {cpu_temp:.1f} C", 0, 40)
|
||||
self.fb.text(f"WiFi: {rssi_percent}%", 0, 50)
|
||||
|
||||
# Rotate and display
|
||||
self.rotate_180()
|
||||
self.oled.show()
|
||||
153
html/app.js
Normal file
153
html/app.js
Normal file
@@ -0,0 +1,153 @@
|
||||
// =========================================================
|
||||
// Tank Monitor - app.js
|
||||
// =========================================================
|
||||
|
||||
const TANK_FULL_PASSWORD = "1234"; // 🔐 CHANGE THIS
|
||||
|
||||
// ===============================
|
||||
// Fetch sensor data and update UI
|
||||
// ===============================
|
||||
async function fetchSensorData() {
|
||||
try {
|
||||
const response = await fetch("/sensor");
|
||||
const data = await response.json();
|
||||
|
||||
// Level
|
||||
const level_mm = data.pressure / 9.810;
|
||||
document.getElementById("level").innerText =
|
||||
level_mm.toFixed(3) + " m";
|
||||
|
||||
|
||||
// Pressure
|
||||
document.getElementById("pressure").innerText =
|
||||
data.pressure.toFixed(3) + " Kpa";
|
||||
|
||||
// Tank % + Liters (robust)
|
||||
const pct = data.tank_percent;
|
||||
const capacity = Number(TANK_CAPACITY_L);
|
||||
|
||||
const percentEl = document.getElementById("percent");
|
||||
|
||||
if (!isNaN(capacity) && capacity > 0) {
|
||||
const liters = (pct / 100) * capacity;
|
||||
percentEl.innerText =
|
||||
`${pct.toFixed(1)} % [${liters.toFixed(0)} L]`;
|
||||
} else {
|
||||
percentEl.innerText =
|
||||
`${pct.toFixed(1)} %`;
|
||||
}
|
||||
|
||||
// CPU temp
|
||||
document.getElementById("cpu").innerText =
|
||||
data.cpu_temp.toFixed(1) + " °C";
|
||||
|
||||
// RSSI: dBm → %
|
||||
let rssiPercent = (data.rssi + 100) * 2;
|
||||
rssiPercent = Math.max(0, Math.min(100, rssiPercent));
|
||||
document.getElementById("rssi").innerText =
|
||||
rssiPercent.toFixed(0) + " %";
|
||||
|
||||
// Free memory → KB
|
||||
document.getElementById("mem").innerText =
|
||||
(data.mem / 1024).toFixed(1) + " KB";
|
||||
|
||||
// Uptime
|
||||
document.getElementById("uptime").innerText =
|
||||
data.uptime;
|
||||
|
||||
updateTankBar(pct);
|
||||
|
||||
// Change footer on html page
|
||||
// data.footer = "Powered by Raspberry Pi Pico W & HX710B - Test"
|
||||
document.getElementById("footer-text").innerText = data.footer;
|
||||
|
||||
|
||||
} catch (err) {
|
||||
console.log("Sensor fetch error:", err);
|
||||
}
|
||||
}
|
||||
|
||||
// ===============================
|
||||
// Tank animation + color scaling
|
||||
// ===============================
|
||||
function updateTankBar(percent) {
|
||||
const fill = document.getElementById("tank-fill");
|
||||
|
||||
percent = Math.max(0, Math.min(100, percent));
|
||||
fill.style.height = percent + "%";
|
||||
fill.style.backgroundColor = tankColor(percent);
|
||||
}
|
||||
|
||||
function tankColor(p) {
|
||||
let r, g, b;
|
||||
|
||||
if (p <= 50) {
|
||||
const t = p / 50;
|
||||
r = 211 + (251 - 211) * t;
|
||||
g = 50 + (192 - 50) * t;
|
||||
b = 47 + (45 - 47) * t;
|
||||
} else {
|
||||
const t = (p - 50) / 50;
|
||||
r = 251 + (76 - 251) * t;
|
||||
g = 192 + (175 - 192) * t;
|
||||
b = 45 + (80 - 45) * t;
|
||||
}
|
||||
|
||||
return `rgb(${Math.round(r)}, ${Math.round(g)}, ${Math.round(b)})`;
|
||||
}
|
||||
|
||||
// ===============================
|
||||
// Date & Time
|
||||
// ===============================
|
||||
function updateClock() {
|
||||
const now = new Date();
|
||||
|
||||
document.getElementById("date").innerText =
|
||||
`${now.getFullYear()}-${String(now.getMonth()+1).padStart(2,"0")}-${String(now.getDate()).padStart(2,"0")}`;
|
||||
|
||||
document.getElementById("time").innerText =
|
||||
`${String(now.getHours()).padStart(2,"0")}:${String(now.getMinutes()).padStart(2,"0")}:${String(now.getSeconds()).padStart(2,"0")}`;
|
||||
}
|
||||
|
||||
// ===============================
|
||||
// Theme toggle
|
||||
// ===============================
|
||||
const modeSwitch = document.getElementById("modeSwitch");
|
||||
|
||||
if (localStorage.getItem("theme") === "dark") {
|
||||
document.body.classList.add("dark");
|
||||
if (modeSwitch) modeSwitch.checked = true;
|
||||
}
|
||||
|
||||
if (modeSwitch) {
|
||||
modeSwitch.addEventListener("change", () => {
|
||||
document.body.classList.toggle("dark", modeSwitch.checked);
|
||||
localStorage.setItem("theme", modeSwitch.checked ? "dark" : "light");
|
||||
});
|
||||
}
|
||||
|
||||
// ===============================
|
||||
// Password-protected Tank Full
|
||||
// ===============================
|
||||
function tankFullProtected() {
|
||||
const entered = prompt("Enter password to set Tank FULL:");
|
||||
if (entered !== TANK_FULL_PASSWORD) {
|
||||
alert("❌ Incorrect password");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!confirm("Confirm: Set tank to FULL?")) return;
|
||||
|
||||
fetch("/tare");
|
||||
}
|
||||
|
||||
window.tankFullProtected = tankFullProtected;
|
||||
|
||||
// ===============================
|
||||
// Start
|
||||
// ===============================
|
||||
setInterval(fetchSensorData, 1000);
|
||||
setInterval(updateClock, 1000);
|
||||
|
||||
fetchSensorData();
|
||||
updateClock();
|
||||
BIN
html/favicon.ico
Normal file
BIN
html/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
287
html/index.html
Normal file
287
html/index.html
Normal file
@@ -0,0 +1,287 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Tank Monitor</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
|
||||
<style>
|
||||
|
||||
/* ======================= */
|
||||
/* THEME VARIABLES */
|
||||
/* ======================= */
|
||||
:root {
|
||||
--bg: #f4f6f8;
|
||||
--text: #000;
|
||||
--card-bg: #ffffff;
|
||||
--accent: #1976d2;
|
||||
--tank-bg: #e0e0e0;
|
||||
--wave: rgba(255,255,255,0.35);
|
||||
--footer: #555;
|
||||
}
|
||||
|
||||
body.dark {
|
||||
--bg: #121212;
|
||||
--text: #e0e0e0;
|
||||
--card-bg: #1f1f1f;
|
||||
--accent: #1e88e5;
|
||||
--tank-bg: #333;
|
||||
--wave: rgba(255,255,255,0.25);
|
||||
--footer: #888;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
transition: background 0.3s, color 0.3s;
|
||||
}
|
||||
|
||||
/* ======================= */
|
||||
/* HEADER + THEME SWITCH */
|
||||
/* ======================= */
|
||||
.header {
|
||||
background: var(--accent);
|
||||
color: white;
|
||||
padding: 15px;
|
||||
text-align: center;
|
||||
font-size: 22px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.theme-toggle {
|
||||
position: absolute;
|
||||
right: 20px;
|
||||
top: 15px;
|
||||
}
|
||||
|
||||
.theme-toggle input {
|
||||
appearance: none;
|
||||
width: 50px;
|
||||
height: 25px;
|
||||
background: #555;
|
||||
border-radius: 50px;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
transition: background 0.3s;
|
||||
}
|
||||
|
||||
.theme-toggle input:checked {
|
||||
background: #90caf9;
|
||||
}
|
||||
|
||||
.theme-toggle input::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: 21px;
|
||||
height: 21px;
|
||||
background: white;
|
||||
border-radius: 50%;
|
||||
top: 2px;
|
||||
left: 2px;
|
||||
transition: transform 0.3s;
|
||||
}
|
||||
|
||||
.theme-toggle input:checked::before {
|
||||
transform: translateX(25px);
|
||||
}
|
||||
|
||||
/* ======================= */
|
||||
/* CONTAINERS + CARDS */
|
||||
/* ======================= */
|
||||
.container {
|
||||
width: 95%;
|
||||
max-width: 900px;
|
||||
margin: auto;
|
||||
padding-top: 20px;
|
||||
}
|
||||
|
||||
.card {
|
||||
background: var(--card-bg);
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.25);
|
||||
transition: background 0.3s;
|
||||
}
|
||||
|
||||
.card h3 {
|
||||
margin-top: 0;
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.stats-block p {
|
||||
margin: 6px 0;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.tank-flex-wrapper {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 30px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
/* ======================= */
|
||||
/* VERTICAL TANK WAVE */
|
||||
/* ======================= */
|
||||
.tank-container {
|
||||
width: min(100vw, 150px);
|
||||
height: min(75vh, 250px);
|
||||
background: var(--tank-bg);
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
box-shadow: inset 0 0 10px rgba(0,0,0,0.6);
|
||||
transition: width 0.3s, height 0.3s;
|
||||
}
|
||||
|
||||
#tank-fill {
|
||||
width: 100%;
|
||||
height: 0%;
|
||||
background: #4caf50;
|
||||
position: relative;
|
||||
transition: height 1s linear, background-color 1s linear;
|
||||
}
|
||||
|
||||
#tank-fill::before,
|
||||
#tank-fill::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
width: 200%;
|
||||
height: 20px;
|
||||
background: var(--wave);
|
||||
border-radius: 50%;
|
||||
animation: wave 3s infinite linear;
|
||||
}
|
||||
|
||||
#tank-fill::before { top: -5px; }
|
||||
#tank-fill::after {
|
||||
top: -12px;
|
||||
opacity: 0.5;
|
||||
animation-duration: 5s;
|
||||
}
|
||||
|
||||
@keyframes wave {
|
||||
from { transform: translateX(-50%); }
|
||||
to { transform: translateX(0%); }
|
||||
}
|
||||
|
||||
/* ======================= */
|
||||
/* BUTTONS */
|
||||
/* ======================= */
|
||||
button {
|
||||
padding: 12px 20px;
|
||||
background: #e53935;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-size: 16px;
|
||||
cursor: pointer;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background: #ff5252;
|
||||
}
|
||||
|
||||
.button-row {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.button-row button {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.button-row {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
/* ======================= */
|
||||
/* FOOTER */
|
||||
/* ======================= */
|
||||
.footer {
|
||||
text-align: center;
|
||||
margin-top: 30px;
|
||||
color: var(--footer);
|
||||
font-size: 12px;
|
||||
padding-bottom: 30px;
|
||||
}
|
||||
|
||||
/* Mobile tank adjustment */
|
||||
@media (max-width: 600px) {
|
||||
.tank-container {
|
||||
width: 65px;
|
||||
height: 25vh;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<script src="/app.js" defer></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div class="header">
|
||||
Tank Monitor
|
||||
<label class="theme-toggle">
|
||||
<input id="modeSwitch" type="checkbox">
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
|
||||
<!-- System Stats + Tank -->
|
||||
<div class="card">
|
||||
<h3>System Stats</h3>
|
||||
|
||||
<div class="tank-flex-wrapper">
|
||||
|
||||
<div class="stats-block">
|
||||
<p><b>Date:</b> <span id="date">--</span></p>
|
||||
<p><b>Time:</b> <span id="time">--</span></p>
|
||||
<p><b>Uptime:</b> <span id="uptime">--</span></p>
|
||||
<p><b>Level:</b> <span id="level">--</span></p>
|
||||
<p><b>Pressure:</b> <span id="pressure">--</span></p>
|
||||
<p><b>Tank Level:</b> <span id="percent">--</span></p>
|
||||
<p><b>CPU Temp:</b> <span id="cpu">--</span></p>
|
||||
<p><b>WiFi RSSI:</b> <span id="rssi">--</span></p>
|
||||
<p><b>Free Memory:</b> <span id="mem">--</span></p>
|
||||
</div>
|
||||
|
||||
<div class="tank-container">
|
||||
<div id="tank-fill"></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Actions -->
|
||||
<div class="card">
|
||||
<h3>Actions</h3>
|
||||
|
||||
<div class="button-row">
|
||||
<!-- <button onclick="fetch('/tare')">Tank Full</button> -->
|
||||
<button onclick="tankFullProtected()">Tank Full</button>
|
||||
<button onclick="fetch('/calibrate')">Calibrate</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="footer" id="footer-text">
|
||||
Powered by Raspberry Pi Pico W & HX710B
|
||||
</div>
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
116
hx710b.py
Normal file
116
hx710b.py
Normal file
@@ -0,0 +1,116 @@
|
||||
from machine import Pin
|
||||
import time
|
||||
import json
|
||||
import os
|
||||
|
||||
CALIB_FILE = "calibration.cfg"
|
||||
|
||||
|
||||
# ---------------------------------------------------------
|
||||
# Calibration File Helpers
|
||||
# ---------------------------------------------------------
|
||||
|
||||
def save_calibration(zero_offset, scale_factor):
|
||||
"""Save calibration values to internal flash."""
|
||||
data = {
|
||||
"zero_offset": zero_offset,
|
||||
"scale_factor": scale_factor
|
||||
}
|
||||
with open(CALIB_FILE, "w") as f:
|
||||
json.dump(data, f)
|
||||
print("✔ Calibration saved.")
|
||||
|
||||
|
||||
def load_calibration():
|
||||
"""Load existing calibration or return defaults."""
|
||||
if CALIB_FILE not in os.listdir():
|
||||
print("⚠ No calibration file found, using defaults.")
|
||||
return 0, 1
|
||||
|
||||
try:
|
||||
with open(CALIB_FILE, "r") as f:
|
||||
data = json.load(f)
|
||||
print("✔ Calibration loaded.")
|
||||
return data.get("zero_offset", 0), data.get("scale_factor", 1)
|
||||
except Exception as e:
|
||||
print("⚠ Calibration load error:", e)
|
||||
return 0, 1
|
||||
|
||||
|
||||
# ---------------------------------------------------------
|
||||
# HX710B Class
|
||||
# ---------------------------------------------------------
|
||||
|
||||
class HX710B:
|
||||
def __init__(self, dout_pin, sck_pin):
|
||||
self.dout = Pin(dout_pin, Pin.IN)
|
||||
self.sck = Pin(sck_pin, Pin.OUT)
|
||||
self.sck.value(0)
|
||||
|
||||
# Load previous calibration (or defaults)
|
||||
self.zero_offset, self.scale_factor = load_calibration()
|
||||
|
||||
# -----------------------------------------------------
|
||||
# Low-level raw ADC read
|
||||
# -----------------------------------------------------
|
||||
def read_raw(self):
|
||||
"""Read the 24-bit raw ADC value."""
|
||||
while self.dout.value():
|
||||
pass # wait for data ready
|
||||
|
||||
count = 0
|
||||
for _ in range(24):
|
||||
self.sck.value(1)
|
||||
count = (count << 1) | self.dout.value()
|
||||
self.sck.value(0)
|
||||
|
||||
# 25th pulse to set gain for next reading
|
||||
self.sck.value(1)
|
||||
self.sck.value(0)
|
||||
|
||||
# Signed 24-bit conversion
|
||||
if count & 0x800000:
|
||||
count -= 0x1000000
|
||||
|
||||
return count
|
||||
|
||||
# -----------------------------------------------------
|
||||
# Zero Calibration
|
||||
# -----------------------------------------------------
|
||||
def calibrate_zero(self, samples=20):
|
||||
"""Measure 0 kPa and determine zero offset."""
|
||||
total = 0
|
||||
for _ in range(samples):
|
||||
total += self.read_raw()
|
||||
time.sleep_ms(5)
|
||||
|
||||
self.zero_offset = total / samples
|
||||
save_calibration(self.zero_offset, self.scale_factor)
|
||||
|
||||
print("✔ Zero calibrated:", self.zero_offset)
|
||||
return self.zero_offset
|
||||
|
||||
# -----------------------------------------------------
|
||||
# Full-scale / Known Pressure Calibration
|
||||
# -----------------------------------------------------
|
||||
def calibrate_full(self, known_kpa, samples=20):
|
||||
"""Measure raw ADC at known pressure and compute scaling."""
|
||||
total = 0
|
||||
for _ in range(samples):
|
||||
total += self.read_raw()
|
||||
time.sleep_ms(5)
|
||||
|
||||
raw_avg = total / samples
|
||||
self.scale_factor = known_kpa / (raw_avg - self.zero_offset)
|
||||
|
||||
save_calibration(self.zero_offset, self.scale_factor)
|
||||
|
||||
print("✔ Scale calibrated:", self.scale_factor)
|
||||
return self.scale_factor
|
||||
|
||||
# -----------------------------------------------------
|
||||
# Convert raw → kPa using saved calibration
|
||||
# -----------------------------------------------------
|
||||
def read_kpa(self):
|
||||
raw = self.read_raw()
|
||||
return (raw - self.zero_offset) * self.scale_factor
|
||||
116
lib/hx710b.py
Normal file
116
lib/hx710b.py
Normal file
@@ -0,0 +1,116 @@
|
||||
from machine import Pin
|
||||
import time
|
||||
import json
|
||||
import os
|
||||
|
||||
CALIB_FILE = "calibration.cfg"
|
||||
|
||||
|
||||
# ---------------------------------------------------------
|
||||
# Calibration File Helpers
|
||||
# ---------------------------------------------------------
|
||||
|
||||
def save_calibration(zero_offset, scale_factor):
|
||||
"""Save calibration values to internal flash."""
|
||||
data = {
|
||||
"zero_offset": zero_offset,
|
||||
"scale_factor": scale_factor
|
||||
}
|
||||
with open(CALIB_FILE, "w") as f:
|
||||
json.dump(data, f)
|
||||
print("✔ Calibration saved.")
|
||||
|
||||
|
||||
def load_calibration():
|
||||
"""Load existing calibration or return defaults."""
|
||||
if CALIB_FILE not in os.listdir():
|
||||
print("⚠ No calibration file found, using defaults.")
|
||||
return 0, 1
|
||||
|
||||
try:
|
||||
with open(CALIB_FILE, "r") as f:
|
||||
data = json.load(f)
|
||||
print("✔ Calibration loaded.")
|
||||
return data.get("zero_offset", 0), data.get("scale_factor", 1)
|
||||
except Exception as e:
|
||||
print("⚠ Calibration load error:", e)
|
||||
return 0, 1
|
||||
|
||||
|
||||
# ---------------------------------------------------------
|
||||
# HX710B Class
|
||||
# ---------------------------------------------------------
|
||||
|
||||
class HX710B:
|
||||
def __init__(self, dout_pin, sck_pin):
|
||||
self.dout = Pin(dout_pin, Pin.IN)
|
||||
self.sck = Pin(sck_pin, Pin.OUT)
|
||||
self.sck.value(0)
|
||||
|
||||
# Load previous calibration (or defaults)
|
||||
self.zero_offset, self.scale_factor = load_calibration()
|
||||
|
||||
# -----------------------------------------------------
|
||||
# Low-level raw ADC read
|
||||
# -----------------------------------------------------
|
||||
def read_raw(self):
|
||||
"""Read the 24-bit raw ADC value."""
|
||||
while self.dout.value():
|
||||
pass # wait for data ready
|
||||
|
||||
count = 0
|
||||
for _ in range(24):
|
||||
self.sck.value(1)
|
||||
count = (count << 1) | self.dout.value()
|
||||
self.sck.value(0)
|
||||
|
||||
# 25th pulse to set gain for next reading
|
||||
self.sck.value(1)
|
||||
self.sck.value(0)
|
||||
|
||||
# Signed 24-bit conversion
|
||||
if count & 0x800000:
|
||||
count -= 0x1000000
|
||||
|
||||
return count
|
||||
|
||||
# -----------------------------------------------------
|
||||
# Zero Calibration
|
||||
# -----------------------------------------------------
|
||||
def calibrate_zero(self, samples=20):
|
||||
"""Measure 0 kPa and determine zero offset."""
|
||||
total = 0
|
||||
for _ in range(samples):
|
||||
total += self.read_raw()
|
||||
time.sleep_ms(5)
|
||||
|
||||
self.zero_offset = total / samples
|
||||
save_calibration(self.zero_offset, self.scale_factor)
|
||||
|
||||
print("✔ Zero calibrated:", self.zero_offset)
|
||||
return self.zero_offset
|
||||
|
||||
# -----------------------------------------------------
|
||||
# Full-scale / Known Pressure Calibration
|
||||
# -----------------------------------------------------
|
||||
def calibrate_full(self, known_kpa, samples=20):
|
||||
"""Measure raw ADC at known pressure and compute scaling."""
|
||||
total = 0
|
||||
for _ in range(samples):
|
||||
total += self.read_raw()
|
||||
time.sleep_ms(5)
|
||||
|
||||
raw_avg = total / samples
|
||||
self.scale_factor = known_kpa / (raw_avg - self.zero_offset)
|
||||
|
||||
save_calibration(self.zero_offset, self.scale_factor)
|
||||
|
||||
print("✔ Scale calibrated:", self.scale_factor)
|
||||
return self.scale_factor
|
||||
|
||||
# -----------------------------------------------------
|
||||
# Convert raw → kPa using saved calibration
|
||||
# -----------------------------------------------------
|
||||
def read_kpa(self):
|
||||
raw = self.read_raw()
|
||||
return (raw - self.zero_offset) * self.scale_factor
|
||||
89
lib/ssd1306.py
Normal file
89
lib/ssd1306.py
Normal file
@@ -0,0 +1,89 @@
|
||||
# ssd1306.py
|
||||
# MicroPython SSD1306 OLED driver (I2C)
|
||||
# Compatible with 128x64 and 128x32 displays
|
||||
|
||||
from machine import I2C, Pin
|
||||
import framebuf
|
||||
import time
|
||||
|
||||
# SSD1306 commands
|
||||
SET_CONTRAST = 0x81
|
||||
DISPLAY_ALL_ON_RESUME = 0xA4
|
||||
DISPLAY_ALL_ON = 0xA5
|
||||
NORMAL_DISPLAY = 0xA6
|
||||
INVERT_DISPLAY = 0xA7
|
||||
DISPLAY_OFF = 0xAE
|
||||
DISPLAY_ON = 0xAF
|
||||
SET_DISPLAY_OFFSET = 0xD3
|
||||
SET_COM_PINS = 0xDA
|
||||
SET_VCOM_DETECT = 0xDB
|
||||
SET_DISPLAY_CLOCK_DIV = 0xD5
|
||||
SET_PRECHARGE = 0xD9
|
||||
SET_MULTIPLEX = 0xA8
|
||||
SET_LOW_COLUMN = 0x00
|
||||
SET_HIGH_COLUMN = 0x10
|
||||
SET_START_LINE = 0x40
|
||||
MEMORY_MODE = 0x20
|
||||
COLUMN_ADDR = 0x21
|
||||
PAGE_ADDR = 0x22
|
||||
COM_SCAN_INC = 0xC0
|
||||
COM_SCAN_DEC = 0xC8
|
||||
SEG_REMAP = 0xA0
|
||||
CHARGE_PUMP = 0x8D
|
||||
EXTERNAL_VCC = 0x1
|
||||
SWITCH_CAP_VCC = 0x2
|
||||
|
||||
class SSD1306_I2C(framebuf.FrameBuffer):
|
||||
def __init__(self, width, height, i2c, addr=0x3c):
|
||||
self.width = width
|
||||
self.height = height
|
||||
self.i2c = i2c
|
||||
self.addr = addr
|
||||
self.pages = self.height // 8
|
||||
self.buffer = bytearray(self.pages * self.width)
|
||||
super().__init__(self.buffer, self.width, self.height, framebuf.MONO_VLSB)
|
||||
self.init_display()
|
||||
|
||||
def write_cmd(self, cmd):
|
||||
self.i2c.writeto(self.addr, bytearray([0x00, cmd]))
|
||||
|
||||
def init_display(self):
|
||||
for cmd in (
|
||||
DISPLAY_OFF,
|
||||
SET_DISPLAY_CLOCK_DIV, 0x80,
|
||||
SET_MULTIPLEX, self.height - 1,
|
||||
SET_DISPLAY_OFFSET, 0x00,
|
||||
SET_START_LINE | 0x00,
|
||||
CHARGE_PUMP, 0x14,
|
||||
MEMORY_MODE, 0x00,
|
||||
SEG_REMAP | 0x1,
|
||||
COM_SCAN_DEC,
|
||||
SET_COM_PINS, 0x12 if self.height == 64 else 0x02,
|
||||
SET_CONTRAST, 0xCF,
|
||||
SET_PRECHARGE, 0xF1,
|
||||
SET_VCOM_DETECT, 0x40,
|
||||
DISPLAY_ALL_ON_RESUME,
|
||||
NORMAL_DISPLAY,
|
||||
DISPLAY_ON
|
||||
):
|
||||
self.write_cmd(cmd)
|
||||
self.fill(0)
|
||||
self.show()
|
||||
|
||||
def show(self):
|
||||
for page in range(self.pages):
|
||||
self.write_cmd(0xB0 + page)
|
||||
self.write_cmd(SET_LOW_COLUMN)
|
||||
self.write_cmd(SET_HIGH_COLUMN)
|
||||
start = self.width * page
|
||||
end = start + self.width
|
||||
self.i2c.writeto(self.addr, b'\x40' + self.buffer[start:end])
|
||||
|
||||
def poweroff(self):
|
||||
self.write_cmd(DISPLAY_OFF)
|
||||
|
||||
def poweron(self):
|
||||
self.write_cmd(DISPLAY_ON)
|
||||
|
||||
def invert(self, invert):
|
||||
self.write_cmd(INVERT_DISPLAY if invert else NORMAL_DISPLAY)
|
||||
238
main.py
Normal file
238
main.py
Normal file
@@ -0,0 +1,238 @@
|
||||
import time
|
||||
import uasyncio as asyncio
|
||||
import machine
|
||||
import gc
|
||||
import neopixel
|
||||
|
||||
from config import *
|
||||
from wifi import connect_wifi
|
||||
from timeutil import setup_rtc
|
||||
from sensors import Sensors
|
||||
from display import OLED
|
||||
from webserver import WebServer
|
||||
|
||||
|
||||
# Set CPU speed
|
||||
machine.freq(CPU_FREQ)
|
||||
|
||||
#HOSTNAME = "Testname"
|
||||
|
||||
# Connect WiFi
|
||||
print("Version = ", VERSION)
|
||||
print("Hostname = ", HOSTNAME)
|
||||
wlan = connect_wifi(SSID, PASSWORD, HOSTNAME)
|
||||
rtc = setup_rtc(TZ_OFFSET)
|
||||
print("Server running on http://" + wlan.ifconfig()[0])
|
||||
|
||||
#TANK_CAPACITY_L = 3000
|
||||
print("Heart Beat =", HEARTBEAT)
|
||||
print("Tank Volume =", TANK_CAPACITY_L)
|
||||
# Init sensors and display
|
||||
sensors = Sensors()
|
||||
if DISPLAY_ON:
|
||||
oled = OLED()
|
||||
|
||||
sensors.test_pin1.on()
|
||||
sensors.test_pin2.on()
|
||||
sensors.test_pin0.on()
|
||||
# Capture boot time
|
||||
start_time = time.time()
|
||||
|
||||
# print("Boot timestamp:", start_time)
|
||||
|
||||
# Set tank full reference from current reading at boot
|
||||
|
||||
#MAX_PRESSURE_KPA = sensors.read_pressure()
|
||||
time.sleep_ms(250)
|
||||
sensors.test_pin1.off()
|
||||
sensors.test_pin2.off()
|
||||
sensors.test_pin0.off()
|
||||
|
||||
print("✔ Config loaded.")
|
||||
print("Initial MAX_PRESSURE_KPA =", MAX_PRESSURE_KPA, "KPA")
|
||||
print("Initial Known Depth =", KNOWN_DEPTH_M, "m")
|
||||
#print("Initial Known Pressure =", MAX_PRESSURE_KPA)
|
||||
|
||||
def rssi_percent():
|
||||
rssi = wlan.status("rssi")
|
||||
rssi = max(-100, min(-50, rssi))
|
||||
return int((rssi + 100) * 2)
|
||||
|
||||
class HeartbeatFadeNeoPixel:
|
||||
def __init__(self, neopixel_obj, pixel_index=0, color=(255, 0, 0),
|
||||
min_brightness=5, max_brightness=100,
|
||||
step=2, interval_ms=20):
|
||||
|
||||
self.np = neopixel_obj
|
||||
self.index = pixel_index
|
||||
self.color = color
|
||||
|
||||
self.min_brightness = min_brightness
|
||||
self.max_brightness = max_brightness
|
||||
self.step = step
|
||||
self.interval = interval_ms
|
||||
|
||||
self.brightness = min_brightness
|
||||
self.direction = 1 # 1 = fade up, -1 = fade down
|
||||
self.last_time = time.ticks_ms()
|
||||
|
||||
def apply_brightness(self, c, level):
|
||||
r, g, b = c
|
||||
factor = level / 100
|
||||
return (int(r * factor), int(g * factor), int(b * factor))
|
||||
|
||||
def update(self):
|
||||
now = time.ticks_ms()
|
||||
if time.ticks_diff(now, self.last_time) >= self.interval:
|
||||
self.last_time = now
|
||||
|
||||
# Update brightness
|
||||
self.brightness += self.direction * self.step
|
||||
|
||||
# Bounce at limits
|
||||
if self.brightness >= self.max_brightness:
|
||||
self.brightness = self.max_brightness
|
||||
self.direction = -1
|
||||
|
||||
if self.brightness <= self.min_brightness:
|
||||
self.brightness = self.min_brightness
|
||||
self.direction = 1
|
||||
|
||||
# Apply brightness
|
||||
self.np[self.index] = self.apply_brightness(self.color, self.brightness)
|
||||
self.np.write()
|
||||
|
||||
|
||||
# ============================================================
|
||||
# NEOPIXEL SETUP
|
||||
# ============================================================
|
||||
|
||||
STRIP_PIN = 17
|
||||
STRIP_LEDS = 3
|
||||
strip = neopixel.NeoPixel(machine.Pin(STRIP_PIN), STRIP_LEDS)
|
||||
|
||||
SINGLE_PIN = 28
|
||||
single = neopixel.NeoPixel(machine.Pin(SINGLE_PIN), 1)
|
||||
|
||||
# ============================================================
|
||||
# BUTTONS
|
||||
# ============================================================
|
||||
|
||||
button_pins = [20, 21, 22]
|
||||
buttons = [machine.Pin(pin, machine.Pin.IN, machine.Pin.PULL_UP) for pin in button_pins]
|
||||
|
||||
# ============================================================
|
||||
# COLOR PALETTE
|
||||
# ============================================================
|
||||
|
||||
colors = [
|
||||
(0, 0, 0), # Off
|
||||
(255, 0, 0), # Red
|
||||
(0, 255, 0), # Green
|
||||
(0, 0, 255), # Blue
|
||||
(255, 255, 255), # White
|
||||
]
|
||||
|
||||
# ============================================================
|
||||
# STRIP STATE
|
||||
# ============================================================
|
||||
|
||||
color_index = [0, 0, 0]
|
||||
brightness = [50, 50, 50]
|
||||
last_state = [1, 1, 1]
|
||||
press_time = [0, 0, 0]
|
||||
|
||||
# ============================================================
|
||||
# HEARTBEAT CONFIG (NOW HAS MIN + MAX)
|
||||
# ============================================================
|
||||
|
||||
FLASH_COLOR_INDEX = 1 # Palette color
|
||||
FLASH_MIN = 1 # << lowest brightness (0–100)
|
||||
FLASH_MAX = 10 # << highest brightness
|
||||
FLASH_STEP = 0.2 # fade speed
|
||||
FLASH_INTERVAL_MS = 20 # smoothness
|
||||
|
||||
heartbeat = HeartbeatFadeNeoPixel(
|
||||
neopixel_obj=single,
|
||||
pixel_index=0,
|
||||
color=colors[FLASH_COLOR_INDEX],
|
||||
min_brightness=FLASH_MIN,
|
||||
max_brightness=FLASH_MAX,
|
||||
step=FLASH_STEP,
|
||||
interval_ms=FLASH_INTERVAL_MS
|
||||
)
|
||||
|
||||
# ============================================================
|
||||
# HELPERS
|
||||
# ============================================================
|
||||
|
||||
def apply_brightness(color, level):
|
||||
r, g, b = color
|
||||
factor = level / 100
|
||||
return (int(r * factor), int(g * factor), int(b * factor))
|
||||
|
||||
def update_strip():
|
||||
for i in range(STRIP_LEDS):
|
||||
strip[i] = apply_brightness(colors[color_index[i]], brightness[i])
|
||||
strip.write()
|
||||
|
||||
# ============================================================
|
||||
# INITIAL
|
||||
# ============================================================
|
||||
|
||||
update_strip()
|
||||
|
||||
async def sensor_task():
|
||||
global MAX_PRESSURE_KPA
|
||||
while True:
|
||||
gc.collect()
|
||||
sensors.check_buttons()
|
||||
# heartbeat.update()
|
||||
if AVERAGE:
|
||||
pressure = sensors.read_pressure_avg()
|
||||
else:
|
||||
pressure = sensors.read_pressure()
|
||||
#sensors.test_pin1.on()
|
||||
if sensors.tank_full:
|
||||
sensors.tank_full = False
|
||||
MAX_PRESSURE_KPA = sensors.read_pressure()
|
||||
|
||||
if HEARTBEAT:
|
||||
# sensors.test_pin2.on()
|
||||
# time.sleep_ms(100)
|
||||
# sensors.test_pin2.off()
|
||||
# color_index[i] = 0
|
||||
i=0
|
||||
color_index[i] = (color_index[i] + 1) % len(colors)
|
||||
update_strip()
|
||||
# time.sleep_ms(100)
|
||||
|
||||
else:
|
||||
# sensors.test_pin2.off()
|
||||
# color_index[i] = 3
|
||||
update_strip()
|
||||
# time.sleep_ms(100)
|
||||
|
||||
percent = pressure / MAX_PRESSURE_KPA
|
||||
depth_mm = percent * MAX_DEPTH_M * 1000
|
||||
sensors.tank_percentage = percent * 100
|
||||
|
||||
if DISPLAY_ON:
|
||||
oled.show(
|
||||
rtc,
|
||||
pressure,
|
||||
sensors.tank_percentage,
|
||||
sensors.cpu_temp(),
|
||||
rssi_percent(),
|
||||
depth_mm
|
||||
)
|
||||
await asyncio.sleep(1)
|
||||
|
||||
async def main():
|
||||
asyncio.create_task(sensor_task())
|
||||
server = WebServer(wlan, sensors, start_time, MAX_CONNECTIONS)
|
||||
srv = await asyncio.start_server(server.handle, "0.0.0.0", 80)
|
||||
async with srv:
|
||||
await asyncio.Event().wait()
|
||||
|
||||
asyncio.run(main())
|
||||
30
scan-i2c.py
Normal file
30
scan-i2c.py
Normal file
@@ -0,0 +1,30 @@
|
||||
from machine import Pin, I2C
|
||||
import time
|
||||
|
||||
# Scan I2C0 on GP0=SDA, GP1=SCL
|
||||
i2c0 = I2C(0, scl=Pin(1), sda=Pin(0), freq=400000)
|
||||
|
||||
# Scan I2C1 on GP2=SDA, GP3=SCL
|
||||
i2c1 = I2C(1, scl=Pin(3), sda=Pin(2), freq=400000)
|
||||
|
||||
while True:
|
||||
print("Scanning I2C buses...")
|
||||
devices0 = i2c0.scan()
|
||||
devices1 = i2c1.scan()
|
||||
|
||||
if devices0:
|
||||
print("I2C0 Devices found:")
|
||||
for d in devices0:
|
||||
print(" - Address: 0x{:02X}".format(d))
|
||||
else:
|
||||
print("No devices found on I2C0")
|
||||
|
||||
if devices1:
|
||||
print("I2C1 Devices found:")
|
||||
for d in devices1:
|
||||
print(" - Address: 0x{:02X}".format(d))
|
||||
else:
|
||||
print("No devices found on I2C1")
|
||||
|
||||
print("-----------------------------")
|
||||
time.sleep(3)
|
||||
57
sensors.py
Normal file
57
sensors.py
Normal file
@@ -0,0 +1,57 @@
|
||||
from machine import Pin, ADC
|
||||
import time
|
||||
from hx710b import HX710B
|
||||
|
||||
class Sensors:
|
||||
def __init__(self):
|
||||
self.hx = HX710B(dout_pin=0, sck_pin=1)
|
||||
|
||||
print("Using calibration:", self.hx.zero_offset, self.hx.scale_factor)
|
||||
|
||||
self.pressure = 0.0
|
||||
self.tank_full_kpa = 13.72
|
||||
self.tank_full = False
|
||||
self.tank_percent = 0.0
|
||||
|
||||
self.tare_btn = Pin(9, Pin.IN, Pin.PULL_UP)
|
||||
self._tare_lock = False
|
||||
|
||||
self.test_pin0 = Pin(20, Pin.OUT) # Green
|
||||
self.test_pin1 = Pin(21, Pin.OUT) # Blue
|
||||
self.test_pin2 = Pin(22, Pin.OUT)
|
||||
|
||||
self.cpu_adc = ADC(4)
|
||||
|
||||
def read_pressure(self):
|
||||
#self.test_pin2.on()
|
||||
self.pressure = self.hx.read_kpa()
|
||||
#self.test_pin2.off()
|
||||
return self.pressure
|
||||
|
||||
def read_pressure_avg(self, alpha=0.2):
|
||||
new = self.hx.read_kpa()
|
||||
|
||||
if not hasattr(self, "pressure"):
|
||||
self.pressure = new
|
||||
else:
|
||||
self.pressure = (alpha * new) + ((1 - alpha) * self.pressure)
|
||||
|
||||
return self.pressure
|
||||
|
||||
def set_tank_full(self):
|
||||
self.tank_full_kpa = self.pressure
|
||||
self.tank_full = True
|
||||
self.tank_percent = 100.0
|
||||
print("Tank FULL set at", self.tank_full_kpa)
|
||||
|
||||
def check_buttons(self):
|
||||
if self.tare_btn.value() == 0 and not self._tare_lock:
|
||||
self._tare_lock = True
|
||||
time.sleep_ms(200)
|
||||
self.set_tank_full()
|
||||
elif self.tare_btn.value() == 1:
|
||||
self._tare_lock = False
|
||||
|
||||
def cpu_temp(self):
|
||||
v = self.cpu_adc.read_u16() * 3.3 / 65535
|
||||
return 27 - (v - 0.706) / 0.001721
|
||||
89
ssd1306.py
Normal file
89
ssd1306.py
Normal file
@@ -0,0 +1,89 @@
|
||||
# ssd1306.py
|
||||
# MicroPython SSD1306 OLED driver (I2C)
|
||||
# Compatible with 128x64 and 128x32 displays
|
||||
|
||||
from machine import I2C, Pin
|
||||
import framebuf
|
||||
import time
|
||||
|
||||
# SSD1306 commands
|
||||
SET_CONTRAST = 0x81
|
||||
DISPLAY_ALL_ON_RESUME = 0xA4
|
||||
DISPLAY_ALL_ON = 0xA5
|
||||
NORMAL_DISPLAY = 0xA6
|
||||
INVERT_DISPLAY = 0xA7
|
||||
DISPLAY_OFF = 0xAE
|
||||
DISPLAY_ON = 0xAF
|
||||
SET_DISPLAY_OFFSET = 0xD3
|
||||
SET_COM_PINS = 0xDA
|
||||
SET_VCOM_DETECT = 0xDB
|
||||
SET_DISPLAY_CLOCK_DIV = 0xD5
|
||||
SET_PRECHARGE = 0xD9
|
||||
SET_MULTIPLEX = 0xA8
|
||||
SET_LOW_COLUMN = 0x00
|
||||
SET_HIGH_COLUMN = 0x10
|
||||
SET_START_LINE = 0x40
|
||||
MEMORY_MODE = 0x20
|
||||
COLUMN_ADDR = 0x21
|
||||
PAGE_ADDR = 0x22
|
||||
COM_SCAN_INC = 0xC0
|
||||
COM_SCAN_DEC = 0xC8
|
||||
SEG_REMAP = 0xA0
|
||||
CHARGE_PUMP = 0x8D
|
||||
EXTERNAL_VCC = 0x1
|
||||
SWITCH_CAP_VCC = 0x2
|
||||
|
||||
class SSD1306_I2C(framebuf.FrameBuffer):
|
||||
def __init__(self, width, height, i2c, addr=0x3c):
|
||||
self.width = width
|
||||
self.height = height
|
||||
self.i2c = i2c
|
||||
self.addr = addr
|
||||
self.pages = self.height // 8
|
||||
self.buffer = bytearray(self.pages * self.width)
|
||||
super().__init__(self.buffer, self.width, self.height, framebuf.MONO_VLSB)
|
||||
self.init_display()
|
||||
|
||||
def write_cmd(self, cmd):
|
||||
self.i2c.writeto(self.addr, bytearray([0x00, cmd]))
|
||||
|
||||
def init_display(self):
|
||||
for cmd in (
|
||||
DISPLAY_OFF,
|
||||
SET_DISPLAY_CLOCK_DIV, 0x80,
|
||||
SET_MULTIPLEX, self.height - 1,
|
||||
SET_DISPLAY_OFFSET, 0x00,
|
||||
SET_START_LINE | 0x00,
|
||||
CHARGE_PUMP, 0x14,
|
||||
MEMORY_MODE, 0x00,
|
||||
SEG_REMAP | 0x1,
|
||||
COM_SCAN_DEC,
|
||||
SET_COM_PINS, 0x12 if self.height == 64 else 0x02,
|
||||
SET_CONTRAST, 0xCF,
|
||||
SET_PRECHARGE, 0xF1,
|
||||
SET_VCOM_DETECT, 0x40,
|
||||
DISPLAY_ALL_ON_RESUME,
|
||||
NORMAL_DISPLAY,
|
||||
DISPLAY_ON
|
||||
):
|
||||
self.write_cmd(cmd)
|
||||
self.fill(0)
|
||||
self.show()
|
||||
|
||||
def show(self):
|
||||
for page in range(self.pages):
|
||||
self.write_cmd(0xB0 + page)
|
||||
self.write_cmd(SET_LOW_COLUMN)
|
||||
self.write_cmd(SET_HIGH_COLUMN)
|
||||
start = self.width * page
|
||||
end = start + self.width
|
||||
self.i2c.writeto(self.addr, b'\x40' + self.buffer[start:end])
|
||||
|
||||
def poweroff(self):
|
||||
self.write_cmd(DISPLAY_OFF)
|
||||
|
||||
def poweron(self):
|
||||
self.write_cmd(DISPLAY_ON)
|
||||
|
||||
def invert(self, invert):
|
||||
self.write_cmd(INVERT_DISPLAY if invert else NORMAL_DISPLAY)
|
||||
9
test-time.py
Normal file
9
test-time.py
Normal file
@@ -0,0 +1,9 @@
|
||||
import time
|
||||
import ntptime
|
||||
tz_offset = 0
|
||||
utc = time.localtime()
|
||||
local = time.localtime(time.mktime(utc) + tz_offset * 3600)
|
||||
ctime = f"{local[0]:02}-{local[1]:02}-{local[2]:02} {local[3]:02}:{local[4]:02}:{local[5]:02}"
|
||||
#print("Time =", local[0] + '-' + local[1] + '-' + local[2])
|
||||
#print("Time =", local)
|
||||
print("Time =", ctime)
|
||||
34
timeutil.py
Normal file
34
timeutil.py
Normal file
@@ -0,0 +1,34 @@
|
||||
import time
|
||||
import ntptime
|
||||
import machine
|
||||
|
||||
def setup_rtc(tz_offset):
|
||||
ntptime.settime()
|
||||
rtc = machine.RTC()
|
||||
|
||||
utc = time.localtime()
|
||||
local = time.localtime(time.mktime(utc) + tz_offset * 3600)
|
||||
|
||||
rtc.datetime((
|
||||
local[0], local[1], local[2], 0,
|
||||
local[3], local[4], local[5], 0
|
||||
))
|
||||
return rtc
|
||||
|
||||
# Function to calculate the difference
|
||||
def calculate_difference(dt1, dt2):
|
||||
timestamp1 = rtc_to_timestamp(dt1)
|
||||
timestamp2 = rtc_to_timestamp(dt2)
|
||||
difference = abs(timestamp2 - timestamp1)
|
||||
days = difference // (24 * 3600)
|
||||
difference %= (24 * 3600)
|
||||
hours = difference // 3600
|
||||
difference %= 3600
|
||||
minutes = difference // 60
|
||||
seconds = difference % 60
|
||||
return days, hours, minutes, seconds
|
||||
|
||||
# Function to convert RTC datetime tuple to a timestamp
|
||||
def rtc_to_timestamp(dt):
|
||||
year, month, day, weekday, hour, minute, second, subseconds = dt
|
||||
return time.mktime((year, month, day, hour, minute, second, 0, 0))
|
||||
106
webserver.py
Normal file
106
webserver.py
Normal file
@@ -0,0 +1,106 @@
|
||||
import json
|
||||
import gc
|
||||
import time
|
||||
import uasyncio as asyncio
|
||||
|
||||
# Import config value
|
||||
from config import TANK_CAPACITY_L, VERSION
|
||||
|
||||
|
||||
class WebServer:
|
||||
def __init__(self, wlan, sensors, start_time, max_conn):
|
||||
self.wlan = wlan
|
||||
self.sensors = sensors
|
||||
self.start_time = start_time
|
||||
self.max_conn = max_conn
|
||||
self.current = 0
|
||||
self.lock = asyncio.Lock()
|
||||
|
||||
async def handle(self, reader, writer):
|
||||
async with self.lock:
|
||||
if self.current >= self.max_conn:
|
||||
writer.close()
|
||||
return
|
||||
self.current += 1
|
||||
|
||||
try:
|
||||
request = await reader.readline()
|
||||
path = request.decode().split(" ")[1]
|
||||
|
||||
# Discard headers
|
||||
while await reader.readline() != b"\r\n":
|
||||
pass
|
||||
|
||||
# ---------- SENSOR JSON ----------
|
||||
if path == "/sensor":
|
||||
uptime = int(time.time() - self.start_time)
|
||||
|
||||
days = uptime // 86400
|
||||
hours = (uptime % 86400) // 3600
|
||||
minutes = (uptime % 3600) // 60
|
||||
seconds = uptime % 60
|
||||
|
||||
uptime_text = f"{days}d {hours:02}h {minutes:02}m {seconds:02}s"
|
||||
|
||||
data = {
|
||||
"pressure": self.sensors.pressure,
|
||||
"tank_percent": self.sensors.tank_percentage,
|
||||
"cpu_temp": self.sensors.cpu_temp(),
|
||||
"rssi": self.wlan.status("rssi"),
|
||||
"uptime": uptime_text,
|
||||
"mem": gc.mem_free(),
|
||||
"footer": "Powered by Raspberry Pi Pico W & HX710B (" + VERSION + ")"
|
||||
}
|
||||
|
||||
writer.write(
|
||||
b"HTTP/1.1 200 OK\r\n"
|
||||
b"Content-Type: application/json\r\n\r\n" +
|
||||
json.dumps(data).encode()
|
||||
)
|
||||
|
||||
# ---------- TARE ----------
|
||||
elif path == "/tare":
|
||||
self.sensors.set_tank_full()
|
||||
|
||||
writer.write(
|
||||
b"HTTP/1.1 200 OK\r\n"
|
||||
b"Content-Type: application/json\r\n\r\n" +
|
||||
json.dumps({"ok": True}).encode()
|
||||
)
|
||||
|
||||
# ---------- JAVASCRIPT (CONFIG INJECTED) ----------
|
||||
elif path == "/app.js":
|
||||
with open("/html/app.js") as f:
|
||||
js = f.read()
|
||||
|
||||
injected = (
|
||||
"// injected by webserver.py\n"
|
||||
f"const TANK_CAPACITY_L = {TANK_CAPACITY_L};\n\n"
|
||||
)
|
||||
|
||||
writer.write(
|
||||
b"HTTP/1.1 200 OK\r\n"
|
||||
b"Content-Type: application/javascript\r\n"
|
||||
b"Cache-Control: no-store\r\n\r\n" +
|
||||
injected.encode() +
|
||||
js.encode()
|
||||
)
|
||||
|
||||
# ---------- HTML ----------
|
||||
else:
|
||||
with open("/html/index.html") as f:
|
||||
html = f.read()
|
||||
|
||||
writer.write(
|
||||
b"HTTP/1.1 200 OK\r\n"
|
||||
b"Content-Type: text/html\r\n\r\n" +
|
||||
html.encode()
|
||||
)
|
||||
|
||||
await writer.drain()
|
||||
|
||||
finally:
|
||||
writer.close()
|
||||
await writer.wait_closed()
|
||||
async with self.lock:
|
||||
self.current -= 1
|
||||
15
wifi.py
Normal file
15
wifi.py
Normal file
@@ -0,0 +1,15 @@
|
||||
import network
|
||||
import time
|
||||
|
||||
def connect_wifi(ssid, password, hostname):
|
||||
wlan = network.WLAN(network.STA_IF)
|
||||
wlan.active(True)
|
||||
wlan.config(hostname=hostname)
|
||||
wlan.connect(ssid, password)
|
||||
|
||||
for _ in range(10):
|
||||
if wlan.isconnected():
|
||||
return wlan
|
||||
time.sleep(1)
|
||||
|
||||
raise RuntimeError("WiFi connection failed")
|
||||
Reference in New Issue
Block a user