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