123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247 |
- #!/usr/bin/env python3
-
- # ----------------------------------------------------------------------------
- # Copyright (c) 2023 Thomas Buck (thomas@xythobuz.de)
- #
- # This program is free software: you can redistribute it and/or modify
- # it under the terms of the GNU General Public License as published by
- # the Free Software Foundation, either version 3 of the License, or
- # (at your option) any later version.
- #
- # This program is distributed in the hope that it will be useful,
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- # GNU General Public License for more details.
- #
- # See <http://www.gnu.org/licenses/>.
- # ----------------------------------------------------------------------------
-
- from lcd import LCD
- lcd = LCD()
-
- import uasyncio as asyncio
- import io
- import sys
- import machine
- import os
- import gc
- import time
- from state_wait_temp import from_hsv, translate
-
- # https://github.com/pimoroni/pimoroni-pico/blob/main/micropython/examples/pico_lipo_shim/battery_pico.py
- # https://github.com/pimoroni/enviro/pull/146
- # TODO https://github.com/micropython/micropython/issues/11185
-
- full_battery = 4.1
- empty_battery = 3.2
- batt_warn_limit = 15
- batt_reread_limit = 2.7
-
- charging = machine.Pin("WL_GPIO2", machine.Pin.IN)
- conversion_factor = 3 * 3.3 / 65535
- cachedVoltage = None
- lastCaching = time.time()
-
- def set_pad(gpio, value):
- machine.mem32[0x4001c000 | (4 + (4 * gpio))] = value
-
- def get_pad(gpio):
- return machine.mem32[0x4001c000 | (4 + (4 * gpio))]
-
- def batteryVoltageRead():
- vsys = machine.ADC(3)
- voltage = vsys.read_u16() * conversion_factor
- return voltage
-
- def batteryVoltageAverage():
- old_pad = get_pad(29)
- set_pad(29, 128) # no pulls, no output, no input
-
- sample_count = 3
- voltage = 0
- for i in range(0, sample_count):
- voltage += batteryVoltageRead()
- voltage /= sample_count
-
- set_pad(29, old_pad)
- return voltage
-
- def batteryVoltage():
- global cachedVoltage, lastCaching
-
- if ((time.time() - lastCaching) > 0) or (cachedVoltage == None):
- lastCaching = time.time()
- cachedVoltage = batteryVoltageAverage()
- if cachedVoltage <= batt_reread_limit:
- cachedVoltage = batteryVoltageAverage()
-
- percentage = 100.0 * ((cachedVoltage - empty_battery) / (full_battery - empty_battery))
- if percentage >= 100.0:
- percentage = 99.0
- elif percentage < 0.0:
- percentage = 0.0
-
- return cachedVoltage, percentage
-
- class States:
- def __init__(self, lcd):
- self.lcd = lcd
- self.states = []
- self.current = None
-
- def add(self, s):
- self.states.append(s)
-
- async def draw(self):
- self.lcd.fill(self.lcd.black)
- self.lcd.text("Volcano Remote Control App", 0, 0, self.lcd.green)
-
- ret = await self.states[self.current].draw()
-
- voltage, percentage = batteryVoltage()
- s = "Charging"
- c = self.lcd.white
- if charging.value() != 1:
- s = "{:.0f}% ({:.2f}V)".format(percentage, voltage)
- if percentage <= batt_warn_limit:
- c = self.lcd.red
- else:
- hue = translate(percentage, batt_warn_limit, 100, 0.0, 0.333)
- r, g, b = from_hsv(hue, 1.0, 1.0)
- c = self.lcd.color(r, g, b)
- whole = "Batt: {}".format(s)
- self.lcd.text(whole, 0, self.lcd.height - 10, c)
-
- off = (len(whole) + 1) * 8
- if percentage <= batt_warn_limit:
- self.lcd.text("CHARGE NOW!", off, self.lcd.height - 10, self.lcd.red)
- elif charging.value() != 1:
- self.lcd.rect(off, self.lcd.height - 10, self.lcd.width - off, 8, c, False)
- max_w = self.lcd.width - off - 2
- w = int(percentage / 100.0 * max_w)
- self.lcd.rect(off + 1, self.lcd.height - 9, w, 6, c, True)
- else:
- pass # TODO charge indicator (lightning bolt?)
-
- self.lcd.show()
- return ret
-
- def run(self):
- if self.current == None:
- self.current = 0
- self.states[self.current].enter()
-
- next = asyncio.run(self.draw())
- if next >= 0:
- val = self.states[self.current].exit()
- self.current = next
- self.states[self.current].enter(val)
-
- def state_machine(lcd):
- states = States(lcd)
-
- # 0 - Scan
- from state_scan import StateScan
- scan = StateScan(lcd)
- states.add(scan)
-
- # 1 - Connect
- from state_connect import StateConnect
- conn = StateConnect(lcd, True)
- states.add(conn)
-
- # 2 - Select
- from state_select import StateSelect
- select = StateSelect(lcd)
- states.add(select)
-
- # 3 - Heater On
- from state_heat import StateHeat
- heatOn = StateHeat(lcd, True)
- states.add(heatOn)
-
- # 4 - Heater Off
- heatOff = StateHeat(lcd, False)
- states.add(heatOff)
-
- # 5 - Disconnect
- disconn = StateConnect(lcd, False)
- states.add(disconn)
-
- # 6 - Wait for temperature
- from state_wait_temp import StateWaitTemp
- waitTemp = StateWaitTemp(lcd)
- states.add(waitTemp)
-
- # 7 - Wait for time
- from state_wait_time import StateWaitTime
- waitTime = StateWaitTime(lcd)
- states.add(waitTime)
-
- # 8 - Pump
- from state_pump import StatePump
- pump = StatePump(lcd)
- states.add(pump)
-
- # 9 - Notify
- from state_notify import StateNotify
- notify = StateNotify(lcd)
- states.add(notify)
-
- # 10 - OTA Update
- #from ota import StateUpdate
- #update = StateUpdate(lcd)
- #states.add(update)
-
- while True:
- states.run()
-
- def main():
- # splash screen
- from state_wait_temp import from_hsv
- for x in range(0, lcd.width):
- hue = x / (lcd.width - 1)
- r, g, b = from_hsv(hue, 1.0, 1.0)
- c = lcd.color(r, g, b)
- lcd.rect(x, 0, 1, lcd.height, c)
-
- lcd.textC("S&B Volcano Remote", int(lcd.width / 2), 10, lcd.green, lcd.black)
- lcd.textC("by xythobuz", int(lcd.width / 2), 20, lcd.yellow, lcd.black)
- lcd.textC("Initializing...", int(lcd.width / 2), 30, lcd.white, lcd.black)
-
- import _git
- lcd.textC(_git.git_branch, int(lcd.width / 2), int(lcd.height / 2) - 10, lcd.green, lcd.black)
- lcd.textC(_git.git_hash, int(lcd.width / 2), int(lcd.height / 2), lcd.yellow, lcd.black)
- lcd.textC(_git.build_date, int(lcd.width / 2), int(lcd.height / 2) + 10, lcd.white, lcd.black)
-
- lcd.textC(os.uname()[0][ 0 : 30], int(lcd.width / 2), lcd.height - 50, lcd.green, lcd.black)
- lcd.textC(os.uname()[3][ 0 : 30], int(lcd.width / 2), lcd.height - 40, lcd.yellow, lcd.black)
- lcd.textC(os.uname()[3][30 : 60], int(lcd.width / 2), lcd.height - 30, lcd.yellow, lcd.black)
- lcd.textC(os.uname()[4][ 0 : 30], int(lcd.width / 2), lcd.height - 20, lcd.white, lcd.black)
- lcd.textC(os.uname()[4][30 : 60], int(lcd.width / 2), lcd.height - 10, lcd.white, lcd.black)
-
- lcd.show()
-
- # bootloader access with face buttons
- keys = lcd.buttons()
- if keys.once("a") and keys.once("b"):
- machine.bootloader()
-
- state_machine(lcd)
-
- try:
- main()
- except Exception as e:
- sys.print_exception(e)
-
- gc.collect()
- os = io.StringIO()
- sys.print_exception(e, os)
- s = os.getvalue()
- os.close()
-
- lcd.fill(lcd.black)
- lcd.textBlock(s, lcd.white)
- lcd.show()
-
- raise e
|