Merge branch 'main' of https://github.com/adafruit/Adafruit_Learning_System_Guides
|
|
@ -0,0 +1,47 @@
|
||||||
|
// SPDX-FileCopyrightText: 2025 Liz Clark for Adafruit Industries
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
#include <Adafruit_MCP23X17.h>
|
||||||
|
|
||||||
|
#define NOID_1 0 // MCP23XXX pin LED is attached to
|
||||||
|
#define NOID_2 4 // MCP23XXX pin LED is attached to
|
||||||
|
|
||||||
|
Adafruit_MCP23X17 mcp;
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
Serial.begin(115200);
|
||||||
|
while (!Serial);
|
||||||
|
Serial.println("8 Channel Solenoid Driver Demo");
|
||||||
|
if (!mcp.begin_I2C()) {
|
||||||
|
Serial.println("Couldn't find MCP23017..");
|
||||||
|
while (1);
|
||||||
|
}
|
||||||
|
mcp.pinMode(NOID_1, OUTPUT);
|
||||||
|
mcp.pinMode(NOID_2, OUTPUT);
|
||||||
|
|
||||||
|
Serial.println("Found MCP23017, looping...");
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
Serial.println("Solenoid 1!");
|
||||||
|
mcp.digitalWrite(NOID_1, HIGH);
|
||||||
|
delay(500);
|
||||||
|
mcp.digitalWrite(NOID_1, LOW);
|
||||||
|
delay(500);
|
||||||
|
Serial.println("Solenoid 2!");
|
||||||
|
mcp.digitalWrite(NOID_2, HIGH);
|
||||||
|
delay(500);
|
||||||
|
mcp.digitalWrite(NOID_2, LOW);
|
||||||
|
delay(500);
|
||||||
|
Serial.println("Together!");
|
||||||
|
mcp.digitalWrite(NOID_1, HIGH);
|
||||||
|
mcp.digitalWrite(NOID_2, HIGH);
|
||||||
|
delay(1000);
|
||||||
|
mcp.digitalWrite(NOID_1, LOW);
|
||||||
|
mcp.digitalWrite(NOID_2, LOW);
|
||||||
|
delay(2000);
|
||||||
|
Serial.println("Repeat!");
|
||||||
|
Serial.println();
|
||||||
|
delay(500);
|
||||||
|
}
|
||||||
38
8_Channel_Solenoid_Driver/CircuitPython/code.py
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
# SPDX-FileCopyrightText: 2025 Liz Clark for Adafruit Industries
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
import time
|
||||||
|
import board
|
||||||
|
from adafruit_mcp230xx.mcp23017 import MCP23017
|
||||||
|
|
||||||
|
i2c = board.STEMMA_I2C()
|
||||||
|
|
||||||
|
mcp = MCP23017(i2c)
|
||||||
|
|
||||||
|
noid_1 = mcp.get_pin(0)
|
||||||
|
noid_2 = mcp.get_pin(4)
|
||||||
|
noid_1.switch_to_output(value=False)
|
||||||
|
noid_2.switch_to_output(value=False)
|
||||||
|
|
||||||
|
while True:
|
||||||
|
noid_1.value = True
|
||||||
|
print(f"Solenoid 1: {noid_1.value}, Solenoid 2: {noid_2.value}")
|
||||||
|
time.sleep(0.2)
|
||||||
|
noid_1.value = False
|
||||||
|
print(f"Solenoid 1: {noid_1.value}, Solenoid 2: {noid_2.value}")
|
||||||
|
time.sleep(0.2)
|
||||||
|
noid_2.value = True
|
||||||
|
print(f"Solenoid 1: {noid_1.value}, Solenoid 2: {noid_2.value}")
|
||||||
|
time.sleep(0.2)
|
||||||
|
noid_2.value = False
|
||||||
|
print(f"Solenoid 1: {noid_1.value}, Solenoid 2: {noid_2.value}")
|
||||||
|
time.sleep(1)
|
||||||
|
noid_1.value = True
|
||||||
|
noid_2.value = True
|
||||||
|
print(f"Solenoid 1: {noid_1.value}, Solenoid 2: {noid_2.value}")
|
||||||
|
time.sleep(1)
|
||||||
|
noid_1.value = False
|
||||||
|
noid_2.value = False
|
||||||
|
print(f"Solenoid 1: {noid_1.value}, Solenoid 2: {noid_2.value}")
|
||||||
|
time.sleep(2)
|
||||||
|
|
@ -58,7 +58,7 @@ group.append(press_data)
|
||||||
|
|
||||||
display.root_group = group
|
display.root_group = group
|
||||||
|
|
||||||
# function to convert celcius to fahrenheit
|
# function to convert celsius to fahrenheit
|
||||||
def c_to_f(temp):
|
def c_to_f(temp):
|
||||||
temp_f = (temp * 9/5) + 32
|
temp_f = (temp * 9/5) + 32
|
||||||
return temp_f
|
return temp_f
|
||||||
|
|
@ -87,12 +87,12 @@ while True:
|
||||||
print(servo_value)
|
print(servo_value)
|
||||||
# if metric units...
|
# if metric units...
|
||||||
if metric_units:
|
if metric_units:
|
||||||
# update temp & pressure text in celcius and hPa
|
# update temp & pressure text in celsius and hPa
|
||||||
temp_data.text = "%0.1f ºC" % bmp280.temperature
|
temp_data.text = "%0.1f ºC" % bmp280.temperature
|
||||||
press_data.text = "%0.1f hPa" % bmp280.pressure
|
press_data.text = "%0.1f hPa" % bmp280.pressure
|
||||||
# if imperial units...
|
# if imperial units...
|
||||||
else:
|
else:
|
||||||
# convert celcius to fahrenheit
|
# convert celsius to fahrenheit
|
||||||
temp_fahrenheit = c_to_f(bmp280.temperature)
|
temp_fahrenheit = c_to_f(bmp280.temperature)
|
||||||
# convert hPa to inHg
|
# convert hPa to inHg
|
||||||
pressure_inHg = hpa_to_inHg(bmp280.pressure)
|
pressure_inHg = hpa_to_inHg(bmp280.pressure)
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ pixels = neopixel.NeoPixel(board.D1, # NeoPixels on pin D1
|
||||||
# For the Gemma M0 onboard DotStar LED
|
# For the Gemma M0 onboard DotStar LED
|
||||||
dotstar = adafruit_dotstar.DotStar(board.APA102_SCK, board.APA102_MOSI, 1)
|
dotstar = adafruit_dotstar.DotStar(board.APA102_SCK, board.APA102_MOSI, 1)
|
||||||
|
|
||||||
def deg_f(deg_c): # Convert Celcius to Fahrenheit
|
def deg_f(deg_c): # Convert celsius to Fahrenheit
|
||||||
return(deg_c * 9 / 5) + 32.0
|
return(deg_c * 9 / 5) + 32.0
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
|
|
|
||||||
251
Metro/Metro_RP2350_Match3/autosave_resume_demo/code.py
Normal file
|
|
@ -0,0 +1,251 @@
|
||||||
|
# SPDX-FileCopyrightText: 2024 Tim Cocks for Adafruit Industries
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
"""
|
||||||
|
This example demonstrates basic autosave and resume functionality. There are two buttons
|
||||||
|
that can be clicked to increment respective counters. The number of clicks is stored
|
||||||
|
in a game_state dictionary and saved to a data file on the SDCard. When the code first
|
||||||
|
launches it will read the data file and load the game_state from it.
|
||||||
|
"""
|
||||||
|
import array
|
||||||
|
from io import BytesIO
|
||||||
|
import os
|
||||||
|
|
||||||
|
import board
|
||||||
|
import busio
|
||||||
|
import digitalio
|
||||||
|
import displayio
|
||||||
|
import msgpack
|
||||||
|
import storage
|
||||||
|
import supervisor
|
||||||
|
import terminalio
|
||||||
|
import usb
|
||||||
|
|
||||||
|
import adafruit_sdcard
|
||||||
|
from adafruit_display_text.bitmap_label import Label
|
||||||
|
from adafruit_button import Button
|
||||||
|
|
||||||
|
# use the default built-in display
|
||||||
|
display = supervisor.runtime.display
|
||||||
|
|
||||||
|
# button configuration
|
||||||
|
BUTTON_WIDTH = 100
|
||||||
|
BUTTON_HEIGHT = 30
|
||||||
|
BUTTON_STYLE = Button.ROUNDRECT
|
||||||
|
|
||||||
|
# game state object will get loaded from SDCard
|
||||||
|
# or a new one initialized as a dictionary
|
||||||
|
game_state = None
|
||||||
|
|
||||||
|
save_to = None
|
||||||
|
|
||||||
|
# boolean variables for possible SDCard states
|
||||||
|
sd_pins_in_use = False
|
||||||
|
|
||||||
|
# The SD_CS pin is the chip select line.
|
||||||
|
SD_CS = board.SD_CS
|
||||||
|
|
||||||
|
# try to Connect to the sdcard card and mount the filesystem.
|
||||||
|
try:
|
||||||
|
# initialze CS pin
|
||||||
|
cs = digitalio.DigitalInOut(SD_CS)
|
||||||
|
except ValueError:
|
||||||
|
# likely the SDCard was auto-initialized by the core
|
||||||
|
sd_pins_in_use = True
|
||||||
|
|
||||||
|
try:
|
||||||
|
# if sd CS pin was not in use
|
||||||
|
if not sd_pins_in_use:
|
||||||
|
# try to initialize and mount the SDCard
|
||||||
|
sdcard = adafruit_sdcard.SDCard(
|
||||||
|
busio.SPI(board.SD_SCK, board.SD_MOSI, board.SD_MISO), cs
|
||||||
|
)
|
||||||
|
vfs = storage.VfsFat(sdcard)
|
||||||
|
storage.mount(vfs, "/sd")
|
||||||
|
|
||||||
|
# check for the autosave data file
|
||||||
|
if "autosave_demo.dat" in os.listdir("/sd/"):
|
||||||
|
# if the file is found read data from it into a BytesIO buffer
|
||||||
|
buffer = BytesIO()
|
||||||
|
with open("/sd/autosave_demo.dat", "rb") as f:
|
||||||
|
buffer.write(f.read())
|
||||||
|
buffer.seek(0)
|
||||||
|
|
||||||
|
# unpack the game_state object from the read data in the buffer
|
||||||
|
game_state = msgpack.unpack(buffer)
|
||||||
|
print(game_state)
|
||||||
|
|
||||||
|
# if placeholder.txt file does not exist
|
||||||
|
if "placeholder.txt" not in os.listdir("/sd/"):
|
||||||
|
# if we made it to here then /sd/ exists and has a card
|
||||||
|
# so use it for save data
|
||||||
|
save_to = "/sd/autosave_demo.dat"
|
||||||
|
except OSError as e:
|
||||||
|
# sdcard init or mounting failed
|
||||||
|
raise OSError(
|
||||||
|
"This demo requires an SDCard. Please power off the device " +
|
||||||
|
"insert an SDCard and then plug it back in."
|
||||||
|
) from e
|
||||||
|
|
||||||
|
# if no saved game_state was loaded
|
||||||
|
if game_state is None:
|
||||||
|
# create a new game state dictionary
|
||||||
|
game_state = {"pink_count": 0, "blue_count": 0}
|
||||||
|
|
||||||
|
# Make the display context
|
||||||
|
main_group = displayio.Group()
|
||||||
|
display.root_group = main_group
|
||||||
|
|
||||||
|
# make buttons
|
||||||
|
blue_button = Button(
|
||||||
|
x=30,
|
||||||
|
y=40,
|
||||||
|
width=BUTTON_WIDTH,
|
||||||
|
height=BUTTON_HEIGHT,
|
||||||
|
style=BUTTON_STYLE,
|
||||||
|
fill_color=0x0000FF,
|
||||||
|
outline_color=0xFFFFFF,
|
||||||
|
label="BLUE",
|
||||||
|
label_font=terminalio.FONT,
|
||||||
|
label_color=0xFFFFFF,
|
||||||
|
)
|
||||||
|
|
||||||
|
pink_button = Button(
|
||||||
|
x=30,
|
||||||
|
y=80,
|
||||||
|
width=BUTTON_WIDTH,
|
||||||
|
height=BUTTON_HEIGHT,
|
||||||
|
style=BUTTON_STYLE,
|
||||||
|
fill_color=0xFF00FF,
|
||||||
|
outline_color=0xFFFFFF,
|
||||||
|
label="PINK",
|
||||||
|
label_font=terminalio.FONT,
|
||||||
|
label_color=0x000000,
|
||||||
|
)
|
||||||
|
|
||||||
|
# add them to a list for easy iteration
|
||||||
|
all_buttons = [blue_button, pink_button]
|
||||||
|
|
||||||
|
# Add buttons to the display context
|
||||||
|
main_group.append(blue_button)
|
||||||
|
main_group.append(pink_button)
|
||||||
|
|
||||||
|
# make labels for each button
|
||||||
|
blue_lbl = Label(
|
||||||
|
terminalio.FONT, text=f"Blue: {game_state['blue_count']}", color=0x3F3FFF
|
||||||
|
)
|
||||||
|
blue_lbl.anchor_point = (0, 0)
|
||||||
|
blue_lbl.anchored_position = (4, 4)
|
||||||
|
pink_lbl = Label(
|
||||||
|
terminalio.FONT, text=f"Pink: {game_state['pink_count']}", color=0xFF00FF
|
||||||
|
)
|
||||||
|
pink_lbl.anchor_point = (0, 0)
|
||||||
|
pink_lbl.anchored_position = (4, 4 + 14)
|
||||||
|
main_group.append(blue_lbl)
|
||||||
|
main_group.append(pink_lbl)
|
||||||
|
|
||||||
|
# load the mouse cursor bitmap
|
||||||
|
mouse_bmp = displayio.OnDiskBitmap("mouse_cursor.bmp")
|
||||||
|
|
||||||
|
# make the background pink pixels transparent
|
||||||
|
mouse_bmp.pixel_shader.make_transparent(0)
|
||||||
|
|
||||||
|
# create a TileGrid for the mouse, using its bitmap and pixel_shader
|
||||||
|
mouse_tg = displayio.TileGrid(mouse_bmp, pixel_shader=mouse_bmp.pixel_shader)
|
||||||
|
|
||||||
|
# move it to the center of the display
|
||||||
|
mouse_tg.x = display.width // 2
|
||||||
|
mouse_tg.y = display.height // 2
|
||||||
|
|
||||||
|
# add the mouse tilegrid to main_group
|
||||||
|
main_group.append(mouse_tg)
|
||||||
|
|
||||||
|
# scan for connected USB device and loop over any found
|
||||||
|
for device in usb.core.find(find_all=True):
|
||||||
|
# print device info
|
||||||
|
print(f"{device.idVendor:04x}:{device.idProduct:04x}")
|
||||||
|
print(device.manufacturer, device.product)
|
||||||
|
print(device.serial_number)
|
||||||
|
# assume the device is the mouse
|
||||||
|
mouse = device
|
||||||
|
|
||||||
|
# detach the kernel driver if needed
|
||||||
|
if mouse.is_kernel_driver_active(0):
|
||||||
|
mouse.detach_kernel_driver(0)
|
||||||
|
|
||||||
|
# set configuration on the mouse so we can use it
|
||||||
|
mouse.set_configuration()
|
||||||
|
|
||||||
|
# buffer to hold mouse data
|
||||||
|
# Boot mice have 4 byte reports
|
||||||
|
buf = array.array("b", [0] * 4)
|
||||||
|
|
||||||
|
|
||||||
|
def save_game_state():
|
||||||
|
"""
|
||||||
|
msgpack the game_state and save it to the autosave data file
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
b = BytesIO()
|
||||||
|
msgpack.pack(game_state, b)
|
||||||
|
b.seek(0)
|
||||||
|
with open(save_to, "wb") as savefile:
|
||||||
|
savefile.write(b.read())
|
||||||
|
|
||||||
|
|
||||||
|
# main loop
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
# attempt to read data from the mouse
|
||||||
|
# 10ms timeout, so we don't block long if there
|
||||||
|
# is no data
|
||||||
|
count = mouse.read(0x81, buf, timeout=10)
|
||||||
|
except usb.core.USBTimeoutError:
|
||||||
|
# skip the rest of the loop if there is no data
|
||||||
|
continue
|
||||||
|
|
||||||
|
# update the mouse tilegrid x and y coordinates
|
||||||
|
# based on the delta values read from the mouse
|
||||||
|
mouse_tg.x = max(0, min(display.width - 1, mouse_tg.x + buf[1]))
|
||||||
|
mouse_tg.y = max(0, min(display.height - 1, mouse_tg.y + buf[2]))
|
||||||
|
|
||||||
|
# if left click is pressed
|
||||||
|
if buf[0] & (1 << 0) != 0:
|
||||||
|
# get the current cursor coordinates
|
||||||
|
coords = (mouse_tg.x, mouse_tg.y, 0)
|
||||||
|
|
||||||
|
# loop over the buttons
|
||||||
|
for button in all_buttons:
|
||||||
|
# if the current button contains the mouse coords
|
||||||
|
if button.contains(coords):
|
||||||
|
# if the button isn't already in the selected state
|
||||||
|
if not button.selected:
|
||||||
|
# enter selected state
|
||||||
|
button.selected = True
|
||||||
|
|
||||||
|
# if it is the pink button
|
||||||
|
if button == pink_button:
|
||||||
|
# increment pink count
|
||||||
|
game_state["pink_count"] += 1
|
||||||
|
# update the label
|
||||||
|
pink_lbl.text = f"Pink: {game_state['pink_count']}"
|
||||||
|
|
||||||
|
# if it is the blue button
|
||||||
|
elif button == blue_button:
|
||||||
|
# increment blue count
|
||||||
|
game_state["blue_count"] += 1
|
||||||
|
# update the label
|
||||||
|
blue_lbl.text = f"Blue: {game_state['blue_count']}"
|
||||||
|
|
||||||
|
# save the new game state
|
||||||
|
save_game_state()
|
||||||
|
|
||||||
|
# if the click is not on the current button
|
||||||
|
else:
|
||||||
|
# set this button as not selected
|
||||||
|
button.selected = False
|
||||||
|
|
||||||
|
# left click is not pressed
|
||||||
|
else:
|
||||||
|
# set all buttons as not selected
|
||||||
|
for button in all_buttons:
|
||||||
|
button.selected = False
|
||||||
BIN
Metro/Metro_RP2350_Match3/autosave_resume_demo/mouse_cursor.bmp
Normal file
|
After Width: | Height: | Size: 198 B |
BIN
Metro/Metro_RP2350_Match3/match3_game/btn_exit.bmp
Normal file
|
After Width: | Height: | Size: 922 B |
BIN
Metro/Metro_RP2350_Match3/match3_game/btn_no_set.bmp
Normal file
|
After Width: | Height: | Size: 922 B |
BIN
Metro/Metro_RP2350_Match3/match3_game/btn_play_again.bmp
Normal file
|
After Width: | Height: | Size: 922 B |
458
Metro/Metro_RP2350_Match3/match3_game/code.py
Normal file
|
|
@ -0,0 +1,458 @@
|
||||||
|
# SPDX-FileCopyrightText: Copyright (c) 2025 Tim Cocks for Adafruit Industries
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
"""
|
||||||
|
Match3 game inspired by the Set card game. Two players compete
|
||||||
|
to find sets of cards that share matching or mis-matching traits.
|
||||||
|
"""
|
||||||
|
import array
|
||||||
|
import atexit
|
||||||
|
import io
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
|
||||||
|
import board
|
||||||
|
import busio
|
||||||
|
import digitalio
|
||||||
|
import supervisor
|
||||||
|
import terminalio
|
||||||
|
import usb
|
||||||
|
from tilepalettemapper import TilePaletteMapper
|
||||||
|
from displayio import TileGrid, Group, Palette, OnDiskBitmap, Bitmap
|
||||||
|
from adafruit_display_text.text_box import TextBox
|
||||||
|
import adafruit_usb_host_descriptors
|
||||||
|
from adafruit_debouncer import Debouncer
|
||||||
|
import adafruit_sdcard
|
||||||
|
import msgpack
|
||||||
|
import storage
|
||||||
|
from match3_game_helpers import (
|
||||||
|
Match3Game,
|
||||||
|
STATE_GAMEOVER,
|
||||||
|
STATE_PLAYING_SETCALLED,
|
||||||
|
GameOverException,
|
||||||
|
)
|
||||||
|
|
||||||
|
original_autoreload_val = supervisor.runtime.autoreload
|
||||||
|
supervisor.runtime.autoreload = False
|
||||||
|
|
||||||
|
AUTOSAVE_FILENAME = "match3_game_autosave.dat"
|
||||||
|
|
||||||
|
main_group = Group()
|
||||||
|
display = supervisor.runtime.display
|
||||||
|
|
||||||
|
# set up scale factor of 2 for larger display resolution
|
||||||
|
scale_factor = 1
|
||||||
|
if display.width > 360:
|
||||||
|
scale_factor = 2
|
||||||
|
main_group.scale = scale_factor
|
||||||
|
|
||||||
|
save_to = None
|
||||||
|
game_state = None
|
||||||
|
try:
|
||||||
|
# check for autosave file on CPSAVES drive
|
||||||
|
if AUTOSAVE_FILENAME in os.listdir("/saves/"):
|
||||||
|
savegame_buffer = io.BytesIO()
|
||||||
|
with open(f"/saves/{AUTOSAVE_FILENAME}", "rb") as f:
|
||||||
|
savegame_buffer.write(f.read())
|
||||||
|
savegame_buffer.seek(0)
|
||||||
|
game_state = msgpack.unpack(savegame_buffer)
|
||||||
|
print(game_state)
|
||||||
|
|
||||||
|
# if we made it to here then /saves/ exist so use it for
|
||||||
|
# save data
|
||||||
|
save_to = f"/saves/{AUTOSAVE_FILENAME}"
|
||||||
|
except OSError as e:
|
||||||
|
# no /saves/ dir likely means no CPSAVES
|
||||||
|
pass
|
||||||
|
|
||||||
|
sd_pins_in_use = False
|
||||||
|
|
||||||
|
if game_state is None:
|
||||||
|
# try to use sdcard for saves
|
||||||
|
# The SD_CS pin is the chip select line.
|
||||||
|
SD_CS = board.SD_CS
|
||||||
|
|
||||||
|
# Connect to the card and mount the filesystem.
|
||||||
|
try:
|
||||||
|
cs = digitalio.DigitalInOut(SD_CS)
|
||||||
|
except ValueError:
|
||||||
|
sd_pins_in_use = True
|
||||||
|
|
||||||
|
print(f"sd pins in use: {sd_pins_in_use}")
|
||||||
|
try:
|
||||||
|
if not sd_pins_in_use:
|
||||||
|
sdcard = adafruit_sdcard.SDCard(
|
||||||
|
busio.SPI(board.SD_SCK, board.SD_MOSI, board.SD_MISO), cs
|
||||||
|
)
|
||||||
|
vfs = storage.VfsFat(sdcard)
|
||||||
|
storage.mount(vfs, "/sd")
|
||||||
|
|
||||||
|
if "set_game_autosave.dat" in os.listdir("/sd/"):
|
||||||
|
savegame_buffer = io.BytesIO()
|
||||||
|
with open("/sd/set_game_autosave.dat", "rb") as f:
|
||||||
|
savegame_buffer.write(f.read())
|
||||||
|
savegame_buffer.seek(0)
|
||||||
|
game_state = msgpack.unpack(savegame_buffer)
|
||||||
|
print(game_state)
|
||||||
|
|
||||||
|
if "placeholder.txt" not in os.listdir("/sd/"):
|
||||||
|
# if we made it to here then /sd/ exists and has a card
|
||||||
|
# so use it for save data
|
||||||
|
save_to = "/sd/set_game_autosave.dat"
|
||||||
|
except OSError:
|
||||||
|
# no SDcard
|
||||||
|
pass
|
||||||
|
|
||||||
|
# background color
|
||||||
|
bg_bmp = Bitmap(
|
||||||
|
display.width // scale_factor // 10, display.height // scale_factor // 10, 1
|
||||||
|
)
|
||||||
|
bg_palette = Palette(1)
|
||||||
|
bg_palette[0] = 0x888888
|
||||||
|
bg_tg = TileGrid(bg_bmp, pixel_shader=bg_palette)
|
||||||
|
bg_group = Group(scale=10)
|
||||||
|
bg_group.append(bg_tg)
|
||||||
|
main_group.append(bg_group)
|
||||||
|
|
||||||
|
# create Game helper object
|
||||||
|
match3_game = Match3Game(
|
||||||
|
game_state=game_state,
|
||||||
|
display_size=(display.width // scale_factor, display.height // scale_factor),
|
||||||
|
save_location=save_to,
|
||||||
|
)
|
||||||
|
main_group.append(match3_game)
|
||||||
|
|
||||||
|
# create a group to hold the game over elements
|
||||||
|
game_over_group = Group()
|
||||||
|
|
||||||
|
# create a TextBox to hold the game over message
|
||||||
|
game_over_label = TextBox(
|
||||||
|
terminalio.FONT,
|
||||||
|
text="",
|
||||||
|
color=0xFFFFFF,
|
||||||
|
background_color=0x111111,
|
||||||
|
width=display.width // scale_factor // 2,
|
||||||
|
height=110,
|
||||||
|
align=TextBox.ALIGN_CENTER,
|
||||||
|
)
|
||||||
|
# move it to the center top of the display
|
||||||
|
game_over_label.anchor_point = (0, 0)
|
||||||
|
game_over_label.anchored_position = (
|
||||||
|
display.width // scale_factor // 2 - (game_over_label.width) // 2,
|
||||||
|
40,
|
||||||
|
)
|
||||||
|
|
||||||
|
# make it hidden, we'll show it when the game is over.
|
||||||
|
game_over_group.hidden = True
|
||||||
|
|
||||||
|
# add the game over lable to the game over group
|
||||||
|
game_over_group.append(game_over_label)
|
||||||
|
|
||||||
|
# load the play again, and exit button bitmaps
|
||||||
|
play_again_btn_bmp = OnDiskBitmap("btn_play_again.bmp")
|
||||||
|
exit_btn_bmp = OnDiskBitmap("btn_exit.bmp")
|
||||||
|
|
||||||
|
# create TileGrid for the play again button
|
||||||
|
play_again_btn = TileGrid(
|
||||||
|
bitmap=play_again_btn_bmp, pixel_shader=play_again_btn_bmp.pixel_shader
|
||||||
|
)
|
||||||
|
|
||||||
|
# transparent pixels in the corners for the rounded corner effect
|
||||||
|
play_again_btn_bmp.pixel_shader.make_transparent(0)
|
||||||
|
|
||||||
|
# centered within the display, offset to the left
|
||||||
|
play_again_btn.x = (
|
||||||
|
display.width // scale_factor // 2 - (play_again_btn_bmp.width) // 2 - 30
|
||||||
|
)
|
||||||
|
|
||||||
|
# inside the bounds of the game over label, so it looks like a dialog visually
|
||||||
|
play_again_btn.y = 100
|
||||||
|
|
||||||
|
# create TileGrid for the exit button
|
||||||
|
exit_btn = TileGrid(bitmap=exit_btn_bmp, pixel_shader=exit_btn_bmp.pixel_shader)
|
||||||
|
|
||||||
|
# transparent pixels in the corners for the rounded corner effect
|
||||||
|
exit_btn_bmp.pixel_shader.make_transparent(0)
|
||||||
|
|
||||||
|
# centered within the display, offset to the right
|
||||||
|
exit_btn.x = display.width // scale_factor // 2 - (exit_btn_bmp.width) // 2 + 30
|
||||||
|
|
||||||
|
# inside the bounds of the game over label, so it looks like a dialog visually
|
||||||
|
exit_btn.y = 100
|
||||||
|
|
||||||
|
# add the play again and exit buttons to the game over group
|
||||||
|
game_over_group.append(play_again_btn)
|
||||||
|
game_over_group.append(exit_btn)
|
||||||
|
main_group.append(game_over_group)
|
||||||
|
|
||||||
|
# wait a second for USB devices to be ready
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
# load the mouse bitmap
|
||||||
|
mouse_bmp = OnDiskBitmap("mouse_cursor.bmp")
|
||||||
|
|
||||||
|
# make the background pink pixels transparent
|
||||||
|
mouse_bmp.pixel_shader.make_transparent(0)
|
||||||
|
|
||||||
|
# list for mouse tilegrids
|
||||||
|
mouse_tgs = []
|
||||||
|
# list for palette mappers, one for each mouse
|
||||||
|
palette_mappers = []
|
||||||
|
# list for mouse colors
|
||||||
|
colors = [0x2244FF, 0xFFFF00]
|
||||||
|
|
||||||
|
# remap palette will have the 3 colors from mouse bitmap
|
||||||
|
# and the two colors from the mouse colors list
|
||||||
|
remap_palette = Palette(3 + len(colors))
|
||||||
|
# index 0 is transparent
|
||||||
|
remap_palette.make_transparent(0)
|
||||||
|
|
||||||
|
# copy the 3 colors from the mouse bitmap palette
|
||||||
|
for i in range(3):
|
||||||
|
remap_palette[i] = mouse_bmp.pixel_shader[i]
|
||||||
|
|
||||||
|
# copy the 2 colors from the mouse colors list
|
||||||
|
for i in range(2):
|
||||||
|
remap_palette[i + 3] = colors[i]
|
||||||
|
|
||||||
|
# create tile palette mappers
|
||||||
|
for i in range(2):
|
||||||
|
palette_mapper = TilePaletteMapper(remap_palette, 3, 1, 1)
|
||||||
|
# remap index 2 to each of the colors in mouse colors list
|
||||||
|
palette_mapper[0] = [0, 1, i + 3]
|
||||||
|
palette_mappers.append(palette_mapper)
|
||||||
|
|
||||||
|
# create tilegrid for each mouse
|
||||||
|
mouse_tg = TileGrid(mouse_bmp, pixel_shader=palette_mapper)
|
||||||
|
mouse_tg.x = display.width // scale_factor // 2 - (i * 12)
|
||||||
|
mouse_tg.y = display.height // scale_factor // 2
|
||||||
|
mouse_tgs.append(mouse_tg)
|
||||||
|
|
||||||
|
# USB info lists
|
||||||
|
mouse_interface_indexes = []
|
||||||
|
mouse_endpoint_addresses = []
|
||||||
|
kernel_driver_active_flags = []
|
||||||
|
# USB device object instance list
|
||||||
|
mice = []
|
||||||
|
# buffers list for mouse packet data
|
||||||
|
mouse_bufs = []
|
||||||
|
# debouncers list for debouncing mouse left clicks
|
||||||
|
mouse_debouncers = []
|
||||||
|
|
||||||
|
# scan for connected USB devices
|
||||||
|
for device in usb.core.find(find_all=True):
|
||||||
|
# check if current device is has a boot mouse endpoint
|
||||||
|
mouse_interface_index, mouse_endpoint_address = (
|
||||||
|
adafruit_usb_host_descriptors.find_boot_mouse_endpoint(device)
|
||||||
|
)
|
||||||
|
if mouse_interface_index is not None and mouse_endpoint_address is not None:
|
||||||
|
# if it does have a boot mouse endpoint then add information to the
|
||||||
|
# usb info lists
|
||||||
|
mouse_interface_indexes.append(mouse_interface_index)
|
||||||
|
mouse_endpoint_addresses.append(mouse_endpoint_address)
|
||||||
|
|
||||||
|
# add the mouse device instance to list
|
||||||
|
mice.append(device)
|
||||||
|
print(
|
||||||
|
f"mouse interface: {mouse_interface_index} "
|
||||||
|
+ f"endpoint_address: {hex(mouse_endpoint_address)}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# detach kernel driver if needed
|
||||||
|
kernel_driver_active_flags.append(device.is_kernel_driver_active(0))
|
||||||
|
if device.is_kernel_driver_active(0):
|
||||||
|
device.detach_kernel_driver(0)
|
||||||
|
|
||||||
|
# set the mouse configuration so it can be used
|
||||||
|
device.set_configuration()
|
||||||
|
|
||||||
|
|
||||||
|
def is_mouse1_left_clicked():
|
||||||
|
"""
|
||||||
|
Check if mouse 1 left click is pressed
|
||||||
|
:return: True if mouse 1 left click is pressed
|
||||||
|
"""
|
||||||
|
return is_left_mouse_clicked(mouse_bufs[0])
|
||||||
|
|
||||||
|
|
||||||
|
def is_mouse2_left_clicked():
|
||||||
|
"""
|
||||||
|
Check if mouse 2 left click is pressed
|
||||||
|
:return: True if mouse 2 left click is pressed
|
||||||
|
"""
|
||||||
|
return is_left_mouse_clicked(mouse_bufs[1])
|
||||||
|
|
||||||
|
|
||||||
|
def is_left_mouse_clicked(buf):
|
||||||
|
"""
|
||||||
|
Check if a mouse is pressed given its packet buffer
|
||||||
|
filled with read data
|
||||||
|
:param buf: the buffer containing the packet data
|
||||||
|
:return: True if mouse left click is pressed
|
||||||
|
"""
|
||||||
|
val = buf[0] & (1 << 0) != 0
|
||||||
|
return val
|
||||||
|
|
||||||
|
|
||||||
|
def is_right_mouse_clicked(buf):
|
||||||
|
"""
|
||||||
|
check if a mouse right click is pressed given its packet buffer
|
||||||
|
:param buf: the buffer containing the packet data
|
||||||
|
:return: True if mouse right click is pressed
|
||||||
|
"""
|
||||||
|
val = buf[0] & (1 << 1) != 0
|
||||||
|
return val
|
||||||
|
|
||||||
|
|
||||||
|
# print(f"addresses: {mouse_endpoint_addresses}")
|
||||||
|
# print(f"indexes: {mouse_interface_indexes}")
|
||||||
|
|
||||||
|
for mouse_tg in mouse_tgs:
|
||||||
|
# add the mouse to the main group
|
||||||
|
main_group.append(mouse_tg)
|
||||||
|
|
||||||
|
# Buffer to hold data read from the mouse
|
||||||
|
# Boot mice have 4 byte reports
|
||||||
|
mouse_bufs.append(array.array("b", [0] * 8))
|
||||||
|
|
||||||
|
# create debouncer objects for left click functions
|
||||||
|
mouse_debouncers.append(Debouncer(is_mouse1_left_clicked))
|
||||||
|
mouse_debouncers.append(Debouncer(is_mouse2_left_clicked))
|
||||||
|
|
||||||
|
# set main_group as root_group, so it is visible on the display
|
||||||
|
display.root_group = main_group
|
||||||
|
|
||||||
|
# variable to hold winning player
|
||||||
|
winner = None
|
||||||
|
|
||||||
|
|
||||||
|
def get_mouse_deltas(buffer, read_count):
|
||||||
|
"""
|
||||||
|
Given a mouse packet buffer and a read count of number of bytes read,
|
||||||
|
return the delta x and y values of the mouse.
|
||||||
|
:param buffer: the buffer containing the packet data
|
||||||
|
:param read_count: the number of bytes read from the mouse
|
||||||
|
:return: tuple containing x and y delta values
|
||||||
|
"""
|
||||||
|
if read_count == 4:
|
||||||
|
delta_x = buffer[1]
|
||||||
|
delta_y = buffer[2]
|
||||||
|
elif read_count == 8:
|
||||||
|
delta_x = buffer[2]
|
||||||
|
delta_y = buffer[4]
|
||||||
|
else:
|
||||||
|
raise ValueError(f"Unsupported mouse packet size: {read_count}, must be 4 or 8")
|
||||||
|
return delta_x, delta_y
|
||||||
|
|
||||||
|
|
||||||
|
def atexit_callback():
|
||||||
|
"""
|
||||||
|
re-attach USB devices to kernel if needed, and set
|
||||||
|
autoreload back to the original state.
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
for _i, _mouse in enumerate(mice):
|
||||||
|
if kernel_driver_active_flags[_i]:
|
||||||
|
if not _mouse.is_kernel_driver_active(0):
|
||||||
|
_mouse.attach_kernel_driver(0)
|
||||||
|
supervisor.runtime.autoreload = original_autoreload_val
|
||||||
|
|
||||||
|
|
||||||
|
atexit.register(atexit_callback)
|
||||||
|
|
||||||
|
# main loop
|
||||||
|
while True:
|
||||||
|
|
||||||
|
# if set has been called
|
||||||
|
if match3_game.cur_state == STATE_PLAYING_SETCALLED:
|
||||||
|
# update the progress bar ticking down
|
||||||
|
match3_game.update_active_turn_progress()
|
||||||
|
|
||||||
|
# loop over the mice objects
|
||||||
|
for i, mouse in enumerate(mice):
|
||||||
|
mouse_tg = mouse_tgs[i]
|
||||||
|
# attempt mouse read
|
||||||
|
try:
|
||||||
|
# read data from the mouse, small timeout so we move on
|
||||||
|
# quickly if there is no data
|
||||||
|
data_len = mouse.read(
|
||||||
|
mouse_endpoint_addresses[i], mouse_bufs[i], timeout=10
|
||||||
|
)
|
||||||
|
mouse_deltas = get_mouse_deltas(mouse_bufs[i], data_len)
|
||||||
|
# if we got data, then update the mouse cursor on the display
|
||||||
|
# using min and max to keep it within the bounds of the display
|
||||||
|
mouse_tg.x = max(
|
||||||
|
0,
|
||||||
|
min(
|
||||||
|
display.width // scale_factor - 1, mouse_tg.x + mouse_deltas[0] // 2
|
||||||
|
),
|
||||||
|
)
|
||||||
|
mouse_tg.y = max(
|
||||||
|
0,
|
||||||
|
min(
|
||||||
|
display.height // scale_factor - 1,
|
||||||
|
mouse_tg.y + mouse_deltas[1] // 2,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
# timeout error is raised if no data was read within the allotted timeout
|
||||||
|
except usb.core.USBTimeoutError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# update the mouse debouncers
|
||||||
|
mouse_debouncers[i].update()
|
||||||
|
|
||||||
|
try:
|
||||||
|
# if the current mouse is right-clicking
|
||||||
|
if is_right_mouse_clicked(mouse_bufs[i]):
|
||||||
|
# let the game object handle the right-click
|
||||||
|
match3_game.handle_right_click(i)
|
||||||
|
|
||||||
|
# if the current mouse left-clicked
|
||||||
|
if mouse_debouncers[i].rose:
|
||||||
|
# get the current mouse coordinates
|
||||||
|
coords = (mouse_tg.x, mouse_tg.y, 0)
|
||||||
|
|
||||||
|
# if the current state is GAMEOVER
|
||||||
|
if match3_game.cur_state != STATE_GAMEOVER:
|
||||||
|
# let the game object handle the click event
|
||||||
|
match3_game.handle_left_click(i, coords)
|
||||||
|
else:
|
||||||
|
# if the mouse point is within the play again
|
||||||
|
# button bounding box
|
||||||
|
if play_again_btn.contains(coords):
|
||||||
|
# set next code file to this one
|
||||||
|
supervisor.set_next_code_file(__file__)
|
||||||
|
# reload
|
||||||
|
supervisor.reload()
|
||||||
|
|
||||||
|
# if the mouse point is within the exit
|
||||||
|
# button bounding box
|
||||||
|
if exit_btn.contains(coords):
|
||||||
|
supervisor.reload()
|
||||||
|
|
||||||
|
# if the game is over
|
||||||
|
except GameOverException:
|
||||||
|
# check for a winner
|
||||||
|
winner = None
|
||||||
|
if match3_game.scores[0] > match3_game.scores[1]:
|
||||||
|
winner = 0
|
||||||
|
elif match3_game.scores[0] < match3_game.scores[1]:
|
||||||
|
winner = 1
|
||||||
|
|
||||||
|
# if there was a winner
|
||||||
|
if winner is not None:
|
||||||
|
# show a message with the winning player
|
||||||
|
message = f"\nGame Over\nPlayer{winner + 1} Wins!"
|
||||||
|
game_over_label.color = colors[winner]
|
||||||
|
game_over_label.text = message
|
||||||
|
|
||||||
|
else: # there wasn't a winner
|
||||||
|
# show a tie game message
|
||||||
|
message = "\nGame Over\nTie Game Everyone Wins!"
|
||||||
|
|
||||||
|
# make the gameover group visible
|
||||||
|
game_over_group.hidden = False
|
||||||
|
|
||||||
|
# delete the autosave file.
|
||||||
|
os.remove(save_to)
|
||||||
|
After Width: | Height: | Size: 11 KiB |
779
Metro/Metro_RP2350_Match3/match3_game/match3_game_helpers.py
Normal file
|
|
@ -0,0 +1,779 @@
|
||||||
|
# SPDX-FileCopyrightText: Copyright (c) 2025 Tim Cocks for Adafruit Industries
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
import os
|
||||||
|
import random
|
||||||
|
import time
|
||||||
|
from io import BytesIO
|
||||||
|
|
||||||
|
import terminalio
|
||||||
|
|
||||||
|
import adafruit_imageload
|
||||||
|
from displayio import Group, TileGrid, OnDiskBitmap, Palette, Bitmap
|
||||||
|
import bitmaptools
|
||||||
|
import msgpack
|
||||||
|
from tilepalettemapper import TilePaletteMapper
|
||||||
|
import ulab.numpy as np
|
||||||
|
|
||||||
|
from adafruit_display_text.bitmap_label import Label
|
||||||
|
from adafruit_displayio_layout.layouts.grid_layout import GridLayout
|
||||||
|
from adafruit_button import Button
|
||||||
|
from adafruit_progressbar.horizontalprogressbar import (
|
||||||
|
HorizontalProgressBar,
|
||||||
|
HorizontalFillDirection,
|
||||||
|
)
|
||||||
|
|
||||||
|
# pylint: disable=too-many-locals, too-many-nested-blocks, too-many-branches, too-many-statements
|
||||||
|
colors = [0x2244FF, 0xFFFF00]
|
||||||
|
STATE_TITLE = 0
|
||||||
|
STATE_PLAYING_OPEN = 1
|
||||||
|
STATE_PLAYING_SETCALLED = 2
|
||||||
|
STATE_GAMEOVER = 3
|
||||||
|
|
||||||
|
ACTIVE_TURN_TIME_LIMIT = 10.0
|
||||||
|
|
||||||
|
|
||||||
|
def random_selection(lst, count):
|
||||||
|
"""
|
||||||
|
Select items randomly from a list of items.
|
||||||
|
|
||||||
|
returns a list of length count containing the selected items.
|
||||||
|
"""
|
||||||
|
if len(lst) < count:
|
||||||
|
raise ValueError("Count must be less than or equal to length of list")
|
||||||
|
selection = []
|
||||||
|
while len(selection) < count:
|
||||||
|
selection.append(lst.pop(random.randrange(len(lst))))
|
||||||
|
return selection
|
||||||
|
|
||||||
|
|
||||||
|
def validate_set(card_1, card_2, card_3):
|
||||||
|
"""
|
||||||
|
Check if a set of 3 cards is valid
|
||||||
|
:param card_1: the first card
|
||||||
|
:param card_2: the second card
|
||||||
|
:param card_3: the third card
|
||||||
|
:return: True if they are a valid set, False otherwise
|
||||||
|
"""
|
||||||
|
matrix_sums = card_1.tuple + card_2.tuple + card_3.tuple
|
||||||
|
for val in matrix_sums:
|
||||||
|
if val % 3 != 0:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
class Match3Card(Group):
|
||||||
|
"""
|
||||||
|
Class representing a Match3 Card. Keeps track of shape, count, color, and fill.
|
||||||
|
|
||||||
|
tuple value mappings:
|
||||||
|
|
||||||
|
color, shape, fill, count
|
||||||
|
0 , 1 , 2 , 1
|
||||||
|
|
||||||
|
colors
|
||||||
|
purple: 0
|
||||||
|
red: 1
|
||||||
|
green: 2
|
||||||
|
|
||||||
|
shapes
|
||||||
|
rectangle: 0
|
||||||
|
triangle: 1
|
||||||
|
circle: 2
|
||||||
|
|
||||||
|
fill
|
||||||
|
outline: 0
|
||||||
|
filled: 1
|
||||||
|
striped: 2
|
||||||
|
|
||||||
|
count
|
||||||
|
one: 0
|
||||||
|
two: 1
|
||||||
|
three: 2
|
||||||
|
"""
|
||||||
|
|
||||||
|
TUPLE_VALUE_TO_TILE_INDEX_LUT = {
|
||||||
|
# rectangle filled
|
||||||
|
(0, 1, 0): 0,
|
||||||
|
(0, 1, 1): 1,
|
||||||
|
(0, 1, 2): 2,
|
||||||
|
# triangle filled
|
||||||
|
(1, 1, 0): 3,
|
||||||
|
(1, 1, 1): 4,
|
||||||
|
(1, 1, 2): 5,
|
||||||
|
# circle filled
|
||||||
|
(2, 1, 0): 6,
|
||||||
|
(2, 1, 1): 13,
|
||||||
|
(2, 1, 2): 20,
|
||||||
|
# rectangle outline
|
||||||
|
(0, 0, 0): 7,
|
||||||
|
(0, 0, 1): 8,
|
||||||
|
(0, 0, 2): 9,
|
||||||
|
# triangle outline
|
||||||
|
(1, 0, 0): 10,
|
||||||
|
(1, 0, 1): 11,
|
||||||
|
(1, 0, 2): 12,
|
||||||
|
# circle outline
|
||||||
|
(2, 0, 0): 21,
|
||||||
|
(2, 0, 1): 22,
|
||||||
|
(2, 0, 2): 23,
|
||||||
|
# rectangle striped
|
||||||
|
(0, 2, 0): 14,
|
||||||
|
(0, 2, 1): 15,
|
||||||
|
(0, 2, 2): 16,
|
||||||
|
# triangle striped
|
||||||
|
(1, 2, 0): 17,
|
||||||
|
(1, 2, 1): 18,
|
||||||
|
(1, 2, 2): 19,
|
||||||
|
# circle striped
|
||||||
|
(2, 2, 0): 24,
|
||||||
|
(2, 2, 1): 25,
|
||||||
|
(2, 2, 2): 26,
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, card_tuple, **kwargs):
|
||||||
|
# tile palette mapper to color the card
|
||||||
|
self._mapper = TilePaletteMapper(kwargs["pixel_shader"], 5, 1, 1)
|
||||||
|
kwargs["pixel_shader"] = self._mapper
|
||||||
|
# tile grid to for the visible sprite
|
||||||
|
self._tilegrid = TileGrid(**kwargs)
|
||||||
|
self._tilegrid.x = 4
|
||||||
|
self._tilegrid.y = 4
|
||||||
|
# initialize super class Group
|
||||||
|
super().__init__()
|
||||||
|
# add tilegrid to self instance Group
|
||||||
|
self.append(self._tilegrid)
|
||||||
|
# numpy array of the card tuple values
|
||||||
|
self._tuple = np.array(list(card_tuple), dtype=np.uint8)
|
||||||
|
# set the sprite and color based on card attributes
|
||||||
|
self._update_card_attributes()
|
||||||
|
|
||||||
|
def _update_card_attributes(self):
|
||||||
|
"""
|
||||||
|
set the sprite and color based on card attributes
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
|
# set color
|
||||||
|
color_tuple_val = self._tuple[0]
|
||||||
|
self._mapper[0] = [0, color_tuple_val + 2, 2, 3, 4]
|
||||||
|
|
||||||
|
# set tile
|
||||||
|
self._tilegrid[0] = Match3Card.TUPLE_VALUE_TO_TILE_INDEX_LUT[
|
||||||
|
(self._tuple[1], self._tuple[2], self._tuple[3])
|
||||||
|
]
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.tuple
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return self.tuple
|
||||||
|
|
||||||
|
@property
|
||||||
|
def tuple(self):
|
||||||
|
"""
|
||||||
|
The tuple containing attributes values for this card.
|
||||||
|
"""
|
||||||
|
return self._tuple
|
||||||
|
|
||||||
|
def contains(self, coordinates):
|
||||||
|
"""
|
||||||
|
Check if the cards bounding box contains the given coordinates.
|
||||||
|
:param coordinates: the coordinates to check
|
||||||
|
:return: True if the bounding box contains the given coordinates, False otherwise
|
||||||
|
"""
|
||||||
|
return (
|
||||||
|
self.x <= coordinates[0] <= self.x + self._tilegrid.tile_width
|
||||||
|
and self.y <= coordinates[1] <= self.y + self._tilegrid.tile_height
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Match3Game(Group):
|
||||||
|
"""
|
||||||
|
Match3 Game helper class
|
||||||
|
|
||||||
|
Holds visual elements, manages state machine.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, game_state=None, display_size=None, save_location=None):
|
||||||
|
# initialize super Group instance
|
||||||
|
super().__init__()
|
||||||
|
self.game_state = game_state
|
||||||
|
self.display_size = display_size
|
||||||
|
|
||||||
|
# list of Match3Card instances representing the current deck
|
||||||
|
self.play_deck = []
|
||||||
|
|
||||||
|
# load the spritesheet
|
||||||
|
self.card_spritesheet, self.card_palette = adafruit_imageload.load(
|
||||||
|
"match3_cards_spritesheet.bmp"
|
||||||
|
)
|
||||||
|
|
||||||
|
# start in the TITLE state
|
||||||
|
self.cur_state = STATE_TITLE
|
||||||
|
|
||||||
|
# create a grid layout to help place cards neatly
|
||||||
|
# into a grid on the display
|
||||||
|
grid_size = (6, 3)
|
||||||
|
self.card_grid = GridLayout(
|
||||||
|
x=10, y=10, width=260, height=200, grid_size=grid_size
|
||||||
|
)
|
||||||
|
|
||||||
|
# no set button in the bottom right
|
||||||
|
self.no_set_btn_bmp = OnDiskBitmap("btn_no_set.bmp")
|
||||||
|
self.no_set_btn_bmp.pixel_shader.make_transparent(0)
|
||||||
|
self.no_set_btn = TileGrid(
|
||||||
|
bitmap=self.no_set_btn_bmp, pixel_shader=self.no_set_btn_bmp.pixel_shader
|
||||||
|
)
|
||||||
|
self.no_set_btn.x = display_size[0] - self.no_set_btn.tile_width
|
||||||
|
self.no_set_btn.y = display_size[1] - self.no_set_btn.tile_height
|
||||||
|
self.append(self.no_set_btn)
|
||||||
|
|
||||||
|
# list to hold score labels, one for each player
|
||||||
|
self.score_lbls = []
|
||||||
|
|
||||||
|
# player scores start at 0
|
||||||
|
self.scores = [0, 0]
|
||||||
|
|
||||||
|
self.save_location = save_location
|
||||||
|
|
||||||
|
# initialize and position the score labels
|
||||||
|
for i in range(2):
|
||||||
|
score_lbl = Label(terminalio.FONT, text=f"P{i + 1}: 0", color=colors[i])
|
||||||
|
self.score_lbls.append(score_lbl)
|
||||||
|
score_lbl.anchor_point = (1.0, 0.0)
|
||||||
|
score_lbl.anchored_position = (display_size[0] - 2, 2 + i * 12)
|
||||||
|
self.append(score_lbl)
|
||||||
|
|
||||||
|
# deck count label in the bottom left
|
||||||
|
self.deck_count_lbl = Label(
|
||||||
|
terminalio.FONT, text=f"Deck: {len(self.play_deck)}"
|
||||||
|
)
|
||||||
|
self.deck_count_lbl.anchor_point = (0.0, 1.0)
|
||||||
|
self.deck_count_lbl.anchored_position = (2, display_size[1] - 2)
|
||||||
|
self.append(self.deck_count_lbl)
|
||||||
|
|
||||||
|
# will hold active player index
|
||||||
|
self.active_player = None
|
||||||
|
|
||||||
|
# list of player index who have called no set
|
||||||
|
self.no_set_called_player_indexes = []
|
||||||
|
|
||||||
|
# active turn countdown progress bar
|
||||||
|
# below the score labels
|
||||||
|
self.active_turn_countdown = HorizontalProgressBar(
|
||||||
|
(display_size[0] - 64, 30),
|
||||||
|
(60, 6),
|
||||||
|
direction=HorizontalFillDirection.LEFT_TO_RIGHT,
|
||||||
|
min_value=0,
|
||||||
|
max_value=ACTIVE_TURN_TIME_LIMIT * 10,
|
||||||
|
)
|
||||||
|
self.active_turn_countdown.hidden = True
|
||||||
|
self.append(self.active_turn_countdown)
|
||||||
|
|
||||||
|
# will hold the timestamp when active turn began
|
||||||
|
self.active_turn_start_time = None
|
||||||
|
|
||||||
|
# add the card grid to self instance Group
|
||||||
|
self.append(self.card_grid)
|
||||||
|
|
||||||
|
# list of card objects that have been clicked
|
||||||
|
self.clicked_cards = []
|
||||||
|
|
||||||
|
# list of coordinates that have been clicked
|
||||||
|
self.clicked_coordinates = []
|
||||||
|
|
||||||
|
# initialize title screen
|
||||||
|
self.title_screen = Match3TitleScreen(display_size)
|
||||||
|
self.append(self.title_screen)
|
||||||
|
|
||||||
|
# set up the clicked card indicator borders
|
||||||
|
self.clicked_card_indicator_palette = Palette(2)
|
||||||
|
self.clicked_card_indicator_palette[0] = 0x000000
|
||||||
|
self.clicked_card_indicator_palette.make_transparent(0)
|
||||||
|
self.clicked_card_indicator_palette[1] = colors[0]
|
||||||
|
self.clicked_card_indicator_bmp = Bitmap(24 + 8, 32 + 8, 2)
|
||||||
|
self.clicked_card_indicator_bmp.fill(1)
|
||||||
|
bitmaptools.fill_region(
|
||||||
|
self.clicked_card_indicator_bmp,
|
||||||
|
2,
|
||||||
|
2,
|
||||||
|
self.clicked_card_indicator_bmp.width - 2,
|
||||||
|
self.clicked_card_indicator_bmp.height - 2,
|
||||||
|
value=0,
|
||||||
|
)
|
||||||
|
self.clicked_card_indicators = []
|
||||||
|
for _ in range(3):
|
||||||
|
self.clicked_card_indicators.append(
|
||||||
|
TileGrid(
|
||||||
|
bitmap=self.clicked_card_indicator_bmp,
|
||||||
|
pixel_shader=self.clicked_card_indicator_palette,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def update_scores(self):
|
||||||
|
"""
|
||||||
|
Update the score labels to reflect the current player scores
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
|
for player_index in range(2):
|
||||||
|
prefix = ""
|
||||||
|
if player_index == self.active_player:
|
||||||
|
prefix = ">"
|
||||||
|
if player_index in self.no_set_called_player_indexes:
|
||||||
|
prefix = "*"
|
||||||
|
self.score_lbls[player_index].text = (
|
||||||
|
f"{prefix}P{player_index + 1}: {self.scores[player_index]}"
|
||||||
|
)
|
||||||
|
|
||||||
|
def save_game_state(self):
|
||||||
|
"""
|
||||||
|
Save the game state to a file
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
|
# if there is a valid save location
|
||||||
|
if self.save_location is not None:
|
||||||
|
# create a dictionary object to store the game state
|
||||||
|
game_state = {"scores": self.scores, "board": {}, "deck": []}
|
||||||
|
# read the current board state into the dictionary object
|
||||||
|
for _y in range(3):
|
||||||
|
for _x in range(6):
|
||||||
|
try:
|
||||||
|
content = self.card_grid.get_content((_x, _y))
|
||||||
|
game_state["board"][f"{_x},{_y}"] = tuple(content.tuple)
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
# read the current deck state into the dictionary object
|
||||||
|
for card in self.play_deck:
|
||||||
|
game_state["deck"].append(tuple(card.tuple))
|
||||||
|
|
||||||
|
# msgpack the object and write it to a file
|
||||||
|
b = BytesIO()
|
||||||
|
msgpack.pack(game_state, b)
|
||||||
|
b.seek(0)
|
||||||
|
with open(self.save_location, "wb") as f:
|
||||||
|
f.write(b.read())
|
||||||
|
|
||||||
|
def load_from_game_state(self, game_state):
|
||||||
|
"""
|
||||||
|
Load game state from a dictionary.
|
||||||
|
:param game_state: The dictionary of game state to load
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
|
# loop over cards in the deck
|
||||||
|
for card_tuple in game_state["deck"]:
|
||||||
|
# create a card instance and add it to the deck
|
||||||
|
self.play_deck.append(
|
||||||
|
Match3Card(
|
||||||
|
card_tuple,
|
||||||
|
bitmap=self.card_spritesheet,
|
||||||
|
pixel_shader=self.card_palette,
|
||||||
|
tile_width=24,
|
||||||
|
tile_height=32,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# loop over grid cells
|
||||||
|
for y in range(3):
|
||||||
|
for x in range(6):
|
||||||
|
# if the current cell is in the board state of the save game
|
||||||
|
if f"{x},{y}" in game_state["board"]:
|
||||||
|
# create a card instance and put it in the grid here
|
||||||
|
card_tuple = game_state["board"][f"{x},{y}"]
|
||||||
|
self.card_grid.add_content(
|
||||||
|
Match3Card(
|
||||||
|
card_tuple,
|
||||||
|
bitmap=self.card_spritesheet,
|
||||||
|
pixel_shader=self.card_palette,
|
||||||
|
tile_width=24,
|
||||||
|
tile_height=32,
|
||||||
|
),
|
||||||
|
(x, y),
|
||||||
|
(1, 1),
|
||||||
|
)
|
||||||
|
# set the scores from the game state
|
||||||
|
self.scores = game_state["scores"]
|
||||||
|
# update the visible score labels
|
||||||
|
self.update_scores()
|
||||||
|
# update the deck count label
|
||||||
|
self.deck_count_lbl.text = f"Deck: {len(self.play_deck)}"
|
||||||
|
|
||||||
|
def init_new_game(self):
|
||||||
|
"""
|
||||||
|
Initialize a new game state.
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
|
self.play_deck = []
|
||||||
|
# loop over the 3 possibilities in each of the 4 attributes
|
||||||
|
for _color in range(0, 3):
|
||||||
|
for _shape in range(0, 3):
|
||||||
|
for _fill in range(0, 3):
|
||||||
|
for _count in range(0, 3):
|
||||||
|
# create a card instance with the current attributes
|
||||||
|
self.play_deck.append(
|
||||||
|
Match3Card(
|
||||||
|
(_color, _shape, _fill, _count),
|
||||||
|
bitmap=self.card_spritesheet,
|
||||||
|
pixel_shader=self.card_palette,
|
||||||
|
tile_width=24,
|
||||||
|
tile_height=32,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# draw the starting cards at random
|
||||||
|
starting_pool = random_selection(self.play_deck, 12)
|
||||||
|
|
||||||
|
# put the starting cards into the grid layout
|
||||||
|
for y in range(3):
|
||||||
|
for x in range(4):
|
||||||
|
self.card_grid.add_content(starting_pool[y * 4 + x], (x, y), (1, 1))
|
||||||
|
|
||||||
|
# update the deck count label
|
||||||
|
self.deck_count_lbl.text = f"Deck: {len(self.play_deck)}"
|
||||||
|
|
||||||
|
def handle_right_click(self, player_index):
|
||||||
|
"""
|
||||||
|
Handle right click event
|
||||||
|
:param player_index: the index of the player who clicked
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
|
# if the current state is open play
|
||||||
|
if self.cur_state == STATE_PLAYING_OPEN:
|
||||||
|
# if there is no active player
|
||||||
|
if self.active_player is None:
|
||||||
|
# if the player who right clicked is in the no set called list
|
||||||
|
if player_index in self.no_set_called_player_indexes:
|
||||||
|
# remove them from the no set called list
|
||||||
|
self.no_set_called_player_indexes.remove(player_index)
|
||||||
|
# set the active player to the player that clicked
|
||||||
|
self.active_player = player_index
|
||||||
|
# set the clicked card indicators to the active player's color
|
||||||
|
self.clicked_card_indicator_palette[1] = colors[player_index]
|
||||||
|
# set the current state to the set called state
|
||||||
|
self.cur_state = STATE_PLAYING_SETCALLED
|
||||||
|
# store timestamp of when the active turn began
|
||||||
|
self.active_turn_start_time = time.monotonic()
|
||||||
|
# make the countdown progress bar visible
|
||||||
|
self.active_turn_countdown.hidden = False
|
||||||
|
# set the value to the maximum of the progress bar
|
||||||
|
self.active_turn_countdown.value = 60
|
||||||
|
# update the score labels to show the active player indicator
|
||||||
|
self.update_scores()
|
||||||
|
|
||||||
|
def handle_left_click(self, player_index, coords):
|
||||||
|
"""
|
||||||
|
Handle left click events
|
||||||
|
:param player_index: the index of the player who clicked
|
||||||
|
:param coords: the coordinates where the mouse clicked
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
|
# if the current state is open playing
|
||||||
|
if self.cur_state == STATE_PLAYING_OPEN:
|
||||||
|
# if the click is on the no set button
|
||||||
|
if self.no_set_btn.contains(coords):
|
||||||
|
# if the player that clicked is not in the net set called list
|
||||||
|
if player_index not in self.no_set_called_player_indexes:
|
||||||
|
# add them to the no set called list
|
||||||
|
self.no_set_called_player_indexes.append(player_index)
|
||||||
|
|
||||||
|
# if both players have called no set
|
||||||
|
if len(self.no_set_called_player_indexes) == 2:
|
||||||
|
# if there are no cards left in the deck
|
||||||
|
if len(self.play_deck) == 0:
|
||||||
|
# set the state to game over
|
||||||
|
self.cur_state = STATE_GAMEOVER
|
||||||
|
raise GameOverException()
|
||||||
|
|
||||||
|
# empty the no set called list
|
||||||
|
self.no_set_called_player_indexes = []
|
||||||
|
|
||||||
|
# find the empty cells in the card grid
|
||||||
|
empty_cells = self.find_empty_cells()
|
||||||
|
# if there are more than 3 empty cells
|
||||||
|
if len(empty_cells) >= 3:
|
||||||
|
# draw 3 new cards
|
||||||
|
_new_cards = random_selection(self.play_deck, 3)
|
||||||
|
# place them in 3 of the empty cells
|
||||||
|
for i, _new_card in enumerate(_new_cards):
|
||||||
|
self.card_grid.add_content(
|
||||||
|
_new_card, empty_cells[i], (1, 1)
|
||||||
|
)
|
||||||
|
|
||||||
|
else: # there are no 3 empty cells
|
||||||
|
# redraw the original grid with 12 new cards
|
||||||
|
|
||||||
|
# remove existing cards from the grid and
|
||||||
|
# return them to the deck.
|
||||||
|
for _y in range(3):
|
||||||
|
for _x in range(6):
|
||||||
|
try:
|
||||||
|
_remove_card = self.card_grid.pop_content(
|
||||||
|
(_x, _y)
|
||||||
|
)
|
||||||
|
print(f"remove_card: {_remove_card}")
|
||||||
|
self.play_deck.append(_remove_card)
|
||||||
|
|
||||||
|
except KeyError:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# draw 12 new cards from the deck
|
||||||
|
starting_pool = random_selection(self.play_deck, 12)
|
||||||
|
# place them into the grid
|
||||||
|
for y in range(3):
|
||||||
|
for x in range(4):
|
||||||
|
self.card_grid.add_content(
|
||||||
|
starting_pool[y * 4 + x], (x + 1, y), (1, 1)
|
||||||
|
)
|
||||||
|
|
||||||
|
# update the deck count label
|
||||||
|
self.deck_count_lbl.text = f"Deck: {len(self.play_deck)}"
|
||||||
|
# save the game state
|
||||||
|
self.save_game_state()
|
||||||
|
|
||||||
|
# update the score labels to show the no set called indicator(s)
|
||||||
|
self.update_scores()
|
||||||
|
|
||||||
|
# if the current state is set called
|
||||||
|
elif self.cur_state == STATE_PLAYING_SETCALLED:
|
||||||
|
# if the player that clicked is the active player
|
||||||
|
if player_index == self.active_player:
|
||||||
|
# get the coordinates that were clicked adjusting for the card_grid position
|
||||||
|
adjusted_coords = (
|
||||||
|
coords[0] - self.card_grid.x,
|
||||||
|
coords[1] - self.card_grid.y,
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
# check which cell contains the clicked coordinates
|
||||||
|
clicked_grid_cell_coordinates = self.card_grid.which_cell_contains(
|
||||||
|
coords
|
||||||
|
)
|
||||||
|
# print(clicked_grid_cell_coordinates)
|
||||||
|
|
||||||
|
# if a cell in the grid was clicked
|
||||||
|
if clicked_grid_cell_coordinates is not None:
|
||||||
|
# try to get the content of the clicked cell, a Card instance potentially
|
||||||
|
try:
|
||||||
|
clicked_cell_content = self.card_grid.get_content(
|
||||||
|
clicked_grid_cell_coordinates
|
||||||
|
)
|
||||||
|
except KeyError:
|
||||||
|
# if no content is in the cell just return
|
||||||
|
return
|
||||||
|
|
||||||
|
# check if the Card instance was clicked, and if the card
|
||||||
|
# isn't already in the list of clicked cards
|
||||||
|
if (
|
||||||
|
clicked_cell_content.contains(adjusted_coords)
|
||||||
|
and clicked_cell_content not in self.clicked_cards
|
||||||
|
):
|
||||||
|
|
||||||
|
clicked_card = clicked_cell_content
|
||||||
|
# show the clicked card indicator in this cell
|
||||||
|
clicked_cell_content.insert(
|
||||||
|
0, self.clicked_card_indicators[len(self.clicked_cards)]
|
||||||
|
)
|
||||||
|
# add the card instance to the clicked cards list
|
||||||
|
self.clicked_cards.append(clicked_card)
|
||||||
|
|
||||||
|
# add the coordinates to the clicked coordinates list
|
||||||
|
self.clicked_coordinates.append(clicked_grid_cell_coordinates)
|
||||||
|
|
||||||
|
# if 3 cards have been clicked
|
||||||
|
if len(self.clicked_cards) == 3:
|
||||||
|
# check if the 3 cards make a valid set
|
||||||
|
valid_set = validate_set(self.clicked_cards[0],
|
||||||
|
self.clicked_cards[1],
|
||||||
|
self.clicked_cards[2])
|
||||||
|
|
||||||
|
# if they are a valid set
|
||||||
|
if valid_set:
|
||||||
|
# award a point to the active player
|
||||||
|
self.scores[self.active_player] += 1
|
||||||
|
|
||||||
|
# loop over the clicked coordinates
|
||||||
|
for coord in self.clicked_coordinates:
|
||||||
|
# remove the old card from this cell
|
||||||
|
_remove_card = self.card_grid.pop_content(coord)
|
||||||
|
# remove border from Match3Card group
|
||||||
|
_remove_card.pop(0)
|
||||||
|
|
||||||
|
# find empty cells in the grid
|
||||||
|
empty_cells = self.find_empty_cells()
|
||||||
|
|
||||||
|
# if there are at least 3 cards in the deck and
|
||||||
|
# at least 6 empty cells in the grid
|
||||||
|
if len(self.play_deck) >= 3 and len(empty_cells) > 6:
|
||||||
|
# deal 3 new cards to empty spots in the grid
|
||||||
|
for i in range(3):
|
||||||
|
_new_card = random_selection(self.play_deck, 1)[
|
||||||
|
0
|
||||||
|
]
|
||||||
|
self.card_grid.add_content(
|
||||||
|
_new_card, empty_cells[i], (1, 1)
|
||||||
|
)
|
||||||
|
# update the deck count label
|
||||||
|
self.deck_count_lbl.text = (
|
||||||
|
f"Deck: {len(self.play_deck)}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# there are not at least 3 cards in the deck
|
||||||
|
# and at least 6 empty cells
|
||||||
|
else:
|
||||||
|
# if there are no empty cells
|
||||||
|
if len(self.find_empty_cells()) == 0:
|
||||||
|
# set the current state to game over
|
||||||
|
self.cur_state = STATE_GAMEOVER
|
||||||
|
raise GameOverException()
|
||||||
|
|
||||||
|
else: # the 3 clicked cards are not a valid set
|
||||||
|
|
||||||
|
# remove the clicked card indicators
|
||||||
|
for _ in range(3):
|
||||||
|
coords = self.clicked_coordinates.pop()
|
||||||
|
self.card_grid.get_content(coords).pop(0)
|
||||||
|
|
||||||
|
# subtract a point from the active player
|
||||||
|
self.scores[self.active_player] -= 1
|
||||||
|
|
||||||
|
# save the game state
|
||||||
|
self.save_game_state()
|
||||||
|
# reset the clicked cards and coordinates lists
|
||||||
|
self.clicked_cards = []
|
||||||
|
self.clicked_coordinates = []
|
||||||
|
|
||||||
|
# set the current state to open play
|
||||||
|
self.cur_state = STATE_PLAYING_OPEN
|
||||||
|
# set active player and active turn vars
|
||||||
|
self.active_player = None
|
||||||
|
self.active_turn_start_time = None
|
||||||
|
self.active_turn_countdown.hidden = True
|
||||||
|
# update the score labels
|
||||||
|
self.update_scores()
|
||||||
|
|
||||||
|
# if the current state is title state
|
||||||
|
elif self.cur_state == STATE_TITLE:
|
||||||
|
# if the resume button is visible and was clicked
|
||||||
|
if (
|
||||||
|
not self.title_screen.resume_btn.hidden
|
||||||
|
and self.title_screen.resume_btn.contains(coords)
|
||||||
|
):
|
||||||
|
|
||||||
|
# load the game from the given game state
|
||||||
|
self.load_from_game_state(self.game_state)
|
||||||
|
# hide the title screen
|
||||||
|
self.title_screen.hidden = True # pylint: disable=attribute-defined-outside-init
|
||||||
|
# set the current state to open play
|
||||||
|
self.cur_state = STATE_PLAYING_OPEN
|
||||||
|
|
||||||
|
# if the new game button was clicked
|
||||||
|
elif self.title_screen.new_game_btn.contains(coords):
|
||||||
|
self.game_state = None
|
||||||
|
# delete the autosave file
|
||||||
|
try:
|
||||||
|
os.remove(self.save_location)
|
||||||
|
print("removed old game save file")
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
# initialize a new game
|
||||||
|
self.init_new_game()
|
||||||
|
# hide the title screen
|
||||||
|
self.title_screen.hidden = True # pylint: disable=attribute-defined-outside-init
|
||||||
|
# set the current state to open play
|
||||||
|
self.cur_state = STATE_PLAYING_OPEN
|
||||||
|
|
||||||
|
def find_empty_cells(self):
|
||||||
|
"""
|
||||||
|
find the cells within the card grid that are empty
|
||||||
|
:return: list of empty cell coordinate tuples.
|
||||||
|
"""
|
||||||
|
empty_cells = []
|
||||||
|
for x in range(6):
|
||||||
|
for y in range(3):
|
||||||
|
try:
|
||||||
|
_content = self.card_grid.get_content((x, y))
|
||||||
|
except KeyError:
|
||||||
|
empty_cells.append((x, y))
|
||||||
|
return empty_cells
|
||||||
|
|
||||||
|
def update_active_turn_progress(self):
|
||||||
|
"""
|
||||||
|
update the active turn progress bar countdown
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
if self.cur_state == STATE_PLAYING_SETCALLED:
|
||||||
|
time_diff = time.monotonic() - self.active_turn_start_time
|
||||||
|
if time_diff > ACTIVE_TURN_TIME_LIMIT:
|
||||||
|
self.scores[self.active_player] -= 1
|
||||||
|
self.active_player = None
|
||||||
|
self.update_scores()
|
||||||
|
self.cur_state = STATE_PLAYING_OPEN
|
||||||
|
self.active_turn_countdown.hidden = True
|
||||||
|
else:
|
||||||
|
self.active_turn_countdown.value = int(
|
||||||
|
(ACTIVE_TURN_TIME_LIMIT - time_diff) * 10
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class GameOverException(Exception):
|
||||||
|
"""
|
||||||
|
Exception that indicates the game is over.
|
||||||
|
Message will contain the reason.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class Match3TitleScreen(Group):
|
||||||
|
"""
|
||||||
|
Title screen for the Match3 game.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, display_size):
|
||||||
|
super().__init__()
|
||||||
|
self.display_size = display_size
|
||||||
|
# background bitmap color
|
||||||
|
bg_bmp = Bitmap(display_size[0] // 10, display_size[1] // 10, 1)
|
||||||
|
bg_palette = Palette(1)
|
||||||
|
bg_palette[0] = 0xFFFFFF
|
||||||
|
bg_tg = TileGrid(bg_bmp, pixel_shader=bg_palette)
|
||||||
|
bg_group = Group(scale=10)
|
||||||
|
bg_group.append(bg_tg)
|
||||||
|
self.append(bg_group)
|
||||||
|
|
||||||
|
# load title card bitmap
|
||||||
|
title_card_bmp = OnDiskBitmap("title_card_match3.bmp")
|
||||||
|
title_card_tg = TileGrid(
|
||||||
|
title_card_bmp, pixel_shader=title_card_bmp.pixel_shader
|
||||||
|
)
|
||||||
|
title_card_tg.x = 2
|
||||||
|
if display_size[1] > 200:
|
||||||
|
title_card_tg.y = 20
|
||||||
|
self.append(title_card_tg)
|
||||||
|
|
||||||
|
# make resume and new game buttons
|
||||||
|
BUTTON_X = display_size[0] - 90
|
||||||
|
BUTTON_WIDTH = 70
|
||||||
|
BUTTON_HEIGHT = 20
|
||||||
|
self.resume_btn = Button(
|
||||||
|
x=BUTTON_X,
|
||||||
|
y=40,
|
||||||
|
width=BUTTON_WIDTH,
|
||||||
|
height=BUTTON_HEIGHT,
|
||||||
|
style=Button.ROUNDRECT,
|
||||||
|
fill_color=0x6D2EDC,
|
||||||
|
outline_color=0x888888,
|
||||||
|
label="Resume",
|
||||||
|
label_font=terminalio.FONT,
|
||||||
|
label_color=0xFFFFFF,
|
||||||
|
)
|
||||||
|
self.append(self.resume_btn)
|
||||||
|
self.new_game_btn = Button(
|
||||||
|
x=BUTTON_X,
|
||||||
|
y=40 + BUTTON_HEIGHT + 10,
|
||||||
|
width=BUTTON_WIDTH,
|
||||||
|
height=BUTTON_HEIGHT,
|
||||||
|
style=Button.RECT,
|
||||||
|
fill_color=0x0C9F0C,
|
||||||
|
outline_color=0x111111,
|
||||||
|
label="New Game",
|
||||||
|
label_font=terminalio.FONT,
|
||||||
|
label_color=0xFFFFFF,
|
||||||
|
)
|
||||||
|
self.append(self.new_game_btn)
|
||||||
BIN
Metro/Metro_RP2350_Match3/match3_game/mouse_cursor.bmp
Normal file
|
After Width: | Height: | Size: 198 B |
BIN
Metro/Metro_RP2350_Match3/match3_game/title_card_match3.bmp
Normal file
|
After Width: | Height: | Size: 16 KiB |
50
Metro/Metro_RP2350_Match3/tilepalettemapper_demo/code.py
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
# SPDX-FileCopyrightText: Copyright (c) 2025 Tim Cocks for Adafruit Industries
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
import supervisor
|
||||||
|
from displayio import Group, OnDiskBitmap, TileGrid
|
||||||
|
from tilepalettemapper import TilePaletteMapper
|
||||||
|
|
||||||
|
# use the default built-in display,
|
||||||
|
# the HSTX / PicoDVI display for the Metro RP2350
|
||||||
|
display = supervisor.runtime.display
|
||||||
|
|
||||||
|
# a group to hold all other visual elements
|
||||||
|
main_group = Group(scale=4, x=30, y=30)
|
||||||
|
|
||||||
|
# set the main group to show on the display
|
||||||
|
display.root_group = main_group
|
||||||
|
|
||||||
|
# load the sprite sheet bitmap
|
||||||
|
spritesheet_bmp = OnDiskBitmap("match3_cards_spritesheet.bmp")
|
||||||
|
|
||||||
|
# create a TilePaletteMapper
|
||||||
|
tile_palette_mapper = TilePaletteMapper(
|
||||||
|
spritesheet_bmp.pixel_shader, # input pixel_shader
|
||||||
|
5, # input color count
|
||||||
|
3, # grid width
|
||||||
|
1 # grid height
|
||||||
|
)
|
||||||
|
|
||||||
|
# create a TileGrid to show some cards
|
||||||
|
cards_tilegrid = TileGrid(spritesheet_bmp, pixel_shader=tile_palette_mapper,
|
||||||
|
width=3, height=1, tile_width=24, tile_height=32)
|
||||||
|
|
||||||
|
# set each tile in the grid to a different sprite index
|
||||||
|
cards_tilegrid[0, 0] = 10
|
||||||
|
cards_tilegrid[1, 0] = 25
|
||||||
|
cards_tilegrid[2, 0] = 2
|
||||||
|
|
||||||
|
# re-map each tile in the grid to use a different color for index 1
|
||||||
|
# all other indexes remain their default values
|
||||||
|
tile_palette_mapper[0, 0] = [0, 2, 2, 3, 4]
|
||||||
|
tile_palette_mapper[1, 0] = [0, 3, 2, 3, 4]
|
||||||
|
tile_palette_mapper[2, 0] = [0, 4, 2, 3, 4]
|
||||||
|
|
||||||
|
# add the tilegrid to the main group
|
||||||
|
main_group.append(cards_tilegrid)
|
||||||
|
|
||||||
|
# wait forever so it remains visible on the display
|
||||||
|
while True:
|
||||||
|
pass
|
||||||
|
After Width: | Height: | Size: 11 KiB |
184
Metro/Metro_RP2350_Match3/two_mice_demo/code.py
Normal file
|
|
@ -0,0 +1,184 @@
|
||||||
|
# SPDX-FileCopyrightText: Copyright (c) 2025 Tim Cocks for Adafruit Industries
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
import array
|
||||||
|
import supervisor
|
||||||
|
import terminalio
|
||||||
|
import usb.core
|
||||||
|
from adafruit_display_text.bitmap_label import Label
|
||||||
|
from displayio import Group, OnDiskBitmap, TileGrid, Palette
|
||||||
|
|
||||||
|
import adafruit_usb_host_descriptors
|
||||||
|
|
||||||
|
# use the default built-in display,
|
||||||
|
# the HSTX / PicoDVI display for the Metro RP2350
|
||||||
|
display = supervisor.runtime.display
|
||||||
|
|
||||||
|
# a group to hold all other visual elements
|
||||||
|
main_group = Group()
|
||||||
|
|
||||||
|
# set the main group to show on the display
|
||||||
|
display.root_group = main_group
|
||||||
|
|
||||||
|
# load the cursor bitmap file
|
||||||
|
mouse_bmp = OnDiskBitmap("mouse_cursor.bmp")
|
||||||
|
|
||||||
|
# lists for labels, mouse tilegrids, and palettes.
|
||||||
|
# each mouse will get 1 of each item. All lists
|
||||||
|
# will end up with length 2.
|
||||||
|
output_lbls = []
|
||||||
|
mouse_tgs = []
|
||||||
|
palettes = []
|
||||||
|
|
||||||
|
# the different colors to use for each mouse cursor
|
||||||
|
# and labels
|
||||||
|
colors = [0xFF00FF, 0x00FF00]
|
||||||
|
|
||||||
|
for i in range(2):
|
||||||
|
# create a palette for this mouse
|
||||||
|
mouse_palette = Palette(3)
|
||||||
|
# index zero is used for transparency
|
||||||
|
mouse_palette.make_transparent(0)
|
||||||
|
# add the palette to the list of palettes
|
||||||
|
palettes.append(mouse_palette)
|
||||||
|
|
||||||
|
# copy the first two colors from mouse palette
|
||||||
|
for palette_color_index in range(2):
|
||||||
|
mouse_palette[palette_color_index] = mouse_bmp.pixel_shader[palette_color_index]
|
||||||
|
|
||||||
|
# replace the last color with different color for each mouse
|
||||||
|
mouse_palette[2] = colors[i]
|
||||||
|
|
||||||
|
# create a TileGrid for this mouse cursor.
|
||||||
|
# use the palette created above
|
||||||
|
mouse_tg = TileGrid(mouse_bmp, pixel_shader=mouse_palette)
|
||||||
|
|
||||||
|
# move the mouse tilegrid to near the center of the display
|
||||||
|
mouse_tg.x = display.width // 2 - (i * 12)
|
||||||
|
mouse_tg.y = display.height // 2
|
||||||
|
|
||||||
|
# add this mouse tilegrid to the list of mouse tilegrids
|
||||||
|
mouse_tgs.append(mouse_tg)
|
||||||
|
|
||||||
|
# add this mouse tilegrid to the main group so it will show
|
||||||
|
# on the display
|
||||||
|
main_group.append(mouse_tg)
|
||||||
|
|
||||||
|
# create a label for this mouse
|
||||||
|
output_lbl = Label(terminalio.FONT, text=f"{mouse_tg.x},{mouse_tg.y}", color=colors[i], scale=1)
|
||||||
|
# anchored to the top left corner of the label
|
||||||
|
output_lbl.anchor_point = (0, 0)
|
||||||
|
|
||||||
|
# move to op left corner of the display, moving
|
||||||
|
# down by a static amount to static the two labels
|
||||||
|
# one below the other
|
||||||
|
output_lbl.anchored_position = (1, 1 + i * 13)
|
||||||
|
|
||||||
|
# add the label to the list of labels
|
||||||
|
output_lbls.append(output_lbl)
|
||||||
|
|
||||||
|
# add the label to the main group so it will show
|
||||||
|
# on the display
|
||||||
|
main_group.append(output_lbl)
|
||||||
|
|
||||||
|
# lists for mouse interface indexes, endpoint addresses, and USB Device instances
|
||||||
|
# each of these will end up with length 2 once we find both mice
|
||||||
|
mouse_interface_indexes = []
|
||||||
|
mouse_endpoint_addresses = []
|
||||||
|
mice = []
|
||||||
|
|
||||||
|
# scan for connected USB devices
|
||||||
|
for device in usb.core.find(find_all=True):
|
||||||
|
# check for boot mouse endpoints on this device
|
||||||
|
mouse_interface_index, mouse_endpoint_address = (
|
||||||
|
adafruit_usb_host_descriptors.find_boot_mouse_endpoint(device)
|
||||||
|
)
|
||||||
|
# if a boot mouse interface index and endpoint address were found
|
||||||
|
if mouse_interface_index is not None and mouse_endpoint_address is not None:
|
||||||
|
# add the interface index to the list of indexes
|
||||||
|
mouse_interface_indexes.append(mouse_interface_index)
|
||||||
|
# add the endpoint address to the list of addresses
|
||||||
|
mouse_endpoint_addresses.append(mouse_endpoint_address)
|
||||||
|
# add the device instance to the list of mice
|
||||||
|
mice.append(device)
|
||||||
|
|
||||||
|
# print details to the console
|
||||||
|
print(f"mouse interface: {mouse_interface_index} ", end="")
|
||||||
|
print(f"endpoint_address: {hex(mouse_endpoint_address)}")
|
||||||
|
|
||||||
|
# detach device from kernel if needed
|
||||||
|
if device.is_kernel_driver_active(0):
|
||||||
|
device.detach_kernel_driver(0)
|
||||||
|
|
||||||
|
# set the mouse configuration so it can be used
|
||||||
|
device.set_configuration()
|
||||||
|
|
||||||
|
# This is ordered by bit position.
|
||||||
|
BUTTONS = ["left", "right", "middle"]
|
||||||
|
|
||||||
|
# list of buffers, will hold one buffer for each mouse
|
||||||
|
mouse_bufs = []
|
||||||
|
for i in range(2):
|
||||||
|
# Buffer to hold data read from the mouse
|
||||||
|
mouse_bufs.append(array.array("b", [0] * 8))
|
||||||
|
|
||||||
|
|
||||||
|
def get_mouse_deltas(buffer, read_count):
|
||||||
|
"""
|
||||||
|
Given a buffer and read_count return the x and y delta values
|
||||||
|
:param buffer: A buffer containing data read from the mouse
|
||||||
|
:param read_count: How many bytes of data were read from the mouse
|
||||||
|
:return: tuple x,y delta values
|
||||||
|
"""
|
||||||
|
if read_count == 4:
|
||||||
|
delta_x = buffer[1]
|
||||||
|
delta_y = buffer[2]
|
||||||
|
elif read_count == 8:
|
||||||
|
delta_x = buffer[2]
|
||||||
|
delta_y = buffer[4]
|
||||||
|
else:
|
||||||
|
raise ValueError(f"Unsupported mouse packet size: {read_count}, must be 4 or 8")
|
||||||
|
return delta_x, delta_y
|
||||||
|
|
||||||
|
# main loop
|
||||||
|
while True:
|
||||||
|
# for each mouse instance
|
||||||
|
for mouse_index, mouse in enumerate(mice):
|
||||||
|
# try to read data from the mouse
|
||||||
|
try:
|
||||||
|
count = mouse.read(
|
||||||
|
mouse_endpoint_addresses[mouse_index], mouse_bufs[mouse_index], timeout=10
|
||||||
|
)
|
||||||
|
|
||||||
|
# if there is no data it will raise USBTimeoutError
|
||||||
|
except usb.core.USBTimeoutError:
|
||||||
|
# Nothing to do if there is no data for this mouse
|
||||||
|
continue
|
||||||
|
|
||||||
|
# there was mouse data, so get the delta x and y values from it
|
||||||
|
mouse_deltas = get_mouse_deltas(mouse_bufs[mouse_index], count)
|
||||||
|
|
||||||
|
# update the x position of this mouse cursor using the delta value
|
||||||
|
# clamped to the display size
|
||||||
|
mouse_tgs[mouse_index].x = max(
|
||||||
|
0, min(display.width - 1, mouse_tgs[mouse_index].x + mouse_deltas[0])
|
||||||
|
)
|
||||||
|
# update the y position of this mouse cursor using the delta value
|
||||||
|
# clamped to the display size
|
||||||
|
mouse_tgs[mouse_index].y = max(
|
||||||
|
0, min(display.height - 1, mouse_tgs[mouse_index].y + mouse_deltas[1])
|
||||||
|
)
|
||||||
|
|
||||||
|
# output string with the new cursor position
|
||||||
|
out_str = f"{mouse_tgs[mouse_index].x},{mouse_tgs[mouse_index].y}"
|
||||||
|
|
||||||
|
# loop over possible button bit indexes
|
||||||
|
for i, button in enumerate(BUTTONS):
|
||||||
|
# check each bit index to determin if the button was pressed
|
||||||
|
if mouse_bufs[mouse_index][0] & (1 << i) != 0:
|
||||||
|
# if it was pressed, add the button to the output string
|
||||||
|
out_str += f" {button}"
|
||||||
|
|
||||||
|
# set the output string into text of the label
|
||||||
|
# to show it on the display
|
||||||
|
output_lbls[mouse_index].text = out_str
|
||||||
BIN
Metro/Metro_RP2350_Match3/two_mice_demo/mouse_cursor.bmp
Normal file
|
After Width: | Height: | Size: 198 B |
|
|
@ -76,8 +76,27 @@ def open_next_image():
|
||||||
return open(filename, "wb")
|
return open(filename, "wb")
|
||||||
|
|
||||||
cam.colorspace = adafruit_ov5640.OV5640_COLOR_JPEG
|
cam.colorspace = adafruit_ov5640.OV5640_COLOR_JPEG
|
||||||
cam.quality = 3
|
|
||||||
b = bytearray(cam.capture_buffer_size)
|
# Different platforms have different amounts of memory available.
|
||||||
|
# Typically a Pico 2 can handle quality = 2 and a Pico can handle quality = 5.
|
||||||
|
# Rather than detect and select sizes, let's try to detect the best dynamically
|
||||||
|
# for broader platform support.
|
||||||
|
# Start with the highest quality setting and attempt to allocate a buffer
|
||||||
|
# of the necessary size. If it fails, try the next lowest.
|
||||||
|
b = None
|
||||||
|
for quality in range(2,55): #valid range is 2 to 54 inclusive
|
||||||
|
try:
|
||||||
|
cam.quality = quality
|
||||||
|
print(f"Attempting to use quality {quality}.")
|
||||||
|
b = bytearray(cam.capture_buffer_size)
|
||||||
|
print(f"Quality {quality} successfully selected.")
|
||||||
|
break
|
||||||
|
except MemoryError:
|
||||||
|
print(f"Quality {quality} was too big. Trying next lowest.")
|
||||||
|
|
||||||
|
if b is None:
|
||||||
|
print("There wasn't enough system memory to allocate the lowest quality buffer.")
|
||||||
|
|
||||||
jpeg = cam.capture(b)
|
jpeg = cam.capture(b)
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
#
|
#
|
||||||
# SPDX-License-Identifier: MIT
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
from os import getenv
|
||||||
import time
|
import time
|
||||||
import ssl
|
import ssl
|
||||||
import wifi
|
import wifi
|
||||||
|
|
@ -14,6 +15,21 @@ import adafruit_requests
|
||||||
from digitalio import DigitalInOut, Direction, Pull
|
from digitalio import DigitalInOut, Direction, Pull
|
||||||
from adafruit_debouncer import Debouncer
|
from adafruit_debouncer import Debouncer
|
||||||
|
|
||||||
|
# Get WiFi details and Adafruit IO keys, ensure these are setup in settings.toml
|
||||||
|
# (visit io.adafruit.com if you need to create an account, or if you need your Adafruit IO key.)
|
||||||
|
ssid = getenv("CIRCUITPY_WIFI_SSID")
|
||||||
|
password = getenv("CIRCUITPY_WIFI_PASSWORD")
|
||||||
|
aio_username = getenv("ADAFRUIT_AIO_USERNAME")
|
||||||
|
aio_key = getenv("ADAFRUIT_AIO_KEY")
|
||||||
|
|
||||||
|
if None in [ssid, password, aio_username, aio_key]:
|
||||||
|
raise RuntimeError(
|
||||||
|
"WiFi and Adafruit IO settings are kept in settings.toml, "
|
||||||
|
"please add them there. The settings file must contain "
|
||||||
|
"'CIRCUITPY_WIFI_SSID', 'CIRCUITPY_WIFI_PASSWORD', "
|
||||||
|
"'ADAFRUIT_AIO_USERNAME' and 'ADAFRUIT_AIO_KEY' at a minimum."
|
||||||
|
)
|
||||||
|
|
||||||
alarm_out = DigitalInOut(board.A1)
|
alarm_out = DigitalInOut(board.A1)
|
||||||
alarm_out.direction = Direction.OUTPUT
|
alarm_out.direction = Direction.OUTPUT
|
||||||
alarm_out.value = False
|
alarm_out.value = False
|
||||||
|
|
@ -23,37 +39,28 @@ button_in.pull = Pull.UP
|
||||||
button = Debouncer(button_in)
|
button = Debouncer(button_in)
|
||||||
|
|
||||||
|
|
||||||
# Get wifi details and more from a secrets.py file
|
|
||||||
try:
|
|
||||||
from secrets import secrets
|
|
||||||
except ImportError:
|
|
||||||
print("WiFi secrets are kept in secrets.py, please add them there!")
|
|
||||||
raise
|
|
||||||
|
|
||||||
print("Adafruit Raspberry Pi In Stock Tweet Listener")
|
print("Adafruit Raspberry Pi In Stock Tweet Listener")
|
||||||
|
|
||||||
# import your bearer token
|
# import your bearer token
|
||||||
bear = secrets['bearer_token']
|
bearer_token = getenv('bearer_token')
|
||||||
|
|
||||||
# query URL for tweets. looking for hashtag partyparrot sent to a specific username
|
# query URL for tweets. looking for hashtag partyparrot sent to a specific username
|
||||||
# disabling line-too-long because queries for tweet_query & TIME_URL cannot have line breaks
|
# disabling line-too-long because queries for tweet_query & TIME_URL cannot have line breaks
|
||||||
# pylint: disable=line-too-long
|
# pylint: disable=line-too-long
|
||||||
tweet_query = 'https://api.twitter.com/2/tweets/search/recent?query=In Stock at Adafruit from:rpilocator&tweet.fields=created_at'
|
tweet_query = 'https://api.twitter.com/2/tweets/search/recent?query=In Stock at Adafruit from:rpilocator&tweet.fields=created_at'
|
||||||
|
|
||||||
headers = {'Authorization': 'Bearer ' + bear}
|
headers = {'Authorization': 'Bearer ' + bearer_token}
|
||||||
|
|
||||||
print("Connecting to %s"%secrets["ssid"])
|
print(f"Connecting to {ssid}")
|
||||||
wifi.radio.connect(secrets["ssid"], secrets["password"])
|
wifi.radio.connect(ssid, password)
|
||||||
print("Connected to %s!"%secrets["ssid"])
|
print(f"Connected to {ssid}!")
|
||||||
print("My IP address is", wifi.radio.ipv4_address)
|
print(f"My IP address is {wifi.radio.ipv4_address}")
|
||||||
|
|
||||||
pool = socketpool.SocketPool(wifi.radio)
|
pool = socketpool.SocketPool(wifi.radio)
|
||||||
requests = adafruit_requests.Session(pool, ssl.create_default_context())
|
requests = adafruit_requests.Session(pool, ssl.create_default_context())
|
||||||
|
|
||||||
# gets and formats time from adafruit.io
|
# gets and formats time from adafruit.io
|
||||||
aio_username = secrets["aio_username"]
|
location = getenv("timezone", None)
|
||||||
aio_key = secrets["aio_key"]
|
|
||||||
location = secrets.get("timezone", None)
|
|
||||||
TIME_URL = "https://io.adafruit.com/api/v2/%s/integrations/time/strftime?x-aio-key=%s" % (aio_username, aio_key)
|
TIME_URL = "https://io.adafruit.com/api/v2/%s/integrations/time/strftime?x-aio-key=%s" % (aio_username, aio_key)
|
||||||
TIME_URL += "&fmt=%25Y-%25m-%25dT%25H%3A%25M%3A%25S.%25L%25j%25u%25z%25Z"
|
TIME_URL += "&fmt=%25Y-%25m-%25dT%25H%3A%25M%3A%25S.%25L%25j%25u%25z%25Z"
|
||||||
|
|
||||||
|
|
@ -132,7 +139,7 @@ while True:
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# if it's not new, then the wait continues
|
# if it's not new, then the wait continues
|
||||||
no_tweet_text = ("No stock in last hour :( Last stock: %s" % (timestamp))
|
no_tweet_text = "No stock in last hour :( Last stock: %s" % (timestamp)
|
||||||
text_area.text = "\n".join(wrap_text_to_lines(no_tweet_text, 21))
|
text_area.text = "\n".join(wrap_text_to_lines(no_tweet_text, 21))
|
||||||
print("no new in stock notifications :(")
|
print("no new in stock notifications :(")
|
||||||
# updates tweet ID
|
# updates tweet ID
|
||||||
|
|
@ -140,6 +147,6 @@ while True:
|
||||||
# if the tweet wasn't today
|
# if the tweet wasn't today
|
||||||
else:
|
else:
|
||||||
# if it's not new, then the wait continues
|
# if it's not new, then the wait continues
|
||||||
no_tweet_text = ("No stock in last hour :( Last stock: %s" % (timestamp))
|
no_tweet_text = "No stock in last hour :( Last stock: %s" % (timestamp)
|
||||||
text_area.text = "\n".join(wrap_text_to_lines(no_tweet_text, 21))
|
text_area.text = "\n".join(wrap_text_to_lines(no_tweet_text, 21))
|
||||||
print("no new in stock notifications :(")
|
print("no new in stock notifications :(")
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ ow_bus = OneWireBus(board.GP6)
|
||||||
# scan for temp sensor
|
# scan for temp sensor
|
||||||
ds18 = DS18X20(ow_bus, ow_bus.scan()[0])
|
ds18 = DS18X20(ow_bus, ow_bus.scan()[0])
|
||||||
|
|
||||||
# function to convert celcius to fahrenheit
|
# function to convert celsius to fahrenheit
|
||||||
def c_to_f(temp):
|
def c_to_f(temp):
|
||||||
temp_f = (temp * 9/5) + 32
|
temp_f = (temp * 9/5) + 32
|
||||||
return temp_f
|
return temp_f
|
||||||
|
|
|
||||||
|
|
@ -2,17 +2,29 @@
|
||||||
#
|
#
|
||||||
# SPDX-License-Identifier: MIT
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
import os
|
from os import getenv
|
||||||
import ipaddress
|
import ipaddress
|
||||||
import wifi
|
import wifi
|
||||||
import socketpool
|
import socketpool
|
||||||
|
|
||||||
|
# Get WiFi details, ensure these are setup in settings.toml
|
||||||
|
ssid = getenv("CIRCUITPY_WIFI_SSID")
|
||||||
|
password = getenv("CIRCUITPY_WIFI_PASSWORD")
|
||||||
|
|
||||||
|
if None in [ssid, password]:
|
||||||
|
raise RuntimeError(
|
||||||
|
"WiFi settings are kept in settings.toml, "
|
||||||
|
"please add them there. The settings file must contain "
|
||||||
|
"'CIRCUITPY_WIFI_SSID', 'CIRCUITPY_WIFI_PASSWORD', "
|
||||||
|
"at a minimum."
|
||||||
|
)
|
||||||
|
|
||||||
print()
|
print()
|
||||||
print("Connecting to WiFi")
|
print("Connecting to WiFi")
|
||||||
|
|
||||||
# connect to your SSID
|
# connect to your SSID
|
||||||
try:
|
try:
|
||||||
wifi.radio.connect(os.getenv('CIRCUITPY_WIFI_SSID'), os.getenv('CIRCUITPY_WIFI_PASSWORD'))
|
wifi.radio.connect(ssid, password)
|
||||||
except TypeError:
|
except TypeError:
|
||||||
print("Could not find WiFi info. Check your settings.toml file!")
|
print("Could not find WiFi info. Check your settings.toml file!")
|
||||||
raise
|
raise
|
||||||
|
|
@ -25,7 +37,7 @@ pool = socketpool.SocketPool(wifi.radio)
|
||||||
print("My MAC addr:", [hex(i) for i in wifi.radio.mac_address])
|
print("My MAC addr:", [hex(i) for i in wifi.radio.mac_address])
|
||||||
|
|
||||||
# prints IP address to REPL
|
# prints IP address to REPL
|
||||||
print("My IP address is", wifi.radio.ipv4_address)
|
print(f"My IP address is {wifi.radio.ipv4_address}")
|
||||||
|
|
||||||
# pings Google
|
# pings Google
|
||||||
ipv4 = ipaddress.ip_address("8.8.4.4")
|
ipv4 = ipaddress.ip_address("8.8.4.4")
|
||||||
|
|
|
||||||
|
|
@ -7,17 +7,23 @@
|
||||||
# or Matrix Portal
|
# or Matrix Portal
|
||||||
# and 64 x 32 RGB LED Matrix
|
# and 64 x 32 RGB LED Matrix
|
||||||
|
|
||||||
|
from os import getenv
|
||||||
import time
|
import time
|
||||||
import board
|
import board
|
||||||
import terminalio
|
import terminalio
|
||||||
from adafruit_matrixportal.matrixportal import MatrixPortal
|
from adafruit_matrixportal.matrixportal import MatrixPortal
|
||||||
|
|
||||||
# Get wifi details and more from a secrets.py file
|
# Get WiFi details, ensure these are setup in settings.toml
|
||||||
try:
|
ssid = getenv("CIRCUITPY_WIFI_SSID")
|
||||||
from secrets import secrets
|
password = getenv("CIRCUITPY_WIFI_PASSWORD")
|
||||||
except ImportError:
|
|
||||||
print("WiFi secrets are kept in secrets.py, please add them there!")
|
if None in [ssid, password]:
|
||||||
raise
|
raise RuntimeError(
|
||||||
|
"WiFi settings are kept in settings.toml, "
|
||||||
|
"please add them there. The settings file must contain "
|
||||||
|
"'CIRCUITPY_WIFI_SSID', 'CIRCUITPY_WIFI_PASSWORD', "
|
||||||
|
"at a minimum."
|
||||||
|
)
|
||||||
|
|
||||||
def aqi_transform(val):
|
def aqi_transform(val):
|
||||||
aqi = pm_to_aqi(val) # derive Air Quality Index from Particulate Matter 2.5 value
|
aqi = pm_to_aqi(val) # derive Air Quality Index from Particulate Matter 2.5 value
|
||||||
|
|
@ -49,7 +55,7 @@ matrixportal = MatrixPortal(
|
||||||
status_neopixel=board.NEOPIXEL,
|
status_neopixel=board.NEOPIXEL,
|
||||||
debug=True,
|
debug=True,
|
||||||
url=DATA_SOURCE,
|
url=DATA_SOURCE,
|
||||||
headers={"X-API-Key": secrets["purple_air_api_key"], # purpleair.com
|
headers={"X-API-Key": getenv("purple_air_api_key"), # purpleair.com
|
||||||
"Accept": "application/json"
|
"Accept": "application/json"
|
||||||
},
|
},
|
||||||
json_path=(DATA_LOCATION, DATA_LOCATION),
|
json_path=(DATA_LOCATION, DATA_LOCATION),
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,8 @@ notifications when it needs watering with your PyPortal.
|
||||||
|
|
||||||
Author: Brent Rubell for Adafruit Industries, 2019
|
Author: Brent Rubell for Adafruit Industries, 2019
|
||||||
"""
|
"""
|
||||||
import os
|
|
||||||
|
from os import getenv
|
||||||
import time
|
import time
|
||||||
import json
|
import json
|
||||||
import board
|
import board
|
||||||
|
|
@ -28,10 +29,17 @@ import aws_gfx_helper
|
||||||
# Time between polling the STEMMA, in minutes
|
# Time between polling the STEMMA, in minutes
|
||||||
SENSOR_DELAY = 15
|
SENSOR_DELAY = 15
|
||||||
|
|
||||||
secrets = {
|
# Get WiFi details, ensure these are setup in settings.toml
|
||||||
"ssid" : os.getenv("CIRCUITPY_WIFI_SSID"),
|
ssid = getenv("CIRCUITPY_WIFI_SSID")
|
||||||
"password" : os.getenv("CIRCUITPY_WIFI_PASSWORD"),
|
password = getenv("CIRCUITPY_WIFI_PASSWORD")
|
||||||
}
|
|
||||||
|
if None in [ssid, password]:
|
||||||
|
raise RuntimeError(
|
||||||
|
"WiFi settings are kept in settings.toml, "
|
||||||
|
"please add them there. The settings file must contain "
|
||||||
|
"'CIRCUITPY_WIFI_SSID', 'CIRCUITPY_WIFI_PASSWORD', "
|
||||||
|
"at a minimum."
|
||||||
|
)
|
||||||
|
|
||||||
# Get device certificate
|
# Get device certificate
|
||||||
try:
|
try:
|
||||||
|
|
@ -65,9 +73,10 @@ esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset)
|
||||||
# Verify nina-fw version >= 1.4.0
|
# Verify nina-fw version >= 1.4.0
|
||||||
assert int(bytes(esp.firmware_version).decode("utf-8")[2]) >= 4, "Please update nina-fw to >=1.4.0."
|
assert int(bytes(esp.firmware_version).decode("utf-8")[2]) >= 4, "Please update nina-fw to >=1.4.0."
|
||||||
|
|
||||||
status_light = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.2)
|
status_pixel = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.2)
|
||||||
wifi = adafruit_esp32spi_wifimanager.ESPSPI_WiFiManager(
|
wifi = adafruit_esp32spi_wifimanager.WiFiManager(
|
||||||
esp, secrets, status_light)
|
esp, ssid, password, status_pixel=status_pixel
|
||||||
|
)
|
||||||
|
|
||||||
# Initialize the graphics helper
|
# Initialize the graphics helper
|
||||||
print("Loading AWS IoT Graphics...")
|
print("Loading AWS IoT Graphics...")
|
||||||
|
|
@ -126,8 +135,8 @@ def message(client, topic, msg):
|
||||||
print("Message from {}: {}".format(topic, msg))
|
print("Message from {}: {}".format(topic, msg))
|
||||||
|
|
||||||
# Set up a new MiniMQTT Client
|
# Set up a new MiniMQTT Client
|
||||||
client = MQTT.MQTT(broker = os.getenv("BROKER"),
|
client = MQTT.MQTT(broker = getenv("BROKER"),
|
||||||
client_id = os.getenv("CLIENT_ID"),
|
client_id = getenv("CLIENT_ID"),
|
||||||
socket_pool=pool,
|
socket_pool=pool,
|
||||||
ssl_context=ssl_context)
|
ssl_context=ssl_context)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,19 +7,29 @@ This example will access the adafruit.io API, grab a number like active users
|
||||||
and io plus subscribers... and display it on a screen
|
and io plus subscribers... and display it on a screen
|
||||||
If you can find something that spits out JSON data, we can display it!
|
If you can find something that spits out JSON data, we can display it!
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from os import getenv
|
||||||
import time
|
import time
|
||||||
import board
|
import board
|
||||||
from adafruit_pyportal import PyPortal
|
from adafruit_pyportal import PyPortal
|
||||||
|
|
||||||
# Get wifi details and more from a secrets.py file
|
# Get WiFi details and Adafruit IO keys, ensure these are setup in settings.toml
|
||||||
try:
|
# (visit io.adafruit.com if you need to create an account, or if you need your Adafruit IO key.)
|
||||||
from secrets import secrets
|
ssid = getenv("CIRCUITPY_WIFI_SSID")
|
||||||
except ImportError:
|
password = getenv("CIRCUITPY_WIFI_PASSWORD")
|
||||||
print("WiFi secrets are kept in secrets.py, please add them there!")
|
aio_username = getenv("ADAFRUIT_AIO_USERNAME")
|
||||||
raise
|
aio_key = getenv("ADAFRUIT_AIO_KEY")
|
||||||
|
|
||||||
|
if None in [ssid, password, aio_username, aio_key]:
|
||||||
|
raise RuntimeError(
|
||||||
|
"WiFi and Adafruit IO settings are kept in settings.toml, "
|
||||||
|
"please add them there. The settings file must contain "
|
||||||
|
"'CIRCUITPY_WIFI_SSID', 'CIRCUITPY_WIFI_PASSWORD', "
|
||||||
|
"'ADAFRUIT_AIO_USERNAME' and 'ADAFRUIT_AIO_KEY' at a minimum."
|
||||||
|
)
|
||||||
|
|
||||||
# Set up where we'll be fetching data from
|
# Set up where we'll be fetching data from
|
||||||
DATA_SOURCE = "https://io.adafruit.com/api/v2/stats?x-aio-key="+secrets['aio_key']
|
DATA_SOURCE = f"https://io.adafruit.com/api/v2/stats?x-aio-key={aio_key}"
|
||||||
DATA_LOCATION1 = ["io_plus", "io_plus_subscriptions"]
|
DATA_LOCATION1 = ["io_plus", "io_plus_subscriptions"]
|
||||||
DATA_LOCATION2 = ["users", "users_active_30_days"]
|
DATA_LOCATION2 = ["users", "users_active_30_days"]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,8 @@ Dependencies:
|
||||||
* CircuitPython_AdafruitIO
|
* CircuitPython_AdafruitIO
|
||||||
https://github.com/adafruit/Adafruit_CircuitPython_AdafruitIO
|
https://github.com/adafruit/Adafruit_CircuitPython_AdafruitIO
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from os import getenv
|
||||||
import time
|
import time
|
||||||
import board
|
import board
|
||||||
import busio
|
import busio
|
||||||
|
|
@ -33,12 +35,20 @@ import adafruit_adt7410
|
||||||
# Timeout between sending data to Adafruit IO, in seconds
|
# Timeout between sending data to Adafruit IO, in seconds
|
||||||
IO_DELAY = 30
|
IO_DELAY = 30
|
||||||
|
|
||||||
# Get wifi details and more from a secrets.py file
|
# Get WiFi details and Adafruit IO keys, ensure these are setup in settings.toml
|
||||||
try:
|
# (visit io.adafruit.com if you need to create an account, or if you need your Adafruit IO key.)
|
||||||
from secrets import secrets
|
ssid = getenv("CIRCUITPY_WIFI_SSID")
|
||||||
except ImportError:
|
password = getenv("CIRCUITPY_WIFI_PASSWORD")
|
||||||
print("WiFi secrets are kept in secrets.py, please add them there!")
|
aio_username = getenv("ADAFRUIT_AIO_USERNAME")
|
||||||
raise
|
aio_key = getenv("ADAFRUIT_AIO_KEY")
|
||||||
|
|
||||||
|
if None in [ssid, password, aio_username, aio_key]:
|
||||||
|
raise RuntimeError(
|
||||||
|
"WiFi and Adafruit IO settings are kept in settings.toml, "
|
||||||
|
"please add them there. The settings file must contain "
|
||||||
|
"'CIRCUITPY_WIFI_SSID', 'CIRCUITPY_WIFI_PASSWORD', "
|
||||||
|
"'ADAFRUIT_AIO_USERNAME' and 'ADAFRUIT_AIO_KEY' at a minimum."
|
||||||
|
)
|
||||||
|
|
||||||
# PyPortal ESP32 Setup
|
# PyPortal ESP32 Setup
|
||||||
esp32_cs = DigitalInOut(board.ESP_CS)
|
esp32_cs = DigitalInOut(board.ESP_CS)
|
||||||
|
|
@ -46,17 +56,11 @@ esp32_ready = DigitalInOut(board.ESP_BUSY)
|
||||||
esp32_reset = DigitalInOut(board.ESP_RESET)
|
esp32_reset = DigitalInOut(board.ESP_RESET)
|
||||||
spi = busio.SPI(board.SCK, board.MOSI, board.MISO)
|
spi = busio.SPI(board.SCK, board.MOSI, board.MISO)
|
||||||
esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset)
|
esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset)
|
||||||
status_light = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.2)
|
status_pixel = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.2)
|
||||||
wifi = adafruit_esp32spi_wifimanager.ESPSPI_WiFiManager(esp, secrets, status_light)
|
wifi = adafruit_esp32spi_wifimanager.WiFiManager(esp, ssid, password, status_pixel=status_pixel)
|
||||||
|
|
||||||
# Set your Adafruit IO Username and Key in secrets.py
|
|
||||||
# (visit io.adafruit.com if you need to create an account,
|
|
||||||
# or if you need your Adafruit IO key.)
|
|
||||||
ADAFRUIT_IO_USER = secrets['aio_username']
|
|
||||||
ADAFRUIT_IO_KEY = secrets['aio_key']
|
|
||||||
|
|
||||||
# Create an instance of the Adafruit IO HTTP client
|
# Create an instance of the Adafruit IO HTTP client
|
||||||
io = IO_HTTP(ADAFRUIT_IO_USER, ADAFRUIT_IO_KEY, wifi)
|
io = IO_HTTP(aio_username, aio_key, wifi)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Get the 'temperature' feed from Adafruit IO
|
# Get the 'temperature' feed from Adafruit IO
|
||||||
|
|
|
||||||
|
|
@ -20,9 +20,9 @@ All text above must be included in any redistribution.
|
||||||
#pylint:disable=no-self-use,too-many-branches,too-many-statements
|
#pylint:disable=no-self-use,too-many-branches,too-many-statements
|
||||||
#pylint:disable=useless-super-delegation, too-many-locals
|
#pylint:disable=useless-super-delegation, too-many-locals
|
||||||
|
|
||||||
|
from os import getenv
|
||||||
import time
|
import time
|
||||||
import json
|
import json
|
||||||
from secrets import secrets
|
|
||||||
import board
|
import board
|
||||||
from adafruit_pyportal import PyPortal
|
from adafruit_pyportal import PyPortal
|
||||||
from adafruit_bitmap_font import bitmap_font
|
from adafruit_bitmap_font import bitmap_font
|
||||||
|
|
@ -32,9 +32,21 @@ import analogio
|
||||||
import displayio
|
import displayio
|
||||||
import adafruit_logging as logging
|
import adafruit_logging as logging
|
||||||
|
|
||||||
|
# Get WiFi details, ensure these are setup in settings.toml
|
||||||
|
ssid = getenv("CIRCUITPY_WIFI_SSID")
|
||||||
|
password = getenv("CIRCUITPY_WIFI_PASSWORD")
|
||||||
|
|
||||||
|
if None in [ssid, password]:
|
||||||
|
raise RuntimeError(
|
||||||
|
"WiFi settings are kept in settings.toml, "
|
||||||
|
"please add them there. The settings file must contain "
|
||||||
|
"'CIRCUITPY_WIFI_SSID', 'CIRCUITPY_WIFI_PASSWORD', "
|
||||||
|
"at a minimum."
|
||||||
|
)
|
||||||
|
|
||||||
# Set up where we'll be fetching data from
|
# Set up where we'll be fetching data from
|
||||||
DATA_SOURCE = 'http://api.openweathermap.org/data/2.5/weather?id='+secrets['city_id']
|
DATA_SOURCE = 'http://api.openweathermap.org/data/2.5/weather?id='+getenv('city_id')
|
||||||
DATA_SOURCE += '&appid='+secrets['openweather_token']
|
DATA_SOURCE += '&appid='+getenv('openweather_token')
|
||||||
# You'll need to get a token from openweather.org, looks like 'b6907d289e10d714a6e88b30761fae22'
|
# You'll need to get a token from openweather.org, looks like 'b6907d289e10d714a6e88b30761fae22'
|
||||||
DATA_LOCATION = []
|
DATA_LOCATION = []
|
||||||
|
|
||||||
|
|
@ -73,7 +85,7 @@ mugsy_background = 'mugsy_background.bmp'
|
||||||
|
|
||||||
icon_file = None
|
icon_file = None
|
||||||
icon_sprite = None
|
icon_sprite = None
|
||||||
celcius = secrets['celcius']
|
celsius = getenv('celsius')
|
||||||
|
|
||||||
# display/data refresh timers
|
# display/data refresh timers
|
||||||
|
|
||||||
|
|
@ -243,7 +255,7 @@ class Time_State(State):
|
||||||
if (not self.refresh_time) or ((now - self.refresh_time) > 3600):
|
if (not self.refresh_time) or ((now - self.refresh_time) > 3600):
|
||||||
logger.debug('Fetching time')
|
logger.debug('Fetching time')
|
||||||
try:
|
try:
|
||||||
pyportal.get_local_time(location=secrets['timezone'])
|
pyportal.get_local_time(location=getenv('timezone'))
|
||||||
self.refresh_time = now
|
self.refresh_time = now
|
||||||
except RuntimeError as e:
|
except RuntimeError as e:
|
||||||
self.refresh_time = now - 3000 # delay 10 minutes before retrying
|
self.refresh_time = now - 3000 # delay 10 minutes before retrying
|
||||||
|
|
@ -282,7 +294,7 @@ class Time_State(State):
|
||||||
self.weather_icon.append(icon_sprite)
|
self.weather_icon.append(icon_sprite)
|
||||||
|
|
||||||
temperature = weather['main']['temp'] - 273.15 # its...in kelvin
|
temperature = weather['main']['temp'] - 273.15 # its...in kelvin
|
||||||
if celcius:
|
if celsius:
|
||||||
temperature_text = '%3d C' % round(temperature)
|
temperature_text = '%3d C' % round(temperature)
|
||||||
else:
|
else:
|
||||||
temperature_text = '%3d F' % round(((temperature * 9 / 5) + 32))
|
temperature_text = '%3d F' % round(((temperature * 9 / 5) + 32))
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,8 @@ your PyPortal
|
||||||
Authors: Brent Rubell for Adafruit Industries, 2019
|
Authors: Brent Rubell for Adafruit Industries, 2019
|
||||||
: Jim Bennett for Microsoft, 2020
|
: Jim Bennett for Microsoft, 2020
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from os import getenv
|
||||||
import time
|
import time
|
||||||
import json
|
import json
|
||||||
import board
|
import board
|
||||||
|
|
@ -30,12 +32,17 @@ import azure_gfx_helper
|
||||||
# init. graphics helper
|
# init. graphics helper
|
||||||
gfx = azure_gfx_helper.Azure_GFX(is_celsius=True)
|
gfx = azure_gfx_helper.Azure_GFX(is_celsius=True)
|
||||||
|
|
||||||
# Get wifi details and more from a secrets.py file
|
# Get WiFi details, ensure these are setup in settings.toml
|
||||||
try:
|
ssid = getenv("CIRCUITPY_WIFI_SSID")
|
||||||
from secrets import secrets
|
password = getenv("CIRCUITPY_WIFI_PASSWORD")
|
||||||
except ImportError:
|
|
||||||
print("WiFi secrets are kept in secrets.py, please add them there!")
|
if None in [ssid, password]:
|
||||||
raise
|
raise RuntimeError(
|
||||||
|
"WiFi settings are kept in settings.toml, "
|
||||||
|
"please add them there. The settings file must contain "
|
||||||
|
"'CIRCUITPY_WIFI_SSID', 'CIRCUITPY_WIFI_PASSWORD', "
|
||||||
|
"at a minimum."
|
||||||
|
)
|
||||||
|
|
||||||
# PyPortal ESP32 Setup
|
# PyPortal ESP32 Setup
|
||||||
esp32_cs = DigitalInOut(board.ESP_CS)
|
esp32_cs = DigitalInOut(board.ESP_CS)
|
||||||
|
|
@ -45,8 +52,8 @@ spi = busio.SPI(board.SCK, board.MOSI, board.MISO)
|
||||||
esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset)
|
esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset)
|
||||||
|
|
||||||
# Set up the WiFi manager with a status light to show the WiFi connection status
|
# Set up the WiFi manager with a status light to show the WiFi connection status
|
||||||
status_light = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.2)
|
status_pixel = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.2)
|
||||||
wifi = adafruit_esp32spi_wifimanager.ESPSPI_WiFiManager(esp, secrets, status_light)
|
wifi = adafruit_esp32spi_wifimanager.WiFiManager(esp, ssid, password, status_pixel=status_pixel)
|
||||||
print("WiFi connecting...")
|
print("WiFi connecting...")
|
||||||
wifi.connect()
|
wifi.connect()
|
||||||
print("WiFi connected!")
|
print("WiFi connected!")
|
||||||
|
|
@ -68,7 +75,7 @@ ss = Seesaw(i2c_bus, addr=0x36)
|
||||||
|
|
||||||
# Create an instance of the Azure IoT Central device
|
# Create an instance of the Azure IoT Central device
|
||||||
device = IoTCentralDevice(
|
device = IoTCentralDevice(
|
||||||
socket, esp, secrets["id_scope"], secrets["device_id"], secrets["key"]
|
socket, esp, getenv("id_scope"), getenv("device_id"), getenv("key")
|
||||||
)
|
)
|
||||||
|
|
||||||
# Connect to Azure IoT Central
|
# Connect to Azure IoT Central
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
#
|
#
|
||||||
# SPDX-License-Identifier: MIT
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
from os import getenv
|
||||||
import time
|
import time
|
||||||
import random
|
import random
|
||||||
import board
|
import board
|
||||||
|
|
@ -11,6 +12,21 @@ from adafruit_display_shapes.circle import Circle
|
||||||
WIDTH = board.DISPLAY.width
|
WIDTH = board.DISPLAY.width
|
||||||
HEIGHT = board.DISPLAY.height
|
HEIGHT = board.DISPLAY.height
|
||||||
|
|
||||||
|
# Get WiFi details and Adafruit IO keys, ensure these are setup in settings.toml
|
||||||
|
# (visit io.adafruit.com if you need to create an account, or if you need your Adafruit IO key.)
|
||||||
|
ssid = getenv("CIRCUITPY_WIFI_SSID")
|
||||||
|
password = getenv("CIRCUITPY_WIFI_PASSWORD")
|
||||||
|
aio_username = getenv("ADAFRUIT_AIO_USERNAME")
|
||||||
|
aio_key = getenv("ADAFRUIT_AIO_KEY")
|
||||||
|
|
||||||
|
if None in [ssid, password, aio_username, aio_key]:
|
||||||
|
raise RuntimeError(
|
||||||
|
"WiFi and Adafruit IO settings are kept in settings.toml, "
|
||||||
|
"please add them there. The settings file must contain "
|
||||||
|
"'CIRCUITPY_WIFI_SSID', 'CIRCUITPY_WIFI_PASSWORD', "
|
||||||
|
"'ADAFRUIT_AIO_USERNAME' and 'ADAFRUIT_AIO_KEY' at a minimum."
|
||||||
|
)
|
||||||
|
|
||||||
#pylint: disable=line-too-long
|
#pylint: disable=line-too-long
|
||||||
|
|
||||||
# these lines show the entire collection
|
# these lines show the entire collection
|
||||||
|
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
# SPDX-FileCopyrightText: 2019 Kattni Rembor for Adafruit Industries
|
|
||||||
#
|
|
||||||
# SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
# This file is where you keep secret settings, passwords, and tokens!
|
|
||||||
# If you put them in the code you risk committing that info or sharing it
|
|
||||||
|
|
||||||
secrets = {
|
|
||||||
'ssid' : 'CHANGE ME',
|
|
||||||
'password' : 'CHANGE ME',
|
|
||||||
'aio_username' : 'CHANGE ME',
|
|
||||||
'aio_key' : 'CHANGE ME',
|
|
||||||
}
|
|
||||||
12
PyPortal/PyPortal_CMA_Art_Frame/settings.toml
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
# SPDX-FileCopyrightText: 2019 Kattni Rembor for Adafruit Industries
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
# This file is where you keep private settings, passwords, and tokens!
|
||||||
|
# If you put them in the code you risk committing that info or sharing it
|
||||||
|
|
||||||
|
CIRCUITPY_WIFI_SSID="your-wifi-ssid"
|
||||||
|
CIRCUITPY_WIFI_PASSWORD="your-wifi-password"
|
||||||
|
ADAFRUIT_AIO_USERNAME="my_username"
|
||||||
|
ADAFRUIT_AIO_KEY="my_key"
|
||||||
|
CIRCUITPY_PYSTACK_SIZE=2048
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
PyPortal_Electioncal_US/electioncal_graphics.py 73: Line too long (120/100) (line-too-long)
|
|
||||||
PyPortal_Electioncal_US/electioncal_graphics.py 75: Line too long (108/100) (line-too-long)
|
|
||||||
PyPortal_Electioncal_US/electioncal_graphics.py 86: Line too long (141/100) (line-too-long)
|
|
||||||
PyPortal_Electioncal_US/electioncal_graphics.py 107: Unused variable 'time_str' (unused-variable)
|
|
||||||
PyPortal_Electioncal_US/electioncal_graphics.py 111: Method could be a function (no-self-use)
|
|
||||||
PyPortal_Electioncal_US/electioncal_graphics.py 72: Attribute 'electioncal' defined outside __init__ (attribute-defined-outside-init)
|
|
||||||
PyPortal_Electioncal_US/electioncal.py 11: Unused secrets imported from secrets (unused-import)
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
#
|
#
|
||||||
# SPDX-License-Identifier: MIT
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
from os import getenv
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
import board
|
import board
|
||||||
|
|
@ -10,12 +11,17 @@ cwd = ("/"+__file__).rsplit('/', 1)[0] # the current working directory (where th
|
||||||
sys.path.append(cwd)
|
sys.path.append(cwd)
|
||||||
import electioncal_graphics # pylint: disable=wrong-import-position
|
import electioncal_graphics # pylint: disable=wrong-import-position
|
||||||
|
|
||||||
# Get wifi details and more from a secrets.py file
|
# Get WiFi details, ensure these are setup in settings.toml
|
||||||
try:
|
ssid = getenv("CIRCUITPY_WIFI_SSID")
|
||||||
from secrets import secrets # pylint: disable=unused-import
|
password = getenv("CIRCUITPY_WIFI_PASSWORD")
|
||||||
except ImportError:
|
|
||||||
print("WiFi secrets are kept in secrets.py, please add them there!")
|
if None in [ssid, password]:
|
||||||
raise
|
raise RuntimeError(
|
||||||
|
"WiFi settings are kept in settings.toml, "
|
||||||
|
"please add them there. The settings file must contain "
|
||||||
|
"'CIRCUITPY_WIFI_SSID', 'CIRCUITPY_WIFI_PASSWORD', "
|
||||||
|
"at a minimum."
|
||||||
|
)
|
||||||
|
|
||||||
# Change this to your state and county, replacing spaces for underscores and in lowercase
|
# Change this to your state and county, replacing spaces for underscores and in lowercase
|
||||||
STATE="new_york"
|
STATE="new_york"
|
||||||
|
|
|
||||||
|
|
@ -112,7 +112,8 @@ class Electioncal_Graphics(displayio.Group):
|
||||||
date_str = date_format_str % (year, month, day)
|
date_str = date_format_str % (year, month, day)
|
||||||
self.date_text.text = "Today is: " + date_str
|
self.date_text.text = "Today is: " + date_str
|
||||||
|
|
||||||
def paragrapher(self, text, cut): # pylint: disable=no-self-use
|
@staticmethod
|
||||||
|
def paragrapher(text, cut):
|
||||||
""" Cuts a long line into two, having spaces in mind.
|
""" Cuts a long line into two, having spaces in mind.
|
||||||
Note we return line2 first as it looks better to clear the line2
|
Note we return line2 first as it looks better to clear the line2
|
||||||
before printing a line1 with empty line2
|
before printing a line1 with empty line2
|
||||||
|
|
|
||||||
|
|
@ -7,25 +7,30 @@ PyPortal Adafruit IO Feed Display
|
||||||
|
|
||||||
Displays an Adafruit IO Feed on a PyPortal.
|
Displays an Adafruit IO Feed on a PyPortal.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from os import getenv
|
||||||
import time
|
import time
|
||||||
import board
|
import board
|
||||||
from adafruit_pyportal import PyPortal
|
from adafruit_pyportal import PyPortal
|
||||||
|
|
||||||
# Get Adafruit IO details and more from a secrets.py file
|
# (visit io.adafruit.com if you need to create an account, or if you need your Adafruit IO key.)
|
||||||
try:
|
ssid = getenv("CIRCUITPY_WIFI_SSID")
|
||||||
from secrets import secrets
|
password = getenv("CIRCUITPY_WIFI_PASSWORD")
|
||||||
except ImportError:
|
aio_username = getenv("ADAFRUIT_AIO_USERNAME")
|
||||||
print("Adafruit IO secrets are kept in secrets.py, please add them there!")
|
aio_key = getenv("ADAFRUIT_AIO_KEY")
|
||||||
raise
|
|
||||||
|
if None in [ssid, password, aio_username, aio_key]:
|
||||||
|
raise RuntimeError(
|
||||||
|
"WiFi and Adafruit IO settings are kept in settings.toml, "
|
||||||
|
"please add them there. The settings file must contain "
|
||||||
|
"'CIRCUITPY_WIFI_SSID', 'CIRCUITPY_WIFI_PASSWORD', "
|
||||||
|
"'ADAFRUIT_AIO_USERNAME' and 'ADAFRUIT_AIO_KEY' at a minimum."
|
||||||
|
)
|
||||||
|
|
||||||
# Adafruit IO Account
|
|
||||||
IO_USER = secrets['aio_username']
|
|
||||||
IO_KEY = secrets['aio_key']
|
|
||||||
# Adafruit IO Feed
|
# Adafruit IO Feed
|
||||||
IO_FEED = 'zapemail'
|
io_feed = 'zapemail'
|
||||||
|
|
||||||
DATA_SOURCE = "https://io.adafruit.com/api/v2/{0}/feeds/{1}?X-AIO-Key={2}".format(IO_USER,
|
DATA_SOURCE = f"https://io.adafruit.com/api/v2/{aio_username}/feeds/{io_feed}?X-AIO-Key={aio_key}"
|
||||||
IO_FEED, IO_KEY)
|
|
||||||
FEED_VALUE_LOCATION = ['last_value']
|
FEED_VALUE_LOCATION = ['last_value']
|
||||||
|
|
||||||
cwd = ("/"+__file__).rsplit('/', 1)[0]
|
cwd = ("/"+__file__).rsplit('/', 1)[0]
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,8 @@ Cloud IoT Core with your PyPortal.
|
||||||
|
|
||||||
Author: Brent Rubell for Adafruit Industries, 2019
|
Author: Brent Rubell for Adafruit Industries, 2019
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from os import getenv
|
||||||
import time
|
import time
|
||||||
import json
|
import json
|
||||||
import board
|
import board
|
||||||
|
|
@ -22,26 +24,33 @@ from adafruit_gc_iot_core import MQTT_API, Cloud_Core
|
||||||
import adafruit_minimqtt.adafruit_minimqtt as MQTT
|
import adafruit_minimqtt.adafruit_minimqtt as MQTT
|
||||||
from adafruit_seesaw.seesaw import Seesaw
|
from adafruit_seesaw.seesaw import Seesaw
|
||||||
import digitalio
|
import digitalio
|
||||||
|
from rsa_private_key import private_key
|
||||||
|
|
||||||
|
# Get WiFi details, ensure these are setup in settings.toml
|
||||||
|
ssid = getenv("CIRCUITPY_WIFI_SSID")
|
||||||
|
password = getenv("CIRCUITPY_WIFI_PASSWORD")
|
||||||
|
|
||||||
|
if None in [ssid, password]:
|
||||||
|
raise RuntimeError(
|
||||||
|
"WiFi settings are kept in settings.toml, "
|
||||||
|
"please add them there. The settings file must contain "
|
||||||
|
"'CIRCUITPY_WIFI_SSID', 'CIRCUITPY_WIFI_PASSWORD', "
|
||||||
|
"at a minimum."
|
||||||
|
)
|
||||||
|
|
||||||
# Delay before reading the sensors, in minutes
|
# Delay before reading the sensors, in minutes
|
||||||
SENSOR_DELAY = 10
|
SENSOR_DELAY = 10
|
||||||
|
|
||||||
# Get wifi details and more from a secrets.py file
|
|
||||||
try:
|
|
||||||
from secrets import secrets
|
|
||||||
except ImportError:
|
|
||||||
print("WiFi secrets are kept in secrets.py, please add them there!")
|
|
||||||
raise
|
|
||||||
|
|
||||||
# PyPortal ESP32 Setup
|
# PyPortal ESP32 Setup
|
||||||
esp32_cs = digitalio.DigitalInOut(board.ESP_CS)
|
esp32_cs = digitalio.DigitalInOut(board.ESP_CS)
|
||||||
esp32_ready = digitalio.DigitalInOut(board.ESP_BUSY)
|
esp32_ready = digitalio.DigitalInOut(board.ESP_BUSY)
|
||||||
esp32_reset = digitalio.DigitalInOut(board.ESP_RESET)
|
esp32_reset = digitalio.DigitalInOut(board.ESP_RESET)
|
||||||
spi = busio.SPI(board.SCK, board.MOSI, board.MISO)
|
spi = busio.SPI(board.SCK, board.MOSI, board.MISO)
|
||||||
esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset)
|
esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset)
|
||||||
status_light = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.2)
|
status_pixel = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.2)
|
||||||
wifi = adafruit_esp32spi_wifimanager.ESPSPI_WiFiManager(
|
wifi = adafruit_esp32spi_wifimanager.WiFiManager(
|
||||||
esp, secrets, status_light)
|
esp, ssid, password, status_pixel=status_pixel
|
||||||
|
)
|
||||||
|
|
||||||
# Connect to WiFi
|
# Connect to WiFi
|
||||||
print("Connecting to WiFi...")
|
print("Connecting to WiFi...")
|
||||||
|
|
@ -137,7 +146,14 @@ def handle_pump(command):
|
||||||
|
|
||||||
|
|
||||||
# Initialize Google Cloud IoT Core interface
|
# Initialize Google Cloud IoT Core interface
|
||||||
google_iot = Cloud_Core(esp, secrets)
|
settings = {
|
||||||
|
"cloud_region": getenv("cloud_region"),
|
||||||
|
"device_id": getenv("device_id"),
|
||||||
|
"private_key": private_key,
|
||||||
|
"project_id": getenv("project_id"),
|
||||||
|
"registry_id": getenv("registry_id"),
|
||||||
|
}
|
||||||
|
google_iot = Cloud_Core(esp, settings)
|
||||||
|
|
||||||
# JSON-Web-Token (JWT) Generation
|
# JSON-Web-Token (JWT) Generation
|
||||||
print("Generating JWT...")
|
print("Generating JWT...")
|
||||||
|
|
|
||||||
|
|
@ -59,7 +59,7 @@ class Google_GFX(displayio.Group):
|
||||||
temp_group.append(temp_label)
|
temp_group.append(temp_label)
|
||||||
|
|
||||||
self.temp_data_label = Label(font, text="75 F")
|
self.temp_data_label = Label(font, text="75 F")
|
||||||
self.temp_data_label.x = (self.display.width//3)
|
self.temp_data_label.x = self.display.width//3
|
||||||
self.temp_data_label.y = 55
|
self.temp_data_label.y = 55
|
||||||
temp_group.append(self.temp_data_label)
|
temp_group.append(self.temp_data_label)
|
||||||
self._text_group.append(temp_group)
|
self._text_group.append(temp_group)
|
||||||
|
|
@ -72,7 +72,7 @@ class Google_GFX(displayio.Group):
|
||||||
water_group.append(self.water_level)
|
water_group.append(self.water_level)
|
||||||
|
|
||||||
self.water_lvl_label = Label(font, text="350")
|
self.water_lvl_label = Label(font, text="350")
|
||||||
self.water_lvl_label.x = (self.display.width//3)
|
self.water_lvl_label.x = self.display.width//3
|
||||||
self.water_lvl_label.y = 75
|
self.water_lvl_label.y = 75
|
||||||
temp_group.append(self.water_lvl_label)
|
temp_group.append(self.water_lvl_label)
|
||||||
self._text_group.append(water_group)
|
self._text_group.append(water_group)
|
||||||
|
|
|
||||||
6
PyPortal/PyPortal_GCP_IOT_Planter/rsa_private_key.py
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
# SPDX-FileCopyrightText: 2025 Tim Cocks for Adafruit Industries
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
# Replace the value with your generated RSA Private Key
|
||||||
|
private_key = (0, 0, 0, 0, 0)
|
||||||
|
|
@ -7,23 +7,31 @@ This example will access the github API, grab a number like
|
||||||
the number of stars for a repository... and display it on a screen!
|
the number of stars for a repository... and display it on a screen!
|
||||||
if you can find something that spits out JSON data, we can display it
|
if you can find something that spits out JSON data, we can display it
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from os import getenv
|
||||||
import time
|
import time
|
||||||
import board
|
import board
|
||||||
from adafruit_pyportal import PyPortal
|
from adafruit_pyportal import PyPortal
|
||||||
|
|
||||||
# Get wifi details and more from a secrets.py file
|
# Get WiFi details, ensure these are setup in settings.toml
|
||||||
try:
|
ssid = getenv("CIRCUITPY_WIFI_SSID")
|
||||||
from secrets import secrets
|
password = getenv("CIRCUITPY_WIFI_PASSWORD")
|
||||||
except ImportError:
|
|
||||||
print("WiFi secrets are kept in secrets.py, please add them there!")
|
if None in [ssid, password]:
|
||||||
raise
|
raise RuntimeError(
|
||||||
|
"WiFi settings are kept in settings.toml, "
|
||||||
|
"please add them there. The settings file must contain "
|
||||||
|
"'CIRCUITPY_WIFI_SSID', 'CIRCUITPY_WIFI_PASSWORD', "
|
||||||
|
"at a minimum."
|
||||||
|
)
|
||||||
|
|
||||||
# Set up where we'll be fetching data from
|
# Set up where we'll be fetching data from
|
||||||
DATA_SOURCE = "https://api.github.com/repos/adafruit/circuitpython"
|
DATA_SOURCE = "https://api.github.com/repos/adafruit/circuitpython"
|
||||||
CAPTION = "www.github.com/adafruit/circuitpython"
|
CAPTION = "www.github.com/adafruit/circuitpython"
|
||||||
# If we have an access token, we can query more often
|
# If we have an access token, we can query more often
|
||||||
if 'github_token' in secrets:
|
github_token = getenv("github_token")
|
||||||
DATA_SOURCE += "?access_token="+secrets['github_token']
|
if github_token:
|
||||||
|
DATA_SOURCE += f"?access_token={github_token}"
|
||||||
|
|
||||||
# The data we want to display
|
# The data we want to display
|
||||||
DATA_LOCATION = ["stargazers_count"]
|
DATA_LOCATION = ["stargazers_count"]
|
||||||
|
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
# SPDX-FileCopyrightText: 2020 Liz Clark for Adafruit Industries
|
|
||||||
#
|
|
||||||
# SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
secrets = {
|
|
||||||
'ssid' : 'your-ssid-here',
|
|
||||||
'password' : 'your-password-here',
|
|
||||||
'openweather_token' : 'your-openweather-token-here',
|
|
||||||
'aio_username' : "your-aio-username-here",
|
|
||||||
'aio_key' : 'your-aio-key-here',
|
|
||||||
'location' : 'New York, US'
|
|
||||||
}
|
|
||||||
|
|
@ -8,20 +8,27 @@ and display it on a screen
|
||||||
If you can find something that spits out JSON data, we can display it!
|
If you can find something that spits out JSON data, we can display it!
|
||||||
Note that you need a hackaday API key to access the API!
|
Note that you need a hackaday API key to access the API!
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from os import getenv
|
||||||
import time
|
import time
|
||||||
import board
|
import board
|
||||||
from adafruit_pyportal import PyPortal
|
from adafruit_pyportal import PyPortal
|
||||||
|
|
||||||
# Get wifi details and more from a secrets.py file
|
# Get WiFi details, ensure these are setup in settings.toml
|
||||||
try:
|
ssid = getenv("CIRCUITPY_WIFI_SSID")
|
||||||
from secrets import secrets
|
password = getenv("CIRCUITPY_WIFI_PASSWORD")
|
||||||
except ImportError:
|
|
||||||
print("WiFi secrets are kept in secrets.py, please add them there!")
|
if None in [ssid, password]:
|
||||||
raise
|
raise RuntimeError(
|
||||||
|
"WiFi settings are kept in settings.toml, "
|
||||||
|
"please add them there. The settings file must contain "
|
||||||
|
"'CIRCUITPY_WIFI_SSID', 'CIRCUITPY_WIFI_PASSWORD', "
|
||||||
|
"at a minimum."
|
||||||
|
)
|
||||||
|
|
||||||
# Some data sources and JSON locations to try out
|
# Some data sources and JSON locations to try out
|
||||||
CAPTION="hackaday.io/project/163309-circuitpython-hackaday"
|
CAPTION="hackaday.io/project/163309-circuitpython-hackaday"
|
||||||
DATA_SOURCE = "https://api.hackaday.io/v1/projects/163309?api_key="+secrets['hackaday_token']
|
DATA_SOURCE = "https://api.hackaday.io/v1/projects/163309?api_key="+getenv('hackaday_token')
|
||||||
DATA_LOCATION = ["skulls"]
|
DATA_LOCATION = ["skulls"]
|
||||||
|
|
||||||
# the current working directory (where this file is)
|
# the current working directory (where this file is)
|
||||||
|
|
|
||||||
|
|
@ -2,23 +2,29 @@
|
||||||
#
|
#
|
||||||
# SPDX-License-Identifier: MIT
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
from os import getenv
|
||||||
import time
|
import time
|
||||||
import board
|
import board
|
||||||
from adafruit_pyportal import PyPortal
|
from adafruit_pyportal import PyPortal
|
||||||
|
|
||||||
# Get wifi details and more from a secrets.py file
|
# Get WiFi details, ensure these are setup in settings.toml
|
||||||
try:
|
ssid = getenv("CIRCUITPY_WIFI_SSID")
|
||||||
from secrets import secrets
|
password = getenv("CIRCUITPY_WIFI_PASSWORD")
|
||||||
except ImportError:
|
|
||||||
print("WiFi secrets are kept in secrets.py, please add them there!")
|
if None in [ssid, password]:
|
||||||
raise
|
raise RuntimeError(
|
||||||
|
"WiFi settings are kept in settings.toml, "
|
||||||
|
"please add them there. The settings file must contain "
|
||||||
|
"'CIRCUITPY_WIFI_SSID', 'CIRCUITPY_WIFI_PASSWORD', "
|
||||||
|
"at a minimum."
|
||||||
|
)
|
||||||
|
|
||||||
PROJECT_NAME = "3c92f0"
|
PROJECT_NAME = "3c92f0"
|
||||||
|
|
||||||
# Set up where we'll be fetching data from
|
# Set up where we'll be fetching data from
|
||||||
DATA_SOURCE = "https://api.hackster.io/v2/projects/"+PROJECT_NAME+"?"
|
DATA_SOURCE = "https://api.hackster.io/v2/projects/"+PROJECT_NAME+"?"
|
||||||
DATA_SOURCE += "client_id="+secrets['hackster_clientid']
|
DATA_SOURCE += "client_id="+getenv('hackster_clientid')
|
||||||
DATA_SOURCE += "&client_secret="+secrets['hackster_secret']
|
DATA_SOURCE += "&client_secret="+getenv('hackster_secret')
|
||||||
VIEWS_LOCATION = ['stats', 'views']
|
VIEWS_LOCATION = ['stats', 'views']
|
||||||
LIKES_LOCATION = ['stats', 'respects']
|
LIKES_LOCATION = ['stats', 'respects']
|
||||||
CAPTION = "http://www.hackster.com/project/"+PROJECT_NAME
|
CAPTION = "http://www.hackster.com/project/"+PROJECT_NAME
|
||||||
|
|
|
||||||
|
|
@ -2,22 +2,28 @@
|
||||||
#
|
#
|
||||||
# SPDX-License-Identifier: MIT
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
from os import getenv
|
||||||
import time
|
import time
|
||||||
import random
|
import random
|
||||||
import board
|
import board
|
||||||
import adafruit_pyportal
|
import adafruit_pyportal
|
||||||
|
|
||||||
# Get wifi details and more from a settings.py file
|
# Get WiFi details, ensure these are setup in settings.toml
|
||||||
try:
|
ssid = getenv("CIRCUITPY_WIFI_SSID")
|
||||||
from secrets import secrets
|
password = getenv("CIRCUITPY_WIFI_PASSWORD")
|
||||||
except ImportError:
|
|
||||||
print("WiFi secrets are kept in secrets.py, please add them there!")
|
if None in [ssid, password]:
|
||||||
raise
|
raise RuntimeError(
|
||||||
|
"WiFi settings are kept in settings.toml, "
|
||||||
|
"please add them there. The settings file must contain "
|
||||||
|
"'CIRCUITPY_WIFI_SSID', 'CIRCUITPY_WIFI_PASSWORD', "
|
||||||
|
"at a minimum."
|
||||||
|
)
|
||||||
|
|
||||||
# Set up where we'll be fetching data from
|
# Set up where we'll be fetching data from
|
||||||
DATA_SOURCE = "https://api.hackster.io/v2/projects?"
|
DATA_SOURCE = "https://api.hackster.io/v2/projects?"
|
||||||
DATA_SOURCE += "client_id="+secrets['hackster_clientid']
|
DATA_SOURCE += "client_id="+getenv('hackster_clientid')
|
||||||
DATA_SOURCE += "&client_secret="+secrets['hackster_secret']
|
DATA_SOURCE += "&client_secret="+getenv('hackster_secret')
|
||||||
IMAGE_LOCATION = ['records', 0, "cover_image_url"]
|
IMAGE_LOCATION = ['records', 0, "cover_image_url"]
|
||||||
TITLE_LOCATION = ['records',0, "name"]
|
TITLE_LOCATION = ['records',0, "name"]
|
||||||
HID_LOCATION = ['records', 0, "hid"]
|
HID_LOCATION = ['records', 0, "hid"]
|
||||||
|
|
|
||||||
|
|
@ -16,8 +16,7 @@ Licensed under the MIT license.
|
||||||
All text above must be included in any redistribution.
|
All text above must be included in any redistribution.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
#pylint:disable=invalid-name
|
from os import getenv
|
||||||
import os
|
|
||||||
import time
|
import time
|
||||||
import random
|
import random
|
||||||
import board
|
import board
|
||||||
|
|
@ -25,6 +24,19 @@ from adafruit_pyportal import PyPortal
|
||||||
from adafruit_bitmap_font import bitmap_font
|
from adafruit_bitmap_font import bitmap_font
|
||||||
from adafruit_display_text.label import Label
|
from adafruit_display_text.label import Label
|
||||||
|
|
||||||
|
# Get WiFi details, ensure these are setup in settings.toml
|
||||||
|
ssid = getenv("CIRCUITPY_WIFI_SSID")
|
||||||
|
password = getenv("CIRCUITPY_WIFI_PASSWORD")
|
||||||
|
|
||||||
|
if None in [ssid, password]:
|
||||||
|
raise RuntimeError(
|
||||||
|
"WiFi settings are kept in settings.toml, "
|
||||||
|
"please add them there. The settings file must contain "
|
||||||
|
"'CIRCUITPY_WIFI_SSID', 'CIRCUITPY_WIFI_PASSWORD', "
|
||||||
|
"at a minimum."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
# The time of the thing!
|
# The time of the thing!
|
||||||
EVENT_YEAR = 2019
|
EVENT_YEAR = 2019
|
||||||
EVENT_MONTH = 10
|
EVENT_MONTH = 10
|
||||||
|
|
@ -83,7 +95,7 @@ while True:
|
||||||
if (not refresh_time) or (time.monotonic() - refresh_time) > 3600:
|
if (not refresh_time) or (time.monotonic() - refresh_time) > 3600:
|
||||||
try:
|
try:
|
||||||
print("Getting time from internet!")
|
print("Getting time from internet!")
|
||||||
pyportal.get_local_time(location=os.getenv("TIMEZONE"))
|
pyportal.get_local_time(location=getenv("timezone"))
|
||||||
refresh_time = time.monotonic()
|
refresh_time = time.monotonic()
|
||||||
except RuntimeError as e:
|
except RuntimeError as e:
|
||||||
print("Some error occured, retrying! -", e)
|
print("Some error occured, retrying! -", e)
|
||||||
|
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
# SPDX-FileCopyrightText: 2019 Kattni Rembor for Adafruit Industries
|
|
||||||
#
|
|
||||||
# SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
# This file is where you keep secret settings, passwords, and tokens!
|
|
||||||
# If you put them in the code you risk committing that info or sharing it
|
|
||||||
|
|
||||||
secrets = {
|
|
||||||
'ssid' : 'CHANGE ME',
|
|
||||||
'password' : 'CHANGE ME',
|
|
||||||
'timezone' : 'CHANGE ME',
|
|
||||||
'aio_username' : 'CHANGE ME',
|
|
||||||
'aio_key' : 'CHANGE ME',
|
|
||||||
}
|
|
||||||
12
PyPortal/PyPortal_Halloween_Countdown/settings.toml
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
# SPDX-FileCopyrightText: 2019 Kattni Rembor for Adafruit Industries
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
# This file is where you keep private settings, passwords, and tokens!
|
||||||
|
# If you put them in the code you risk committing that info or sharing it
|
||||||
|
|
||||||
|
CIRCUITPY_WIFI_SSID="your-wifi-ssid"
|
||||||
|
CIRCUITPY_WIFI_PASSWORD="your-wifi-password"
|
||||||
|
ADAFRUIT_AIO_USERNAME="my_username"
|
||||||
|
ADAFRUIT_AIO_KEY="my_key"
|
||||||
|
timezone="America/New_York" # http://worldtimeapi.org/timezones
|
||||||
|
|
@ -8,6 +8,8 @@ an internet of things smart-scale for Adafruit IO
|
||||||
|
|
||||||
Brent Rubell for Adafruit Industries, 2019
|
Brent Rubell for Adafruit Industries, 2019
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from os import getenv
|
||||||
import time
|
import time
|
||||||
import board
|
import board
|
||||||
import adafruit_dymoscale
|
import adafruit_dymoscale
|
||||||
|
|
@ -22,12 +24,20 @@ from adafruit_esp32spi import adafruit_esp32spi, adafruit_esp32spi_wifimanager
|
||||||
import neopixel
|
import neopixel
|
||||||
from adafruit_io.adafruit_io import IO_HTTP
|
from adafruit_io.adafruit_io import IO_HTTP
|
||||||
|
|
||||||
# Get wifi details and more from a secrets.py file
|
# Get WiFi details and Adafruit IO keys, ensure these are setup in settings.toml
|
||||||
try:
|
# (visit io.adafruit.com if you need to create an account, or if you need your Adafruit IO key.)
|
||||||
from secrets import secrets
|
ssid = getenv("CIRCUITPY_WIFI_SSID")
|
||||||
except ImportError:
|
password = getenv("CIRCUITPY_WIFI_PASSWORD")
|
||||||
print("WiFi secrets are kept in secrets.py, please add them there!")
|
aio_username = getenv("ADAFRUIT_AIO_USERNAME")
|
||||||
raise
|
aio_key = getenv("ADAFRUIT_AIO_KEY")
|
||||||
|
|
||||||
|
if None in [ssid, password, aio_username, aio_key]:
|
||||||
|
raise RuntimeError(
|
||||||
|
"WiFi and Adafruit IO settings are kept in settings.toml, "
|
||||||
|
"please add them there. The settings file must contain "
|
||||||
|
"'CIRCUITPY_WIFI_SSID', 'CIRCUITPY_WIFI_PASSWORD', "
|
||||||
|
"'ADAFRUIT_AIO_USERNAME' and 'ADAFRUIT_AIO_KEY' at a minimum."
|
||||||
|
)
|
||||||
|
|
||||||
# the current working directory (where this file is)
|
# the current working directory (where this file is)
|
||||||
cwd = ("/"+__file__).rsplit('/', 1)[0]
|
cwd = ("/"+__file__).rsplit('/', 1)[0]
|
||||||
|
|
@ -69,14 +79,8 @@ esp32_ready = digitalio.DigitalInOut(board.ESP_BUSY)
|
||||||
esp32_reset = digitalio.DigitalInOut(board.ESP_RESET)
|
esp32_reset = digitalio.DigitalInOut(board.ESP_RESET)
|
||||||
spi = busio.SPI(board.SCK, board.MOSI, board.MISO)
|
spi = busio.SPI(board.SCK, board.MOSI, board.MISO)
|
||||||
esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset)
|
esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset)
|
||||||
status_light = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.2)
|
status_pixel = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.2)
|
||||||
wifi = adafruit_esp32spi_wifimanager.ESPSPI_WiFiManager(esp, secrets, status_light)
|
wifi = adafruit_esp32spi_wifimanager.WiFiManager(esp, ssid, password, status_pixel=status_pixel)
|
||||||
|
|
||||||
# Set your Adafruit IO Username and Key in secrets.py
|
|
||||||
# (visit io.adafruit.com if you need to create an account,
|
|
||||||
# or if you need your Adafruit IO key.)
|
|
||||||
aio_username = secrets['aio_username']
|
|
||||||
aio_key = secrets['aio_key']
|
|
||||||
|
|
||||||
# Create an instance of the IO_HTTP client
|
# Create an instance of the IO_HTTP client
|
||||||
io = IO_HTTP(aio_username, aio_key, wifi)
|
io = IO_HTTP(aio_username, aio_key, wifi)
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,8 @@ https://learn.adafruit.com/pyportal-smart-lighting-controller
|
||||||
|
|
||||||
Brent Rubell for Adafruit Industries, 2019
|
Brent Rubell for Adafruit Industries, 2019
|
||||||
"""
|
"""
|
||||||
import os
|
|
||||||
|
from os import getenv
|
||||||
|
|
||||||
import board
|
import board
|
||||||
import displayio
|
import displayio
|
||||||
|
|
@ -26,10 +27,17 @@ from adafruit_esp32spi import adafruit_esp32spi_wifimanager
|
||||||
# import lifx library
|
# import lifx library
|
||||||
import adafruit_lifx
|
import adafruit_lifx
|
||||||
|
|
||||||
secrets = {
|
# Get WiFi details, ensure these are setup in settings.toml
|
||||||
"ssid" : os.getenv("CIRCUITPY_WIFI_SSID"),
|
ssid = getenv("CIRCUITPY_WIFI_SSID")
|
||||||
"password" : os.getenv("CIRCUITPY_WIFI_PASSWORD"),
|
password = getenv("CIRCUITPY_WIFI_PASSWORD")
|
||||||
}
|
|
||||||
|
if None in [ssid, password]:
|
||||||
|
raise RuntimeError(
|
||||||
|
"WiFi settings are kept in settings.toml, "
|
||||||
|
"please add them there. The settings file must contain "
|
||||||
|
"'CIRCUITPY_WIFI_SSID', 'CIRCUITPY_WIFI_PASSWORD', "
|
||||||
|
"at a minimum."
|
||||||
|
)
|
||||||
|
|
||||||
# ESP32 SPI
|
# ESP32 SPI
|
||||||
esp32_cs = DigitalInOut(board.ESP_CS)
|
esp32_cs = DigitalInOut(board.ESP_CS)
|
||||||
|
|
@ -37,8 +45,8 @@ esp32_ready = DigitalInOut(board.ESP_BUSY)
|
||||||
esp32_reset = DigitalInOut(board.ESP_RESET)
|
esp32_reset = DigitalInOut(board.ESP_RESET)
|
||||||
spi = busio.SPI(board.SCK, board.MOSI, board.MISO)
|
spi = busio.SPI(board.SCK, board.MOSI, board.MISO)
|
||||||
esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset)
|
esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset)
|
||||||
status_light = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.2)
|
status_pixel = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.2)
|
||||||
wifi = adafruit_esp32spi_wifimanager.ESPSPI_WiFiManager(esp, secrets, status_light)
|
wifi = adafruit_esp32spi_wifimanager.WiFiManager(esp, ssid, password, status_pixel=status_pixel)
|
||||||
|
|
||||||
# These pins are used as both analog and digital! XL, XR and YU must be analog
|
# These pins are used as both analog and digital! XL, XR and YU must be analog
|
||||||
# and digital capable. YD just need to be digital
|
# and digital capable. YD just need to be digital
|
||||||
|
|
@ -49,7 +57,7 @@ ts = adafruit_touchscreen.Touchscreen(board.TOUCH_XL, board.TOUCH_XR,
|
||||||
|
|
||||||
# Set this to your LIFX personal access token in settings.toml
|
# Set this to your LIFX personal access token in settings.toml
|
||||||
# (to obtain a token, visit: https://cloud.lifx.com/settings)
|
# (to obtain a token, visit: https://cloud.lifx.com/settings)
|
||||||
lifx_token = os.getenv("LIFX_TOKEN")
|
lifx_token = getenv("LIFX_TOKEN")
|
||||||
|
|
||||||
# Initialize the LIFX API Helper
|
# Initialize the LIFX API Helper
|
||||||
lifx = adafruit_lifx.LIFX(wifi, lifx_token)
|
lifx = adafruit_lifx.LIFX(wifi, lifx_token)
|
||||||
|
|
|
||||||
|
|
@ -7,26 +7,35 @@ This example will access the lastFM API, grab a number like subreddit followers
|
||||||
and display it on a screen
|
and display it on a screen
|
||||||
If you can find something that spits out JSON data, we can display it!
|
If you can find something that spits out JSON data, we can display it!
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from os import getenv
|
||||||
import time
|
import time
|
||||||
import board
|
import board
|
||||||
from adafruit_pyportal import PyPortal
|
from adafruit_pyportal import PyPortal
|
||||||
|
|
||||||
# Get wifi details and more from a secrets.py file
|
# Get WiFi details, ensure these are setup in settings.toml
|
||||||
try:
|
ssid = getenv("CIRCUITPY_WIFI_SSID")
|
||||||
from secrets import secrets
|
password = getenv("CIRCUITPY_WIFI_PASSWORD")
|
||||||
except ImportError:
|
|
||||||
print("WiFi secrets are kept in secrets.py, please add them there!")
|
if None in [ssid, password]:
|
||||||
raise
|
raise RuntimeError(
|
||||||
|
"WiFi settings are kept in settings.toml, "
|
||||||
|
"please add them there. The settings file must contain "
|
||||||
|
"'CIRCUITPY_WIFI_SSID', 'CIRCUITPY_WIFI_PASSWORD', "
|
||||||
|
"at a minimum."
|
||||||
|
)
|
||||||
|
|
||||||
# Set up where we'll be fetching data from
|
# Set up where we'll be fetching data from
|
||||||
DATA_SOURCE = "http://ws.audioscrobbler.com/2.0/?method=user.getrecenttracks&limit=1&format=json"
|
DATA_SOURCE = "http://ws.audioscrobbler.com/2.0/?method=user.getrecenttracks&limit=1&format=json"
|
||||||
CAPTION = "www.last.fm/user"
|
CAPTION = "www.last.fm/user"
|
||||||
# If we have an access token, we can query more often
|
# If we have an access token, we can query more often
|
||||||
if 'lfm_username' in secrets:
|
lfm_username = getenv("lfm_username")
|
||||||
DATA_SOURCE += "&user="+secrets['lfm_username']
|
lfm_key = getenv("lfm_key")
|
||||||
CAPTION += "/"+secrets['lfm_username']
|
if lfm_username:
|
||||||
if 'lfm_key' in secrets:
|
DATA_SOURCE += "&user=" + lfm_username
|
||||||
DATA_SOURCE += "&api_key="+secrets['lfm_key']
|
CAPTION += "/" + lfm_username
|
||||||
|
if lfm_key:
|
||||||
|
DATA_SOURCE += "&api_key=" + lfm_key
|
||||||
print(DATA_SOURCE)
|
print(DATA_SOURCE)
|
||||||
|
|
||||||
# Total number of plays
|
# Total number of plays
|
||||||
|
|
|
||||||
|
|
@ -5,20 +5,33 @@
|
||||||
"""
|
"""
|
||||||
This project will access the League of Legends API, grab a Summoner's Level
|
This project will access the League of Legends API, grab a Summoner's Level
|
||||||
and display it on a screen.
|
and display it on a screen.
|
||||||
You'll need a Riot API key in your secrets.py file
|
You'll need a Riot API key in your settings.toml file
|
||||||
If you can find something that spits out JSON data, we can display it!
|
If you can find something that spits out JSON data, we can display it!
|
||||||
"""
|
"""
|
||||||
import os
|
|
||||||
|
from os import getenv
|
||||||
import time
|
import time
|
||||||
import board
|
import board
|
||||||
from adafruit_pyportal import PyPortal
|
from adafruit_pyportal import PyPortal
|
||||||
|
|
||||||
|
# Get WiFi details, ensure these are setup in settings.toml
|
||||||
|
ssid = getenv("CIRCUITPY_WIFI_SSID")
|
||||||
|
password = getenv("CIRCUITPY_WIFI_PASSWORD")
|
||||||
|
|
||||||
|
if None in [ssid, password]:
|
||||||
|
raise RuntimeError(
|
||||||
|
"WiFi settings are kept in settings.toml, "
|
||||||
|
"please add them there. The settings file must contain "
|
||||||
|
"'CIRCUITPY_WIFI_SSID', 'CIRCUITPY_WIFI_PASSWORD', "
|
||||||
|
"at a minimum."
|
||||||
|
)
|
||||||
|
|
||||||
#Choose a valid Summoner name
|
#Choose a valid Summoner name
|
||||||
SUMMONER_NAME = "RiotSchmick"
|
SUMMONER_NAME = "RiotSchmick"
|
||||||
|
|
||||||
# Set up where we'll be fetching data from
|
# Set up where we'll be fetching data from
|
||||||
DATA_SOURCE = "https://na1.api.riotgames.com/lol/summoner/v4/summoners/by-name/"+SUMMONER_NAME
|
DATA_SOURCE = "https://na1.api.riotgames.com/lol/summoner/v4/summoners/by-name/"+SUMMONER_NAME
|
||||||
DATA_SOURCE += "?api_key=" + os.getenv("LEAGUE_TOKEN")
|
DATA_SOURCE += "?api_key=" + getenv("LEAGUE_TOKEN")
|
||||||
DATA_LOCATION = ["summonerLevel"]
|
DATA_LOCATION = ["summonerLevel"]
|
||||||
CAPTION = "SUMMONER "+SUMMONER_NAME
|
CAPTION = "SUMMONER "+SUMMONER_NAME
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
#
|
#
|
||||||
# SPDX-License-Identifier: MIT
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
from os import getenv
|
||||||
import board
|
import board
|
||||||
import displayio
|
import displayio
|
||||||
import busio
|
import busio
|
||||||
|
|
@ -18,14 +19,19 @@ from adafruit_button import Button
|
||||||
import adafruit_touchscreen
|
import adafruit_touchscreen
|
||||||
import adafruit_minimqtt.adafruit_minimqtt as MQTT
|
import adafruit_minimqtt.adafruit_minimqtt as MQTT
|
||||||
|
|
||||||
# ------------- WiFi ------------- #
|
# Get WiFi details, ensure these are setup in settings.toml
|
||||||
|
ssid = getenv("CIRCUITPY_WIFI_SSID")
|
||||||
|
password = getenv("CIRCUITPY_WIFI_PASSWORD")
|
||||||
|
|
||||||
# Get wifi details and more from a secrets.py file
|
if None in [ssid, password]:
|
||||||
try:
|
raise RuntimeError(
|
||||||
from secrets import secrets
|
"WiFi settings are kept in settings.toml, "
|
||||||
except ImportError:
|
"please add them there. The settings file must contain "
|
||||||
print("WiFi secrets are kept in secrets.py, please add them there!")
|
"'CIRCUITPY_WIFI_SSID', 'CIRCUITPY_WIFI_PASSWORD', "
|
||||||
raise
|
"at a minimum."
|
||||||
|
)
|
||||||
|
|
||||||
|
# ------------- WiFi ------------- #
|
||||||
|
|
||||||
# If you are using a board with pre-defined ESP32 Pins:
|
# If you are using a board with pre-defined ESP32 Pins:
|
||||||
esp32_cs = DigitalInOut(board.ESP_CS)
|
esp32_cs = DigitalInOut(board.ESP_CS)
|
||||||
|
|
@ -34,8 +40,8 @@ esp32_reset = DigitalInOut(board.ESP_RESET)
|
||||||
|
|
||||||
spi = busio.SPI(board.SCK, board.MOSI, board.MISO)
|
spi = busio.SPI(board.SCK, board.MOSI, board.MISO)
|
||||||
esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset)
|
esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset)
|
||||||
status_light = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.2)
|
status_pixel = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.2)
|
||||||
wifi = adafruit_esp32spi_wifimanager.ESPSPI_WiFiManager(esp, secrets, status_light)
|
wifi = adafruit_esp32spi_wifimanager.WiFiManager(esp, ssid, password, status_pixel=status_pixel)
|
||||||
|
|
||||||
# ------- Sensor Setup ------- #
|
# ------- Sensor Setup ------- #
|
||||||
# init. the temperature sensor
|
# init. the temperature sensor
|
||||||
|
|
@ -234,10 +240,10 @@ ssl_context = adafruit_connection_manager.get_radio_ssl_context(esp)
|
||||||
|
|
||||||
# Set up a MiniMQTT Client
|
# Set up a MiniMQTT Client
|
||||||
client = MQTT.MQTT(
|
client = MQTT.MQTT(
|
||||||
broker=secrets["broker"],
|
broker=getenv("mqtt_broker"),
|
||||||
port=1883,
|
port=1883,
|
||||||
username=secrets["user"],
|
username=getenv("mqtt_username"),
|
||||||
password=secrets["pass"],
|
password=getenv("mqtt_password"),
|
||||||
socket_pool=pool,
|
socket_pool=pool,
|
||||||
ssl_context=ssl_context,
|
ssl_context=ssl_context,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
# SPDX-FileCopyrightText: 2020 Anne Barela for Adafruit Industries
|
|
||||||
#
|
|
||||||
# SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
secrets = {
|
|
||||||
'ssid' : '_your_wifi_ssid_',
|
|
||||||
'password' : '_your_wifi_password_',
|
|
||||||
'broker' : '_your_mqtt_broker_url_or_ip',
|
|
||||||
'user' : '_your_mqtt_broker_username_',
|
|
||||||
'pass' : '_your_mqtt_broker_password_'
|
|
||||||
}
|
|
||||||
12
PyPortal/PyPortal_MQTT_Control/settings.toml
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
# SPDX-FileCopyrightText: 2020 Anne Barela for Adafruit Industries
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
# This file is where you keep private settings, passwords, and tokens!
|
||||||
|
# If you put them in the code you risk committing that info or sharing it
|
||||||
|
|
||||||
|
CIRCUITPY_WIFI_SSID="your-wifi-ssid"
|
||||||
|
CIRCUITPY_WIFI_PASSWORD="your-wifi-password"
|
||||||
|
mqtt_broker="your-mqtt-broker-url-or-ip"
|
||||||
|
mqtt_username="your-mqtt-broker-username"
|
||||||
|
mqtt_password="your-mqtt-broker-password"
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
#
|
#
|
||||||
# SPDX-License-Identifier: MIT
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
from os import getenv
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
import board
|
import board
|
||||||
|
|
@ -11,12 +12,17 @@ cwd = ("/"+__file__).rsplit('/', 1)[0] # the current working directory (where th
|
||||||
sys.path.append(cwd)
|
sys.path.append(cwd)
|
||||||
import openweather_graphics # pylint: disable=wrong-import-position
|
import openweather_graphics # pylint: disable=wrong-import-position
|
||||||
|
|
||||||
# Get wifi details and more from a secrets.py file
|
# Get WiFi details, ensure these are setup in settings.toml
|
||||||
try:
|
ssid = getenv("CIRCUITPY_WIFI_SSID")
|
||||||
from secrets import secrets
|
password = getenv("CIRCUITPY_WIFI_PASSWORD")
|
||||||
except ImportError:
|
|
||||||
print("WiFi secrets are kept in secrets.py, please add them there!")
|
if None in [ssid, password]:
|
||||||
raise
|
raise RuntimeError(
|
||||||
|
"WiFi settings are kept in settings.toml, "
|
||||||
|
"please add them there. The settings file must contain "
|
||||||
|
"'CIRCUITPY_WIFI_SSID', 'CIRCUITPY_WIFI_PASSWORD', "
|
||||||
|
"at a minimum."
|
||||||
|
)
|
||||||
|
|
||||||
# Use cityname, country code where countrycode is ISO3166 format.
|
# Use cityname, country code where countrycode is ISO3166 format.
|
||||||
# E.g. "New York, US" or "London, GB"
|
# E.g. "New York, US" or "London, GB"
|
||||||
|
|
@ -24,7 +30,7 @@ LOCATION = "New York, US"
|
||||||
|
|
||||||
# Set up where we'll be fetching data from
|
# Set up where we'll be fetching data from
|
||||||
DATA_SOURCE = "http://api.openweathermap.org/data/2.5/weather?q="+LOCATION
|
DATA_SOURCE = "http://api.openweathermap.org/data/2.5/weather?q="+LOCATION
|
||||||
DATA_SOURCE += "&appid="+secrets['openweather_token']
|
DATA_SOURCE += "&appid=" + getenv('openweather_token')
|
||||||
# You'll need to get a token from openweather.org, looks like 'b6907d289e10d714a6e88b30761fae22'
|
# You'll need to get a token from openweather.org, looks like 'b6907d289e10d714a6e88b30761fae22'
|
||||||
DATA_LOCATION = []
|
DATA_LOCATION = []
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,8 @@ This example queries the Open Weather Maps site API to find out the current
|
||||||
weather for your location... and display it on a screen!
|
weather for your location... and display it on a screen!
|
||||||
if you can find something that spits out JSON data, we can display it
|
if you can find something that spits out JSON data, we can display it
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from os import getenv
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
import board
|
import board
|
||||||
|
|
@ -15,12 +17,17 @@ cwd = ("/"+__file__).rsplit('/', 1)[0] # the current working directory (where th
|
||||||
sys.path.append(cwd)
|
sys.path.append(cwd)
|
||||||
import openweather_graphics # pylint: disable=wrong-import-position
|
import openweather_graphics # pylint: disable=wrong-import-position
|
||||||
|
|
||||||
# Get wifi details and more from a secrets.py file
|
# Get WiFi details, ensure these are setup in settings.toml
|
||||||
try:
|
ssid = getenv("CIRCUITPY_WIFI_SSID")
|
||||||
from secrets import secrets
|
password = getenv("CIRCUITPY_WIFI_PASSWORD")
|
||||||
except ImportError:
|
|
||||||
print("WiFi secrets are kept in secrets.py, please add them there!")
|
if None in [ssid, password]:
|
||||||
raise
|
raise RuntimeError(
|
||||||
|
"WiFi settings are kept in settings.toml, "
|
||||||
|
"please add them there. The settings file must contain "
|
||||||
|
"'CIRCUITPY_WIFI_SSID', 'CIRCUITPY_WIFI_PASSWORD', "
|
||||||
|
"at a minimum."
|
||||||
|
)
|
||||||
|
|
||||||
# Use cityname, country code where countrycode is ISO3166 format.
|
# Use cityname, country code where countrycode is ISO3166 format.
|
||||||
# E.g. "New York, US" or "London, GB"
|
# E.g. "New York, US" or "London, GB"
|
||||||
|
|
@ -28,7 +35,7 @@ LOCATION = "Manhattan, US"
|
||||||
|
|
||||||
# Set up where we'll be fetching data from
|
# Set up where we'll be fetching data from
|
||||||
DATA_SOURCE = "http://api.openweathermap.org/data/2.5/weather?q="+LOCATION
|
DATA_SOURCE = "http://api.openweathermap.org/data/2.5/weather?q="+LOCATION
|
||||||
DATA_SOURCE += "&appid="+secrets['openweather_token']
|
DATA_SOURCE += "&appid=" + getenv('openweather_token')
|
||||||
# You'll need to get a token from openweather.org, looks like 'b6907d289e10d714a6e88b30761fae22'
|
# You'll need to get a token from openweather.org, looks like 'b6907d289e10d714a6e88b30761fae22'
|
||||||
DATA_LOCATION = []
|
DATA_LOCATION = []
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,8 @@ PyPortal Philips Hue Lighting Controller
|
||||||
|
|
||||||
Brent Rubell for Adafruit Industries, 2019
|
Brent Rubell for Adafruit Industries, 2019
|
||||||
"""
|
"""
|
||||||
import os
|
|
||||||
|
from os import getenv
|
||||||
|
|
||||||
import board
|
import board
|
||||||
import displayio
|
import displayio
|
||||||
|
|
@ -23,9 +24,17 @@ from adafruit_esp32spi import adafruit_esp32spi_wifimanager
|
||||||
# Import Philips Hue Bridge
|
# Import Philips Hue Bridge
|
||||||
from adafruit_hue import Bridge
|
from adafruit_hue import Bridge
|
||||||
|
|
||||||
secrets = dict()
|
# Get WiFi details, ensure these are setup in settings.toml
|
||||||
secrets["ssid"] = os.getenv("CIRCUITPY_WIFI_SSID")
|
ssid = getenv("CIRCUITPY_WIFI_SSID")
|
||||||
secrets["password"] = os.getenv("CIRCUITPY_WIFI_PASSWORD")
|
password = getenv("CIRCUITPY_WIFI_PASSWORD")
|
||||||
|
|
||||||
|
if None in [ssid, password]:
|
||||||
|
raise RuntimeError(
|
||||||
|
"WiFi settings are kept in settings.toml, "
|
||||||
|
"please add them there. The settings file must contain "
|
||||||
|
"'CIRCUITPY_WIFI_SSID', 'CIRCUITPY_WIFI_PASSWORD', "
|
||||||
|
"at a minimum."
|
||||||
|
)
|
||||||
|
|
||||||
# ESP32 SPI
|
# ESP32 SPI
|
||||||
esp32_cs = DigitalInOut(board.ESP_CS)
|
esp32_cs = DigitalInOut(board.ESP_CS)
|
||||||
|
|
@ -33,13 +42,13 @@ esp32_ready = DigitalInOut(board.ESP_BUSY)
|
||||||
esp32_reset = DigitalInOut(board.ESP_RESET)
|
esp32_reset = DigitalInOut(board.ESP_RESET)
|
||||||
spi = busio.SPI(board.SCK, board.MOSI, board.MISO)
|
spi = busio.SPI(board.SCK, board.MOSI, board.MISO)
|
||||||
esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset)
|
esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset)
|
||||||
status_light = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.2)
|
status_pixel = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.2)
|
||||||
wifi = adafruit_esp32spi_wifimanager.ESPSPI_WiFiManager(esp, secrets, status_light)
|
wifi = adafruit_esp32spi_wifimanager.WiFiManager(esp, ssid, password, status_pixel=status_pixel)
|
||||||
|
|
||||||
# Attempt to load bridge username and IP address from secrets.py
|
# Attempt to load bridge username and IP address from settings.toml
|
||||||
try:
|
try:
|
||||||
username = os.getenv("HUE_USERNAME")
|
username = getenv("HUE_USERNAME")
|
||||||
bridge_ip = os.getenv("BRIDGE_IP")
|
bridge_ip = getenv("BRIDGE_IP")
|
||||||
my_bridge = Bridge(wifi, bridge_ip, username)
|
my_bridge = Bridge(wifi, bridge_ip, username)
|
||||||
except:
|
except:
|
||||||
# Perform first-time bridge setup
|
# Perform first-time bridge setup
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
#
|
#
|
||||||
# SPDX-License-Identifier: MIT
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
from os import getenv
|
||||||
import time
|
import time
|
||||||
import board
|
import board
|
||||||
import busio
|
import busio
|
||||||
|
|
@ -13,12 +14,17 @@ from adafruit_pyportal import PyPortal
|
||||||
from adafruit_bitmap_font import bitmap_font
|
from adafruit_bitmap_font import bitmap_font
|
||||||
from adafruit_display_text import label
|
from adafruit_display_text import label
|
||||||
|
|
||||||
try:
|
# Get WiFi details, ensure these are setup in settings.toml
|
||||||
from secrets import secrets
|
ssid = getenv("CIRCUITPY_WIFI_SSID")
|
||||||
except ImportError:
|
password = getenv("CIRCUITPY_WIFI_PASSWORD")
|
||||||
print("""WiFi settings are kept in secrets.py, please add them there!
|
|
||||||
the secrets dictionary must contain 'ssid' and 'password' at a minimum""")
|
if None in [ssid, password]:
|
||||||
raise
|
raise RuntimeError(
|
||||||
|
"WiFi settings are kept in settings.toml, "
|
||||||
|
"please add them there. The settings file must contain "
|
||||||
|
"'CIRCUITPY_WIFI_SSID', 'CIRCUITPY_WIFI_PASSWORD', "
|
||||||
|
"at a minimum."
|
||||||
|
)
|
||||||
|
|
||||||
# Label colors
|
# Label colors
|
||||||
LABEL_DAY_COLOR = 0xFFFFFF
|
LABEL_DAY_COLOR = 0xFFFFFF
|
||||||
|
|
@ -88,7 +94,7 @@ if esp.status == adafruit_esp32spi.WL_IDLE_STATUS:
|
||||||
print("Connecting to AP...")
|
print("Connecting to AP...")
|
||||||
while not esp.is_connected:
|
while not esp.is_connected:
|
||||||
try:
|
try:
|
||||||
esp.connect_AP(secrets['ssid'], secrets['password'])
|
esp.connect_AP(ssid, password)
|
||||||
except RuntimeError as e:
|
except RuntimeError as e:
|
||||||
print("could not connect to AP, retrying: ", e)
|
print("could not connect to AP, retrying: ", e)
|
||||||
continue
|
continue
|
||||||
|
|
@ -117,7 +123,7 @@ while True:
|
||||||
if (not refresh_time) or (time.monotonic() - refresh_time) > 3600:
|
if (not refresh_time) or (time.monotonic() - refresh_time) > 3600:
|
||||||
try:
|
try:
|
||||||
print("Getting new time from internet...")
|
print("Getting new time from internet...")
|
||||||
pyportal.get_local_time(secrets['timezone'])
|
pyportal.get_local_time(getenv('timezone'))
|
||||||
refresh_time = time.monotonic()
|
refresh_time = time.monotonic()
|
||||||
# set the_time
|
# set the_time
|
||||||
the_time = time.localtime()
|
the_time = time.localtime()
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
# SPDX-FileCopyrightText: 2019 ladyada for Adafruit Industries
|
# SPDX-FileCopyrightText: 2019 ladyada for Adafruit Industries
|
||||||
# SPDX-License-Identifier: MIT
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
from os import getenv
|
||||||
import time
|
import time
|
||||||
import math
|
import math
|
||||||
import board
|
import board
|
||||||
|
|
@ -13,6 +14,18 @@ import displayio
|
||||||
import adafruit_touchscreen
|
import adafruit_touchscreen
|
||||||
import adafruit_imageload
|
import adafruit_imageload
|
||||||
|
|
||||||
|
# Get WiFi details, ensure these are setup in settings.toml
|
||||||
|
ssid = getenv("CIRCUITPY_WIFI_SSID")
|
||||||
|
password = getenv("CIRCUITPY_WIFI_PASSWORD")
|
||||||
|
|
||||||
|
if None in [ssid, password]:
|
||||||
|
raise RuntimeError(
|
||||||
|
"WiFi settings are kept in settings.toml, "
|
||||||
|
"please add them there. The settings file must contain "
|
||||||
|
"'CIRCUITPY_WIFI_SSID', 'CIRCUITPY_WIFI_PASSWORD', "
|
||||||
|
"at a minimum."
|
||||||
|
)
|
||||||
|
|
||||||
# Set up the touchscreen
|
# Set up the touchscreen
|
||||||
ts = adafruit_touchscreen.Touchscreen(
|
ts = adafruit_touchscreen.Touchscreen(
|
||||||
board.TOUCH_XL,
|
board.TOUCH_XL,
|
||||||
|
|
@ -23,13 +36,6 @@ ts = adafruit_touchscreen.Touchscreen(
|
||||||
size=(320, 240),
|
size=(320, 240),
|
||||||
)
|
)
|
||||||
|
|
||||||
# Get wifi details and more from a secrets.py file
|
|
||||||
try:
|
|
||||||
from secrets import secrets
|
|
||||||
except ImportError:
|
|
||||||
print("WiFi secrets are kept in secrets.py, please add them there!")
|
|
||||||
raise
|
|
||||||
|
|
||||||
# If you are using a board with pre-defined ESP32 Pins:
|
# If you are using a board with pre-defined ESP32 Pins:
|
||||||
esp32_cs = DigitalInOut(board.ESP_CS)
|
esp32_cs = DigitalInOut(board.ESP_CS)
|
||||||
esp32_ready = DigitalInOut(board.ESP_BUSY)
|
esp32_ready = DigitalInOut(board.ESP_BUSY)
|
||||||
|
|
@ -38,9 +44,9 @@ esp32_reset = DigitalInOut(board.ESP_RESET)
|
||||||
spi = busio.SPI(board.SCK, board.MOSI, board.MISO)
|
spi = busio.SPI(board.SCK, board.MOSI, board.MISO)
|
||||||
esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset)
|
esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset)
|
||||||
|
|
||||||
status_light = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.2)
|
status_pixel = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.2)
|
||||||
|
|
||||||
wifi = adafruit_esp32spi_wifimanager.ESPSPI_WiFiManager(esp, secrets, status_light)
|
wifi = adafruit_esp32spi_wifimanager.WiFiManager(esp, ssid, password, status_pixel=status_pixel)
|
||||||
|
|
||||||
# Set the ip of your Roku here
|
# Set the ip of your Roku here
|
||||||
ip = "192.168.1.3"
|
ip = "192.168.1.3"
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
#
|
#
|
||||||
# SPDX-License-Identifier: MIT
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
from os import getenv
|
||||||
import time
|
import time
|
||||||
import gc
|
import gc
|
||||||
import board
|
import board
|
||||||
|
|
@ -20,6 +21,21 @@ import adafruit_touchscreen
|
||||||
|
|
||||||
from adafruit_minimqtt import MQTT
|
from adafruit_minimqtt import MQTT
|
||||||
|
|
||||||
|
# Get WiFi details and Adafruit IO keys, ensure these are setup in settings.toml
|
||||||
|
# (visit io.adafruit.com if you need to create an account, or if you need your Adafruit IO key.)
|
||||||
|
ssid = getenv("CIRCUITPY_WIFI_SSID")
|
||||||
|
password = getenv("CIRCUITPY_WIFI_PASSWORD")
|
||||||
|
aio_username = getenv("ADAFRUIT_AIO_USERNAME")
|
||||||
|
aio_key = getenv("ADAFRUIT_AIO_KEY")
|
||||||
|
|
||||||
|
if None in [ssid, password, aio_username, aio_key]:
|
||||||
|
raise RuntimeError(
|
||||||
|
"WiFi and Adafruit IO settings are kept in settings.toml, "
|
||||||
|
"please add them there. The settings file must contain "
|
||||||
|
"'CIRCUITPY_WIFI_SSID', 'CIRCUITPY_WIFI_PASSWORD', "
|
||||||
|
"'ADAFRUIT_AIO_USERNAME' and 'ADAFRUIT_AIO_KEY' at a minimum."
|
||||||
|
)
|
||||||
|
|
||||||
DISPLAY_COLOR = 0x006600
|
DISPLAY_COLOR = 0x006600
|
||||||
SWITCH_COLOR = 0x008800
|
SWITCH_COLOR = 0x008800
|
||||||
SWITCH_FILL_COLOR = 0xffffff
|
SWITCH_FILL_COLOR = 0xffffff
|
||||||
|
|
@ -39,15 +55,8 @@ def get_local_timestamp(location=None):
|
||||||
"""Fetch and "set" the local time of this microcontroller to the local time at the location, using an internet time API.
|
"""Fetch and "set" the local time of this microcontroller to the local time at the location, using an internet time API.
|
||||||
:param str location: Your city and country, e.g. ``"New York, US"``.
|
:param str location: Your city and country, e.g. ``"New York, US"``.
|
||||||
"""
|
"""
|
||||||
# pylint: enable=line-too-long
|
|
||||||
api_url = None
|
api_url = None
|
||||||
try:
|
location = getenv('timezone', location)
|
||||||
aio_username = secrets['aio_username']
|
|
||||||
aio_key = secrets['aio_key']
|
|
||||||
except KeyError:
|
|
||||||
raise KeyError("\n\nOur time service requires a login/password to rate-limit. Please register for a free adafruit.io account and place the user/key in your secrets file under 'aio_username' and 'aio_key'")# pylint: disable=line-too-long
|
|
||||||
|
|
||||||
location = secrets.get('timezone', location)
|
|
||||||
if location:
|
if location:
|
||||||
print("Getting time for timezone", location)
|
print("Getting time for timezone", location)
|
||||||
api_url = (TIME_SERVICE + "&tz=%s") % (aio_username, aio_key, location)
|
api_url = (TIME_SERVICE + "&tz=%s") % (aio_username, aio_key, location)
|
||||||
|
|
@ -70,7 +79,7 @@ def get_local_timestamp(location=None):
|
||||||
tzseconds += tzminutes * 60
|
tzseconds += tzminutes * 60
|
||||||
print(seconds + tzseconds, tzoffset, tzhours, tzminutes)
|
print(seconds + tzseconds, tzoffset, tzhours, tzminutes)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise KeyError("Was unable to lookup the time, try setting secrets['timezone'] according to http://worldtimeapi.org/timezones") # pylint: disable=line-too-long
|
raise KeyError("Was unable to lookup the time, try setting timezone in your settings.toml according to http://worldtimeapi.org/timezones") # pylint: disable=line-too-long
|
||||||
|
|
||||||
# now clean up
|
# now clean up
|
||||||
response.close()
|
response.close()
|
||||||
|
|
@ -157,7 +166,7 @@ class Clock(object):
|
||||||
# Update the time
|
# Update the time
|
||||||
print("update the time")
|
print("update the time")
|
||||||
self.update_time = int(now)
|
self.update_time = int(now)
|
||||||
self.snapshot_time = get_local_timestamp(secrets['timezone'])
|
self.snapshot_time = get_local_timestamp(getenv("timezone"))
|
||||||
self.current_time = time.localtime(self.snapshot_time)
|
self.current_time = time.localtime(self.snapshot_time)
|
||||||
else:
|
else:
|
||||||
self.current_time = time.localtime(int(now) - self.update_time + self.snapshot_time)
|
self.current_time = time.localtime(int(now) - self.update_time + self.snapshot_time)
|
||||||
|
|
@ -185,8 +194,8 @@ class Clock(object):
|
||||||
def connected(client, userdata, flags, rc):
|
def connected(client, userdata, flags, rc):
|
||||||
# This function will be called when the client is connected
|
# This function will be called when the client is connected
|
||||||
# successfully to the broker.
|
# successfully to the broker.
|
||||||
onoff_feed = secrets['aio_username'] + '/feeds/' + FEED_NAME
|
onoff_feed = f"{aio_username}/feeds/{FEED_NAME}"
|
||||||
print('Connected to Adafruit IO! Listening for topic changes on %s' % onoff_feed)
|
print(f"Connected to Adafruit IO! Listening for topic changes on {onoff_feed}")
|
||||||
# Subscribe to all changes on the onoff_feed.
|
# Subscribe to all changes on the onoff_feed.
|
||||||
client.subscribe(onoff_feed)
|
client.subscribe(onoff_feed)
|
||||||
|
|
||||||
|
|
@ -205,13 +214,6 @@ def message(client, topic, message):
|
||||||
|
|
||||||
############################################
|
############################################
|
||||||
|
|
||||||
try:
|
|
||||||
from secrets import secrets
|
|
||||||
except ImportError:
|
|
||||||
print("""WiFi settings are kept in secrets.py, please add them there!
|
|
||||||
the secrets dictionary must contain 'ssid' and 'password' at a minimum""")
|
|
||||||
raise
|
|
||||||
|
|
||||||
esp32_cs = digitalio.DigitalInOut(board.ESP_CS)
|
esp32_cs = digitalio.DigitalInOut(board.ESP_CS)
|
||||||
esp32_ready = digitalio.DigitalInOut(board.ESP_BUSY)
|
esp32_ready = digitalio.DigitalInOut(board.ESP_BUSY)
|
||||||
esp32_reset = digitalio.DigitalInOut(board.ESP_RESET)
|
esp32_reset = digitalio.DigitalInOut(board.ESP_RESET)
|
||||||
|
|
@ -257,7 +259,7 @@ for ap in esp.scan_networks():
|
||||||
print("Connecting to AP...")
|
print("Connecting to AP...")
|
||||||
while not esp.is_connected:
|
while not esp.is_connected:
|
||||||
try:
|
try:
|
||||||
esp.connect_AP(secrets['ssid'], secrets['password'])
|
esp.connect_AP(ssid, password)
|
||||||
except RuntimeError as e:
|
except RuntimeError as e:
|
||||||
print("could not connect to AP, retrying: ",e)
|
print("could not connect to AP, retrying: ",e)
|
||||||
continue
|
continue
|
||||||
|
|
@ -265,14 +267,15 @@ print("Connected to", str(esp.ssid, 'utf-8'), "\tRSSI:", esp.rssi)
|
||||||
print("My IP address is", esp.pretty_ip(esp.ip_address))
|
print("My IP address is", esp.pretty_ip(esp.ip_address))
|
||||||
|
|
||||||
|
|
||||||
wifi = adafruit_esp32spi_wifimanager.ESPSPI_WiFiManager(
|
wifi = adafruit_esp32spi_wifimanager.WiFiManager(
|
||||||
esp, secrets, debug = True)
|
esp, ssid, password, debug=True
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
# Set up a MiniMQTT Client
|
# Set up a MiniMQTT Client
|
||||||
mqtt_client = MQTT(broker='io.adafruit.com',
|
mqtt_client = MQTT(broker='io.adafruit.com',
|
||||||
username=secrets['aio_username'],
|
username=aio_username,
|
||||||
password=secrets['aio_key'],
|
password=aio_key,
|
||||||
network_manager=wifi,
|
network_manager=wifi,
|
||||||
socket_pool=pool,
|
socket_pool=pool,
|
||||||
ssl_context=ssl_context)
|
ssl_context=ssl_context)
|
||||||
|
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
# SPDX-FileCopyrightText: 2019 Kattni Rembor for Adafruit Industries
|
|
||||||
#
|
|
||||||
# SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
# This file is where you keep secret settings, passwords, and tokens!
|
|
||||||
# If you put them in the code you risk committing that info or sharing it
|
|
||||||
|
|
||||||
secrets = {
|
|
||||||
'ssid' : 'CHANGE ME',
|
|
||||||
'password' : 'CHANGE ME',
|
|
||||||
# leave blank or use timezone from # http://worldtimeapi.org/timezones
|
|
||||||
'timezone' : '',
|
|
||||||
'aio_username' : 'CHANGE ME',
|
|
||||||
'aio_key' : 'CHANGE ME',
|
|
||||||
}
|
|
||||||
12
PyPortal/PyPortal_Smart_Switch/settings.toml
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
# SPDX-FileCopyrightText: 2019 Kattni Rembor for Adafruit Industries
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
# This file is where you keep private settings, passwords, and tokens!
|
||||||
|
# If you put them in the code you risk committing that info or sharing it
|
||||||
|
|
||||||
|
CIRCUITPY_WIFI_SSID="your-wifi-ssid"
|
||||||
|
CIRCUITPY_WIFI_PASSWORD="your-wifi-password"
|
||||||
|
ADAFRUIT_AIO_USERNAME="my_username"
|
||||||
|
ADAFRUIT_AIO_KEY="my_key"
|
||||||
|
timezone="" # leave blank or use timezone from # http://worldtimeapi.org/timezones
|
||||||
|
|
@ -10,6 +10,8 @@ thermometer with Adafruit IO
|
||||||
|
|
||||||
Author: Brent Rubell for Adafruit Industries, 2019
|
Author: Brent Rubell for Adafruit Industries, 2019
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from os import getenv
|
||||||
import time
|
import time
|
||||||
import board
|
import board
|
||||||
import neopixel
|
import neopixel
|
||||||
|
|
@ -27,12 +29,20 @@ import thermometer_helper
|
||||||
# rate at which to refresh the pyportal screen, in seconds
|
# rate at which to refresh the pyportal screen, in seconds
|
||||||
PYPORTAL_REFRESH = 2
|
PYPORTAL_REFRESH = 2
|
||||||
|
|
||||||
# Get wifi details and more from a secrets.py file
|
# Get WiFi details and Adafruit IO keys, ensure these are setup in settings.toml
|
||||||
try:
|
# (visit io.adafruit.com if you need to create an account, or if you need your Adafruit IO key.)
|
||||||
from secrets import secrets
|
ssid = getenv("CIRCUITPY_WIFI_SSID")
|
||||||
except ImportError:
|
password = getenv("CIRCUITPY_WIFI_PASSWORD")
|
||||||
print("WiFi secrets are kept in secrets.py, please add them there!")
|
aio_username = getenv("ADAFRUIT_AIO_USERNAME")
|
||||||
raise
|
aio_key = getenv("ADAFRUIT_AIO_KEY")
|
||||||
|
|
||||||
|
if None in [ssid, password, aio_username, aio_key]:
|
||||||
|
raise RuntimeError(
|
||||||
|
"WiFi and Adafruit IO settings are kept in settings.toml, "
|
||||||
|
"please add them there. The settings file must contain "
|
||||||
|
"'CIRCUITPY_WIFI_SSID', 'CIRCUITPY_WIFI_PASSWORD', "
|
||||||
|
"'ADAFRUIT_AIO_USERNAME' and 'ADAFRUIT_AIO_KEY' at a minimum."
|
||||||
|
)
|
||||||
|
|
||||||
# PyPortal ESP32 Setup
|
# PyPortal ESP32 Setup
|
||||||
esp32_cs = DigitalInOut(board.ESP_CS)
|
esp32_cs = DigitalInOut(board.ESP_CS)
|
||||||
|
|
@ -40,21 +50,11 @@ esp32_ready = DigitalInOut(board.ESP_BUSY)
|
||||||
esp32_reset = DigitalInOut(board.ESP_RESET)
|
esp32_reset = DigitalInOut(board.ESP_RESET)
|
||||||
spi = busio.SPI(board.SCK, board.MOSI, board.MISO)
|
spi = busio.SPI(board.SCK, board.MOSI, board.MISO)
|
||||||
esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset)
|
esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset)
|
||||||
status_light = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.2)
|
status_pixel = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.2)
|
||||||
wifi = adafruit_esp32spi_wifimanager.ESPSPI_WiFiManager(esp, secrets, status_light)
|
wifi = adafruit_esp32spi_wifimanager.WiFiManager(esp, ssid, password, status_pixel=status_pixel)
|
||||||
|
|
||||||
# Set your Adafruit IO Username and Key in secrets.py
|
|
||||||
# (visit io.adafruit.com if you need to create an account,
|
|
||||||
# or if you need your Adafruit IO key.)
|
|
||||||
try:
|
|
||||||
ADAFRUIT_IO_USER = secrets['aio_username']
|
|
||||||
ADAFRUIT_IO_KEY = secrets['aio_key']
|
|
||||||
except KeyError:
|
|
||||||
raise KeyError('To use this code, you need to include your Adafruit IO username \
|
|
||||||
and password in a secrets.py file on the CIRCUITPY drive.')
|
|
||||||
|
|
||||||
# Create an instance of the IO_HTTP client
|
# Create an instance of the IO_HTTP client
|
||||||
io = IO_HTTP(ADAFRUIT_IO_USER, ADAFRUIT_IO_KEY, wifi)
|
io = IO_HTTP(aio_username, aio_key, wifi)
|
||||||
|
|
||||||
# Get the temperature feed from Adafruit IO
|
# Get the temperature feed from Adafruit IO
|
||||||
temperature_feed = io.get_feed('temperature')
|
temperature_feed = io.get_feed('temperature')
|
||||||
|
|
|
||||||
|
|
@ -36,12 +36,11 @@ ALWAYS_ON = True
|
||||||
# How long to stay on if not in always_on mode
|
# How long to stay on if not in always_on mode
|
||||||
ON_SECONDS = 60
|
ON_SECONDS = 60
|
||||||
|
|
||||||
# Get totp keys from a secrets.py file
|
# Get totp_keys from a totp_keys.py file
|
||||||
try:
|
try:
|
||||||
from secrets import secrets
|
from totp_keys import totp_keys
|
||||||
except ImportError:
|
except ImportError:
|
||||||
print("TOTP keys are kept in secrets.py, please add them there!")
|
print("TOTP info not found in totp_keys.py, please add them there!")
|
||||||
raise
|
|
||||||
|
|
||||||
# Initialize PyPortal Display
|
# Initialize PyPortal Display
|
||||||
display = board.DISPLAY
|
display = board.DISPLAY
|
||||||
|
|
@ -228,13 +227,13 @@ mono_time = int(time.monotonic())
|
||||||
print("Monotonic time", mono_time)
|
print("Monotonic time", mono_time)
|
||||||
|
|
||||||
# Add buttons to the interface
|
# Add buttons to the interface
|
||||||
assert len(secrets['totp_keys']) < 6, "This code can only display 5 keys at a time"
|
assert len(totp_keys) < 6, "This code can only display 5 keys at a time"
|
||||||
|
|
||||||
# generate buttons
|
# generate buttons
|
||||||
buttons = []
|
buttons = []
|
||||||
|
|
||||||
btn_x = 5
|
btn_x = 5
|
||||||
for i in secrets['totp_keys']:
|
for i in totp_keys:
|
||||||
button = Button(name=i[0], x=btn_x,
|
button = Button(name=i[0], x=btn_x,
|
||||||
y=175, width=60,
|
y=175, width=60,
|
||||||
height=60, label=i[0].strip(" "),
|
height=60, label=i[0].strip(" "),
|
||||||
|
|
@ -264,7 +263,7 @@ splash.append(progress_bar)
|
||||||
countdown = ON_SECONDS
|
countdown = ON_SECONDS
|
||||||
|
|
||||||
# current button state, defaults to first item in totp_keys
|
# current button state, defaults to first item in totp_keys
|
||||||
current_button = secrets['totp_keys'][0][0]
|
current_button = totp_keys[0][0]
|
||||||
buttons[0].selected = True
|
buttons[0].selected = True
|
||||||
|
|
||||||
while ALWAYS_ON or (countdown > 0):
|
while ALWAYS_ON or (countdown > 0):
|
||||||
|
|
@ -295,7 +294,7 @@ while ALWAYS_ON or (countdown > 0):
|
||||||
for i, b in enumerate(buttons):
|
for i, b in enumerate(buttons):
|
||||||
if b.contains(p):
|
if b.contains(p):
|
||||||
b.selected = True
|
b.selected = True
|
||||||
for name, secret in secrets['totp_keys']:
|
for name, secret in totp_keys:
|
||||||
# check if button name is the same as a key name
|
# check if button name is the same as a key name
|
||||||
if b.name == name:
|
if b.name == name:
|
||||||
current_button = name
|
current_button = name
|
||||||
|
|
@ -305,7 +304,7 @@ while ALWAYS_ON or (countdown > 0):
|
||||||
else:
|
else:
|
||||||
b.selected = False
|
b.selected = False
|
||||||
else:
|
else:
|
||||||
for name, secret in secrets['totp_keys']:
|
for name, secret in totp_keys:
|
||||||
if current_button == name:
|
if current_button == name:
|
||||||
# Generate OTP
|
# Generate OTP
|
||||||
otp = generate_otp(unix_time // 30, secret)
|
otp = generate_otp(unix_time // 30, secret)
|
||||||
|
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
# SPDX-FileCopyrightText: 2019 Brent Rubell for Adafruit Industries
|
|
||||||
#
|
|
||||||
# SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
# This file is where you keep secret settings, passwords, and tokens!
|
|
||||||
# If you put them in the code you risk committing that info or sharing it
|
|
||||||
|
|
||||||
secrets = {
|
|
||||||
'totp_keys' : [("Discord ", "JBSWY3DPEHPK3PXP"),
|
|
||||||
("Gmail", "JBSWY3DPEHPK3PZP"),
|
|
||||||
("GitHub", "JBSWY5DZEHPK3PXP"),
|
|
||||||
("Adafruit", "JBSWY6DZEHPK3PXP"),
|
|
||||||
("Outlook", "JBSWY7DZEHPK3PXP")]
|
|
||||||
}
|
|
||||||
14
PyPortal/PyPortal_TOTP_Friend/totp_keys.py
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
# SPDX-FileCopyrightText: 2019 Brent Rubell for Adafruit Industries
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
# This file contains totp codes!
|
||||||
|
# If you put them in the code you risk committing that info or sharing it
|
||||||
|
|
||||||
|
totp_keys = [
|
||||||
|
("Discord ", "JBSWY3DPEHPK3PXP"),
|
||||||
|
("Gmail", "JBSWY3DPEHPK3PZP"),
|
||||||
|
("GitHub", "JBSWY5DZEHPK3PXP"),
|
||||||
|
("Adafruit", "JBSWY6DZEHPK3PXP"),
|
||||||
|
("Outlook", "JBSWY7DZEHPK3PXP"),
|
||||||
|
]
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
#
|
#
|
||||||
# SPDX-License-Identifier: MIT
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
from os import getenv
|
||||||
import time
|
import time
|
||||||
import json
|
import json
|
||||||
import board
|
import board
|
||||||
|
|
@ -9,12 +10,17 @@ from adafruit_pyportal import PyPortal
|
||||||
from adafruit_bitmap_font import bitmap_font
|
from adafruit_bitmap_font import bitmap_font
|
||||||
from adafruit_display_text.label import Label
|
from adafruit_display_text.label import Label
|
||||||
|
|
||||||
# Get wifi details and more from a secrets.py file
|
# Get WiFi details, ensure these are setup in settings.toml
|
||||||
try:
|
ssid = getenv("CIRCUITPY_WIFI_SSID")
|
||||||
from secrets import secrets
|
password = getenv("CIRCUITPY_WIFI_PASSWORD")
|
||||||
except ImportError:
|
|
||||||
print("WiFi secrets are kept in secrets.py, please add them there!")
|
if None in [ssid, password]:
|
||||||
raise
|
raise RuntimeError(
|
||||||
|
"WiFi settings are kept in settings.toml, "
|
||||||
|
"please add them there. The settings file must contain "
|
||||||
|
"'CIRCUITPY_WIFI_SSID', 'CIRCUITPY_WIFI_PASSWORD', "
|
||||||
|
"at a minimum."
|
||||||
|
)
|
||||||
|
|
||||||
#--| USER CONFIG |--------------------------
|
#--| USER CONFIG |--------------------------
|
||||||
STATION_ID = "0245" # tide location, find yours from admiralty website/
|
STATION_ID = "0245" # tide location, find yours from admiralty website/
|
||||||
|
|
@ -30,7 +36,7 @@ DATA_LOCATION = []
|
||||||
# determine the current working directory needed so we know where to find files
|
# determine the current working directory needed so we know where to find files
|
||||||
cwd = ("/"+__file__).rsplit('/', 1)[0]
|
cwd = ("/"+__file__).rsplit('/', 1)[0]
|
||||||
pyportal = PyPortal(url=DATA_SOURCE,
|
pyportal = PyPortal(url=DATA_SOURCE,
|
||||||
headers={"Ocp-Apim-Subscription-Key":secrets['Ocp-Apim-Subscription-Key']},
|
headers={"Ocp-Apim-Subscription-Key": getenv('Ocp-Apim-Subscription-Key')},
|
||||||
json_path=DATA_LOCATION,
|
json_path=DATA_LOCATION,
|
||||||
status_neopixel=board.NEOPIXEL,
|
status_neopixel=board.NEOPIXEL,
|
||||||
default_bg=cwd+"/images/tides_bg.bmp")
|
default_bg=cwd+"/images/tides_bg.bmp")
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
#
|
#
|
||||||
# SPDX-License-Identifier: MIT
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
from os import getenv
|
||||||
import time
|
import time
|
||||||
import json
|
import json
|
||||||
import board
|
import board
|
||||||
|
|
@ -10,12 +11,17 @@ from adafruit_pyportal import PyPortal
|
||||||
from adafruit_bitmap_font import bitmap_font
|
from adafruit_bitmap_font import bitmap_font
|
||||||
from adafruit_display_text.label import Label
|
from adafruit_display_text.label import Label
|
||||||
|
|
||||||
# Get wifi details and more from a secrets.py file
|
# Get WiFi details, ensure these are setup in settings.toml
|
||||||
try:
|
ssid = getenv("CIRCUITPY_WIFI_SSID")
|
||||||
from secrets import secrets
|
password = getenv("CIRCUITPY_WIFI_PASSWORD")
|
||||||
except ImportError:
|
|
||||||
print("WiFi secrets are kept in secrets.py, please add them there!")
|
if None in [ssid, password]:
|
||||||
raise
|
raise RuntimeError(
|
||||||
|
"WiFi settings are kept in settings.toml, "
|
||||||
|
"please add them there. The settings file must contain "
|
||||||
|
"'CIRCUITPY_WIFI_SSID', 'CIRCUITPY_WIFI_PASSWORD', "
|
||||||
|
"at a minimum."
|
||||||
|
)
|
||||||
|
|
||||||
#--| USER CONFIG |--------------------------
|
#--| USER CONFIG |--------------------------
|
||||||
STATION_ID = "0245" # tide location, find yours from admiralty website
|
STATION_ID = "0245" # tide location, find yours from admiralty website
|
||||||
|
|
@ -44,7 +50,7 @@ else:
|
||||||
bg_image_path = "/images/tides_bg_graph.bmp"
|
bg_image_path = "/images/tides_bg_graph.bmp"
|
||||||
|
|
||||||
pyportal = PyPortal(url=DATA_SOURCE,
|
pyportal = PyPortal(url=DATA_SOURCE,
|
||||||
headers={"Ocp-Apim-Subscription-Key":secrets['Ocp-Apim-Subscription-Key']},
|
headers={"Ocp-Apim-Subscription-Key": getenv('Ocp-Apim-Subscription-Key')},
|
||||||
json_path=DATA_LOCATION,
|
json_path=DATA_LOCATION,
|
||||||
status_neopixel=board.NEOPIXEL,
|
status_neopixel=board.NEOPIXEL,
|
||||||
default_bg=cwd+bg_image_path)
|
default_bg=cwd+bg_image_path)
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
#
|
#
|
||||||
# SPDX-License-Identifier: MIT
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
from os import getenv
|
||||||
import time
|
import time
|
||||||
from calendar import alarms
|
from calendar import alarms
|
||||||
from calendar import timers
|
from calendar import timers
|
||||||
|
|
@ -12,20 +13,25 @@ from adafruit_button import Button
|
||||||
from adafruit_pyportal import PyPortal
|
from adafruit_pyportal import PyPortal
|
||||||
import openweather_graphics # pylint: disable=wrong-import-position
|
import openweather_graphics # pylint: disable=wrong-import-position
|
||||||
|
|
||||||
# Get wifi details and more from a secrets.py file
|
# Get WiFi details, ensure these are setup in settings.toml
|
||||||
try:
|
ssid = getenv("CIRCUITPY_WIFI_SSID")
|
||||||
from secrets import secrets
|
password = getenv("CIRCUITPY_WIFI_PASSWORD")
|
||||||
except ImportError:
|
|
||||||
print("WiFi secrets are kept in secrets.py, please add them there!")
|
if None in [ssid, password]:
|
||||||
raise
|
raise RuntimeError(
|
||||||
|
"WiFi settings are kept in settings.toml, "
|
||||||
|
"please add them there. The settings file must contain "
|
||||||
|
"'CIRCUITPY_WIFI_SSID', 'CIRCUITPY_WIFI_PASSWORD', "
|
||||||
|
"at a minimum."
|
||||||
|
)
|
||||||
|
|
||||||
# Use cityname, country code where countrycode is ISO3166 format.
|
# Use cityname, country code where countrycode is ISO3166 format.
|
||||||
# E.g. "New York, US" or "London, GB"
|
# E.g. "New York, US" or "London, GB"
|
||||||
LOCATION = secrets['location']
|
LOCATION = getenv('location')
|
||||||
|
|
||||||
# Set up where we'll be fetching data from
|
# Set up where we'll be fetching data from
|
||||||
DATA_SOURCE = "http://api.openweathermap.org/data/2.5/weather?q="+LOCATION
|
DATA_SOURCE = "http://api.openweathermap.org/data/2.5/weather?q=" + LOCATION
|
||||||
DATA_SOURCE += "&appid="+secrets['openweather_token']
|
DATA_SOURCE += "&appid=" + getenv('openweather_token')
|
||||||
# You'll need to get a token from openweather.org, looks like 'b6907d289e10d714a6e88b30761fae22'
|
# You'll need to get a token from openweather.org, looks like 'b6907d289e10d714a6e88b30761fae22'
|
||||||
DATA_LOCATION = []
|
DATA_LOCATION = []
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
# SPDX-FileCopyrightText: 2020 Liz Clark for Adafruit Industries
|
|
||||||
#
|
|
||||||
# SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
# This file is where you keep secret settings, passwords, and tokens!
|
|
||||||
# If you put them in the code you risk committing that info or sharing it
|
|
||||||
|
|
||||||
secrets = {
|
|
||||||
'ssid' : 'your-ssid-here',
|
|
||||||
'password' : 'your-password-here',
|
|
||||||
'openweather_token' : 'your-openweather-token-here',
|
|
||||||
'aio_username' : "your-aio-username-here",
|
|
||||||
'aio_key' : 'your-aio-key-here',
|
|
||||||
'location' : 'New York, US'
|
|
||||||
}
|
|
||||||
14
PyPortal/PyPortal_Titano_Weather_Station/settings.toml
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
# SPDX-FileCopyrightText: 2020 Liz Clark for Adafruit Industries
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
# This file is where you keep private settings, passwords, and tokens!
|
||||||
|
# If you put them in the code you risk committing that info or sharing it
|
||||||
|
|
||||||
|
CIRCUITPY_WIFI_SSID="your-wifi-ssid"
|
||||||
|
CIRCUITPY_WIFI_PASSWORD="your-wifi-password"
|
||||||
|
ADAFRUIT_AIO_USERNAME="my_username"
|
||||||
|
ADAFRUIT_AIO_KEY="my_key"
|
||||||
|
timezone="America/New_York" # http://worldtimeapi.org/timezones
|
||||||
|
openweather_token="my_openweather_token"
|
||||||
|
location="New York, US"
|
||||||
|
|
@ -14,11 +14,11 @@ analogin = AnalogIn(board.LIGHT)
|
||||||
|
|
||||||
cwd = ("/"+__file__).rsplit('/', 1)[0]
|
cwd = ("/"+__file__).rsplit('/', 1)[0]
|
||||||
|
|
||||||
laura = (cwd+"/laura.bmp")
|
laura = cwd+"/laura.bmp"
|
||||||
|
|
||||||
woodsman = (cwd+"/woodsman.bmp")
|
woodsman = cwd+"/woodsman.bmp"
|
||||||
|
|
||||||
gottaLight = (cwd+"/gottaLight.wav")
|
gottaLight = cwd+"/gottaLight.wav"
|
||||||
|
|
||||||
pyportal = PyPortal(default_bg=laura)
|
pyportal = PyPortal(default_bg=laura)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ Licensed under the MIT license.
|
||||||
All text above must be included in any redistribution.
|
All text above must be included in any redistribution.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from os import getenv
|
||||||
import time
|
import time
|
||||||
import json
|
import json
|
||||||
import board
|
import board
|
||||||
|
|
@ -25,12 +26,17 @@ from adafruit_display_shapes.rect import Rect
|
||||||
from adafruit_display_text.Label import Label
|
from adafruit_display_text.Label import Label
|
||||||
from adafruit_bitmap_font import bitmap_font
|
from adafruit_bitmap_font import bitmap_font
|
||||||
|
|
||||||
try:
|
# Get WiFi details, ensure these are setup in settings.toml
|
||||||
from secrets import secrets
|
ssid = getenv("CIRCUITPY_WIFI_SSID")
|
||||||
except ImportError:
|
password = getenv("CIRCUITPY_WIFI_PASSWORD")
|
||||||
print("""WiFi settings are kept in secrets.py, please add them there!
|
|
||||||
the secrets dictionary must contain 'ssid' and 'password' at a minimum""")
|
if None in [ssid, password]:
|
||||||
raise
|
raise RuntimeError(
|
||||||
|
"WiFi settings are kept in settings.toml, "
|
||||||
|
"please add them there. The settings file must contain "
|
||||||
|
"'CIRCUITPY_WIFI_SSID', 'CIRCUITPY_WIFI_PASSWORD', "
|
||||||
|
"at a minimum."
|
||||||
|
)
|
||||||
|
|
||||||
MAX_BAR_HEIGHT = 160
|
MAX_BAR_HEIGHT = 160
|
||||||
MARGIN = 10
|
MARGIN = 10
|
||||||
|
|
@ -48,7 +54,7 @@ CAPTION_FONT_FILE = cwd+'/fonts/Helvetica-Bold-16.bdf'
|
||||||
BAR_FONT_FILE = cwd+'/fonts/Arial-Bold-12.bdf'
|
BAR_FONT_FILE = cwd+'/fonts/Arial-Bold-12.bdf'
|
||||||
|
|
||||||
#pylint:disable=line-too-long
|
#pylint:disable=line-too-long
|
||||||
url = 'https://enviro.epa.gov/enviro/efservice/getEnvirofactsUVHOURLY/ZIP/{0}/JSON'.format(secrets['zip'])
|
url = f"https://enviro.epa.gov/enviro/efservice/getEnvirofactsUVHOURLY/ZIP/{getenv('zip')}/JSON"
|
||||||
#pylint:enable=line-too-long
|
#pylint:enable=line-too-long
|
||||||
|
|
||||||
def extract_hour(date_time):
|
def extract_hour(date_time):
|
||||||
|
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
# SPDX-FileCopyrightText: 2019 Kattni Rembor for Adafruit Industries
|
|
||||||
#
|
|
||||||
# SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
# This file is where you keep secret settings, passwords, and tokens!
|
|
||||||
# If you put them in the code you risk committing that info or sharing it
|
|
||||||
|
|
||||||
secrets = {
|
|
||||||
'ssid' : 'CHANGE ME',
|
|
||||||
'password' : 'CHANGE ME',
|
|
||||||
'zip' : 'CHANGE ME',
|
|
||||||
}
|
|
||||||
7
PyPortal/PyPortal_NeoPixel_Color_Picker/secrets.py → PyPortal/PyPortal_UV_Index/settings.toml
Executable file → Normal file
|
|
@ -2,8 +2,9 @@
|
||||||
#
|
#
|
||||||
# SPDX-License-Identifier: MIT
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
# This file is where you keep secret settings, passwords, and tokens!
|
# This file is where you keep private settings, passwords, and tokens!
|
||||||
# If you put them in the code you risk committing that info or sharing it
|
# If you put them in the code you risk committing that info or sharing it
|
||||||
|
|
||||||
secrets = {
|
CIRCUITPY_WIFI_SSID="your-wifi-ssid"
|
||||||
}
|
CIRCUITPY_WIFI_PASSWORD="your-wifi-password"
|
||||||
|
zip="CHANGE ME"
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
PyPortal_User_Interface/code.py 128: Line too long (117/100) (line-too-long)
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
# SPDX-FileCopyrightText: 2020 Anne Barela for Adafruit Industries
|
|
||||||
#
|
|
||||||
# SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
secrets = {
|
|
||||||
'ssid' : '_your_wifi_ssid_',
|
|
||||||
'password' : '_your_wifi_password_',
|
|
||||||
'broker' : '_your_mqtt_broker_url_or_ip',
|
|
||||||
'user' : '_your_mqtt_broker_username_',
|
|
||||||
'pass' : '_your_mqtt_broker_password_'
|
|
||||||
}
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
#
|
#
|
||||||
# SPDX-License-Identifier: MIT
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
import os
|
from os import getenv
|
||||||
import time
|
import time
|
||||||
|
|
||||||
import board
|
import board
|
||||||
|
|
@ -21,6 +21,21 @@ from adafruit_pyportal import PyPortal
|
||||||
from adafruit_seesaw.seesaw import Seesaw
|
from adafruit_seesaw.seesaw import Seesaw
|
||||||
from simpleio import map_range
|
from simpleio import map_range
|
||||||
|
|
||||||
|
# Get WiFi details and Adafruit IO keys, ensure these are setup in settings.toml
|
||||||
|
# (visit io.adafruit.com if you need to create an account, or if you need your Adafruit IO key.)
|
||||||
|
ssid = getenv("CIRCUITPY_WIFI_SSID")
|
||||||
|
password = getenv("CIRCUITPY_WIFI_PASSWORD")
|
||||||
|
aio_username = getenv("ADAFRUIT_AIO_USERNAME")
|
||||||
|
aio_key = getenv("ADAFRUIT_AIO_KEY")
|
||||||
|
|
||||||
|
if None in [ssid, password, aio_username, aio_key]:
|
||||||
|
raise RuntimeError(
|
||||||
|
"WiFi and Adafruit IO settings are kept in settings.toml, "
|
||||||
|
"please add them there. The settings file must contain "
|
||||||
|
"'CIRCUITPY_WIFI_SSID', 'CIRCUITPY_WIFI_PASSWORD', "
|
||||||
|
"'ADAFRUIT_AIO_USERNAME' and 'ADAFRUIT_AIO_KEY' at a minimum."
|
||||||
|
)
|
||||||
|
|
||||||
#---| User Config |---------------
|
#---| User Config |---------------
|
||||||
|
|
||||||
# How often to poll the soil sensor, in seconds
|
# How often to poll the soil sensor, in seconds
|
||||||
|
|
@ -52,11 +67,6 @@ wav_water_low = "/sounds/water-low.wav"
|
||||||
# the current working directory (where this file is)
|
# the current working directory (where this file is)
|
||||||
cwd = ("/"+__file__).rsplit('/', 1)[0]
|
cwd = ("/"+__file__).rsplit('/', 1)[0]
|
||||||
|
|
||||||
secrets = {
|
|
||||||
"ssid" : os.getenv("CIRCUITPY_WIFI_SSID"),
|
|
||||||
"password" : os.getenv("CIRCUITPY_WIFI_PASSWORD"),
|
|
||||||
}
|
|
||||||
|
|
||||||
# Set up i2c bus
|
# Set up i2c bus
|
||||||
i2c_bus = busio.I2C(board.SCL, board.SDA)
|
i2c_bus = busio.I2C(board.SCL, board.SDA)
|
||||||
|
|
||||||
|
|
@ -70,8 +80,8 @@ esp32_reset = DigitalInOut(board.ESP_RESET)
|
||||||
|
|
||||||
spi = busio.SPI(board.SCK, board.MOSI, board.MISO)
|
spi = busio.SPI(board.SCK, board.MOSI, board.MISO)
|
||||||
esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset)
|
esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset)
|
||||||
status_light = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.2)
|
status_pixel = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.2)
|
||||||
wifi = adafruit_esp32spi_wifimanager.ESPSPI_WiFiManager(esp, secrets, status_light)
|
wifi = adafruit_esp32spi_wifimanager.WiFiManager(esp, ssid, password, status_pixel=status_pixel)
|
||||||
|
|
||||||
# Initialize PyPortal Display
|
# Initialize PyPortal Display
|
||||||
display = board.DISPLAY
|
display = board.DISPLAY
|
||||||
|
|
@ -190,8 +200,8 @@ ssl_context = adafruit_connection_manager.get_radio_ssl_context(esp)
|
||||||
|
|
||||||
# Initialize a new MQTT Client object
|
# Initialize a new MQTT Client object
|
||||||
mqtt_client = MQTT.MQTT(broker="io.adafruit.com",
|
mqtt_client = MQTT.MQTT(broker="io.adafruit.com",
|
||||||
username=os.getenv("AIO_USERNAME"),
|
username=aio_username,
|
||||||
password=os.getenv("AIO_KEY"),
|
password=aio_key,
|
||||||
socket_pool=pool,
|
socket_pool=pool,
|
||||||
ssl_context=ssl_context)
|
ssl_context=ssl_context)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,8 @@ Adafruit IO
|
||||||
|
|
||||||
Author: Brent Rubell for Adafruit Industries, 2019
|
Author: Brent Rubell for Adafruit Industries, 2019
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from os import getenv
|
||||||
import time
|
import time
|
||||||
import board
|
import board
|
||||||
import neopixel
|
import neopixel
|
||||||
|
|
@ -38,12 +40,20 @@ anemometer_max_volts = 2.0
|
||||||
min_wind_speed = 0.0
|
min_wind_speed = 0.0
|
||||||
max_wind_speed = 32.4
|
max_wind_speed = 32.4
|
||||||
|
|
||||||
# Get wifi details and more from a secrets.py file
|
# Get WiFi details and Adafruit IO keys, ensure these are setup in settings.toml
|
||||||
try:
|
# (visit io.adafruit.com if you need to create an account, or if you need your Adafruit IO key.)
|
||||||
from secrets import secrets
|
ssid = getenv("CIRCUITPY_WIFI_SSID")
|
||||||
except ImportError:
|
password = getenv("CIRCUITPY_WIFI_PASSWORD")
|
||||||
print("WiFi secrets are kept in secrets.py, please add them there!")
|
aio_username = getenv("ADAFRUIT_AIO_USERNAME")
|
||||||
raise
|
aio_key = getenv("ADAFRUIT_AIO_KEY")
|
||||||
|
|
||||||
|
if None in [ssid, password, aio_username, aio_key]:
|
||||||
|
raise RuntimeError(
|
||||||
|
"WiFi and Adafruit IO settings are kept in settings.toml, "
|
||||||
|
"please add them there. The settings file must contain "
|
||||||
|
"'CIRCUITPY_WIFI_SSID', 'CIRCUITPY_WIFI_PASSWORD', "
|
||||||
|
"'ADAFRUIT_AIO_USERNAME' and 'ADAFRUIT_AIO_KEY' at a minimum."
|
||||||
|
)
|
||||||
|
|
||||||
# PyPortal ESP32 Setup
|
# PyPortal ESP32 Setup
|
||||||
esp32_cs = DigitalInOut(board.ESP_CS)
|
esp32_cs = DigitalInOut(board.ESP_CS)
|
||||||
|
|
@ -51,14 +61,8 @@ esp32_ready = DigitalInOut(board.ESP_BUSY)
|
||||||
esp32_reset = DigitalInOut(board.ESP_RESET)
|
esp32_reset = DigitalInOut(board.ESP_RESET)
|
||||||
spi = busio.SPI(board.SCK, board.MOSI, board.MISO)
|
spi = busio.SPI(board.SCK, board.MOSI, board.MISO)
|
||||||
esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset)
|
esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset)
|
||||||
status_light = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.2)
|
status_pixel = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.2)
|
||||||
wifi = adafruit_esp32spi_wifimanager.ESPSPI_WiFiManager(esp, secrets, status_light)
|
wifi = adafruit_esp32spi_wifimanager.WiFiManager(esp, ssid, password, status_pixel=status_pixel)
|
||||||
|
|
||||||
# Set your Adafruit IO Username and Key in secrets.py
|
|
||||||
# (visit io.adafruit.com if you need to create an account,
|
|
||||||
# or if you need your Adafruit IO key.)
|
|
||||||
ADAFRUIT_IO_USER = secrets['aio_username']
|
|
||||||
ADAFRUIT_IO_KEY = secrets['aio_key']
|
|
||||||
|
|
||||||
# Create an instance of the Adafruit IO HTTP client
|
# Create an instance of the Adafruit IO HTTP client
|
||||||
io = IO_HTTP(ADAFRUIT_IO_USER, ADAFRUIT_IO_KEY, wifi)
|
io = IO_HTTP(ADAFRUIT_IO_USER, ADAFRUIT_IO_KEY, wifi)
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ requests = adafruit_requests.Session(pool, ssl.create_default_context())
|
||||||
# neopixels, 49 total
|
# neopixels, 49 total
|
||||||
OFF = (0, 0, 0)
|
OFF = (0, 0, 0)
|
||||||
ON = (255, 255, 255)
|
ON = (255, 255, 255)
|
||||||
|
RED = (255,0,0)
|
||||||
pixel_pin = board.A3
|
pixel_pin = board.A3
|
||||||
num_pixels = 49
|
num_pixels = 49
|
||||||
pixels = neopixel.NeoPixel(pixel_pin, num_pixels, brightness=0.1, auto_write=False)
|
pixels = neopixel.NeoPixel(pixel_pin, num_pixels, brightness=0.1, auto_write=False)
|
||||||
|
|
@ -43,9 +44,11 @@ FULL_MOON = 4
|
||||||
WANING_GIBBOUS = 5
|
WANING_GIBBOUS = 5
|
||||||
THIRD_QUARTER = 6
|
THIRD_QUARTER = 6
|
||||||
WANING_CRESCENT = 7
|
WANING_CRESCENT = 7
|
||||||
|
DARK_MOON = 8
|
||||||
|
RED_MOON = 9
|
||||||
# strings that match return from API
|
# strings that match return from API
|
||||||
phase_names = ["New Moon", "Waxing Crescent", "First Quarter", "Waxing Gibbous",
|
phase_names = ["New Moon", "Waxing Crescent", "First Quarter", "Waxing Gibbous",
|
||||||
"Full Moon", "Waning Gibbous", "Third Quarter", "Waning Crescent"]
|
"Full Moon", "Waning Gibbous", "Third Quarter", "Waning Crescent","Dark Moon","Red Moon"]
|
||||||
|
|
||||||
# functions for each moon phase to light up based on neopixel orientation
|
# functions for each moon phase to light up based on neopixel orientation
|
||||||
def set_new_moon():
|
def set_new_moon():
|
||||||
|
|
@ -96,6 +99,16 @@ def set_waning_crescent():
|
||||||
pixels[i] = ON
|
pixels[i] = ON
|
||||||
pixels.show()
|
pixels.show()
|
||||||
|
|
||||||
|
def set_dark_moon():
|
||||||
|
pixels.fill(OFF)
|
||||||
|
for i in range(9,14):
|
||||||
|
pixels[i] = ON
|
||||||
|
pixels.show()
|
||||||
|
|
||||||
|
def set_red_moon():
|
||||||
|
pixels.fill(RED)
|
||||||
|
pixels.show()
|
||||||
|
|
||||||
# match functions with phases
|
# match functions with phases
|
||||||
phase_functions = {
|
phase_functions = {
|
||||||
NEW_MOON: set_new_moon,
|
NEW_MOON: set_new_moon,
|
||||||
|
|
@ -105,12 +118,14 @@ phase_functions = {
|
||||||
FULL_MOON: set_full_moon,
|
FULL_MOON: set_full_moon,
|
||||||
WANING_GIBBOUS: set_waning_gibbous,
|
WANING_GIBBOUS: set_waning_gibbous,
|
||||||
THIRD_QUARTER: set_third_quarter,
|
THIRD_QUARTER: set_third_quarter,
|
||||||
WANING_CRESCENT: set_waning_crescent
|
WANING_CRESCENT: set_waning_crescent,
|
||||||
|
DARK_MOON: set_dark_moon,
|
||||||
|
RED_MOON: set_red_moon
|
||||||
}
|
}
|
||||||
|
|
||||||
# test function, runs through all 8 in order
|
# test function, runs through all 8 in order
|
||||||
def demo_all_phases(delay=1):
|
def demo_all_phases(delay=1):
|
||||||
for phase in range(8):
|
for phase in range(9):
|
||||||
print(f"Setting phase: {phase_names[phase]}")
|
print(f"Setting phase: {phase_names[phase]}")
|
||||||
phase_functions[phase]()
|
phase_functions[phase]()
|
||||||
time.sleep(delay)
|
time.sleep(delay)
|
||||||
|
|
@ -119,10 +134,15 @@ demo_all_phases()
|
||||||
# takes response from API, matches to function, runs function
|
# takes response from API, matches to function, runs function
|
||||||
def set_moon_phase(phase):
|
def set_moon_phase(phase):
|
||||||
phase_lower = phase.lower()
|
phase_lower = phase.lower()
|
||||||
|
error_check = 0
|
||||||
for i, name in enumerate(phase_names):
|
for i, name in enumerate(phase_names):
|
||||||
if phase_lower == name.lower():
|
if phase_lower == name.lower():
|
||||||
|
error_check = 1
|
||||||
phase_functions[i]()
|
phase_functions[i]()
|
||||||
print(f"Moon phase set to: {name}")
|
print(f"Moon phase set to: {name}")
|
||||||
|
if error_check == 0:
|
||||||
|
print("ERROR")
|
||||||
|
set_red_moon() #error indicator if API responce is unexpected
|
||||||
|
|
||||||
# time keeping, fetches API every 6 hours
|
# time keeping, fetches API every 6 hours
|
||||||
timer_clock = ticks_ms()
|
timer_clock = ticks_ms()
|
||||||
|
|
|
||||||
172
USB_SNES_Gamepad/CircuitPython_USB_Host/code.py
Normal file
|
|
@ -0,0 +1,172 @@
|
||||||
|
# SPDX-FileCopyrightText: Copyright (c) 2025 Tim Cocks for Adafruit Industries
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
import array
|
||||||
|
import time
|
||||||
|
import usb.core
|
||||||
|
import adafruit_usb_host_descriptors
|
||||||
|
|
||||||
|
# Set to true to print detailed information about all devices found
|
||||||
|
VERBOSE_SCAN = True
|
||||||
|
|
||||||
|
BTN_DPAD_UPDOWN_INDEX = 1
|
||||||
|
BTN_DPAD_RIGHTLEFT_INDEX = 0
|
||||||
|
BTN_ABXY_INDEX = 5
|
||||||
|
BTN_OTHER_INDEX = 6
|
||||||
|
|
||||||
|
DIR_IN = 0x80
|
||||||
|
controller = None
|
||||||
|
|
||||||
|
if VERBOSE_SCAN:
|
||||||
|
for device in usb.core.find(find_all=True):
|
||||||
|
controller = device
|
||||||
|
print("pid", hex(device.idProduct))
|
||||||
|
print("vid", hex(device.idVendor))
|
||||||
|
print("man", device.manufacturer)
|
||||||
|
print("product", device.product)
|
||||||
|
print("serial", device.serial_number)
|
||||||
|
print("config[0]:")
|
||||||
|
config_descriptor = adafruit_usb_host_descriptors.get_configuration_descriptor(
|
||||||
|
device, 0
|
||||||
|
)
|
||||||
|
|
||||||
|
i = 0
|
||||||
|
while i < len(config_descriptor):
|
||||||
|
descriptor_len = config_descriptor[i]
|
||||||
|
descriptor_type = config_descriptor[i + 1]
|
||||||
|
if descriptor_type == adafruit_usb_host_descriptors.DESC_CONFIGURATION:
|
||||||
|
config_value = config_descriptor[i + 5]
|
||||||
|
print(f" value {config_value:d}")
|
||||||
|
elif descriptor_type == adafruit_usb_host_descriptors.DESC_INTERFACE:
|
||||||
|
interface_number = config_descriptor[i + 2]
|
||||||
|
interface_class = config_descriptor[i + 5]
|
||||||
|
interface_subclass = config_descriptor[i + 6]
|
||||||
|
print(f" interface[{interface_number:d}]")
|
||||||
|
print(
|
||||||
|
f" class {interface_class:02x} subclass {interface_subclass:02x}"
|
||||||
|
)
|
||||||
|
elif descriptor_type == adafruit_usb_host_descriptors.DESC_ENDPOINT:
|
||||||
|
endpoint_address = config_descriptor[i + 2]
|
||||||
|
if endpoint_address & DIR_IN:
|
||||||
|
print(f" IN {endpoint_address:02x}")
|
||||||
|
else:
|
||||||
|
print(f" OUT {endpoint_address:02x}")
|
||||||
|
i += descriptor_len
|
||||||
|
|
||||||
|
# get the first device found
|
||||||
|
device = None
|
||||||
|
while device is None:
|
||||||
|
for d in usb.core.find(find_all=True):
|
||||||
|
device = d
|
||||||
|
break
|
||||||
|
time.sleep(0.1)
|
||||||
|
|
||||||
|
# set configuration so we can read data from it
|
||||||
|
device.set_configuration()
|
||||||
|
print(
|
||||||
|
f"configuration set for {device.manufacturer}, {device.product}, {device.serial_number}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Test to see if the kernel is using the device and detach it.
|
||||||
|
if device.is_kernel_driver_active(0):
|
||||||
|
device.detach_kernel_driver(0)
|
||||||
|
|
||||||
|
# buffer to hold 64 bytes
|
||||||
|
buf = array.array("B", [0] * 64)
|
||||||
|
|
||||||
|
|
||||||
|
def print_array(arr, max_index=None, fmt="hex"):
|
||||||
|
"""
|
||||||
|
Print the values of an array
|
||||||
|
:param arr: The array to print
|
||||||
|
:param max_index: The maximum index to print. None means print all.
|
||||||
|
:param fmt: The format to use, either "hex" or "bin"
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
|
out_str = ""
|
||||||
|
if max_index is None or max_index >= len(arr):
|
||||||
|
length = len(arr)
|
||||||
|
else:
|
||||||
|
length = max_index
|
||||||
|
|
||||||
|
for _ in range(length):
|
||||||
|
if fmt == "hex":
|
||||||
|
out_str += f"{int(arr[_]):02x} "
|
||||||
|
elif fmt == "bin":
|
||||||
|
out_str += f"{int(arr[_]):08b} "
|
||||||
|
print(out_str)
|
||||||
|
|
||||||
|
|
||||||
|
def reports_equal(report_a, report_b, check_length=None):
|
||||||
|
"""
|
||||||
|
Test if two reports are equal. If check_length is provided then
|
||||||
|
check for equality in only the first check_length number of bytes.
|
||||||
|
|
||||||
|
:param report_a: First report data
|
||||||
|
:param report_b: Second report data
|
||||||
|
:param check_length: How many bytes to check
|
||||||
|
:return: True if the reports are equal, otherwise False.
|
||||||
|
"""
|
||||||
|
if (
|
||||||
|
report_a is None
|
||||||
|
and report_b is not None
|
||||||
|
or report_b is None
|
||||||
|
and report_a is not None
|
||||||
|
):
|
||||||
|
return False
|
||||||
|
|
||||||
|
length = len(report_a) if check_length is None else check_length
|
||||||
|
for _ in range(length):
|
||||||
|
if report_a[_] != report_b[_]:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
idle_state = None
|
||||||
|
prev_state = None
|
||||||
|
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
count = device.read(0x81, buf)
|
||||||
|
# print(f"read size: {count}")
|
||||||
|
except usb.core.USBTimeoutError:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if idle_state is None:
|
||||||
|
idle_state = buf[:]
|
||||||
|
print("Idle state:")
|
||||||
|
print_array(idle_state[:8], max_index=count)
|
||||||
|
print()
|
||||||
|
|
||||||
|
if not reports_equal(buf, prev_state, 8) and not reports_equal(buf, idle_state, 8):
|
||||||
|
if buf[BTN_DPAD_UPDOWN_INDEX] == 0x0:
|
||||||
|
print("D-Pad UP pressed")
|
||||||
|
elif buf[BTN_DPAD_UPDOWN_INDEX] == 0xFF:
|
||||||
|
print("D-Pad DOWN pressed")
|
||||||
|
|
||||||
|
if buf[BTN_DPAD_RIGHTLEFT_INDEX] == 0:
|
||||||
|
print("D-Pad LEFT pressed")
|
||||||
|
elif buf[BTN_DPAD_RIGHTLEFT_INDEX] == 0xFF:
|
||||||
|
print("D-Pad RIGHT pressed")
|
||||||
|
|
||||||
|
if buf[BTN_ABXY_INDEX] == 0x2F:
|
||||||
|
print("A pressed")
|
||||||
|
elif buf[BTN_ABXY_INDEX] == 0x4F:
|
||||||
|
print("B pressed")
|
||||||
|
elif buf[BTN_ABXY_INDEX] == 0x1F:
|
||||||
|
print("X pressed")
|
||||||
|
elif buf[BTN_ABXY_INDEX] == 0x8F:
|
||||||
|
print("Y pressed")
|
||||||
|
|
||||||
|
if buf[BTN_OTHER_INDEX] == 0x01:
|
||||||
|
print("L shoulder pressed")
|
||||||
|
elif buf[BTN_OTHER_INDEX] == 0x02:
|
||||||
|
print("R shoulder pressed")
|
||||||
|
elif buf[BTN_OTHER_INDEX] == 0x10:
|
||||||
|
print("SELECT pressed")
|
||||||
|
elif buf[BTN_OTHER_INDEX] == 0x20:
|
||||||
|
print("START pressed")
|
||||||
|
|
||||||
|
# print_array(buf[:8])
|
||||||
|
|
||||||
|
prev_state = buf[:]
|
||||||
|
|
@ -1,25 +1,36 @@
|
||||||
{
|
{
|
||||||
"exportedFromDevice": {
|
"exportedFromDevice": {
|
||||||
"referenceVoltage": 3.3,
|
"sd_cs_pin": 23,
|
||||||
"totalGPIOPins": 18,
|
"referenceVoltage": 0,
|
||||||
"totalAnalogPins": 4,
|
"totalGPIOPins": 0,
|
||||||
"sd_cs_pin": 23,
|
"totalAnalogPins": 0,
|
||||||
"statusLEDBrightness": 0.5
|
"statusLEDBrightness": 0.3
|
||||||
},
|
},
|
||||||
"components": [
|
"components": [
|
||||||
{
|
{
|
||||||
"name": "BME280 Sensor",
|
"name": "bme280",
|
||||||
"componentAPI": "i2c",
|
"componentAPI": "i2c",
|
||||||
"i2cDeviceName": "bme280",
|
"i2cDeviceName": "bme280",
|
||||||
"period": 15,
|
"period": 30,
|
||||||
"i2cDeviceAddress": "0x77",
|
"autoConfig": "true",
|
||||||
"i2cDeviceSensorTypes": [
|
"i2cDeviceAddress": "0x77",
|
||||||
{"type": "relative-humidity"},
|
"i2cDeviceSensorTypes": [
|
||||||
{"type": "ambient-temp"},
|
{
|
||||||
{"type": "ambient-temp-fahrenheit"},
|
"type": "ambient-temp"
|
||||||
{"type": "pressure"},
|
},
|
||||||
{"type": "altitude"}
|
{
|
||||||
]
|
"type": "ambient-temp-fahrenheit"
|
||||||
}
|
},
|
||||||
|
{
|
||||||
|
"type": "relative-humidity"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "pressure"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "altitude"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||