kopia lustrzana https://github.com/micropython/micropython
ports/esp32/boards/PYBOX: Add support SparkFun Thing Plus board.
Signed-off-by: Wolf <wolf@paulus.com>pull/10324/head
rodzic
699477d12d
commit
939e99957a
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"deploy": [
|
||||
"../deploy.md"
|
||||
],
|
||||
"docs": "",
|
||||
"features": [
|
||||
"Battery Charging",
|
||||
"Feather",
|
||||
"RGB LED",
|
||||
"STEMMA QT/QWIIC",
|
||||
"BLE",
|
||||
"WiFi"
|
||||
],
|
||||
"id": "pyBox",
|
||||
"images": [
|
||||
"pyBox.jpg"
|
||||
],
|
||||
"mcu": "esp32",
|
||||
"product": "pyBox",
|
||||
"thumbnail": "https://wolfpaulus.com/content/pyBox.jpg",
|
||||
"url": "https://wolfpaulus.com/pyBox",
|
||||
"vendor": "Techcasita Productions"
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
The following files for the SparkFun Thing Plus – ESP32 WROOM (USB-C) paired with the SparkFun 16×2 SerLCD – RGB Text (Qwiic).
|
||||
This firmware is compiled using ESP-IDF v4.4.
|
|
@ -0,0 +1,2 @@
|
|||
include("$(PORT_DIR)/boards/manifest.py")
|
||||
freeze("modules")
|
|
@ -0,0 +1,148 @@
|
|||
"""
|
||||
NeoPixel, BlueLED, SDDrive, WiFi for MicroPython on SparkFun Thing Plus
|
||||
Author: wolf@paulus.com
|
||||
MIT license; Copyright (c) 2022 wolfpaulus.com
|
||||
"""
|
||||
from micropython import const
|
||||
from machine import freq, Pin, SDCard, Signal, RTC
|
||||
from utime import sleep
|
||||
from network import WLAN, STA_IF
|
||||
from neopixel import NeoPixel
|
||||
from uos import mount
|
||||
from pybox_btn import Button
|
||||
from collections import OrderedDict
|
||||
|
||||
_BTN_UP_PIN = const(27)
|
||||
_BTN_DN_PIN = const(33)
|
||||
_TURBO_SPEED = const(240000000)
|
||||
_NORMAL_SPEED = const(160000000)
|
||||
_BLUE_LED_PIN = const(13)
|
||||
_NEO_PIN = const(2)
|
||||
|
||||
|
||||
class Neo:
|
||||
"""
|
||||
NeoPixel
|
||||
"""
|
||||
|
||||
def __init__(self, pin: int = 2):
|
||||
self.neo = NeoPixel(Pin(pin, Pin.OUT, value=False), 1)
|
||||
|
||||
def color(self, r: int = 255, g: int = 255, b: int = 255):
|
||||
self.neo[0] = (r, g, b)
|
||||
self.neo.write()
|
||||
|
||||
def get_color(self) -> (int, int, int):
|
||||
return self.neo[0]
|
||||
|
||||
def off(self) -> None:
|
||||
self.neo[0] = (0, 0, 0)
|
||||
self.neo.write()
|
||||
|
||||
|
||||
def turbo(on: bool = True) -> None:
|
||||
"""
|
||||
Switch ESP's clock speed
|
||||
:param on: 240MHz is True else, defaulting to 160MHz
|
||||
:return: None
|
||||
"""
|
||||
freq(_TURBO_SPEED if on else _NORMAL_SPEED)
|
||||
|
||||
|
||||
def mount_sd(directory: str = "/sd") -> None:
|
||||
"""
|
||||
Define removable storage media - secure digital memory card
|
||||
"""
|
||||
sd = SDCard(slot=2, width=1, cd=None, wp=None, sck=18, miso=19, mosi=23, cs=5, freq=40000000)
|
||||
mount(sd, directory)
|
||||
|
||||
|
||||
def connect(wifi_networks: list) -> bool:
|
||||
"""
|
||||
Connect to Wifi router or become an Access Point
|
||||
:param wifi_networks: list of tuples [(ssid, password),..]
|
||||
:return: True if connected
|
||||
"""
|
||||
global hostname, ip_addr
|
||||
conn = False
|
||||
while not conn:
|
||||
for ssid, pw in wifi_networks:
|
||||
print(ssid)
|
||||
sta_if = WLAN(STA_IF)
|
||||
if not sta_if.isconnected():
|
||||
sta_if.active(True)
|
||||
sta_if.connect(ssid, pw)
|
||||
# sta_if.config(dhcp_hostname=profile.HostName)
|
||||
for i in range(2, 8):
|
||||
sta_if = WLAN(STA_IF)
|
||||
if sta_if.isconnected():
|
||||
conn = True
|
||||
ip_addr = sta_if.ifconfig()[0]
|
||||
hostname = sta_if.config("dhcp_hostname")
|
||||
break
|
||||
else:
|
||||
sleep(i)
|
||||
if conn:
|
||||
break
|
||||
return conn
|
||||
|
||||
|
||||
def is_connected() -> bool:
|
||||
"""
|
||||
:return: True is connected to WiFi
|
||||
"""
|
||||
return WLAN(STA_IF).isconnected()
|
||||
|
||||
|
||||
def set_system_time(d: dict) -> None:
|
||||
"""
|
||||
Set the Systemtime
|
||||
:param d: dictionary
|
||||
:return:
|
||||
"""
|
||||
RTC().datetime((d["year"], d["month"], d["day"], 0, d["hour"], d["minute"], d["seconds"], 0))
|
||||
|
||||
|
||||
def rgb_color() -> (int, int, int):
|
||||
"""
|
||||
RGB color generator (r,g,b in the range 0..255)
|
||||
:return: (r,g,b all in the range 0..255)
|
||||
"""
|
||||
for i in range(1, 86):
|
||||
yield 255 - i * 3, 0, i * 3
|
||||
for i in range(1, 86):
|
||||
yield 0, i * 3, 255 - i * 3
|
||||
for i in range(1, 86):
|
||||
yield i * 3, 255 - i * 3, 0
|
||||
|
||||
|
||||
palette = OrderedDict(
|
||||
[
|
||||
# red
|
||||
("Auriga", (255, 0, 63)),
|
||||
("Antares", (255, 0, 0)),
|
||||
# orange
|
||||
("Jupiter", (255, 32, 0)),
|
||||
("Proxima", (191, 128, 0)),
|
||||
# gray
|
||||
("Sirius", (255, 255, 255)),
|
||||
("Vega", (127, 127, 191)),
|
||||
# green
|
||||
("Nebula", (0, 255, 0)),
|
||||
("Kepler", (0, 127, 63)),
|
||||
("Uranus", (0, 191, 255)),
|
||||
# blue
|
||||
("Rigel", (31, 31, 255)),
|
||||
("Medusa", (127, 0, 255)),
|
||||
("Orion", (255, 31, 255)),
|
||||
]
|
||||
)
|
||||
|
||||
blue_led = Signal(Pin(_BLUE_LED_PIN, mode=Pin.OUT, value=False), invert=False)
|
||||
blue_led.off()
|
||||
neo = Neo(_NEO_PIN)
|
||||
neo.color(0, 0, 0)
|
||||
btn_up = Button(_BTN_UP_PIN)
|
||||
btn_dn = Button(_BTN_DN_PIN)
|
||||
hostname = ""
|
||||
ip_addr = "0.0.0.0"
|
|
@ -0,0 +1,70 @@
|
|||
"""
|
||||
Capacitive Touch Buttons with Press and Long-Press detection
|
||||
Author: Wolf Paulus wolf@paulus.com
|
||||
MIT license; Copyright (c) 2022 wolfpaulus.com
|
||||
"""
|
||||
from machine import Pin, TouchPad
|
||||
from utime import ticks_ms, ticks_diff
|
||||
import pybox_ct as ct
|
||||
|
||||
|
||||
class Button(TouchPad):
|
||||
"""Capacitive Touch Button"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
pin: int,
|
||||
on_press: callable = None,
|
||||
on_long_press: callable = None,
|
||||
threshold: int = 600,
|
||||
hold_ms: int = 500,
|
||||
freq_ms: int = 100,
|
||||
):
|
||||
"""
|
||||
:param pin: port pin this button is connected to
|
||||
:param on_press: function to be called on touch
|
||||
:param on_long_press: function to be called on long touch
|
||||
:param threshold: capacitive touch value that qualifies as a touch
|
||||
:param hold_ms: number of ms button needs to be touch to register a long touch
|
||||
:param freq_ms: number of ms between checks
|
||||
"""
|
||||
super().__init__(Pin(pin))
|
||||
self.on_press = on_press
|
||||
self.on_long_press = on_long_press
|
||||
self._threshold = threshold
|
||||
self._hold = hold_ms
|
||||
self._freq = freq_ms
|
||||
self._acted = False
|
||||
self._started = 0
|
||||
self._active = True
|
||||
|
||||
def set_actions(self, on_press: callable = None, on_long_press: callable = None):
|
||||
self.on_press = on_press
|
||||
self.on_long_press = on_long_press
|
||||
|
||||
def check(self) -> None:
|
||||
"""timer triggered"""
|
||||
if self._active and (self.on_press or self.on_long_press):
|
||||
v = self.read()
|
||||
if v < self._threshold: # button currently touched
|
||||
if not self._started:
|
||||
self._started = ticks_ms()
|
||||
elif self._hold < ticks_diff(ticks_ms(), self._started): # long press detected
|
||||
if not self._acted:
|
||||
self._active = False
|
||||
self.on_long_press()
|
||||
self._active = self._acted = True
|
||||
elif self._started: # button was released
|
||||
if not self._acted:
|
||||
self._active = False
|
||||
self.on_press()
|
||||
self._active = True
|
||||
else:
|
||||
self._acted = False
|
||||
self._started = 0
|
||||
|
||||
def enable(self):
|
||||
ct.register((self.check, self._freq))
|
||||
|
||||
def disable(self):
|
||||
ct.unregister((self.check, self._freq))
|
|
@ -0,0 +1,56 @@
|
|||
"""
|
||||
ct - Central Timer
|
||||
One timer will be used for all of pyBox system calls, leaving remaining three for custom code
|
||||
Author: Wolf Paulus wolf@paulus.com
|
||||
MIT license; Copyright (c) 2022 wolfpaulus.com
|
||||
"""
|
||||
from micropython import const
|
||||
from machine import Timer
|
||||
import pybox_log as log
|
||||
|
||||
_TIMER_ID = const(0)
|
||||
_TICK_MS = const(1)
|
||||
MIN_FREQ = const(1000 // _TICK_MS * 60 * 60) # about 1 hour
|
||||
MAX_FREQ = const(_TICK_MS)
|
||||
|
||||
|
||||
def process(_) -> None:
|
||||
"""
|
||||
this is called every ${frequency}-th of a second and will call every callback that is due.
|
||||
:return: None
|
||||
"""
|
||||
global _counter
|
||||
_counter = _counter + 1 if _counter < MIN_FREQ else 1 # 1..3_600_000
|
||||
for t in _tasks:
|
||||
if _counter % t[1] == 0:
|
||||
t[0]()
|
||||
|
||||
|
||||
def register(task: ()) -> None:
|
||||
"""
|
||||
Registers a callback and call frequency. The callback should have no parameters
|
||||
and the frequency should be in MAX_FREQ..MIN_FREQ range
|
||||
:param task: callback,freq
|
||||
:return: None
|
||||
"""
|
||||
_tasks.append(task)
|
||||
|
||||
|
||||
def unregister(task: (callable, int)) -> None:
|
||||
"""
|
||||
Unregisters a previously registered task
|
||||
:param task: callback,freq
|
||||
:return: None
|
||||
"""
|
||||
for i in range(len(_tasks) - 1, -1, -1):
|
||||
if _tasks[i][0].__name__ == task[0].__name__:
|
||||
_tasks.pop(i)
|
||||
break
|
||||
else:
|
||||
log.log(log.ERROR, f"Could not unregister {task[0].__name__}")
|
||||
|
||||
|
||||
_counter = 0
|
||||
_tasks = []
|
||||
_timer = Timer(_TIMER_ID)
|
||||
_timer.init(mode=Timer.PERIODIC, period=_TICK_MS, callback=process)
|
|
@ -0,0 +1,32 @@
|
|||
"""
|
||||
fg - Fuel Gauge
|
||||
Author: Wolf Paulus wolf@paulus.com
|
||||
MIT license; Copyright (c) 2022 wolfpaulus.com
|
||||
"""
|
||||
from micropython import const
|
||||
import pybox_i2c as i2c
|
||||
|
||||
DEFAULT_ADDRESS = const(0x36)
|
||||
_MAX17048_VCELL = const(0x02)
|
||||
_MAX17048_SOC = const(0x04)
|
||||
|
||||
|
||||
def is_connected(address=DEFAULT_ADDRESS):
|
||||
"""
|
||||
Determine if a device is connected to the system.
|
||||
:return: True if the device is connected, otherwise False.
|
||||
:rtype: bool
|
||||
"""
|
||||
return i2c.is_device_connected(address)
|
||||
|
||||
|
||||
def voltage(address=DEFAULT_ADDRESS) -> float:
|
||||
"""Current Voltage"""
|
||||
raw = i2c.read16(address, _MAX17048_VCELL)
|
||||
return raw * 78.125 / 1_000_000
|
||||
|
||||
|
||||
def remaining(address=DEFAULT_ADDRESS) -> int:
|
||||
"""remaining capacity in percentage"""
|
||||
raw = i2c.read16(address, _MAX17048_SOC)
|
||||
return raw // 256
|
|
@ -0,0 +1,97 @@
|
|||
"""
|
||||
Communicate with devices on the i2c bus (i2c / Qwiic connector)
|
||||
0x72 Sparkfun 16x2 LCD https://www.sparkfun.com/products/16397
|
||||
0x36 MAX17048 fuel gauge https://cdn.sparkfun.com/assets/b/b/2/c/b/MAX17048.pdf
|
||||
Author: Wolf Paulus wolf@paulus.com
|
||||
MIT license; Copyright (c) 2022 wolfpaulus.com
|
||||
"""
|
||||
from micropython import const
|
||||
from machine import Pin, I2C
|
||||
|
||||
_I2C_SDA = const(21)
|
||||
_I2C_SCL = const(22)
|
||||
_FREQ = const(400000)
|
||||
|
||||
|
||||
def write_bytes(address: int, ba: bytearray) -> None:
|
||||
"""Sends a bytearray to the given device address
|
||||
:param address: I2C address of the device to write to
|
||||
:param ba: bytes to write
|
||||
:return: None
|
||||
"""
|
||||
_i2c.writeto(address, ba)
|
||||
|
||||
|
||||
def write_byte(address: int, data: int) -> None:
|
||||
"""Sends a single byte to the device
|
||||
:param address: I2C address of the device to write to
|
||||
:param data: the data will be cast to a byte and then send.
|
||||
:return: None
|
||||
"""
|
||||
ba = bytearray(1)
|
||||
ba[0] = data
|
||||
_i2c.writeto(address, ba)
|
||||
|
||||
|
||||
def write_cmd(address: int, command: int, value: int) -> None:
|
||||
"""Sends two bytes, e.g. a command and a parameter
|
||||
:param address: I2C address of the device to write to
|
||||
:param command: The "command" or register
|
||||
:param value: The byte to write to the I2C bus
|
||||
:return: None
|
||||
"""
|
||||
ba = bytearray(2)
|
||||
ba[0] = command
|
||||
ba[1] = value
|
||||
write_bytes(address, ba)
|
||||
|
||||
|
||||
def write_block(address: int, command: int, values: [int]) -> None:
|
||||
"""Sends a command byte and a data list
|
||||
:param address: I2C address of the device to write to
|
||||
:param command: The "command" or register
|
||||
:param values: a list of ints (cast to bytes) to write on the I2C bus.
|
||||
:return: None
|
||||
"""
|
||||
ba = bytearray(len(values) + 1)
|
||||
ba[0] = command
|
||||
for i in range(len(values)):
|
||||
ba[i + 1] = values[i]
|
||||
write_bytes(address, ba)
|
||||
|
||||
|
||||
def scan() -> []:
|
||||
"""
|
||||
Scan the i2c bus for devices
|
||||
:return: list of device ids
|
||||
"""
|
||||
return _i2c.scan()
|
||||
|
||||
|
||||
def is_device_connected(address: int) -> bool:
|
||||
"""
|
||||
:param address: The I2C address of the device to to look for
|
||||
:return: True, if the device is on the bus
|
||||
"""
|
||||
devices = scan()
|
||||
if address in devices:
|
||||
try:
|
||||
write_byte(address, 0x0)
|
||||
return True
|
||||
except Exception as ee:
|
||||
print("Error connecting to Device: %X, %s" % (address, ee))
|
||||
pass
|
||||
return False
|
||||
|
||||
|
||||
def read16(address: int, register: int) -> int:
|
||||
"""
|
||||
:param address: The I2C address of the device to to look for
|
||||
:param register: int
|
||||
:return: register value
|
||||
"""
|
||||
buffer = _i2c.readfrom_mem(address, register, 2)
|
||||
return buffer[0] << 8 | buffer[1]
|
||||
|
||||
|
||||
_i2c = I2C(0, sda=Pin(_I2C_SDA), scl=Pin(_I2C_SCL), freq=_FREQ)
|
|
@ -0,0 +1,315 @@
|
|||
"""
|
||||
Accessing SparkFun's 16x2 SerLCD - RGB Text via i2c
|
||||
https://www.sparkfun.com/products/16397
|
||||
Author: Wolf Paulus wolf@paulus.com
|
||||
MIT license; Copyright (c) 2022 wolfpaulus.com
|
||||
"""
|
||||
from micropython import const
|
||||
from utime import sleep_ms
|
||||
import pybox_i2c as i2c
|
||||
import pybox_ct as ct
|
||||
|
||||
_DEFAULT_ADDRESS = const(0x72)
|
||||
_LCD_DISPLAYCONTROL = const(0x08)
|
||||
_LCD_ENTRYMODESET = const(0x04)
|
||||
_LCD_DISPLAYON = const(0x04)
|
||||
_LCD_DISPLAYOFF = const(0x00)
|
||||
_LCD_CURSORON = const(0x02)
|
||||
_LCD_CURSOROFF = const(0x00)
|
||||
_LCD_BLINKON = const(0x01)
|
||||
_LCD_BLINKOFF = const(0x00)
|
||||
_LCD_ENTRYRIGHT = const(0x00)
|
||||
_LCD_ENTRYLEFT = const(0x02)
|
||||
_LCD_ENTRYSHIFTINCREMENT = const(0x01)
|
||||
_LCD_ENTRYSHIFTDECREMENT = const(0x00)
|
||||
_LCD_SETDDRAMADDR = const(0x80)
|
||||
_LCD_RETURNHOME = const(0x02)
|
||||
_SPECIAL_COMMAND = const(0xFE) # Magic number for sending a special command
|
||||
_SETTING_COMMAND = const(0x7C) # Command to change settings: baud, lines, width,..
|
||||
_SET_RGB_COMMAND = const(0x2B) # Command to set backlight RGB value
|
||||
_ENABLE_SYSTEM_MESSAGE_DISPLAY = const(0x2E) # Command to enable system messages being displayed
|
||||
_DISABLE_SYSTEM_MESSAGE_DISPLAY = const(0x2F) # Command to disable system messages being displayed
|
||||
_ENABLE_SPLASH_DISPLAY = const(0x30) # Command to enable splash screen at power on
|
||||
_DISABLE_SPLASH_DISPLAY = const(0x31) # Command to disable splash screen at power on
|
||||
_SAVE_CURRENT_DISPLAY_AS_SPLASH = const(0x0A) # Command to save current text on display as splash
|
||||
_CLEAR_COMMAND = const(0x2D) # Command to clear and home the display
|
||||
_CONTRAST_COMMAND = const(0x18) # Command to change the contrast setting
|
||||
|
||||
_MAX_ROWS = const(2)
|
||||
_MAX_COLUMNS = const(16)
|
||||
|
||||
|
||||
def special_command(command, count=1) -> None:
|
||||
"""
|
||||
Send one (or multiple) special commands to the display. Used by other functions.
|
||||
:param command: Command to send (a single byte)
|
||||
:param count: Number of times to send the command (if ommited, then default is once)
|
||||
:return: Returns true if the I2C write was successful, otherwise False.
|
||||
:rtype: bool
|
||||
"""
|
||||
for i in range(count):
|
||||
i2c.write_cmd(address, _SPECIAL_COMMAND, command)
|
||||
sleep_ms(50)
|
||||
|
||||
|
||||
def command(command) -> None:
|
||||
"""
|
||||
Send one setting command to the display. Used by other functions.
|
||||
:param command: Command to send (a single byte)
|
||||
:return: Returns true if the I2C write was successful, otherwise False.
|
||||
:rtype: bool
|
||||
"""
|
||||
i2c.write_cmd(address, _SETTING_COMMAND, command)
|
||||
|
||||
|
||||
def is_connected() -> bool:
|
||||
"""
|
||||
Determine if a device is connected to the system.
|
||||
:return: True if the device is connected, otherwise False.
|
||||
:rtype: bool
|
||||
"""
|
||||
return i2c.is_device_connected(address)
|
||||
|
||||
|
||||
def enable_system_messages() -> None:
|
||||
"""
|
||||
Enable system messages
|
||||
:return: Returns true if the I2C write was successful, otherwise False.
|
||||
"""
|
||||
command(_ENABLE_SYSTEM_MESSAGE_DISPLAY)
|
||||
sleep_ms(10)
|
||||
|
||||
|
||||
def disable_system_messages() -> None:
|
||||
"""
|
||||
Disable system messages
|
||||
:return: Returns true if the I2C write was successful, otherwise False.
|
||||
"""
|
||||
command(_DISABLE_SYSTEM_MESSAGE_DISPLAY)
|
||||
sleep_ms(10)
|
||||
|
||||
|
||||
def enable_splash() -> None:
|
||||
"""
|
||||
Enable splash screen at power on
|
||||
:return: Returns true if the I2C write was successful, otherwise False.
|
||||
"""
|
||||
command(_ENABLE_SPLASH_DISPLAY)
|
||||
sleep_ms(10)
|
||||
|
||||
|
||||
def disable_splash() -> None:
|
||||
"""
|
||||
Disable splash screen at power on
|
||||
:return: Returns true if the I2C write was successful, otherwise False.
|
||||
"""
|
||||
command(_DISABLE_SPLASH_DISPLAY)
|
||||
sleep_ms(10)
|
||||
|
||||
|
||||
def save_splash() -> None:
|
||||
"""
|
||||
Save the current display as the splash. Saves whatever is currently being displayed into EEPROM
|
||||
This will be displayed at next power on as the splash screen
|
||||
:return: Returns true if the I2C write was successful, otherwise False.
|
||||
"""
|
||||
command(_SAVE_CURRENT_DISPLAY_AS_SPLASH)
|
||||
sleep_ms(10)
|
||||
|
||||
|
||||
def set_backlight(r: int, g: int, b: int) -> None:
|
||||
"""
|
||||
Set backlight with no LCD messages or delays
|
||||
:param r: red backlight value 0-255
|
||||
:param g: green backlight value 0-255
|
||||
:param b: blue backlight value 0-255
|
||||
:return: Returns true if the I2C write was successful, otherwise False.
|
||||
"""
|
||||
# create a block of data bytes to send to the screen
|
||||
# This will include the SET_RGB_COMMAND, and three bytes of backlight values
|
||||
block = [0, 1, 2, 3]
|
||||
block[0] = _SET_RGB_COMMAND # command
|
||||
block[1] = r
|
||||
block[2] = g
|
||||
block[3] = b
|
||||
# send the complete bytes (address, settings command , rgb command , red byte, green byte, blue byte)
|
||||
i2c.write_block(_DEFAULT_ADDRESS, _SETTING_COMMAND, block)
|
||||
sleep_ms(10)
|
||||
|
||||
|
||||
def display():
|
||||
"""
|
||||
Turn the display on quickly.
|
||||
:return: Returns true if the I2C write was successful, otherwise False.
|
||||
"""
|
||||
global displayControl
|
||||
|
||||
displayControl |= _LCD_DISPLAYON
|
||||
return special_command(_LCD_DISPLAYCONTROL | displayControl)
|
||||
|
||||
|
||||
def no_display() -> None:
|
||||
"""
|
||||
Turn the display off quickly.
|
||||
:return: Returns true if the I2C write was successful, otherwise False.
|
||||
"""
|
||||
global displayControl
|
||||
displayControl &= _LCD_DISPLAYON
|
||||
special_command(_LCD_DISPLAYCONTROL | displayControl)
|
||||
|
||||
|
||||
def blink() -> None:
|
||||
"""
|
||||
Turn the blink cursor on.
|
||||
:return: Returns true if the I2C write was successful, otherwise False.
|
||||
"""
|
||||
global displayControl
|
||||
displayControl |= _LCD_BLINKON
|
||||
special_command(_LCD_DISPLAYCONTROL | displayControl)
|
||||
|
||||
|
||||
def no_blink() -> None:
|
||||
"""
|
||||
Turn the blink cursor off.
|
||||
:return: Returns true if the I2C write was successful, otherwise False.
|
||||
"""
|
||||
global displayControl
|
||||
displayControl &= ~_LCD_BLINKON
|
||||
special_command(_LCD_DISPLAYCONTROL | displayControl)
|
||||
|
||||
|
||||
def cursor() -> None:
|
||||
"""
|
||||
Turn the underline cursor on.
|
||||
:return: Returns true if the I2C write was successful, otherwise False.
|
||||
"""
|
||||
global displayControl
|
||||
|
||||
displayControl |= _LCD_CURSORON
|
||||
special_command(_LCD_DISPLAYCONTROL | displayControl)
|
||||
|
||||
|
||||
def no_cursor() -> None:
|
||||
"""
|
||||
Turn the underline cursor off.
|
||||
:return: Returns true if the I2C write was successful, otherwise False.
|
||||
"""
|
||||
global displayControl
|
||||
displayControl &= ~_LCD_CURSORON
|
||||
special_command(_LCD_DISPLAYCONTROL | displayControl)
|
||||
|
||||
|
||||
def set_cursor(col, row) -> None:
|
||||
"""
|
||||
Set the cursor position to a particular column and row.
|
||||
:param col: The column postion (0-19)
|
||||
:param row: The row postion (0-3)
|
||||
:return: Returns true if the I2C write was successful, otherwise False.
|
||||
"""
|
||||
row_offsets = [0x00, 0x40, 0x14, 0x54]
|
||||
|
||||
# keep variables in bounds
|
||||
row = max(0, row) # row cannot be less than 0
|
||||
row = min(row, (_MAX_ROWS - 1)) # row cannot be greater than max rows
|
||||
# construct the cursor "command"
|
||||
command = _LCD_SETDDRAMADDR | (col + row_offsets[row])
|
||||
# send the complete bytes (special command + command)
|
||||
special_command(command)
|
||||
|
||||
|
||||
def set_contrast(contrast) -> None:
|
||||
"""
|
||||
Set the contrast of the LCD screen (0-255)
|
||||
:param contrast: The new contrast value (0-255)
|
||||
:return: Returns true if the I2C write was successful, otherwise False.
|
||||
"""
|
||||
# To set the contrast we need to send 3 bytes:
|
||||
# (1) SETTINGS_COMMAND
|
||||
# (2) CONTRAST_COMMAND
|
||||
# (3) contrast value
|
||||
# To do this, we are going to use writeBlock(),
|
||||
# so we need our "block of bytes" to include
|
||||
# CONTRAST_COMMAND and contrast value
|
||||
|
||||
block = [_CONTRAST_COMMAND, contrast]
|
||||
# send the complete bytes (address, settings command , contrast command, contrast value)
|
||||
i2c.write_block(address, _SETTING_COMMAND, block)
|
||||
sleep_ms(10)
|
||||
|
||||
|
||||
def home() -> None:
|
||||
"""
|
||||
Send the home command to the display.
|
||||
This returns the cursor to return to the beginning of the display,
|
||||
without clearing the display.
|
||||
:return: Returns true if the I2C write was successful, otherwise False.
|
||||
"""
|
||||
special_command(_LCD_RETURNHOME)
|
||||
sleep_ms(10)
|
||||
|
||||
|
||||
def clear_screen() -> None:
|
||||
"""
|
||||
Sends the command to clear the screen
|
||||
:return: Returns true if the I2C write was successful, otherwise False.
|
||||
"""
|
||||
print("", "") # this should unregister potentially running scroll tasks
|
||||
set_backlight(255, 255, 255)
|
||||
set_contrast(0)
|
||||
command(_CLEAR_COMMAND)
|
||||
sleep_ms(10)
|
||||
|
||||
|
||||
def print(row0: str = None, row1: str = None) -> None:
|
||||
"""
|
||||
Outputs the given rows.
|
||||
If a provided row in None, its current content will remain, i.e. use "" to clear that row's content
|
||||
If a provided row's content is longer than the display, the content will scroll
|
||||
Only a not scrolling row will be immediately updated
|
||||
:param row0
|
||||
:param row1
|
||||
:return: None
|
||||
"""
|
||||
global scrolling
|
||||
update = row0 is not None or row1 is not None
|
||||
if update:
|
||||
content = [row0, row1]
|
||||
for r in range(_MAX_ROWS):
|
||||
if content[r] is not None:
|
||||
if len(content[r]) > _MAX_COLUMNS: # add a gap for scrolling
|
||||
content_buffer[r] = content[r] + 3 * " "
|
||||
else: # fill the row
|
||||
content_buffer[r] = content[r] + (_MAX_COLUMNS - len(content[r])) * " "
|
||||
longest = max([len(content_buffer[0]), len(content_buffer[1])])
|
||||
if longest > _MAX_COLUMNS: # need_to_scroll
|
||||
if not scrolling:
|
||||
scrolling = True
|
||||
ct.register((print, scroll_speed))
|
||||
elif scrolling:
|
||||
ct.unregister((print, scroll_speed))
|
||||
scrolling = False
|
||||
|
||||
s = ""
|
||||
for r in range(_MAX_ROWS):
|
||||
if len(content_buffer[r]) > _MAX_COLUMNS and not update: # scroll content
|
||||
if chr(126) not in content_buffer[r]: # move 1st char to the end
|
||||
content_buffer[r] = content_buffer[r][1:] + content_buffer[r][0]
|
||||
else: # (->) in str .. move last char to the start
|
||||
content_buffer[r] = content_buffer[r][-1] + content_buffer[r][0:-1]
|
||||
s += content_buffer[r][:_MAX_COLUMNS]
|
||||
i2c.write_bytes(address, bytes(s, "utf-8"))
|
||||
if update:
|
||||
sleep_ms(50)
|
||||
|
||||
|
||||
displayControl = _LCD_DISPLAYON | _LCD_CURSOROFF | _LCD_BLINKOFF
|
||||
displayMode = _LCD_ENTRYLEFT | _LCD_ENTRYSHIFTDECREMENT
|
||||
content_buffer = [" " * _MAX_COLUMNS, " " * _MAX_COLUMNS]
|
||||
scrolling = False
|
||||
address = _DEFAULT_ADDRESS
|
||||
scroll_speed = 350 # 250 .. 450 looks OK
|
||||
|
||||
special_command(_LCD_DISPLAYCONTROL | displayControl)
|
||||
special_command(_LCD_ENTRYMODESET | displayMode)
|
||||
disable_system_messages()
|
||||
clear_screen()
|
|
@ -0,0 +1,34 @@
|
|||
"""
|
||||
Minimal Logger
|
||||
Author: Wolf Paulus wolf@paulus.com
|
||||
MIT license; Copyright (c) 2022 wolfpaulus.com
|
||||
"""
|
||||
from micropython import const
|
||||
from utime import localtime
|
||||
from uos import remove
|
||||
|
||||
CRITICAL = const(50)
|
||||
ERROR = const(40)
|
||||
WARNING = const(30)
|
||||
INFO = const(20)
|
||||
DEBUG = const(10)
|
||||
|
||||
LOG_FILE = "/log.txt"
|
||||
|
||||
log_level = WARNING
|
||||
|
||||
|
||||
def reset() -> None:
|
||||
try:
|
||||
remove(LOG_FILE)
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
def log(level: int, msg: str):
|
||||
try:
|
||||
if level >= log_level:
|
||||
with open(LOG_FILE, "a") as file:
|
||||
file.write(f"{level} : {localtime()} : {msg}\n")
|
||||
except:
|
||||
pass
|
|
@ -0,0 +1,39 @@
|
|||
"""
|
||||
Fetch Time Zone info from timeapi.io
|
||||
Author: wolf@paulus.com
|
||||
MIT license; Copyright (c) 2022 wolfpaulus.com
|
||||
"""
|
||||
from urequests import request
|
||||
from utime import sleep
|
||||
import pybox_log as log
|
||||
|
||||
|
||||
def get_time_stamp(tz: str) -> dict:
|
||||
"""
|
||||
Fetches the current time stamp at a given time zone:
|
||||
:param tz: str
|
||||
:return: {
|
||||
"year": 2022,
|
||||
"month": 12,
|
||||
"day": 18,
|
||||
"hour": 11,
|
||||
"minute": 4,
|
||||
"seconds": 2,
|
||||
"milliSeconds": 692,
|
||||
"dateTime": "2022-12-18T11:04:02.6927298",
|
||||
"date": "12/18/2022",
|
||||
"time": "11:04",
|
||||
"timeZone": "America/Phoenix",
|
||||
"dayOfWeek": "Sunday",
|
||||
"dstActive": false
|
||||
}
|
||||
"""
|
||||
url = f"https://timeapi.io/api/Time/current/zone?timeZone={tz}"
|
||||
headers = {"accept": "application/json"}
|
||||
for i in range(1, 4):
|
||||
try:
|
||||
return request("GET", url, headers=headers).json()
|
||||
except Exception as e:
|
||||
log.log(log.ERROR, str(e))
|
||||
sleep(i)
|
||||
return {}
|
|
@ -0,0 +1,7 @@
|
|||
set(IDF_TARGET esp32)
|
||||
set(SDKCONFIG_DEFAULTS
|
||||
boards/sdkconfig.base
|
||||
boards/sdkconfig.ble
|
||||
boards/PYBOX/sdkconfig.board
|
||||
)
|
||||
set(MICROPY_FROZEN_MANIFEST ${MICROPY_BOARD_DIR}/manifest.py)
|
|
@ -0,0 +1,2 @@
|
|||
#define MICROPY_HW_BOARD_NAME "pyBox"
|
||||
#define MICROPY_HW_MCU_NAME "ESP32"
|
|
@ -0,0 +1,17 @@
|
|||
CONFIG_FLASHMODE_QIO=y
|
||||
CONFIG_ESPTOOLPY_FLASHFREQ_80M=y
|
||||
CONFIG_ESPTOOLPY_FLASHSIZE_DETECT=y
|
||||
CONFIG_ESPTOOLPY_AFTER_NORESET=y
|
||||
|
||||
CONFIG_ESPTOOLPY_FLASHSIZE_4MB=
|
||||
CONFIG_ESPTOOLPY_FLASHSIZE_8MB=
|
||||
CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y
|
||||
CONFIG_PARTITION_TABLE_CUSTOM=y
|
||||
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions-16MiB.csv"
|
||||
|
||||
# Network name
|
||||
|
||||
CONFIG_LWIP_LOCAL_HOSTNAME="pyBox"
|
||||
|
||||
|
||||
|
Ładowanie…
Reference in New Issue