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
|
||||
|
||||
# function to convert celcius to fahrenheit
|
||||
# function to convert celsius to fahrenheit
|
||||
def c_to_f(temp):
|
||||
temp_f = (temp * 9/5) + 32
|
||||
return temp_f
|
||||
|
|
@ -87,12 +87,12 @@ while True:
|
|||
print(servo_value)
|
||||
# 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
|
||||
press_data.text = "%0.1f hPa" % bmp280.pressure
|
||||
# if imperial units...
|
||||
else:
|
||||
# convert celcius to fahrenheit
|
||||
# convert celsius to fahrenheit
|
||||
temp_fahrenheit = c_to_f(bmp280.temperature)
|
||||
# convert hPa to inHg
|
||||
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
|
||||
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
|
||||
|
||||
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")
|
||||
|
||||
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)
|
||||
|
||||
while True:
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
from os import getenv
|
||||
import time
|
||||
import ssl
|
||||
import wifi
|
||||
|
|
@ -14,6 +15,21 @@ import adafruit_requests
|
|||
from digitalio import DigitalInOut, Direction, Pull
|
||||
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.direction = Direction.OUTPUT
|
||||
alarm_out.value = False
|
||||
|
|
@ -23,37 +39,28 @@ button_in.pull = Pull.UP
|
|||
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")
|
||||
|
||||
# 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
|
||||
# disabling line-too-long because queries for tweet_query & TIME_URL cannot have line breaks
|
||||
# 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'
|
||||
|
||||
headers = {'Authorization': 'Bearer ' + bear}
|
||||
headers = {'Authorization': 'Bearer ' + bearer_token}
|
||||
|
||||
print("Connecting to %s"%secrets["ssid"])
|
||||
wifi.radio.connect(secrets["ssid"], secrets["password"])
|
||||
print("Connected to %s!"%secrets["ssid"])
|
||||
print("My IP address is", wifi.radio.ipv4_address)
|
||||
print(f"Connecting to {ssid}")
|
||||
wifi.radio.connect(ssid, password)
|
||||
print(f"Connected to {ssid}!")
|
||||
print(f"My IP address is {wifi.radio.ipv4_address}")
|
||||
|
||||
pool = socketpool.SocketPool(wifi.radio)
|
||||
requests = adafruit_requests.Session(pool, ssl.create_default_context())
|
||||
|
||||
# gets and formats time from adafruit.io
|
||||
aio_username = secrets["aio_username"]
|
||||
aio_key = secrets["aio_key"]
|
||||
location = secrets.get("timezone", None)
|
||||
location = getenv("timezone", None)
|
||||
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"
|
||||
|
||||
|
|
@ -132,7 +139,7 @@ while True:
|
|||
|
||||
else:
|
||||
# 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))
|
||||
print("no new in stock notifications :(")
|
||||
# updates tweet ID
|
||||
|
|
@ -140,6 +147,6 @@ while True:
|
|||
# if the tweet wasn't today
|
||||
else:
|
||||
# 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))
|
||||
print("no new in stock notifications :(")
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ ow_bus = OneWireBus(board.GP6)
|
|||
# scan for temp sensor
|
||||
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):
|
||||
temp_f = (temp * 9/5) + 32
|
||||
return temp_f
|
||||
|
|
|
|||
|
|
@ -2,17 +2,29 @@
|
|||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
import os
|
||||
from os import getenv
|
||||
import ipaddress
|
||||
import wifi
|
||||
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("Connecting to WiFi")
|
||||
|
||||
# connect to your SSID
|
||||
try:
|
||||
wifi.radio.connect(os.getenv('CIRCUITPY_WIFI_SSID'), os.getenv('CIRCUITPY_WIFI_PASSWORD'))
|
||||
wifi.radio.connect(ssid, password)
|
||||
except TypeError:
|
||||
print("Could not find WiFi info. Check your settings.toml file!")
|
||||
raise
|
||||
|
|
@ -25,7 +37,7 @@ pool = socketpool.SocketPool(wifi.radio)
|
|||
print("My MAC addr:", [hex(i) for i in wifi.radio.mac_address])
|
||||
|
||||
# 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
|
||||
ipv4 = ipaddress.ip_address("8.8.4.4")
|
||||
|
|
|
|||
|
|
@ -7,17 +7,23 @@
|
|||
# or Matrix Portal
|
||||
# and 64 x 32 RGB LED Matrix
|
||||
|
||||
from os import getenv
|
||||
import time
|
||||
import board
|
||||
import terminalio
|
||||
from adafruit_matrixportal.matrixportal import MatrixPortal
|
||||
|
||||
# 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
|
||||
# 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."
|
||||
)
|
||||
|
||||
def aqi_transform(val):
|
||||
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,
|
||||
debug=True,
|
||||
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"
|
||||
},
|
||||
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
|
||||
"""
|
||||
import os
|
||||
|
||||
from os import getenv
|
||||
import time
|
||||
import json
|
||||
import board
|
||||
|
|
@ -28,10 +29,17 @@ import aws_gfx_helper
|
|||
# Time between polling the STEMMA, in minutes
|
||||
SENSOR_DELAY = 15
|
||||
|
||||
secrets = {
|
||||
"ssid" : os.getenv("CIRCUITPY_WIFI_SSID"),
|
||||
"password" : os.getenv("CIRCUITPY_WIFI_PASSWORD"),
|
||||
}
|
||||
# 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."
|
||||
)
|
||||
|
||||
# Get device certificate
|
||||
try:
|
||||
|
|
@ -65,9 +73,10 @@ esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset)
|
|||
# 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."
|
||||
|
||||
status_light = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.2)
|
||||
wifi = adafruit_esp32spi_wifimanager.ESPSPI_WiFiManager(
|
||||
esp, secrets, status_light)
|
||||
status_pixel = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.2)
|
||||
wifi = adafruit_esp32spi_wifimanager.WiFiManager(
|
||||
esp, ssid, password, status_pixel=status_pixel
|
||||
)
|
||||
|
||||
# Initialize the graphics helper
|
||||
print("Loading AWS IoT Graphics...")
|
||||
|
|
@ -126,8 +135,8 @@ def message(client, topic, msg):
|
|||
print("Message from {}: {}".format(topic, msg))
|
||||
|
||||
# Set up a new MiniMQTT Client
|
||||
client = MQTT.MQTT(broker = os.getenv("BROKER"),
|
||||
client_id = os.getenv("CLIENT_ID"),
|
||||
client = MQTT.MQTT(broker = getenv("BROKER"),
|
||||
client_id = getenv("CLIENT_ID"),
|
||||
socket_pool=pool,
|
||||
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
|
||||
If you can find something that spits out JSON data, we can display it!
|
||||
"""
|
||||
|
||||
from os import getenv
|
||||
import time
|
||||
import board
|
||||
from adafruit_pyportal import PyPortal
|
||||
|
||||
# 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
|
||||
# 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."
|
||||
)
|
||||
|
||||
# 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_LOCATION2 = ["users", "users_active_30_days"]
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@ Dependencies:
|
|||
* CircuitPython_AdafruitIO
|
||||
https://github.com/adafruit/Adafruit_CircuitPython_AdafruitIO
|
||||
"""
|
||||
|
||||
from os import getenv
|
||||
import time
|
||||
import board
|
||||
import busio
|
||||
|
|
@ -33,12 +35,20 @@ import adafruit_adt7410
|
|||
# Timeout between sending data to Adafruit IO, in seconds
|
||||
IO_DELAY = 30
|
||||
|
||||
# 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
|
||||
# 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."
|
||||
)
|
||||
|
||||
# PyPortal ESP32 Setup
|
||||
esp32_cs = DigitalInOut(board.ESP_CS)
|
||||
|
|
@ -46,17 +56,11 @@ esp32_ready = DigitalInOut(board.ESP_BUSY)
|
|||
esp32_reset = DigitalInOut(board.ESP_RESET)
|
||||
spi = busio.SPI(board.SCK, board.MOSI, board.MISO)
|
||||
esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset)
|
||||
status_light = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.2)
|
||||
wifi = adafruit_esp32spi_wifimanager.ESPSPI_WiFiManager(esp, secrets, status_light)
|
||||
|
||||
# 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']
|
||||
status_pixel = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.2)
|
||||
wifi = adafruit_esp32spi_wifimanager.WiFiManager(esp, ssid, password, status_pixel=status_pixel)
|
||||
|
||||
# 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:
|
||||
# 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=useless-super-delegation, too-many-locals
|
||||
|
||||
from os import getenv
|
||||
import time
|
||||
import json
|
||||
from secrets import secrets
|
||||
import board
|
||||
from adafruit_pyportal import PyPortal
|
||||
from adafruit_bitmap_font import bitmap_font
|
||||
|
|
@ -32,9 +32,21 @@ import analogio
|
|||
import displayio
|
||||
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
|
||||
DATA_SOURCE = 'http://api.openweathermap.org/data/2.5/weather?id='+secrets['city_id']
|
||||
DATA_SOURCE += '&appid='+secrets['openweather_token']
|
||||
DATA_SOURCE = 'http://api.openweathermap.org/data/2.5/weather?id='+getenv('city_id')
|
||||
DATA_SOURCE += '&appid='+getenv('openweather_token')
|
||||
# You'll need to get a token from openweather.org, looks like 'b6907d289e10d714a6e88b30761fae22'
|
||||
DATA_LOCATION = []
|
||||
|
||||
|
|
@ -73,7 +85,7 @@ mugsy_background = 'mugsy_background.bmp'
|
|||
|
||||
icon_file = None
|
||||
icon_sprite = None
|
||||
celcius = secrets['celcius']
|
||||
celsius = getenv('celsius')
|
||||
|
||||
# display/data refresh timers
|
||||
|
||||
|
|
@ -243,7 +255,7 @@ class Time_State(State):
|
|||
if (not self.refresh_time) or ((now - self.refresh_time) > 3600):
|
||||
logger.debug('Fetching time')
|
||||
try:
|
||||
pyportal.get_local_time(location=secrets['timezone'])
|
||||
pyportal.get_local_time(location=getenv('timezone'))
|
||||
self.refresh_time = now
|
||||
except RuntimeError as e:
|
||||
self.refresh_time = now - 3000 # delay 10 minutes before retrying
|
||||
|
|
@ -282,7 +294,7 @@ class Time_State(State):
|
|||
self.weather_icon.append(icon_sprite)
|
||||
|
||||
temperature = weather['main']['temp'] - 273.15 # its...in kelvin
|
||||
if celcius:
|
||||
if celsius:
|
||||
temperature_text = '%3d C' % round(temperature)
|
||||
else:
|
||||
temperature_text = '%3d F' % round(((temperature * 9 / 5) + 32))
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@ your PyPortal
|
|||
Authors: Brent Rubell for Adafruit Industries, 2019
|
||||
: Jim Bennett for Microsoft, 2020
|
||||
"""
|
||||
|
||||
from os import getenv
|
||||
import time
|
||||
import json
|
||||
import board
|
||||
|
|
@ -30,12 +32,17 @@ import azure_gfx_helper
|
|||
# init. graphics helper
|
||||
gfx = azure_gfx_helper.Azure_GFX(is_celsius=True)
|
||||
|
||||
# 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
|
||||
# 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."
|
||||
)
|
||||
|
||||
# PyPortal ESP32 Setup
|
||||
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)
|
||||
|
||||
# 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)
|
||||
wifi = adafruit_esp32spi_wifimanager.ESPSPI_WiFiManager(esp, secrets, status_light)
|
||||
status_pixel = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.2)
|
||||
wifi = adafruit_esp32spi_wifimanager.WiFiManager(esp, ssid, password, status_pixel=status_pixel)
|
||||
print("WiFi connecting...")
|
||||
wifi.connect()
|
||||
print("WiFi connected!")
|
||||
|
|
@ -68,7 +75,7 @@ ss = Seesaw(i2c_bus, addr=0x36)
|
|||
|
||||
# Create an instance of the Azure IoT Central device
|
||||
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
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
from os import getenv
|
||||
import time
|
||||
import random
|
||||
import board
|
||||
|
|
@ -11,6 +12,21 @@ from adafruit_display_shapes.circle import Circle
|
|||
WIDTH = board.DISPLAY.width
|
||||
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
|
||||
|
||||
# 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
|
||||
|
||||
from os import getenv
|
||||
import sys
|
||||
import time
|
||||
import board
|
||||
|
|
@ -10,12 +11,17 @@ cwd = ("/"+__file__).rsplit('/', 1)[0] # the current working directory (where th
|
|||
sys.path.append(cwd)
|
||||
import electioncal_graphics # pylint: disable=wrong-import-position
|
||||
|
||||
# Get wifi details and more from a secrets.py file
|
||||
try:
|
||||
from secrets import secrets # pylint: disable=unused-import
|
||||
except ImportError:
|
||||
print("WiFi secrets are kept in secrets.py, please add them there!")
|
||||
raise
|
||||
# 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."
|
||||
)
|
||||
|
||||
# Change this to your state and county, replacing spaces for underscores and in lowercase
|
||||
STATE="new_york"
|
||||
|
|
|
|||
|
|
@ -112,7 +112,8 @@ class Electioncal_Graphics(displayio.Group):
|
|||
date_str = date_format_str % (year, month, day)
|
||||
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.
|
||||
Note we return line2 first as it looks better to clear the 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.
|
||||
"""
|
||||
|
||||
from os import getenv
|
||||
import time
|
||||
import board
|
||||
from adafruit_pyportal import PyPortal
|
||||
|
||||
# Get Adafruit IO details and more from a secrets.py file
|
||||
try:
|
||||
from secrets import secrets
|
||||
except ImportError:
|
||||
print("Adafruit IO secrets are kept in secrets.py, please add them there!")
|
||||
raise
|
||||
# (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."
|
||||
)
|
||||
|
||||
# Adafruit IO Account
|
||||
IO_USER = secrets['aio_username']
|
||||
IO_KEY = secrets['aio_key']
|
||||
# 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,
|
||||
IO_FEED, IO_KEY)
|
||||
DATA_SOURCE = f"https://io.adafruit.com/api/v2/{aio_username}/feeds/{io_feed}?X-AIO-Key={aio_key}"
|
||||
FEED_VALUE_LOCATION = ['last_value']
|
||||
|
||||
cwd = ("/"+__file__).rsplit('/', 1)[0]
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ Cloud IoT Core with your PyPortal.
|
|||
|
||||
Author: Brent Rubell for Adafruit Industries, 2019
|
||||
"""
|
||||
|
||||
from os import getenv
|
||||
import time
|
||||
import json
|
||||
import board
|
||||
|
|
@ -22,26 +24,33 @@ from adafruit_gc_iot_core import MQTT_API, Cloud_Core
|
|||
import adafruit_minimqtt.adafruit_minimqtt as MQTT
|
||||
from adafruit_seesaw.seesaw import Seesaw
|
||||
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
|
||||
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
|
||||
esp32_cs = digitalio.DigitalInOut(board.ESP_CS)
|
||||
esp32_ready = digitalio.DigitalInOut(board.ESP_BUSY)
|
||||
esp32_reset = digitalio.DigitalInOut(board.ESP_RESET)
|
||||
spi = busio.SPI(board.SCK, board.MOSI, board.MISO)
|
||||
esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset)
|
||||
status_light = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.2)
|
||||
wifi = adafruit_esp32spi_wifimanager.ESPSPI_WiFiManager(
|
||||
esp, secrets, status_light)
|
||||
status_pixel = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.2)
|
||||
wifi = adafruit_esp32spi_wifimanager.WiFiManager(
|
||||
esp, ssid, password, status_pixel=status_pixel
|
||||
)
|
||||
|
||||
# Connect to WiFi
|
||||
print("Connecting to WiFi...")
|
||||
|
|
@ -137,7 +146,14 @@ def handle_pump(command):
|
|||
|
||||
|
||||
# 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
|
||||
print("Generating JWT...")
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ class Google_GFX(displayio.Group):
|
|||
temp_group.append(temp_label)
|
||||
|
||||
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
|
||||
temp_group.append(self.temp_data_label)
|
||||
self._text_group.append(temp_group)
|
||||
|
|
@ -72,7 +72,7 @@ class Google_GFX(displayio.Group):
|
|||
water_group.append(self.water_level)
|
||||
|
||||
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
|
||||
temp_group.append(self.water_lvl_label)
|
||||
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!
|
||||
if you can find something that spits out JSON data, we can display it
|
||||
"""
|
||||
|
||||
from os import getenv
|
||||
import time
|
||||
import board
|
||||
from adafruit_pyportal import PyPortal
|
||||
|
||||
# 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
|
||||
# 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
|
||||
DATA_SOURCE = "https://api.github.com/repos/adafruit/circuitpython"
|
||||
CAPTION = "www.github.com/adafruit/circuitpython"
|
||||
# If we have an access token, we can query more often
|
||||
if 'github_token' in secrets:
|
||||
DATA_SOURCE += "?access_token="+secrets['github_token']
|
||||
github_token = getenv("github_token")
|
||||
if github_token:
|
||||
DATA_SOURCE += f"?access_token={github_token}"
|
||||
|
||||
# The data we want to display
|
||||
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!
|
||||
Note that you need a hackaday API key to access the API!
|
||||
"""
|
||||
|
||||
from os import getenv
|
||||
import time
|
||||
import board
|
||||
from adafruit_pyportal import PyPortal
|
||||
|
||||
# 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
|
||||
# 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."
|
||||
)
|
||||
|
||||
# Some data sources and JSON locations to try out
|
||||
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"]
|
||||
|
||||
# the current working directory (where this file is)
|
||||
|
|
|
|||
|
|
@ -2,23 +2,29 @@
|
|||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
from os import getenv
|
||||
import time
|
||||
import board
|
||||
from adafruit_pyportal import PyPortal
|
||||
|
||||
# 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
|
||||
# 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."
|
||||
)
|
||||
|
||||
PROJECT_NAME = "3c92f0"
|
||||
|
||||
# Set up where we'll be fetching data from
|
||||
DATA_SOURCE = "https://api.hackster.io/v2/projects/"+PROJECT_NAME+"?"
|
||||
DATA_SOURCE += "client_id="+secrets['hackster_clientid']
|
||||
DATA_SOURCE += "&client_secret="+secrets['hackster_secret']
|
||||
DATA_SOURCE += "client_id="+getenv('hackster_clientid')
|
||||
DATA_SOURCE += "&client_secret="+getenv('hackster_secret')
|
||||
VIEWS_LOCATION = ['stats', 'views']
|
||||
LIKES_LOCATION = ['stats', 'respects']
|
||||
CAPTION = "http://www.hackster.com/project/"+PROJECT_NAME
|
||||
|
|
|
|||
|
|
@ -2,22 +2,28 @@
|
|||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
from os import getenv
|
||||
import time
|
||||
import random
|
||||
import board
|
||||
import adafruit_pyportal
|
||||
|
||||
# Get wifi details and more from a settings.py file
|
||||
try:
|
||||
from secrets import secrets
|
||||
except ImportError:
|
||||
print("WiFi secrets are kept in secrets.py, please add them there!")
|
||||
raise
|
||||
# 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
|
||||
DATA_SOURCE = "https://api.hackster.io/v2/projects?"
|
||||
DATA_SOURCE += "client_id="+secrets['hackster_clientid']
|
||||
DATA_SOURCE += "&client_secret="+secrets['hackster_secret']
|
||||
DATA_SOURCE += "client_id="+getenv('hackster_clientid')
|
||||
DATA_SOURCE += "&client_secret="+getenv('hackster_secret')
|
||||
IMAGE_LOCATION = ['records', 0, "cover_image_url"]
|
||||
TITLE_LOCATION = ['records',0, "name"]
|
||||
HID_LOCATION = ['records', 0, "hid"]
|
||||
|
|
|
|||
|
|
@ -16,8 +16,7 @@ Licensed under the MIT license.
|
|||
All text above must be included in any redistribution.
|
||||
"""
|
||||
|
||||
#pylint:disable=invalid-name
|
||||
import os
|
||||
from os import getenv
|
||||
import time
|
||||
import random
|
||||
import board
|
||||
|
|
@ -25,6 +24,19 @@ from adafruit_pyportal import PyPortal
|
|||
from adafruit_bitmap_font import bitmap_font
|
||||
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!
|
||||
EVENT_YEAR = 2019
|
||||
EVENT_MONTH = 10
|
||||
|
|
@ -83,7 +95,7 @@ while True:
|
|||
if (not refresh_time) or (time.monotonic() - refresh_time) > 3600:
|
||||
try:
|
||||
print("Getting time from internet!")
|
||||
pyportal.get_local_time(location=os.getenv("TIMEZONE"))
|
||||
pyportal.get_local_time(location=getenv("timezone"))
|
||||
refresh_time = time.monotonic()
|
||||
except RuntimeError as 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
|
||||
"""
|
||||
|
||||
from os import getenv
|
||||
import time
|
||||
import board
|
||||
import adafruit_dymoscale
|
||||
|
|
@ -22,12 +24,20 @@ from adafruit_esp32spi import adafruit_esp32spi, adafruit_esp32spi_wifimanager
|
|||
import neopixel
|
||||
from adafruit_io.adafruit_io import IO_HTTP
|
||||
|
||||
# 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
|
||||
# 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."
|
||||
)
|
||||
|
||||
# the current working directory (where this file is)
|
||||
cwd = ("/"+__file__).rsplit('/', 1)[0]
|
||||
|
|
@ -69,14 +79,8 @@ esp32_ready = digitalio.DigitalInOut(board.ESP_BUSY)
|
|||
esp32_reset = digitalio.DigitalInOut(board.ESP_RESET)
|
||||
spi = busio.SPI(board.SCK, board.MOSI, board.MISO)
|
||||
esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset)
|
||||
status_light = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.2)
|
||||
wifi = adafruit_esp32spi_wifimanager.ESPSPI_WiFiManager(esp, secrets, status_light)
|
||||
|
||||
# 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']
|
||||
status_pixel = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.2)
|
||||
wifi = adafruit_esp32spi_wifimanager.WiFiManager(esp, ssid, password, status_pixel=status_pixel)
|
||||
|
||||
# Create an instance of the IO_HTTP client
|
||||
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
|
||||
"""
|
||||
import os
|
||||
|
||||
from os import getenv
|
||||
|
||||
import board
|
||||
import displayio
|
||||
|
|
@ -26,10 +27,17 @@ from adafruit_esp32spi import adafruit_esp32spi_wifimanager
|
|||
# import lifx library
|
||||
import adafruit_lifx
|
||||
|
||||
secrets = {
|
||||
"ssid" : os.getenv("CIRCUITPY_WIFI_SSID"),
|
||||
"password" : os.getenv("CIRCUITPY_WIFI_PASSWORD"),
|
||||
}
|
||||
# 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."
|
||||
)
|
||||
|
||||
# ESP32 SPI
|
||||
esp32_cs = DigitalInOut(board.ESP_CS)
|
||||
|
|
@ -37,8 +45,8 @@ esp32_ready = DigitalInOut(board.ESP_BUSY)
|
|||
esp32_reset = DigitalInOut(board.ESP_RESET)
|
||||
spi = busio.SPI(board.SCK, board.MOSI, board.MISO)
|
||||
esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset)
|
||||
status_light = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.2)
|
||||
wifi = adafruit_esp32spi_wifimanager.ESPSPI_WiFiManager(esp, secrets, status_light)
|
||||
status_pixel = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.2)
|
||||
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
|
||||
# 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
|
||||
# (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
|
||||
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
|
||||
If you can find something that spits out JSON data, we can display it!
|
||||
"""
|
||||
|
||||
from os import getenv
|
||||
import time
|
||||
import board
|
||||
from adafruit_pyportal import PyPortal
|
||||
|
||||
# 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
|
||||
# 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
|
||||
DATA_SOURCE = "http://ws.audioscrobbler.com/2.0/?method=user.getrecenttracks&limit=1&format=json"
|
||||
CAPTION = "www.last.fm/user"
|
||||
# If we have an access token, we can query more often
|
||||
if 'lfm_username' in secrets:
|
||||
DATA_SOURCE += "&user="+secrets['lfm_username']
|
||||
CAPTION += "/"+secrets['lfm_username']
|
||||
if 'lfm_key' in secrets:
|
||||
DATA_SOURCE += "&api_key="+secrets['lfm_key']
|
||||
lfm_username = getenv("lfm_username")
|
||||
lfm_key = getenv("lfm_key")
|
||||
if lfm_username:
|
||||
DATA_SOURCE += "&user=" + lfm_username
|
||||
CAPTION += "/" + lfm_username
|
||||
if lfm_key:
|
||||
DATA_SOURCE += "&api_key=" + lfm_key
|
||||
print(DATA_SOURCE)
|
||||
|
||||
# Total number of plays
|
||||
|
|
|
|||
|
|
@ -5,20 +5,33 @@
|
|||
"""
|
||||
This project will access the League of Legends API, grab a Summoner's Level
|
||||
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!
|
||||
"""
|
||||
import os
|
||||
|
||||
from os import getenv
|
||||
import time
|
||||
import board
|
||||
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
|
||||
SUMMONER_NAME = "RiotSchmick"
|
||||
|
||||
# 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 += "?api_key=" + os.getenv("LEAGUE_TOKEN")
|
||||
DATA_SOURCE += "?api_key=" + getenv("LEAGUE_TOKEN")
|
||||
DATA_LOCATION = ["summonerLevel"]
|
||||
CAPTION = "SUMMONER "+SUMMONER_NAME
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
from os import getenv
|
||||
import board
|
||||
import displayio
|
||||
import busio
|
||||
|
|
@ -18,14 +19,19 @@ from adafruit_button import Button
|
|||
import adafruit_touchscreen
|
||||
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
|
||||
try:
|
||||
from secrets import secrets
|
||||
except ImportError:
|
||||
print("WiFi secrets are kept in secrets.py, please add them there!")
|
||||
raise
|
||||
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."
|
||||
)
|
||||
|
||||
# ------------- WiFi ------------- #
|
||||
|
||||
# If you are using a board with pre-defined ESP32 Pins:
|
||||
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)
|
||||
esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset)
|
||||
status_light = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.2)
|
||||
wifi = adafruit_esp32spi_wifimanager.ESPSPI_WiFiManager(esp, secrets, status_light)
|
||||
status_pixel = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.2)
|
||||
wifi = adafruit_esp32spi_wifimanager.WiFiManager(esp, ssid, password, status_pixel=status_pixel)
|
||||
|
||||
# ------- Sensor Setup ------- #
|
||||
# init. the temperature sensor
|
||||
|
|
@ -234,10 +240,10 @@ ssl_context = adafruit_connection_manager.get_radio_ssl_context(esp)
|
|||
|
||||
# Set up a MiniMQTT Client
|
||||
client = MQTT.MQTT(
|
||||
broker=secrets["broker"],
|
||||
broker=getenv("mqtt_broker"),
|
||||
port=1883,
|
||||
username=secrets["user"],
|
||||
password=secrets["pass"],
|
||||
username=getenv("mqtt_username"),
|
||||
password=getenv("mqtt_password"),
|
||||
socket_pool=pool,
|
||||
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
|
||||
|
||||
from os import getenv
|
||||
import sys
|
||||
import time
|
||||
import board
|
||||
|
|
@ -11,12 +12,17 @@ cwd = ("/"+__file__).rsplit('/', 1)[0] # the current working directory (where th
|
|||
sys.path.append(cwd)
|
||||
import openweather_graphics # pylint: disable=wrong-import-position
|
||||
|
||||
# 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
|
||||
# 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."
|
||||
)
|
||||
|
||||
# Use cityname, country code where countrycode is ISO3166 format.
|
||||
# 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
|
||||
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'
|
||||
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!
|
||||
if you can find something that spits out JSON data, we can display it
|
||||
"""
|
||||
|
||||
from os import getenv
|
||||
import sys
|
||||
import time
|
||||
import board
|
||||
|
|
@ -15,12 +17,17 @@ cwd = ("/"+__file__).rsplit('/', 1)[0] # the current working directory (where th
|
|||
sys.path.append(cwd)
|
||||
import openweather_graphics # pylint: disable=wrong-import-position
|
||||
|
||||
# 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
|
||||
# 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."
|
||||
)
|
||||
|
||||
# Use cityname, country code where countrycode is ISO3166 format.
|
||||
# E.g. "New York, US" or "London, GB"
|
||||
|
|
@ -28,7 +35,7 @@ LOCATION = "Manhattan, US"
|
|||
|
||||
# Set up where we'll be fetching data from
|
||||
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'
|
||||
DATA_LOCATION = []
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,8 @@ PyPortal Philips Hue Lighting Controller
|
|||
|
||||
Brent Rubell for Adafruit Industries, 2019
|
||||
"""
|
||||
import os
|
||||
|
||||
from os import getenv
|
||||
|
||||
import board
|
||||
import displayio
|
||||
|
|
@ -23,9 +24,17 @@ from adafruit_esp32spi import adafruit_esp32spi_wifimanager
|
|||
# Import Philips Hue Bridge
|
||||
from adafruit_hue import Bridge
|
||||
|
||||
secrets = dict()
|
||||
secrets["ssid"] = os.getenv("CIRCUITPY_WIFI_SSID")
|
||||
secrets["password"] = os.getenv("CIRCUITPY_WIFI_PASSWORD")
|
||||
# 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."
|
||||
)
|
||||
|
||||
# ESP32 SPI
|
||||
esp32_cs = DigitalInOut(board.ESP_CS)
|
||||
|
|
@ -33,13 +42,13 @@ esp32_ready = DigitalInOut(board.ESP_BUSY)
|
|||
esp32_reset = DigitalInOut(board.ESP_RESET)
|
||||
spi = busio.SPI(board.SCK, board.MOSI, board.MISO)
|
||||
esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset)
|
||||
status_light = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.2)
|
||||
wifi = adafruit_esp32spi_wifimanager.ESPSPI_WiFiManager(esp, secrets, status_light)
|
||||
status_pixel = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.2)
|
||||
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:
|
||||
username = os.getenv("HUE_USERNAME")
|
||||
bridge_ip = os.getenv("BRIDGE_IP")
|
||||
username = getenv("HUE_USERNAME")
|
||||
bridge_ip = getenv("BRIDGE_IP")
|
||||
my_bridge = Bridge(wifi, bridge_ip, username)
|
||||
except:
|
||||
# Perform first-time bridge setup
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
from os import getenv
|
||||
import time
|
||||
import board
|
||||
import busio
|
||||
|
|
@ -13,12 +14,17 @@ from adafruit_pyportal import PyPortal
|
|||
from adafruit_bitmap_font import bitmap_font
|
||||
from adafruit_display_text import label
|
||||
|
||||
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
|
||||
# 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."
|
||||
)
|
||||
|
||||
# Label colors
|
||||
LABEL_DAY_COLOR = 0xFFFFFF
|
||||
|
|
@ -88,7 +94,7 @@ if esp.status == adafruit_esp32spi.WL_IDLE_STATUS:
|
|||
print("Connecting to AP...")
|
||||
while not esp.is_connected:
|
||||
try:
|
||||
esp.connect_AP(secrets['ssid'], secrets['password'])
|
||||
esp.connect_AP(ssid, password)
|
||||
except RuntimeError as e:
|
||||
print("could not connect to AP, retrying: ", e)
|
||||
continue
|
||||
|
|
@ -117,7 +123,7 @@ while True:
|
|||
if (not refresh_time) or (time.monotonic() - refresh_time) > 3600:
|
||||
try:
|
||||
print("Getting new time from internet...")
|
||||
pyportal.get_local_time(secrets['timezone'])
|
||||
pyportal.get_local_time(getenv('timezone'))
|
||||
refresh_time = time.monotonic()
|
||||
# set the_time
|
||||
the_time = time.localtime()
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
# SPDX-FileCopyrightText: 2019 ladyada for Adafruit Industries
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
from os import getenv
|
||||
import time
|
||||
import math
|
||||
import board
|
||||
|
|
@ -13,6 +14,18 @@ import displayio
|
|||
import adafruit_touchscreen
|
||||
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
|
||||
ts = adafruit_touchscreen.Touchscreen(
|
||||
board.TOUCH_XL,
|
||||
|
|
@ -23,13 +36,6 @@ ts = adafruit_touchscreen.Touchscreen(
|
|||
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:
|
||||
esp32_cs = DigitalInOut(board.ESP_CS)
|
||||
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)
|
||||
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
|
||||
ip = "192.168.1.3"
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
from os import getenv
|
||||
import time
|
||||
import gc
|
||||
import board
|
||||
|
|
@ -20,6 +21,21 @@ import adafruit_touchscreen
|
|||
|
||||
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
|
||||
SWITCH_COLOR = 0x008800
|
||||
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.
|
||||
:param str location: Your city and country, e.g. ``"New York, US"``.
|
||||
"""
|
||||
# pylint: enable=line-too-long
|
||||
api_url = None
|
||||
try:
|
||||
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)
|
||||
location = getenv('timezone', location)
|
||||
if location:
|
||||
print("Getting time for timezone", 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
|
||||
print(seconds + tzseconds, tzoffset, tzhours, tzminutes)
|
||||
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
|
||||
response.close()
|
||||
|
|
@ -157,7 +166,7 @@ class Clock(object):
|
|||
# Update the time
|
||||
print("update the time")
|
||||
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)
|
||||
else:
|
||||
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):
|
||||
# This function will be called when the client is connected
|
||||
# successfully to the broker.
|
||||
onoff_feed = secrets['aio_username'] + '/feeds/' + FEED_NAME
|
||||
print('Connected to Adafruit IO! Listening for topic changes on %s' % onoff_feed)
|
||||
onoff_feed = f"{aio_username}/feeds/{FEED_NAME}"
|
||||
print(f"Connected to Adafruit IO! Listening for topic changes on {onoff_feed}")
|
||||
# Subscribe to all changes on the 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_ready = digitalio.DigitalInOut(board.ESP_BUSY)
|
||||
esp32_reset = digitalio.DigitalInOut(board.ESP_RESET)
|
||||
|
|
@ -257,7 +259,7 @@ for ap in esp.scan_networks():
|
|||
print("Connecting to AP...")
|
||||
while not esp.is_connected:
|
||||
try:
|
||||
esp.connect_AP(secrets['ssid'], secrets['password'])
|
||||
esp.connect_AP(ssid, password)
|
||||
except RuntimeError as e:
|
||||
print("could not connect to AP, retrying: ",e)
|
||||
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))
|
||||
|
||||
|
||||
wifi = adafruit_esp32spi_wifimanager.ESPSPI_WiFiManager(
|
||||
esp, secrets, debug = True)
|
||||
wifi = adafruit_esp32spi_wifimanager.WiFiManager(
|
||||
esp, ssid, password, debug=True
|
||||
)
|
||||
|
||||
|
||||
# Set up a MiniMQTT Client
|
||||
mqtt_client = MQTT(broker='io.adafruit.com',
|
||||
username=secrets['aio_username'],
|
||||
password=secrets['aio_key'],
|
||||
username=aio_username,
|
||||
password=aio_key,
|
||||
network_manager=wifi,
|
||||
socket_pool=pool,
|
||||
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
|
||||
"""
|
||||
|
||||
from os import getenv
|
||||
import time
|
||||
import board
|
||||
import neopixel
|
||||
|
|
@ -27,12 +29,20 @@ import thermometer_helper
|
|||
# rate at which to refresh the pyportal screen, in seconds
|
||||
PYPORTAL_REFRESH = 2
|
||||
|
||||
# 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
|
||||
# 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."
|
||||
)
|
||||
|
||||
# PyPortal ESP32 Setup
|
||||
esp32_cs = DigitalInOut(board.ESP_CS)
|
||||
|
|
@ -40,21 +50,11 @@ esp32_ready = DigitalInOut(board.ESP_BUSY)
|
|||
esp32_reset = DigitalInOut(board.ESP_RESET)
|
||||
spi = busio.SPI(board.SCK, board.MOSI, board.MISO)
|
||||
esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset)
|
||||
status_light = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.2)
|
||||
wifi = adafruit_esp32spi_wifimanager.ESPSPI_WiFiManager(esp, secrets, status_light)
|
||||
|
||||
# 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.')
|
||||
status_pixel = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.2)
|
||||
wifi = adafruit_esp32spi_wifimanager.WiFiManager(esp, ssid, password, status_pixel=status_pixel)
|
||||
|
||||
# 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
|
||||
temperature_feed = io.get_feed('temperature')
|
||||
|
|
|
|||
|
|
@ -36,12 +36,11 @@ ALWAYS_ON = True
|
|||
# How long to stay on if not in always_on mode
|
||||
ON_SECONDS = 60
|
||||
|
||||
# Get totp keys from a secrets.py file
|
||||
# Get totp_keys from a totp_keys.py file
|
||||
try:
|
||||
from secrets import secrets
|
||||
from totp_keys import totp_keys
|
||||
except ImportError:
|
||||
print("TOTP keys are kept in secrets.py, please add them there!")
|
||||
raise
|
||||
print("TOTP info not found in totp_keys.py, please add them there!")
|
||||
|
||||
# Initialize PyPortal Display
|
||||
display = board.DISPLAY
|
||||
|
|
@ -228,13 +227,13 @@ mono_time = int(time.monotonic())
|
|||
print("Monotonic time", mono_time)
|
||||
|
||||
# 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
|
||||
buttons = []
|
||||
|
||||
btn_x = 5
|
||||
for i in secrets['totp_keys']:
|
||||
for i in totp_keys:
|
||||
button = Button(name=i[0], x=btn_x,
|
||||
y=175, width=60,
|
||||
height=60, label=i[0].strip(" "),
|
||||
|
|
@ -264,7 +263,7 @@ splash.append(progress_bar)
|
|||
countdown = ON_SECONDS
|
||||
|
||||
# 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
|
||||
|
||||
while ALWAYS_ON or (countdown > 0):
|
||||
|
|
@ -295,7 +294,7 @@ while ALWAYS_ON or (countdown > 0):
|
|||
for i, b in enumerate(buttons):
|
||||
if b.contains(p):
|
||||
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
|
||||
if b.name == name:
|
||||
current_button = name
|
||||
|
|
@ -305,7 +304,7 @@ while ALWAYS_ON or (countdown > 0):
|
|||
else:
|
||||
b.selected = False
|
||||
else:
|
||||
for name, secret in secrets['totp_keys']:
|
||||
for name, secret in totp_keys:
|
||||
if current_button == name:
|
||||
# Generate OTP
|
||||
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
|
||||
|
||||
from os import getenv
|
||||
import time
|
||||
import json
|
||||
import board
|
||||
|
|
@ -9,12 +10,17 @@ from adafruit_pyportal import PyPortal
|
|||
from adafruit_bitmap_font import bitmap_font
|
||||
from adafruit_display_text.label import Label
|
||||
|
||||
# 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
|
||||
# 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."
|
||||
)
|
||||
|
||||
#--| USER CONFIG |--------------------------
|
||||
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
|
||||
cwd = ("/"+__file__).rsplit('/', 1)[0]
|
||||
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,
|
||||
status_neopixel=board.NEOPIXEL,
|
||||
default_bg=cwd+"/images/tides_bg.bmp")
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
from os import getenv
|
||||
import time
|
||||
import json
|
||||
import board
|
||||
|
|
@ -10,12 +11,17 @@ from adafruit_pyportal import PyPortal
|
|||
from adafruit_bitmap_font import bitmap_font
|
||||
from adafruit_display_text.label import Label
|
||||
|
||||
# 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
|
||||
# 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."
|
||||
)
|
||||
|
||||
#--| USER CONFIG |--------------------------
|
||||
STATION_ID = "0245" # tide location, find yours from admiralty website
|
||||
|
|
@ -44,7 +50,7 @@ else:
|
|||
bg_image_path = "/images/tides_bg_graph.bmp"
|
||||
|
||||
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,
|
||||
status_neopixel=board.NEOPIXEL,
|
||||
default_bg=cwd+bg_image_path)
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
from os import getenv
|
||||
import time
|
||||
from calendar import alarms
|
||||
from calendar import timers
|
||||
|
|
@ -12,20 +13,25 @@ from adafruit_button import Button
|
|||
from adafruit_pyportal import PyPortal
|
||||
import openweather_graphics # pylint: disable=wrong-import-position
|
||||
|
||||
# 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
|
||||
# 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."
|
||||
)
|
||||
|
||||
# Use cityname, country code where countrycode is ISO3166 format.
|
||||
# E.g. "New York, US" or "London, GB"
|
||||
LOCATION = secrets['location']
|
||||
LOCATION = getenv('location')
|
||||
|
||||
# Set up where we'll be fetching data from
|
||||
DATA_SOURCE = "http://api.openweathermap.org/data/2.5/weather?q="+LOCATION
|
||||
DATA_SOURCE += "&appid="+secrets['openweather_token']
|
||||
DATA_SOURCE = "http://api.openweathermap.org/data/2.5/weather?q=" + LOCATION
|
||||
DATA_SOURCE += "&appid=" + getenv('openweather_token')
|
||||
# You'll need to get a token from openweather.org, looks like 'b6907d289e10d714a6e88b30761fae22'
|
||||
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]
|
||||
|
||||
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)
|
||||
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ Licensed under the MIT license.
|
|||
All text above must be included in any redistribution.
|
||||
"""
|
||||
|
||||
from os import getenv
|
||||
import time
|
||||
import json
|
||||
import board
|
||||
|
|
@ -25,12 +26,17 @@ from adafruit_display_shapes.rect import Rect
|
|||
from adafruit_display_text.Label import Label
|
||||
from adafruit_bitmap_font import bitmap_font
|
||||
|
||||
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
|
||||
# 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."
|
||||
)
|
||||
|
||||
MAX_BAR_HEIGHT = 160
|
||||
MARGIN = 10
|
||||
|
|
@ -48,7 +54,7 @@ CAPTION_FONT_FILE = cwd+'/fonts/Helvetica-Bold-16.bdf'
|
|||
BAR_FONT_FILE = cwd+'/fonts/Arial-Bold-12.bdf'
|
||||
|
||||
#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
|
||||
|
||||
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
|
||||
|
||||
# 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
|
||||
|
||||
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
|
||||
|
||||
import os
|
||||
from os import getenv
|
||||
import time
|
||||
|
||||
import board
|
||||
|
|
@ -21,6 +21,21 @@ from adafruit_pyportal import PyPortal
|
|||
from adafruit_seesaw.seesaw import Seesaw
|
||||
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 |---------------
|
||||
|
||||
# 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)
|
||||
cwd = ("/"+__file__).rsplit('/', 1)[0]
|
||||
|
||||
secrets = {
|
||||
"ssid" : os.getenv("CIRCUITPY_WIFI_SSID"),
|
||||
"password" : os.getenv("CIRCUITPY_WIFI_PASSWORD"),
|
||||
}
|
||||
|
||||
# Set up i2c bus
|
||||
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)
|
||||
esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset)
|
||||
status_light = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.2)
|
||||
wifi = adafruit_esp32spi_wifimanager.ESPSPI_WiFiManager(esp, secrets, status_light)
|
||||
status_pixel = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.2)
|
||||
wifi = adafruit_esp32spi_wifimanager.WiFiManager(esp, ssid, password, status_pixel=status_pixel)
|
||||
|
||||
# Initialize PyPortal Display
|
||||
display = board.DISPLAY
|
||||
|
|
@ -190,8 +200,8 @@ ssl_context = adafruit_connection_manager.get_radio_ssl_context(esp)
|
|||
|
||||
# Initialize a new MQTT Client object
|
||||
mqtt_client = MQTT.MQTT(broker="io.adafruit.com",
|
||||
username=os.getenv("AIO_USERNAME"),
|
||||
password=os.getenv("AIO_KEY"),
|
||||
username=aio_username,
|
||||
password=aio_key,
|
||||
socket_pool=pool,
|
||||
ssl_context=ssl_context)
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ Adafruit IO
|
|||
|
||||
Author: Brent Rubell for Adafruit Industries, 2019
|
||||
"""
|
||||
|
||||
from os import getenv
|
||||
import time
|
||||
import board
|
||||
import neopixel
|
||||
|
|
@ -38,12 +40,20 @@ anemometer_max_volts = 2.0
|
|||
min_wind_speed = 0.0
|
||||
max_wind_speed = 32.4
|
||||
|
||||
# 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
|
||||
# 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."
|
||||
)
|
||||
|
||||
# PyPortal ESP32 Setup
|
||||
esp32_cs = DigitalInOut(board.ESP_CS)
|
||||
|
|
@ -51,14 +61,8 @@ esp32_ready = DigitalInOut(board.ESP_BUSY)
|
|||
esp32_reset = DigitalInOut(board.ESP_RESET)
|
||||
spi = busio.SPI(board.SCK, board.MOSI, board.MISO)
|
||||
esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset)
|
||||
status_light = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.2)
|
||||
wifi = adafruit_esp32spi_wifimanager.ESPSPI_WiFiManager(esp, secrets, status_light)
|
||||
|
||||
# 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']
|
||||
status_pixel = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.2)
|
||||
wifi = adafruit_esp32spi_wifimanager.WiFiManager(esp, ssid, password, status_pixel=status_pixel)
|
||||
|
||||
# Create an instance of the Adafruit IO HTTP client
|
||||
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
|
||||
OFF = (0, 0, 0)
|
||||
ON = (255, 255, 255)
|
||||
RED = (255,0,0)
|
||||
pixel_pin = board.A3
|
||||
num_pixels = 49
|
||||
pixels = neopixel.NeoPixel(pixel_pin, num_pixels, brightness=0.1, auto_write=False)
|
||||
|
|
@ -43,9 +44,11 @@ FULL_MOON = 4
|
|||
WANING_GIBBOUS = 5
|
||||
THIRD_QUARTER = 6
|
||||
WANING_CRESCENT = 7
|
||||
DARK_MOON = 8
|
||||
RED_MOON = 9
|
||||
# strings that match return from API
|
||||
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
|
||||
def set_new_moon():
|
||||
|
|
@ -96,6 +99,16 @@ def set_waning_crescent():
|
|||
pixels[i] = ON
|
||||
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
|
||||
phase_functions = {
|
||||
NEW_MOON: set_new_moon,
|
||||
|
|
@ -105,12 +118,14 @@ phase_functions = {
|
|||
FULL_MOON: set_full_moon,
|
||||
WANING_GIBBOUS: set_waning_gibbous,
|
||||
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
|
||||
def demo_all_phases(delay=1):
|
||||
for phase in range(8):
|
||||
for phase in range(9):
|
||||
print(f"Setting phase: {phase_names[phase]}")
|
||||
phase_functions[phase]()
|
||||
time.sleep(delay)
|
||||
|
|
@ -119,10 +134,15 @@ demo_all_phases()
|
|||
# takes response from API, matches to function, runs function
|
||||
def set_moon_phase(phase):
|
||||
phase_lower = phase.lower()
|
||||
error_check = 0
|
||||
for i, name in enumerate(phase_names):
|
||||
if phase_lower == name.lower():
|
||||
error_check = 1
|
||||
phase_functions[i]()
|
||||
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
|
||||
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": {
|
||||
"referenceVoltage": 3.3,
|
||||
"totalGPIOPins": 18,
|
||||
"totalAnalogPins": 4,
|
||||
"sd_cs_pin": 23,
|
||||
"statusLEDBrightness": 0.5
|
||||
"sd_cs_pin": 23,
|
||||
"referenceVoltage": 0,
|
||||
"totalGPIOPins": 0,
|
||||
"totalAnalogPins": 0,
|
||||
"statusLEDBrightness": 0.3
|
||||
},
|
||||
"components": [
|
||||
{
|
||||
"name": "BME280 Sensor",
|
||||
"componentAPI": "i2c",
|
||||
"i2cDeviceName": "bme280",
|
||||
"period": 15,
|
||||
"i2cDeviceAddress": "0x77",
|
||||
"i2cDeviceSensorTypes": [
|
||||
{"type": "relative-humidity"},
|
||||
{"type": "ambient-temp"},
|
||||
{"type": "ambient-temp-fahrenheit"},
|
||||
{"type": "pressure"},
|
||||
{"type": "altitude"}
|
||||
]
|
||||
}
|
||||
{
|
||||
"name": "bme280",
|
||||
"componentAPI": "i2c",
|
||||
"i2cDeviceName": "bme280",
|
||||
"period": 30,
|
||||
"autoConfig": "true",
|
||||
"i2cDeviceAddress": "0x77",
|
||||
"i2cDeviceSensorTypes": [
|
||||
{
|
||||
"type": "ambient-temp"
|
||||
},
|
||||
{
|
||||
"type": "ambient-temp-fahrenheit"
|
||||
},
|
||||
{
|
||||
"type": "relative-humidity"
|
||||
},
|
||||
{
|
||||
"type": "pressure"
|
||||
},
|
||||
{
|
||||
"type": "altitude"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||