├── LICENSE ├── README.md ├── bluetooth ├── README.md ├── ble_uart_forwarding.py └── ble_uart_receiving.py ├── displayio_labels ├── README.md ├── display_temperature.py └── display_temperature_color.py ├── ds18b20 ├── README.md ├── ds18_async.py ├── ds18_async_simplified.py ├── ds18_multiple.py └── ds18_multiple_async.py ├── exceptions ├── scroll_exception_displayio.py ├── scroll_exception_macropad.py └── scroll_exception_repl.py ├── hid └── simple_keypad.py ├── led_animations ├── chase_or_comet.py └── chase_or_comet_cp.py ├── ntp_simple_demo ├── README.md ├── aio_time_demo_native.py ├── ntp_demo_native.py └── ntp_demo_nina.py ├── requests ├── README.md └── native_request.py ├── seesaws ├── bulk_joy_featherwing.py ├── bulk_neokey1x4.py └── bulk_neokey1x4_nointerrupt.py ├── serial_to_display ├── code_serlcd.py ├── send_stuff.py ├── serial_to_displayio.py └── serial_to_displayio_clue.py ├── status_led └── status_led.py ├── timings ├── README.md ├── loop_with_timer_states.py ├── loop_with_timers.py ├── monotonic_loop.py ├── timer_class.py ├── timer_example.py └── timers_with_yield.py └── usb_serial ├── README.md ├── boot.py ├── find_serial_port.py ├── serial_exchange-host.py ├── serial_exchange_data-code.py ├── serial_exchange_repl-code.py ├── serial_read-host.py ├── serial_read_data-code.py ├── serial_read_repl-code.py ├── serial_send-host.py ├── serial_send_data-code.py └── serial_send_repl-code.py /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Other sources: 2 | - Todbot's bag of tips and tricks: https://github.com/todbot/circuitpython-tricks 3 | - Anecdata's TCP/UDP examples: https://github.com/anecdata/Socket 4 | -------------------------------------------------------------------------------- /bluetooth/README.md: -------------------------------------------------------------------------------- 1 | ## BLE UART Forwarding 2 | 3 | These two files let you connect a UART device over bluetooth to a computer. The code is not bidirectional, but that can be added with few changes. 4 | 5 | - `ble_uart_forwarding.py` is run on a bluetooth capable board that is connected to a UART peripheral/device and forwards all the data from it to the UART bluetooth service. 6 | - `ble_uart_receiving.py` is run on a bluetooth capable board or computer to receive the data over bluetooth UART, using Circuitpython or Blinka. 7 | -------------------------------------------------------------------------------- /bluetooth/ble_uart_forwarding.py: -------------------------------------------------------------------------------- 1 | import board 2 | import time 3 | import busio 4 | 5 | # common configuration 6 | SERVICE_NAME = "My UART" # 8 chars max 7 | 8 | # setup UART 9 | uart = busio.UART(board.TX, board.RX) 10 | 11 | # setup bluetooth 12 | from adafruit_bluefruit_connect.packet import Packet 13 | from adafruit_bluefruit_connect.color_packet import ColorPacket 14 | from adafruit_ble import BLERadio 15 | from adafruit_ble.advertising.standard import ProvideServicesAdvertisement 16 | from adafruit_ble.services.nordic import UARTService 17 | 18 | ble = BLERadio() 19 | ble.name = "UART With BLE" 20 | uart_service = UARTService() 21 | advertisement = ProvideServicesAdvertisement(uart_service) 22 | advertisement.short_name = SERVICE_NAME 23 | # advertisement.complete_name = "UART BLE" # no more than 8 to not go into extended adv ? 24 | 25 | was_connected = False 26 | while True: 27 | # Advertise BLE when not connected. 28 | if not ble.connected: 29 | was_connected = False 30 | 31 | if not ble.advertising: 32 | print(f'Start advertising as "{SERVICE_NAME}"') 33 | ble.start_advertising(advertisement, interval=0.5, timeout=5) 34 | 35 | # dismiss uart buffer when not connected 36 | if uart.in_waiting: 37 | uart.reset_input_buffer() 38 | 39 | else: 40 | if not was_connected: 41 | was_connected = True 42 | print("Connected") 43 | ble.stop_advertising() 44 | 45 | # pass-through uart data when connected 46 | if nbytes := uart.in_waiting: 47 | # data = uart.read(nbytes) 48 | data = uart.readline() 49 | if data: 50 | print("Broadcasting", data) 51 | uart_service.write(data) 52 | 53 | time.sleep(0.1) 54 | -------------------------------------------------------------------------------- /bluetooth/ble_uart_receiving.py: -------------------------------------------------------------------------------- 1 | import time 2 | from adafruit_ble import BLERadio 3 | from adafruit_ble.advertising.standard import ProvideServicesAdvertisement 4 | from adafruit_ble.services.nordic import UARTService 5 | 6 | SERVICE_NAME = "My UART" 7 | 8 | ble = BLERadio() 9 | uart_connection = None 10 | 11 | while True: 12 | if not uart_connection or not uart_connection.connected: 13 | print(f'Trying to connect to "{SERVICE_NAME}"...') 14 | for adv in ble.start_scan(ProvideServicesAdvertisement): 15 | if UARTService in adv.services and adv.short_name == SERVICE_NAME: 16 | uart_connection = ble.connect(adv) 17 | print("Connected") 18 | break 19 | ble.stop_scan() 20 | 21 | if uart_connection and uart_connection.connected: 22 | uart_service = uart_connection[UARTService] 23 | if uart_service.in_waiting: 24 | data = uart_service.readline().decode("utf-8").strip() 25 | if data: 26 | print(data) 27 | 28 | time.sleep(0.1) 29 | -------------------------------------------------------------------------------- /displayio_labels/README.md: -------------------------------------------------------------------------------- 1 | ## displayio text labels examples 2 | 3 | Simple placement of text labels on the built-in `board.DISPLAY` for boards having one, and showing off updating the content of the label. 4 | 5 | Add your external screen's init code, and your own data source(s). 6 | 7 | Don't forget: setup the labels ONCE, change the text in the loop. Don't create the labels in the loop. 8 | -------------------------------------------------------------------------------- /displayio_labels/display_temperature.py: -------------------------------------------------------------------------------- 1 | import board 2 | import displayio 3 | import microcontroller 4 | import terminalio 5 | import time 6 | from adafruit_display_text.bitmap_label import Label 7 | 8 | display = board.DISPLAY 9 | splash = displayio.Group() 10 | 11 | title_label = Label( 12 | text="My Title", 13 | font=terminalio.FONT, 14 | scale=3, 15 | color=0xFFFFFF, 16 | anchored_position=(display.width // 2, 0), 17 | anchor_point=(0.5, 0), 18 | ) 19 | temp_label = Label( 20 | text="Temperature:", 21 | font=terminalio.FONT, 22 | scale=2, 23 | color=0xFFFFFF, 24 | anchored_position=(0, 70), 25 | anchor_point=(0, 0.5), 26 | ) 27 | temp_value = Label( 28 | text="0 C", 29 | font=terminalio.FONT, 30 | scale=2, 31 | color=0xFFFFFF, 32 | anchored_position=(display.width, 70), 33 | anchor_point=(1, 0.5), 34 | ) 35 | 36 | splash.append(title_label) 37 | splash.append(temp_label) 38 | splash.append(temp_value) 39 | display.show(splash) 40 | 41 | while True: 42 | cpu_temp = microcontroller.cpu.temperature 43 | temp_value.text = f"{cpu_temp:.1f} C" 44 | time.sleep(0.5) 45 | -------------------------------------------------------------------------------- /displayio_labels/display_temperature_color.py: -------------------------------------------------------------------------------- 1 | import board 2 | import displayio 3 | import microcontroller 4 | import terminalio 5 | import time 6 | 7 | from adafruit_display_text.bitmap_label import Label 8 | import adafruit_fancyled.adafruit_fancyled as fancy 9 | 10 | blue = fancy.CRGB(0, 0, 1.0) 11 | red = fancy.CRGB(1.0, 0, 0) 12 | yellow = fancy.CRGB(1.0, 1.0, 0) 13 | white = fancy.CRGB(1.0, 1.0, 1.0) 14 | 15 | oled = board.DISPLAY 16 | splash = displayio.Group() 17 | 18 | title_label = Label( 19 | text="My Title", font=terminalio.FONT, color=0x00FF80, scale=3, 20 | anchored_position=(oled.width // 2, 0), anchor_point=(0.5, 0), 21 | ) 22 | temp_label = Label( 23 | text="Temperature:", font=terminalio.FONT, color=0xFFFFFF, scale=2, 24 | anchored_position=(0, 70), anchor_point=(0, 0.5), 25 | ) 26 | temp_value = Label( 27 | text="0 C", font=terminalio.FONT, color=0xFFFFFF, scale=2, 28 | anchored_position=(oled.width, 70), anchor_point=(1, 0.5), 29 | ) 30 | 31 | splash.append(title_label) 32 | splash.append(temp_label) 33 | splash.append(temp_value) 34 | oled.show(splash) 35 | 36 | def display_temperature(the_temp): 37 | if the_temp > 30: 38 | color = red.pack() 39 | elif the_temp > 20: 40 | color = fancy.mix(red, yellow, (30 - the_temp) / 10).pack() 41 | elif the_temp < 0: 42 | color = blue.pack() 43 | elif the_temp < 10: 44 | color = fancy.mix(blue, white, (the_temp) / 10).pack() 45 | elif the_temp < 20: 46 | color = fancy.mix(white, yellow, (the_temp - 10) / 10).pack() 47 | else: 48 | color = yellow.pack() 49 | temp_value.color = color 50 | temp_value.text = f"{the_temp:.1f} C" 51 | 52 | # test code showing the thing 53 | for cpu_temp in range(-5, 32*2): 54 | cpu_temp = cpu_temp / 2 55 | display_temperature(cpu_temp) 56 | time.sleep(0.1) 57 | 58 | while True: 59 | cpu_temp = microcontroller.cpu.temperature 60 | display_temperature(cpu_temp) 61 | time.sleep(0.5) 62 | -------------------------------------------------------------------------------- /ds18b20/README.md: -------------------------------------------------------------------------------- 1 | A few sample scripts to read one or more DS18B20 temperature sensors connected to the same bus, asynchronously. 2 | 3 | The sensor is used by: 4 | 5 | - A requesting to start a new measure 6 | - B waiting until the conversion is finished (either by asking repeatedly or waiting the time given) 7 | - C asking for and receiving the temperature from the sensor memory 8 | 9 | DS18X20.temperature does all that synchronously (it blocks the code until the conversion has finished). 10 | 11 | The async code here: 12 | 13 | - Checks if the reading delay has passed (line 27) (which handles B) 14 | - Reads the temperature from the sensor (line 31) if it was previously requested (line 29) (part C) 15 | - Sets the time for the next request (line 33) (which can be 0). Which handles how often you want to update. 16 | - The second block requests the next measure (line 43) (part A) 17 | - And sets the time for when the next read is possible (line 45-46) (this is the duration of part B). 18 | 19 | It might seem weird to do A after C, but that's because I want to request the next measure immediately after the read if the delay is 0 (and not wait a full run of the while loop). The variable "ds18_do_read" could be called "ds18_next_measure_has_been_requested", and its initial value (False) ensures that we don't try to read the sensor before requesting the first measure. 20 | 21 | Note that the time needed for the conversion depends of the resolution of the temperature. You can change the resolution with "ds18.resolution = 9" for example, if that resolution is good enough for you. From testing it seems that 9 bits gives a 0.5 °C accuracy, 10 is 0.25 and so on. 22 | 23 | Here is the conversion time per resolution: 24 | 25 | - 9-bit: 93.75 ms 26 | - 10-bit: 187.5 ms 27 | - 11-bit: 375 ms 28 | - 12-bit: 750 ms (default) 29 | -------------------------------------------------------------------------------- /ds18b20/ds18_async.py: -------------------------------------------------------------------------------- 1 | import time 2 | import board 3 | from adafruit_onewire.bus import OneWireBus 4 | from adafruit_ds18x20 import DS18X20 5 | 6 | # Initialize one-wire bus on board D5. 7 | ow_bus = OneWireBus(board.GP2) 8 | # Scan for sensors and grab them all 9 | ds18 = DS18X20(ow_bus, ow_bus.scan()[0]) 10 | 11 | # read (request) the temperature every 5 seconds 12 | # set to 0 to read as fast as possible 13 | TEMPERATURE_DELAY = 5 14 | 15 | # time when the next read should occur. 16 | ds18_next = time.monotonic() 17 | # should we start the async read or do the actual read ? 18 | ds18_do_read = False 19 | # delay for async read (default, real value will be set later) 20 | ds18_async_delay = 1 21 | 22 | # Main loop to print the temperature every 5 second. 23 | while True: 24 | now = time.monotonic() 25 | 26 | # Is it time to read ? 27 | if now > ds18_next: 28 | # if we did request the temperature, now do the read 29 | if ds18_do_read: 30 | # fetch the temperatures from the sensor 31 | temperature = ds18.read_temperature() 32 | # next request is TEMPERATURE_DELAY after the previous request 33 | ds18_next = ds18_next - ds18_async_delay + TEMPERATURE_DELAY 34 | # next action is requesting 35 | ds18_do_read = False 36 | # do something with the temperature 37 | print(temperature) 38 | 39 | # if we have not requested the next one yet, do it 40 | # (we test ds18_next again, because reading might have changed it) 41 | if not ds18_do_read and now > ds18_next: 42 | # request the async read, and receive the delay declared by the sensor 43 | max_read_delay = ds18.start_temperature_read() 44 | # reading the sensor will happen after the async delay 45 | ds18_async_delay = max_read_delay * 1.01 # 1% margin to be sure 46 | ds18_next = now + ds18_async_delay 47 | # next action is reading 48 | ds18_do_read = True 49 | -------------------------------------------------------------------------------- /ds18b20/ds18_async_simplified.py: -------------------------------------------------------------------------------- 1 | import time 2 | import board 3 | from adafruit_onewire.bus import OneWireBus 4 | from adafruit_ds18x20 import DS18X20 5 | 6 | # Initialize one-wire bus on board D5. 7 | ow_bus = OneWireBus(board.GP2) 8 | # Scan for sensors and grab them all 9 | ds18 = DS18X20(ow_bus, ow_bus.scan()[0]) 10 | 11 | # initiate the first measure 12 | max_read_delay = ds18.start_temperature_read() 13 | # reading the sensor will happen after the async delay 14 | ds18_next = time.monotonic() + max_read_delay 15 | 16 | # Main loop to print the temperature every 5 second. 17 | while True: 18 | now = time.monotonic() 19 | 20 | # Is it time to read ? 21 | if now > ds18_next: 22 | # fetch the temperatures from the sensor 23 | temperature = ds18.read_temperature() 24 | # request the async read, and receive the delay declared by the sensor 25 | max_read_delay = ds18.start_temperature_read() 26 | # reading the sensor will happen after the async delay 27 | ds18_next = now + max_read_delay 28 | # do something with the temperature 29 | print(f"{temperature:.2f}°C / {temperature * 9/5 + 30:.2f}°F") 30 | -------------------------------------------------------------------------------- /ds18b20/ds18_multiple.py: -------------------------------------------------------------------------------- 1 | # import midi_testing_stuff 2 | 3 | # A 4.7Kohm pullup between DATA and POWER is REQUIRED! 4 | import time 5 | import board 6 | from adafruit_onewire.bus import OneWireBus 7 | from adafruit_ds18x20 import DS18X20 8 | 9 | # Initialize one-wire bus on board D5. 10 | ow_bus = OneWireBus(board.GP2) 11 | # Scan for sensors and grab them all 12 | devices = ow_bus.scan() 13 | print(devices) 14 | ds18 = [DS18X20(ow_bus, found) for found in devices] 15 | 16 | # Main loop to print the temperature every second. 17 | while True: 18 | temperature = sum(ds.temperature for ds in ds18) / len(ds18) 19 | print("Temperature: {0:0.3f}C".format(temperature)) 20 | print(tuple(ds.temperature for ds in ds18)) 21 | time.sleep(.1) 22 | -------------------------------------------------------------------------------- /ds18b20/ds18_multiple_async.py: -------------------------------------------------------------------------------- 1 | import time 2 | import board 3 | from adafruit_onewire.bus import OneWireBus 4 | from adafruit_ds18x20 import DS18X20 5 | 6 | # Initialize one-wire bus on board D5. 7 | ow_bus = OneWireBus(board.GP2) 8 | # Scan for sensors and grab them all 9 | ds18 = [DS18X20(ow_bus, found) for found in ow_bus.scan()] 10 | # 10-bit resolution (default 12) 11 | ds18.resolution = 10 12 | 13 | # read (request) the temperature every 5 seconds 14 | TEMPERATURE_DELAY = 5 15 | 16 | # time when the next read should occur. 17 | ds18_next = time.monotonic() 18 | # should we start the async read or do the actual read ? 19 | ds18_do_read = False 20 | # delay for async read (default, real value will be set later) 21 | ds18_async_delay = 1 22 | 23 | # Main loop to print the temperature every 5 second. 24 | while True: 25 | now = time.monotonic() 26 | 27 | # Is it time to read ? 28 | if now > ds18_next: 29 | # if we did request the temperature, now do the read 30 | if ds18_do_read: 31 | # fetch the temperatures from the sensor 32 | temperatures = [ds.read_temperature() for ds in ds18] 33 | # next request is TEMPERATURE_DELAY after the previous request 34 | ds18_next = ds18_next - ds18_async_delay + TEMPERATURE_DELAY 35 | # next action is requesting 36 | ds18_do_read = False 37 | # do something with the temperature 38 | average_temp = sum(temperatures) / len(ds18) 39 | print(average_temp) 40 | 41 | # if we have not requested the next one yet, do it 42 | # (we test ds18_next again, because reading might have changed it) 43 | if not ds18_do_read and now > ds18_next: 44 | # request the async read, and receive the delay declared by the sensors 45 | max_read_delay = max(ds.start_temperature_read() for ds in ds18) 46 | # reading the sensors will happen after the async delay 47 | ds18_async_delay = max_read_delay * 1.01 # 1% margin to be sure 48 | ds18_next = now + ds18_async_delay 49 | # next action is reading 50 | ds18_do_read = True 51 | -------------------------------------------------------------------------------- /exceptions/scroll_exception_displayio.py: -------------------------------------------------------------------------------- 1 | try: 2 | import yourcode 3 | except Exception as e: 4 | import board 5 | import displayio 6 | import terminalio 7 | from adafruit_display_text import label, wrap_text_to_pixels 8 | group = displayio.Group() 9 | display = board.DISPLAY 10 | display.show(group) 11 | group.append(textarea := label.Label( 12 | font=terminalio.FONT, 13 | text="Error", 14 | scale=1, 15 | x=0, 16 | y=7, 17 | )) 18 | NLINES = display.height // terminalio.FONT.get_bounding_box()[1] 19 | # Scroll the Error 20 | import time 21 | import traceback 22 | error0 = traceback.format_exception(e, e, e.__traceback__) 23 | error = error0.strip().replace("\r", "") 24 | # error = error.replace(" ", " ") 25 | lines = wrap_text_to_pixels( 26 | string=error, 27 | max_width=display.width, 28 | font=terminalio.FONT, 29 | ) 30 | dashes = "-" * (display.width // terminalio.FONT.get_bounding_box()[0]) 31 | lines = [dashes] + lines 32 | lines_num = len(lines) 33 | while len(lines) < lines_num + NLINES + 1: 34 | lines = lines + lines 35 | 36 | print(lines_num, NLINES) 37 | 38 | while True: 39 | print(error0) 40 | for x in range(max(1, lines_num)): 41 | board.DISPLAY.refresh() 42 | textarea.text = "\n".join(lines[x:x+NLINES]) 43 | time.sleep(1) 44 | -------------------------------------------------------------------------------- /exceptions/scroll_exception_macropad.py: -------------------------------------------------------------------------------- 1 | try: 2 | import yourcode 3 | except KeyboardInterrupt: 4 | pass 5 | except Exception as e: 6 | import traceback 7 | error = traceback.format_exception(e, e, e.__traceback__) 8 | 9 | import board 10 | import displayio 11 | import adafruit_displayio_sh1106 12 | import time 13 | 14 | displayio.release_displays() 15 | spi = board.SPI() 16 | display_bus = displayio.FourWire( 17 | spi, 18 | command=board.OLED_DC, 19 | chip_select=board.OLED_CS, 20 | reset=board.OLED_RESET, 21 | baudrate=1000000, 22 | ) 23 | WIDTH = 128 24 | HEIGHT = 64 25 | display = adafruit_displayio_sh1106.SH1106(display_bus, width=WIDTH, height=HEIGHT) 26 | 27 | error = error.strip().replace("\r\n", " | ") 28 | while True: 29 | print("\n----------") 30 | for word in error.split(" "): 31 | print(word, end=" ") 32 | time.sleep(0.5) 33 | time.sleep(1) 34 | -------------------------------------------------------------------------------- /exceptions/scroll_exception_repl.py: -------------------------------------------------------------------------------- 1 | """ 2 | This will scroll the latest exception on the screen. 3 | If you can't use supervisor.get_previous_traceback 4 | 5 | https://circuitpython.readthedocs.io/en/latest/shared-bindings/supervisor/index.html#supervisor.get_previous_traceback 6 | """ 7 | 8 | try: 9 | import yourcode 10 | except Exception as e: 11 | # Disable the screen 12 | import board 13 | board.DISPLAY.show(None) 14 | # Scroll the Error 15 | import time 16 | import traceback 17 | error = traceback.format_exception(e, e, e.__traceback__) 18 | error = error.strip().replace("\r\n", " | ") 19 | while True: 20 | print("\n----------") 21 | for word in error.split(" "): 22 | print(word, end=" ") 23 | time.sleep(0.5) 24 | time.sleep(1) 25 | -------------------------------------------------------------------------------- /hid/simple_keypad.py: -------------------------------------------------------------------------------- 1 | """ 2 | Minimal keypad example 3 | """ 4 | import board 5 | import keypad 6 | import usb_hid 7 | 8 | ############################################################ 9 | # Input pins, the keypad part 10 | 11 | KEY_PINS = ( 12 | board.GP0, 13 | board.GP1, 14 | # etc. choose your pins 15 | ) 16 | 17 | # Here, they are setup as pull-UP, switch Flase/True if pull down 18 | keys = keypad.Keys(KEY_PINS, value_when_pressed=False, pull=True) 19 | 20 | ############################################################ 21 | # Output keys, the keyboard part 22 | 23 | from adafruit_hid.keycode import Keycode 24 | from adafruit_hid.keyboard import Keyboard 25 | 26 | keyboard = Keyboard(usb_hid.devices) 27 | 28 | keys_to_press = ( 29 | [Keycode.ALT, Keycode.TAB], 30 | [Keycode.F14], 31 | # etc. each entry matches a button, and has to be a tuple or list 32 | ) 33 | 34 | ############################################################ 35 | # The loop 36 | 37 | while True: 38 | event = keys.events.get() 39 | if event: 40 | to_press = keys_to_press[event.key_number] 41 | print(event, to_press) 42 | 43 | if to_press: 44 | if event.pressed: 45 | # the * unpacks the tuple into a list of arguments 46 | keyboard.send(*to_press) 47 | # alternate choice: hold the key while button pressed (6 keys max) 48 | # keyboard.press(*to_press) 49 | else: 50 | pass 51 | # release the key(s) if it was being held 52 | # keyboard.release(*to_press) 53 | -------------------------------------------------------------------------------- /led_animations/chase_or_comet.py: -------------------------------------------------------------------------------- 1 | """ 2 | Example of how to switch between animations 3 | Requires adafruit_led_animation 4 | """ 5 | import board 6 | from digitalio import DigitalInOut, Pull 7 | import time 8 | 9 | from adafruit_led_animation.animation.chase import Chase 10 | from adafruit_led_animation.animation.comet import Comet 11 | from adafruit_led_animation.sequence import AnimationSequence 12 | from adafruit_led_animation.color import AMBER, JADE 13 | 14 | # Update to match the pin connected to your NeoPixels 15 | PIXELS_PIN = board.A3 16 | # Update to match the number of NeoPixels you have connected 17 | PIXELS_NUM = 30 18 | # Update to match the button's pin 19 | BUTTON_PIN = board.D6 20 | # Update to match the button value when pressed 21 | BUTTON_ON_VALUE = False 22 | 23 | button = DigitalInOut(BUTTON_PIN) 24 | button.switch_to_input(Pull.DOWN if BUTTON_ON_VALUE else Pull.UP) 25 | 26 | def button_pressed(): 27 | return button.value == BUTTON_ON_VALUE 28 | 29 | pixels = neopixel.NeoPixel(PIXELS_PIN, PIXELS_NUM, brightness=0.5, auto_write=False) 30 | 31 | chase = Chase(cp.pixels, speed=0.1, size=3, spacing=6, color=AMBER) 32 | comet = Comet(cp.pixels, speed=0.02, color=JADE, tail_length=10, bounce=True) 33 | 34 | animations = AnimationSequence(comet, chase) 35 | 36 | while True: 37 | animations.animate() 38 | # lookup buttons to switch between animations 39 | if button_pressed(): 40 | animations.next() 41 | # wait for button release (kind of debounce) 42 | while button_pressed(): 43 | time.sleep(0.01) 44 | # for slow animations, sleep a bit, but not too long to read buttons 45 | # time.sleep(min(0.1, anim.speed / 10)) 46 | -------------------------------------------------------------------------------- /led_animations/chase_or_comet_cp.py: -------------------------------------------------------------------------------- 1 | """ 2 | Switch between animations with the Circuit Playground Express/Bluefruit 3 | Requires adafruit_led_animation 4 | """ 5 | from adafruit_circuitplayground import cp 6 | import time 7 | 8 | from adafruit_led_animation.animation.chase import Chase 9 | from adafruit_led_animation.animation.comet import Comet 10 | from adafruit_led_animation.color import AMBER, JADE 11 | 12 | chase = Chase(cp.pixels, speed=0.06, size=3, spacing=6, color=AMBER) 13 | comet = Comet(cp.pixels, speed=0.02, color=JADE, tail_length=10, bounce=True) 14 | 15 | anim = comet 16 | 17 | while True: 18 | anim.animate() 19 | # lookup buttons to switch between animations 20 | buttons = cp.were_pressed 21 | if buttons: 22 | if 'A' in buttons: 23 | anim = chase 24 | if 'B' in buttons: 25 | anim = comet 26 | # the switch sets the brightness 27 | if cp.switch: 28 | cp.pixels.brightness = 0.2 29 | else: 30 | cp.pixels.brightness = 0.8 31 | # for slow animations, sleep a bit, but not too long to read buttons 32 | # time.sleep(min(0.1, anim.speed / 10)) 33 | -------------------------------------------------------------------------------- /ntp_simple_demo/README.md: -------------------------------------------------------------------------------- 1 | These scripts require native wifi (on ESP32-S2 boards) and the presence of the usual `secrets.py` file used by adafruit libraries, as [explained in this guide for example](https://learn.adafruit.com/pyportal-titano-weather-station/code-walkthrough-secrets-py). 2 | 3 | **NOTE: this sample code is considered obsolete.** 4 | 5 | - See the [adafruit_ntp library for native NTP support](https://github.com/adafruit/Adafruit_CircuitPython_NTP). 6 | - See this [Metro ESP32S2 guide for Adafruit IO](https://learn.adafruit.com/adafruit-metro-esp32-s2/getting-the-date-time). 7 | 8 | ------------------ 9 | 10 | ## Simple NTP native demo 11 | 12 | This is a simple script that shows download the current time from NTP servers, and displaying a simple clock on the default display if the board has one. Takes into account the value of the board's Epoch (can be 1970 or 2000). 13 | 14 | Your time zone has to be set by modifying `TZ_OFFSET` to the appropriate number of seconds to adjust from GMT. 15 | 16 | Add your own display initialization code if you have an external display or read the time in the REPL. 17 | 18 | Notes: 19 | - this will be rewritten to use the `adafruit_ntp` library. 20 | - ESP32SPI/airlift boards can use ESPget_time() ([see these notes](https://github.com/adafruit/Adafruit_CircuitPython_NTP/releases/tag/3.0.0)). (A new example will be added for that) 21 | 22 | ## Adafruit.io time service demo (native) 23 | 24 | This script uses adafruit_requests to get the time from the adafruit IO time service, using code borrowed from the PortalBase library. 25 | 26 | It requires the presence of valid credentials `aio_username` and `aio_key` in the secrets file. 27 | -------------------------------------------------------------------------------- /ntp_simple_demo/aio_time_demo_native.py: -------------------------------------------------------------------------------- 1 | """ 2 | This file uses code from: 3 | https://github.com/adafruit/Adafruit_CircuitPython_PortalBase.git 4 | 5 | That code is under MIT license. 6 | Copyright (c) 2020 Melissa LeBlanc-Williams for Adafruit Industries 7 | Modified by Neradoc (2021) 8 | 9 | To use it you need the usual secrets.py file, with adafruit IO credentials. 10 | These are used to access the time service while performing rate limiting. 11 | """ 12 | import gc 13 | import rtc 14 | import socketpool 15 | import ssl 16 | import time 17 | import wifi 18 | import adafruit_requests 19 | 20 | 21 | # you'll need to pass in an io username and key 22 | TIME_SERVICE = ( 23 | "https://io.adafruit.com/api/v2/%s/integrations/time/strftime?x-aio-key=%s" 24 | ) 25 | # our strftime is %Y-%m-%d %H:%M:%S.%L %j %u %z %Z see http://strftime.net/ for decoding details 26 | # See https://apidock.com/ruby/DateTime/strftime for full options 27 | TIME_SERVICE_FORMAT = "%Y-%m-%d %H:%M:%S.%L %j %u %z %Z" 28 | 29 | 30 | try: 31 | from secrets import secrets 32 | except ImportError: 33 | print("WiFi secrets are kept in secrets.py, please add them there!") 34 | raise 35 | 36 | print("Connecting to ", secrets["ssid"]) 37 | wifi.radio.connect(ssid=secrets["ssid"], password=secrets["password"]) 38 | socket_pool = socketpool.SocketPool(wifi.radio) 39 | print("Connected with IP ", wifi.radio.ipv4_address) 40 | requests = adafruit_requests.Session(socket_pool, ssl.create_default_context()) 41 | 42 | 43 | def get_strftime(time_format, location=None): 44 | """ 45 | Fetch a custom strftime relative to your location. 46 | 47 | :param str location: Your city and country, e.g. ``"America/New_York"``. 48 | 49 | """ 50 | # pylint: disable=line-too-long 51 | api_url = None 52 | reply = None 53 | try: 54 | aio_username = secrets["aio_username"] 55 | aio_key = secrets["aio_key"] 56 | except KeyError: 57 | raise KeyError( 58 | "\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 59 | ) from KeyError 60 | 61 | if location is None: 62 | location = secrets.get("timezone", location) 63 | if location: 64 | print("Getting time for timezone", location) 65 | api_url = (TIME_SERVICE + "&tz=%s") % (aio_username, aio_key, location) 66 | else: # we'll try to figure it out from the IP address 67 | print("Getting time from IP address") 68 | api_url = TIME_SERVICE % (aio_username, aio_key) 69 | api_url += "&fmt=" + url_encode(time_format) 70 | 71 | try: 72 | response = requests.get(api_url, timeout=10) 73 | if response.status_code != 200: 74 | print(response) 75 | error_message = ( 76 | "Error connecting to Adafruit IO. The response was: " + response.text 77 | ) 78 | raise RuntimeError(error_message) 79 | reply = response.text 80 | except KeyError: 81 | raise KeyError( 82 | "Was unable to lookup the time, try setting secrets['timezone'] according to http://worldtimeapi.org/timezones" # pylint: disable=line-too-long 83 | ) from KeyError 84 | # now clean up 85 | response.close() 86 | response = None 87 | gc.collect() 88 | 89 | return reply 90 | 91 | 92 | def url_encode(url): 93 | """ 94 | A function to perform minimal URL encoding 95 | """ 96 | url = url.replace(" ", "+") 97 | url = url.replace("%", "%25") 98 | url = url.replace(":", "%3A") 99 | return url 100 | 101 | 102 | def get_local_time(location=None): 103 | # pylint: disable=line-too-long 104 | """ 105 | Fetch and "set" the local time of this microcontroller to the local time at the location, using an internet time API. 106 | 107 | :param str location: Your city and country, e.g. ``"America/New_York"``. 108 | 109 | """ 110 | reply = get_strftime(TIME_SERVICE_FORMAT, location=location) 111 | if reply: 112 | times = reply.split(" ") 113 | the_date = times[0] 114 | the_time = times[1] 115 | year_day = int(times[2]) 116 | week_day = int(times[3]) 117 | is_dst = None # no way to know yet 118 | year, month, mday = [int(x) for x in the_date.split("-")] 119 | the_time = the_time.split(".")[0] 120 | hours, minutes, seconds = [int(x) for x in the_time.split(":")] 121 | now = time.struct_time( 122 | (year, month, mday, hours, minutes, seconds, week_day, year_day, is_dst) 123 | ) 124 | 125 | if rtc is not None: 126 | rtc.RTC().datetime = now 127 | 128 | return reply 129 | 130 | 131 | tt = get_local_time() 132 | print(tt) 133 | -------------------------------------------------------------------------------- /ntp_simple_demo/ntp_demo_native.py: -------------------------------------------------------------------------------- 1 | """ 2 | Modified from https://gist.github.com/netroy/d1bff654fa58b884d63894ca2c890fc6 3 | """ 4 | import board 5 | import rtc 6 | import struct 7 | import time 8 | import wifi 9 | import socketpool 10 | import displayio 11 | import terminalio 12 | from adafruit_display_text import bitmap_label as label 13 | 14 | # configure your timezone, DST included 15 | TZ_OFFSET = 3600 * 2 16 | 17 | NTP_TIME_CORRECTION = 2_208_988_800 18 | NTP_SERVER = "pool.ntp.org" 19 | NTP_PORT = 123 20 | 21 | def get_ntp_time(pool): 22 | packet = bytearray(48) 23 | packet[0] = 0b00100011 24 | 25 | for i in range(1, len(packet)): 26 | packet[i] = 0 27 | 28 | with pool.socket(pool.AF_INET, pool.SOCK_DGRAM) as sock: 29 | sock.settimeout(None) 30 | sock.sendto(packet, (NTP_SERVER, NTP_PORT)) 31 | sock.recv_into(packet) 32 | destination = time.monotonic_ns() 33 | 34 | seconds = struct.unpack_from("!I", packet, offset=len(packet) - 8)[0] 35 | ntp_localtime_tz = seconds - NTP_TIME_CORRECTION + TZ_OFFSET 36 | # compensate is there's more than 1s since we retrieved the time 37 | return time.localtime( 38 | ntp_localtime_tz + (time.monotonic_ns() - destination) // 1_000_000_000 39 | ) 40 | 41 | 42 | try: 43 | from secrets import secrets 44 | except ImportError: 45 | print("WiFi secrets are kept in secrets.py, please add them there!") 46 | raise 47 | 48 | print("Connecting to ", secrets["ssid"]) 49 | wifi.radio.connect(ssid=secrets["ssid"], password=secrets["password"]) 50 | socket_pool = socketpool.SocketPool(wifi.radio) 51 | print("Connected with IP ", wifi.radio.ipv4_address) 52 | 53 | now = get_ntp_time(socket_pool) 54 | print(now) 55 | rtc.RTC().datetime = now 56 | print("Synced with NTP") 57 | time.sleep(1.0) 58 | 59 | display = None 60 | if hasattr(board, "DISPLAY"): 61 | display = board.DISPLAY 62 | else: 63 | pass 64 | # insert external display init 65 | 66 | if display: 67 | display.auto_refresh = False 68 | group = displayio.Group() 69 | display.show(group) 70 | text_area = label.Label( 71 | terminalio.FONT, 72 | scale=3, 73 | color=(255, 255, 255), 74 | anchor_point=(0.5, 0.5), 75 | anchored_position=( 76 | display.width // 2, 77 | display.height // 2, 78 | ), 79 | text="Hello", 80 | ) 81 | group.append(text_area) 82 | display.refresh() 83 | 84 | previous_clock = "" 85 | 86 | while True: 87 | now = time.localtime() 88 | clock = "{hour:02d}:{min:02d}:{seconds:02d}".format( 89 | hour=now.tm_hour, min=now.tm_min, seconds=now.tm_sec 90 | ) 91 | if clock != previous_clock: 92 | print(clock) 93 | if display: 94 | text_area.text = clock 95 | display.refresh() 96 | previous_clock = clock 97 | time.sleep(0.2) 98 | -------------------------------------------------------------------------------- /ntp_simple_demo/ntp_demo_nina.py: -------------------------------------------------------------------------------- 1 | """ 2 | Modified from https://gist.github.com/netroy/d1bff654fa58b884d63894ca2c890fc6 3 | """ 4 | import board 5 | import rtc 6 | import time 7 | 8 | ####################################################################### 9 | # Configure your timezone, DST included 10 | ####################################################################### 11 | TZ_OFFSET = 3600 * 2 12 | 13 | ####################################################################### 14 | # Setuo ESP here with your pins 15 | ####################################################################### 16 | from adafruit_esp32spi import adafruit_esp32spi 17 | from adafruit_esp32spi import adafruit_esp32spi_wifimanager 18 | 19 | esp32_cs = DigitalInOut(board.ESP_CS) 20 | esp32_ready = DigitalInOut(board.ESP_BUSY) 21 | esp32_reset = DigitalInOut(board.ESP_RESET) 22 | spi = board.SPI() 23 | esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset) 24 | 25 | ####################################################################### 26 | # Connect to wifi 27 | ####################################################################### 28 | 29 | print("CONNECT WIFI") 30 | while not esp.is_connected: 31 | try: 32 | esp.connect_AP(os.getenv("WIFI_SSID"), os.getenv("WIFI_PASSWORD")) 33 | except RuntimeError as e: 34 | print("could not connect to AP, retrying: ", e) 35 | continue 36 | 37 | ####################################################################### 38 | # Time setting helper 39 | ####################################################################### 40 | 41 | def try_set_time(esp, tz_offset=0): 42 | # get_time will raise ValueError if the time isn't available yet. 43 | try: 44 | now = time.localtime(esp.get_time()[0] + tz_offset) 45 | except OSError: 46 | return False 47 | rtc.RTC().datetime = now 48 | return True 49 | 50 | while not try_set_time(esp, TZ_OFFSET): 51 | time.sleep(0.01) 52 | 53 | ####################################################################### 54 | # Set the time, waiting until it's available 55 | ####################################################################### 56 | 57 | while not try_set_time(esp, TZ_OFFSET): 58 | time.sleep(0.01) 59 | 60 | print("Synced with NTP") 61 | 62 | ####################################################################### 63 | # Simple time formatting helper 64 | ####################################################################### 65 | 66 | def format_time(now): 67 | return "{hour}:{minutes:02}:{seconds:02}".format( 68 | hours=now.tm_hour, minutes=now.tm_min, seconds=now.tm_sec 69 | ) 70 | 71 | ####################################################################### 72 | # Print time 73 | ####################################################################### 74 | 75 | now = time.localtime() 76 | print(format_time(now)) 77 | -------------------------------------------------------------------------------- /requests/README.md: -------------------------------------------------------------------------------- 1 | These scripts access wifi and require the presence of the usual `secrets.py` file used by adafruit libraries, as [explained in this guide for example](https://learn.adafruit.com/pyportal-titano-weather-station/code-walkthrough-secrets-py). 2 | -------------------------------------------------------------------------------- /requests/native_request.py: -------------------------------------------------------------------------------- 1 | import board 2 | import time 3 | import socketpool 4 | import ssl 5 | import wifi 6 | import adafruit_requests 7 | 8 | try: 9 | from secrets import secrets 10 | except ImportError: 11 | print("WiFi secrets are kept in secrets.py, please add them there!") 12 | raise 13 | 14 | print("Connecting to ", secrets["ssid"]) 15 | wifi.radio.connect(ssid=secrets["ssid"], password=secrets["password"]) 16 | socket_pool = socketpool.SocketPool(wifi.radio) 17 | print("Connected with IP ", wifi.radio.ipv4_address) 18 | 19 | print("Wifi test") 20 | 21 | requests = adafruit_requests.Session(socket_pool, ssl.create_default_context()) 22 | response = requests.get("http://wifitest.adafruit.com/testwifi/index.html") 23 | print(response.status_code) 24 | print(response.text) 25 | 26 | response = requests.get("https://api.github.com/repos/adafruit/circuitpython") 27 | print(response.status_code) 28 | print("The Circuitpython repo has {} stars.".format(response.json()["stargazers_count"])) 29 | 30 | print("done") 31 | -------------------------------------------------------------------------------- /seesaws/bulk_joy_featherwing.py: -------------------------------------------------------------------------------- 1 | import board 2 | import countio 3 | import time 4 | import digitalio 5 | from adafruit_seesaw.seesaw import Seesaw 6 | # from adafruit_featherwing.joy_featherwing import * 7 | 8 | # choose the Interrup pin 9 | IRQ_PIN = board.D5 10 | 11 | # copying the constants from adafruit_featherwing, no need to install it 12 | BUTTON_A = const(1 << 6) 13 | BUTTON_B = const(1 << 7) 14 | BUTTON_Y = const(1 << 9) 15 | BUTTON_X = const(1 << 10) 16 | BUTTON_SELECT = const(1 << 14) 17 | button_mask = BUTTON_A|BUTTON_B|BUTTON_X|BUTTON_Y|BUTTON_SELECT 18 | 19 | i2c_bus = board.I2C() 20 | seesaw = Seesaw(i2c_bus) 21 | 22 | # set the buttons to input, pull up 23 | seesaw.pin_mode_bulk(button_mask, seesaw.INPUT_PULLUP) 24 | # enable the interrupts on the buttons 25 | seesaw.set_GPIO_interrupts(button_mask, True) 26 | seesaw.interrupt_enabled = True 27 | # setup the interrupt pin (no pull, it's driven by the wing) 28 | interrupt = digitalio.DigitalInOut(IRQ_PIN) 29 | # past state of the buttons to detect release 30 | buttons_pressed_past = button_mask 31 | 32 | while True: 33 | # if the interrupt pin is set (to LOW) 34 | if not interrupt.value: 35 | # get the bitmask of the buttons state 36 | # this resets the interrupt pin (to HIGH) 37 | buttons_pressed = seesaw.digital_read_bulk(button_mask) 38 | 39 | # buttons that just got pressed 40 | if buttons_pressed & BUTTON_A == 0: 41 | print("Button A") 42 | if buttons_pressed & BUTTON_B == 0: 43 | print("Button B") 44 | if buttons_pressed & BUTTON_X == 0: 45 | print("Button X") 46 | if buttons_pressed & BUTTON_Y == 0: 47 | print("Button Y") 48 | if buttons_pressed & BUTTON_SELECT == 0: 49 | print("Select") 50 | 51 | # buttons that just got released 52 | if buttons_pressed_past & BUTTON_A == 0 and buttons_pressed & BUTTON_A: 53 | print("Button A Released") 54 | if buttons_pressed_past & BUTTON_B == 0 and buttons_pressed & BUTTON_B: 55 | print("Button B Released") 56 | if buttons_pressed_past & BUTTON_X == 0 and buttons_pressed & BUTTON_X: 57 | print("Button X Released") 58 | if buttons_pressed_past & BUTTON_Y == 0 and buttons_pressed & BUTTON_Y: 59 | print("Button Y Released") 60 | if buttons_pressed_past & BUTTON_SELECT == 0 and buttons_pressed & BUTTON_SELECT: 61 | print("Select Released") 62 | 63 | # save the state 64 | buttons_pressed_past = buttons_pressed 65 | -------------------------------------------------------------------------------- /seesaws/bulk_neokey1x4.py: -------------------------------------------------------------------------------- 1 | import board 2 | import digitalio 3 | from adafruit_neokey.neokey1x4 import NeoKey1x4 4 | 5 | i2c_bus = board.STEMMA_I2C() 6 | neokey = NeoKey1x4(i2c_bus, addr=0x30) 7 | 8 | # choose the Interrup pin 9 | IRQ_PIN = board.D5 10 | 11 | # button names are arbitrary 12 | BUTTON_A = const(1 << 4) 13 | BUTTON_B = const(1 << 5) 14 | BUTTON_C = const(1 << 6) 15 | BUTTON_D = const(1 << 7) 16 | button_mask = BUTTON_A|BUTTON_B|BUTTON_C|BUTTON_D 17 | 18 | # set the buttons to input, pull up 19 | neokey.pin_mode_bulk(button_mask, neokey.INPUT_PULLUP) 20 | # enable the interrupts on the buttons 21 | neokey.set_GPIO_interrupts(button_mask, True) 22 | neokey.interrupt_enabled = True 23 | # setup the interrupt pin (no pull, it's driven by the wing) 24 | interrupt = digitalio.DigitalInOut(IRQ_PIN) 25 | 26 | # past state of the buttons to detect release 27 | buttons_pressed_past = button_mask 28 | 29 | while True: 30 | # if the interrupt pin is set (to LOW) 31 | if not interrupt.value: 32 | 33 | # get the bitmask of the buttons state 34 | # this resets the interrupt pin (to HIGH) 35 | buttons_pressed = neokey.digital_read_bulk(button_mask) 36 | 37 | # use get_GPIO_interrupt_flag ? 38 | 39 | # buttons that just got pressed 40 | if buttons_pressed & BUTTON_A == 0 and buttons_pressed_past & BUTTON_A: 41 | print("Button A") 42 | if buttons_pressed & BUTTON_B == 0 and buttons_pressed_past & BUTTON_B: 43 | print("Button B") 44 | if buttons_pressed & BUTTON_C == 0 and buttons_pressed_past & BUTTON_C: 45 | print("Button C") 46 | if buttons_pressed & BUTTON_D == 0 and buttons_pressed_past & BUTTON_D: 47 | print("Button D") 48 | 49 | # buttons that just got released 50 | if buttons_pressed_past & BUTTON_A == 0 and buttons_pressed & BUTTON_A: 51 | print("Button A Released") 52 | if buttons_pressed_past & BUTTON_B == 0 and buttons_pressed & BUTTON_B: 53 | print("Button B Released") 54 | if buttons_pressed_past & BUTTON_C == 0 and buttons_pressed & BUTTON_C: 55 | print("Button C Released") 56 | if buttons_pressed_past & BUTTON_D == 0 and buttons_pressed & BUTTON_D: 57 | print("Button D Released") 58 | 59 | # save the state 60 | buttons_pressed_past = buttons_pressed 61 | -------------------------------------------------------------------------------- /seesaws/bulk_neokey1x4_nointerrupt.py: -------------------------------------------------------------------------------- 1 | import board 2 | import digitalio 3 | from adafruit_neokey.neokey1x4 import NeoKey1x4 4 | 5 | i2c_bus = board.STEMMA_I2C() 6 | neokey = NeoKey1x4(i2c_bus, addr=0x30) 7 | 8 | # button names are arbitrary 9 | BUTTON_A = const(1 << 4) 10 | BUTTON_B = const(1 << 5) 11 | BUTTON_C = const(1 << 6) 12 | BUTTON_D = const(1 << 7) 13 | button_mask = BUTTON_A|BUTTON_B|BUTTON_C|BUTTON_D 14 | 15 | # set the buttons to input, pull up 16 | neokey.pin_mode_bulk(button_mask, neokey.INPUT_PULLUP) 17 | 18 | # past state of the buttons to detect changes 19 | buttons_pressed_past = button_mask 20 | 21 | while True: 22 | # get the bitmask of the buttons state 23 | # this resets the interrupt pin (to HIGH) 24 | buttons_pressed = neokey.digital_read_bulk(button_mask) 25 | 26 | if buttons_pressed_past != buttons_pressed: 27 | 28 | # buttons that just got pressed 29 | if buttons_pressed & BUTTON_A == 0 and buttons_pressed_past & BUTTON_A: 30 | print("Button A") 31 | if buttons_pressed & BUTTON_B == 0 and buttons_pressed_past & BUTTON_B: 32 | print("Button B") 33 | if buttons_pressed & BUTTON_C == 0 and buttons_pressed_past & BUTTON_C: 34 | print("Button C") 35 | if buttons_pressed & BUTTON_D == 0 and buttons_pressed_past & BUTTON_D: 36 | print("Button D") 37 | 38 | # buttons that just got released 39 | if buttons_pressed_past & BUTTON_A == 0 and buttons_pressed & BUTTON_A: 40 | print("Button A Released") 41 | if buttons_pressed_past & BUTTON_B == 0 and buttons_pressed & BUTTON_B: 42 | print("Button B Released") 43 | if buttons_pressed_past & BUTTON_C == 0 and buttons_pressed & BUTTON_C: 44 | print("Button C Released") 45 | if buttons_pressed_past & BUTTON_D == 0 and buttons_pressed & BUTTON_D: 46 | print("Button D Released") 47 | 48 | # save the state 49 | buttons_pressed_past = buttons_pressed 50 | -------------------------------------------------------------------------------- /serial_to_display/code_serlcd.py: -------------------------------------------------------------------------------- 1 | import board 2 | import busio 3 | import openlcd_mini 4 | import time 5 | import usb_cdc 6 | 7 | i2c = busio.I2C(sda=board.SDA1, scl=board.SCL1) 8 | 9 | mini = openlcd_mini.OpenLCD(i2c,114) 10 | mini.clear() 11 | 12 | mini.print("Starting") 13 | mini.print("...") 14 | mini.backlight = (0, 255, 255) 15 | 16 | import neopixel 17 | pix = neopixel.NeoPixel(board.NEOPIXEL, 1) 18 | pix.fill((32, 16, 0)) 19 | 20 | while True: 21 | if usb_cdc.data.in_waiting > 0: 22 | data_in = usb_cdc.data.readline() 23 | mini.clear() 24 | mini.print(data_in.decode()) 25 | -------------------------------------------------------------------------------- /serial_to_display/send_stuff.py: -------------------------------------------------------------------------------- 1 | """ 2 | A simple, json based, serial write. 3 | Loops on a color wheel to send color info to the board. 4 | """ 5 | import argparse 6 | import datetime 7 | import json 8 | import psutil 9 | import serial 10 | import time 11 | 12 | parser = argparse.ArgumentParser() 13 | parser.add_argument("port", type=str, help="Serial port of the board", nargs=1) 14 | args = parser.parse_args() 15 | port = args.port 16 | 17 | channel = serial.Serial(args.port[0]) 18 | channel.timeout = 0.05 19 | 20 | """ 21 | VIOLET GREEN RED 22 | CYAN ORANGE BLUE MAGENTA 23 | SKY YELLOW PURPLE color 24 | TEAL WHITE BLACK GOLD 25 | PINK AQUA JADE AMBER 26 | RAINBOW 27 | """ 28 | 29 | def wheel(wheel_pos): 30 | """Color wheel to allow for cycling through the rainbow of RGB colors.""" 31 | wheel_pos = wheel_pos % 256 32 | if wheel_pos < 85: 33 | return 255 - wheel_pos * 3, 0, wheel_pos * 3 34 | elif wheel_pos < 170: 35 | wheel_pos -= 85 36 | return 0, wheel_pos * 3, 255 - wheel_pos * 3 37 | else: 38 | wheel_pos -= 170 39 | return wheel_pos * 3, 255 - wheel_pos * 3, 0 40 | 41 | 42 | index = 0 43 | while True: 44 | # note: faire le CPU en parallèle 45 | #cpu = psutil.cpu_percent(4) 46 | mem = psutil.virtual_memory()[2] 47 | 48 | now = datetime.datetime.now() 49 | tjour = now.strftime("%a") 50 | tdate = now.strftime("%d/%m/%Y") 51 | ttime = now.strftime("%H:%M:%S").rjust(13) 52 | 53 | channel.write(json.dumps({ 54 | "lines": [ 55 | "This is title", # f"CPU: {cpu} %", 56 | f"RAM: {mem} %", 57 | None, 58 | tjour, 59 | tdate, 60 | ttime, 61 | ], 62 | "colors": [ 63 | "CYAN", 64 | "ORANGE", 65 | None, 66 | None, 67 | "GREEN", 68 | "PINK", 69 | ], 70 | }).encode() + b"\r\n") 71 | index += 8 72 | 73 | print(tjour, tdate, ttime) 74 | 75 | if channel.in_waiting: 76 | data = channel.readline() 77 | print(data.decode().strip()) 78 | 79 | time.sleep(1) 80 | -------------------------------------------------------------------------------- /serial_to_display/serial_to_displayio.py: -------------------------------------------------------------------------------- 1 | import board 2 | import displayio 3 | import gc 4 | import json 5 | import microcontroller 6 | import os 7 | import terminalio 8 | import time 9 | import traceback 10 | import usb_cdc 11 | from adafruit_display_text.bitmap_label import Label 12 | from adafruit_display_text import wrap_text_to_pixels 13 | 14 | NUMLINES = 8 15 | SCALE = 2 16 | 17 | SERIAL = usb_cdc.console 18 | if usb_cdc.data: 19 | SERIAL = usb_cdc.data 20 | 21 | display = board.DISPLAY 22 | 23 | display.auto_refresh = False 24 | splash = displayio.Group() 25 | line_height = terminalio.FONT.get_bounding_box()[1] * SCALE 26 | 27 | colors = { 28 | "RED": (255, 0, 0), 29 | "YELLOW": (255, 255, 0), 30 | "ORANGE": (255, 150, 0), 31 | "GREEN": (0, 255, 0), 32 | "TEAL": (0, 255, 120), 33 | "CYAN": (0, 255, 255), 34 | "BLUE": (0, 0, 255), 35 | "PURPLE": (180, 0, 255), 36 | "MAGENTA": (255, 0, 150), 37 | "WHITE": (255, 255, 255), 38 | "BLACK": (0, 0, 0), 39 | "GOLD": (255, 222, 30), 40 | "PINK": (242, 90, 255), 41 | "AQUA": (50, 255, 255), 42 | "JADE": (0, 255, 40), 43 | "AMBER": (255, 100, 0), 44 | "VIOLET": (255, 0, 255), 45 | "SKY": (0, 180, 255), 46 | } 47 | 48 | labels = [] 49 | for y in range(NUMLINES): 50 | label = Label( 51 | text="", font=terminalio.FONT, color=0x0, scale=SCALE, 52 | anchored_position=(0, 1.25 * y * line_height), anchor_point=(0, 0), 53 | ) 54 | labels.append(label) 55 | splash.append(label) 56 | 57 | def show_the_lines(lines): 58 | for index, line in enumerate(lines[:len(labels)]): 59 | if line is not None: 60 | labels[index].text = line 61 | 62 | def show_the_text(text): 63 | lines = wrap_text_to_pixels(text, font=terminalio.FONT, max_width=display.width / SCALE) 64 | show_the_lines(lines) 65 | 66 | display.show(splash) 67 | 68 | def parse_color(color): 69 | if isinstance(color, (list, tuple)) and len(color) == 3: 70 | return color 71 | if isinstance(color, int): 72 | return color 73 | if color in colors: 74 | return colors[color] 75 | return 0xFFFFFF 76 | 77 | def read_serial(): 78 | data_in = SERIAL.readline() 79 | try: 80 | print("Update", time.monotonic()) 81 | data = json.loads(data_in) 82 | 83 | if "lines" in data: 84 | show_the_lines(data["lines"]) 85 | 86 | if "colors" in data: 87 | for index, color in enumerate(data["colors"][:len(labels)]): 88 | labels[index].color = parse_color(color) 89 | 90 | except ValueError as ex: 91 | # traceback.print_exception(ex,ex,ex.__traceback__) 92 | print("Dropping json data:", ex) 93 | print(data_in) 94 | 95 | finally: 96 | gc.collect() 97 | display.refresh() 98 | 99 | 100 | while True: 101 | if SERIAL.in_waiting > 0: 102 | read_serial() 103 | 104 | time.sleep(0.01) 105 | -------------------------------------------------------------------------------- /serial_to_display/serial_to_displayio_clue.py: -------------------------------------------------------------------------------- 1 | from adafruit_clue import clue 2 | clue.display.bus.send(0x26, b"\x04") 3 | import json 4 | import gc 5 | import time 6 | import usb_cdc 7 | 8 | clue.pixel[0] = (0,0,0) 9 | 10 | """ 11 | VIOLET GREEN RED 12 | CYAN ORANGE BLUE MAGENTA 13 | SKY YELLOW PURPLE color 14 | TEAL WHITE BLACK GOLD 15 | PINK AQUA JADE AMBER 16 | RAINBOW 17 | """ 18 | 19 | def wheel(wheel_pos): 20 | """Color wheel to allow for cycling through the rainbow of RGB colors.""" 21 | wheel_pos = wheel_pos % 255 22 | if wheel_pos < 85: 23 | return 255 - wheel_pos * 3, 0, wheel_pos * 3 24 | elif wheel_pos < 170: 25 | wheel_pos -= 85 26 | return 0, wheel_pos * 3, 255 - wheel_pos * 3 27 | else: 28 | wheel_pos -= 170 29 | return wheel_pos * 3, 255 - wheel_pos * 3, 0 30 | 31 | clue_display = clue.simple_text_display( 32 | text_scale = 3, colors = [ 0xFFFFFF ] * 9 33 | ) 34 | 35 | # 9 lignes 36 | 37 | def lights_toggle(): 38 | clue.red_led = not clue.red_led 39 | clue.white_leds = not clue.white_leds 40 | 41 | def every_x(delay): 42 | t0 = time.monotonic() + delay 43 | while True: 44 | t1 = time.monotonic() 45 | if t1 > t0: 46 | t0 = t1+delay 47 | yield True 48 | yield False 49 | 50 | 51 | def parse_color(color): 52 | if isinstance(color, (list, tuple)) and len(color) == 3: 53 | return color 54 | if isinstance(color, int): 55 | return color 56 | if isinstance(color, str) and hasattr(clue, color.upper()): 57 | return getattr(clue, color.upper()) 58 | 59 | 60 | while True: 61 | if usb_cdc.data.in_waiting > 0: 62 | data_in = usb_cdc.data.readline() 63 | try: 64 | print("Update", time.monotonic()) 65 | data = json.loads(data_in) 66 | 67 | if "lines" in data: 68 | for index, line in enumerate(data["lines"]): 69 | if index in range(0,9) and line is not None: 70 | clue_display[index].text = line 71 | 72 | if "colors" in data: 73 | for index, color in enumerate(data["colors"]): 74 | if index in range(0,9) and color is not None: 75 | clue_display[index].color = parse_color(color) 76 | 77 | except ValueError as ex: 78 | print("Dropping json data", ex) 79 | 80 | 81 | pressed = clue.were_pressed 82 | if pressed: 83 | usb_cdc.data.write(json.dumps({ 84 | "buttons": list(pressed) 85 | }).encode() + b"\r\n") 86 | 87 | clue_display.show() 88 | clue.red_led = not clue.red_led 89 | 90 | gc.collect() 91 | time.sleep(0.01) 92 | -------------------------------------------------------------------------------- /status_led/status_led.py: -------------------------------------------------------------------------------- 1 | """ 2 | A compatibility library to find and enable the on-board RGB LEDs on Circuitpython boards 3 | """ 4 | import board 5 | import os 6 | import time 7 | 8 | # keep these pins, so they don't get freed 9 | keep_pins = {} 10 | # the status LED (can be deinited from the outside or here) 11 | pixels = None 12 | 13 | def get_npixels(): 14 | """ 15 | Find the number of pixels based on hand-filled board information 16 | """ 17 | machine = os.uname().machine 18 | npixels_by_machine = { 19 | "Adafruit MagTag with ESP32S2": 4, 20 | "Adafruit CircuitPlayground Express with samd21g18": 10, 21 | "Adafruit Circuit Playground Bluefruit with nRF52840": 10, 22 | "Adafruit PyGamer with samd51j19": 5, 23 | "Adafruit PyGamer with samd51j20": 5, 24 | "Adafruit Pybadge with samd51j19": 5, 25 | "Adafruit FunHouse with ESP32S2": 5, 26 | "Adafruit NeoPixel Trinkey M0 with samd21e18": 4, 27 | "Adafruit Trellis M4 Express with samd51g19": 4*8, 28 | } 29 | npixels_by_name = { 30 | "Adafruit NeoPixel Trinkey": 4, 31 | "Adafruit FunHouse": 5, 32 | "Adafruit FunHome": 5, # alternate name for FunHouse (bug) 33 | "Adafruit PyGamer": 5, 34 | "Adafruit PyBadge": 5, 35 | "Adafruit CircuitPlayground": 10, # catch all variants (crickit, displayio) 36 | } 37 | 38 | # find by fill machine description 39 | if machine in npixels_by_machine: 40 | return npixels_by_machine[machine] 41 | 42 | # fallback to search in name 43 | for name in npixels_by_name: 44 | if name in machine: 45 | return npixels_by_name[name] 46 | 47 | return 1 48 | 49 | 50 | def get_status_led(npixels = None, *, brightness = None): 51 | """ 52 | Can force the number of pixels used (to 1 for example on the pybadge LC) 53 | """ 54 | global pixels 55 | 56 | if npixels is None: 57 | npixels = get_npixels() 58 | 59 | if hasattr(board,"NEOPIXEL"): 60 | """ 61 | For a board that has a neopixel (eg: QT PY M0) 62 | """ 63 | import neopixel 64 | pixels = neopixel.NeoPixel(board.NEOPIXEL, npixels) 65 | elif hasattr(board,"APA102_SCK"): 66 | """ 67 | For a board that has a APA102 (eg: UnexpectedMaker Feather S2, Trinket M0) 68 | """ 69 | import adafruit_dotstar 70 | pixels = adafruit_dotstar.DotStar(board.APA102_SCK, board.APA102_MOSI, npixels) 71 | elif hasattr(board,"DOTSTAR_CLOCK"): 72 | """ 73 | For a board that has DOTSTAR pins (eg: FunHouse) 74 | """ 75 | import adafruit_dotstar 76 | pixels = adafruit_dotstar.DotStar(board.DOTSTAR_CLOCK, board.DOTSTAR_DATA, npixels) 77 | else: 78 | raise OSError("No hardware pixel identified") 79 | 80 | 81 | if hasattr(board,'LDO2'): 82 | """ 83 | Enable LDO2 on the Feather S2 so we can use the status LED 84 | """ 85 | from digitalio import DigitalInOut 86 | ldo2 = DigitalInOut(board.LDO2) 87 | ldo2.switch_to_output() 88 | ldo2.value = True 89 | keep_pins["ldo2"] = ldo2 90 | time.sleep(0.035) 91 | 92 | 93 | if hasattr(board,"NEOPIXEL_POWER"): 94 | """ 95 | Some boards have a NEOPIXEL_POWER pin 96 | """ 97 | from digitalio import DigitalInOut 98 | neopower = DigitalInOut(board.NEOPIXEL_POWER) 99 | neopower.switch_to_output() 100 | keep_pins["neopower"] = neopower 101 | if "MagTag" in machine: 102 | # On MagTag, pull it down 103 | neopower.value = False 104 | elif "TinyS2" in machine: 105 | # On TinyS2, pull it up 106 | neopower.value = True 107 | else: 108 | # Assume up by default maybe ? 109 | neopower.value = True 110 | 111 | if brightness is not None: 112 | pixels.brightness = brightness 113 | 114 | return pixels 115 | 116 | 117 | def deinit_status_led(): 118 | """ 119 | deinits the status pixels, be careful not to use it after that 120 | reverses and deinits the enable pins 121 | """ 122 | global pixels 123 | if pixels is not None: 124 | pixels.deinit() 125 | pixels = None 126 | 127 | for pin in keep_pins: 128 | pin.value = not pin.value 129 | if hasattr(board,'LDO2'): 130 | time.sleep(0.035) 131 | pin.deinit() 132 | -------------------------------------------------------------------------------- /timings/README.md: -------------------------------------------------------------------------------- 1 | ## Timing examples 2 | 3 | Learn guide on the subject: 4 | https://learn.adafruit.com/multi-tasking-with-circuitpython 5 | 6 | These examples show how to trigger short-term events with a timer inside a while loop, to avoid the blocking nature of `time.sleep`. Longer timings (days or more) are better done using an RTC module, or periodical time synchronisation over the net. 7 | 8 | Note that in Circuitpython, timing will never be very precise. For one, there is some overhead in the execution of the code, but also this is python, so a garbage collection for example can be trigger at any time unexpectedly (though there are ways around that by dong it manually). 9 | 10 | ### Getting A Time Stamp 11 | 12 | There are three main ways to get timing information in Circuitpython. They all "drift" over time because the internal timer is usually not a high precision RTC but based on the frequency of the microcontroller, but that should be good enough for relative times. 13 | 14 | - `time.monotonic()` is a number of seconds, as a float, since the board boot. Because floats are single-precision, the timer looses precision over time. After 5 days it can't differentiate times below 0.1 seconds, and 1 second after a few weeks. [See this discussion](https://github.com/adafruit/circuitpython/issues/342#issuecomment-337228427). 15 | - `time.monotonic_ns()` is a long int in nanoseconds (1_000_000_000 of a second), and does not lose accuracy. It can be used to do any timing measurement with integers. Be careful with non-integer divisions since you might lose accuracy by converting to a float. **It is not available on most non "express" boards due to memoery constraints**. 16 | - `supervisor.ticks_ms()` is an integer timer in milliseconds, that wraps (goes back to 0) every 2**29 milliseconds (around 6 days), so that it does not need long integers. Because of that it can only measure timings below that value, and the math needs to take into account the possibility of wrapping. **It is only available since Circuitpython 7**. 17 | 18 | To unify those uses and pick the best default for short-term timings you can use the [`adafruit_ticks` library](https://github.com/adafruit/Adafruit_CircuitPython_Ticks), available in the Adafruit Library Bundle. 19 | 20 | ### Example Files 21 | 22 | The main point of the examples is to trigger two actions "every X seconds" with a different timing, or "delay" each. The basic example has an action trigger every 2 seconds and the other every 5 seconds. 23 | 24 | - A timer can start as soon as the loop starts or only after the delay runs once. 25 | - The delay can be a strict period, avoiding drift, useful to keep a strict timing. 26 | - Or guarantee that each run is separated by at least the delay, which can be required to deal with rate limiting for example. 27 | 28 | The files include: 29 | 30 | - `loop_with_timers.py`: a loop with 2 timers firing at different intervals. 31 | - `loop_with_timer_states.py`: a loop with one timer changing its timing based on a state variable. One action is triggered after 10 seconds, the next after 50, then back to the first action. 32 | - `timers_with_yield.py`: uses a generator with `yield` as a kind of a light-weight timer library. Schedule multiple timers by calling `setup_timer()` and call `next(a_timer)` to update the timer (and call the callback). 33 | - `timer_class.py` and `timer_example.py`: a larger timer library. Register as many timers as you want with the `add_timer()` decorator, and trigger timer events by calling `update_all()` periodically. 34 | - `monotonic_loop.py`: a loop with 2 timers using time.monotonic(). No dependencies, but don't use for long running programs if it can affect your timers. 35 | -------------------------------------------------------------------------------- /timings/loop_with_timer_states.py: -------------------------------------------------------------------------------- 1 | """ 2 | This does 2 actions, in succession, after 10 seconds and 50 seconds. 3 | The difference with the simple example is that the wait until the next event 4 | changes depending on the state of the application. 5 | For example: turn an LED on for 10 seconds every 60s (50s + 10s = 1 minute). 6 | """ 7 | import time 8 | from adafruit_ticks import ticks_ms, ticks_add, ticks_less 9 | 10 | # if this is 0, the first action will be executed immediately 11 | START_DELAY = 0 12 | # action for 10 seconds: 13 | TIMER_1_DELAY = 10_000 14 | # action for 50 seconds: 15 | TIMER_2_DELAY = 50_000 16 | 17 | # starting state (LED off for example) 18 | current_state = False # off 19 | now = ticks_ms() 20 | next_time = ticks_add(now, START_DELAY) 21 | 22 | while True: 23 | # take the time once per loop, as the reference 24 | now = ticks_ms() 25 | if ticks_less(next_time, now): 26 | if current_state is False: 27 | # actual action (turn LED on for example) 28 | print("Do the thing that lasts {} seconds".format(TIMER_1_DELAY/1000)) 29 | print(time.time()) 30 | # timer management 31 | delta = TIMER_1_DELAY 32 | else: 33 | # actual action (turn LED off for example) 34 | print("Do the thing that lasts {} seconds".format(TIMER_2_DELAY/1000)) 35 | print(time.time()) 36 | # timer management 37 | delta = TIMER_2_DELAY 38 | # set the next event 39 | next_time = ticks_add(next_time, delta) 40 | # switch the state (2 states here, True or False) 41 | current_state = not current_state 42 | # do something else or sleep a little bit to avoid a tight loop 43 | time.sleep(0.01) 44 | -------------------------------------------------------------------------------- /timings/loop_with_timers.py: -------------------------------------------------------------------------------- 1 | """ 2 | Timer example using adafruit_ticks for maximum compatibility. 3 | Timings are in milliseconds here (they could be converted but it's fine). 4 | """ 5 | import time 6 | from adafruit_ticks import ticks_ms, ticks_add, ticks_less 7 | 8 | # every 5 seconds: 9 | TIMER_1_DELAY = 5000 10 | # every 2 seconds: 11 | TIMER_2_DELAY = 2000 12 | 13 | # Change *_next_run to now or 0 if you want to trigger it on the first loop. 14 | # Or "now + X" to trigger at a different time the first time. 15 | """Examples: 16 | - Trigger the first run after the DELAY 17 | next_run = now + DELAY 18 | - Trigger the first run in the first loop 19 | next_run = 0 20 | - Trigger the first run after 10 seconds 21 | next_run = now + 10_000 22 | """ 23 | 24 | now = ticks_ms() 25 | timer_1_next_run = ticks_add(now, TIMER_1_DELAY) 26 | timer_2_next_run = ticks_add(now, TIMER_2_DELAY) 27 | 28 | while True: 29 | # set "now" only once per loop, it's the time reference for the loop. 30 | now = ticks_ms() 31 | if ticks_less(timer_1_next_run, now): 32 | # Next time based on last time 33 | # This avoids drifting, but can cause multiple triggerings if the rest 34 | # of the loop takes too long and delays are too short. 35 | timer_1_next_run = ticks_add(timer_1_next_run, TIMER_1_DELAY) 36 | print("TIMER 1 TRIGGERED", time.monotonic()) 37 | if ticks_less(timer_2_next_run, now): 38 | # Next time based on current time. 39 | # This causes drifting, but guarantees a minimum delay between triggers. 40 | timer_2_next_run = ticks_add(now, TIMER_2_DELAY) 41 | print("TIMER 2 TRIGGERED ", time.monotonic()) 42 | -------------------------------------------------------------------------------- /timings/monotonic_loop.py: -------------------------------------------------------------------------------- 1 | """ 2 | Loop with 2 timers, using time.monotonic() 3 | 4 | NOTE: time.monotonic() loses precision after many hours of running. 5 | Prefer using time.monotonic_ns() on boards that support it. 6 | Since monotonic_ns is in nano seconds, you have to adapt the delays. 7 | """ 8 | import time 9 | 10 | # every 5 seconds: 11 | TIMER_1_DELAY = 5 12 | # every 2 seconds: 13 | TIMER_2_DELAY = 2 14 | 15 | # Change *_next_run to now or 0 if you want to trigger it on the first loop. 16 | # Or "now + X" to trigger at a different time the first time. 17 | now = time.monotonic() 18 | timer_1_next_run = now + TIMER_1_DELAY 19 | timer_2_next_run = now + TIMER_2_DELAY 20 | 21 | """Examples: 22 | - Trigger the first run after the DELAY 23 | next_run = now + DELAY 24 | - Trigger the first run in the first loop 25 | next_run = 0 26 | - Trigger the first run after 10 seconds 27 | next_run = now + 10 28 | """ 29 | 30 | while True: 31 | now = time.monotonic() 32 | if now > timer_1_next_run: 33 | # Next time based on current time. 34 | # This causes drifting, but guarantees a minimum delay between triggers. 35 | timer_1_next_run = now + TIMER_1_DELAY 36 | print("TIMER 1 TRIGGERED") 37 | if now > timer_2_next_run: 38 | # Next time based on last time 39 | # This avoids drifting, but can cause multiple triggerings if the rest 40 | # of the loop takes too long and delays are too short. 41 | timer_2_next_run = timer_2_next_run + TIMER_2_DELAY 42 | print("TIMER 2 TRIGGERED") 43 | # Sleep a little bit to avoid a tight loop. 44 | # Remove this if the loop does other things. 45 | time.sleep(0.01) 46 | -------------------------------------------------------------------------------- /timings/timer_class.py: -------------------------------------------------------------------------------- 1 | """ 2 | A Timer class that will trigger a callback if the time has passed 3 | by calling Timer.update() in a loop. 4 | 5 | Rather than use independent timers, you can use module functions to manage 6 | a list of timers, and update them all in one call. 7 | """ 8 | 9 | import time 10 | 11 | 12 | try: 13 | # this is for most boards and computers 14 | # long ints require memory allocation, but sorting is straightforward 15 | ticks_ms = lambda: time.monotonic_ns() // 1_000_000 16 | ticks_add = lambda a,b: a + b 17 | ticks_less = lambda a,b: a < b 18 | Tick = lambda x: x.next_run 19 | except (ImportError, NameError): 20 | try: 21 | # this is for M0 non express boards where adafruit_ticks is installed 22 | # the memory advantage of ticks is lost due to the sorting trick 23 | from adafruit_ticks import ticks_ms, ticks_add, ticks_less 24 | # required for sorting relative values 25 | class Tick: 26 | def __init__(self, by): 27 | self.val = by.next_run 28 | def __lt__(self, other): 29 | return ticks_less(self.val, other.val) 30 | def __gt__(self, other): 31 | return not ticks_less(self.val, other.val) 32 | def __eq__(self, other): 33 | return self.val == other.val 34 | except (ImportError, NameError): 35 | # this is for M0 non express boards without adafruit_ticks 36 | # maybe you are still using 6.3.0 or just like pain ? 37 | ticks_ms = lambda: int(time.monotonic() * 1000) 38 | ticks_add = lambda a,b: a + b 39 | ticks_less = lambda a,b: a < b 40 | Tick = lambda x: x.next_run 41 | 42 | 43 | class Timer: 44 | def __init__(self, callback, delay, initial=None, data=()): 45 | now = ticks_ms() 46 | self.callback = callback 47 | self.delay = int(delay * 1000) 48 | if initial is None: 49 | self.initial_delay = self.delay 50 | else: 51 | self.initial_delay = int(initial * 1000) 52 | self.data = data 53 | self.next_run = ticks_add(now, self.initial_delay) 54 | 55 | def restart(self): 56 | now = ticks_ms() 57 | if self.initial_delay == 0: 58 | self.run() 59 | else: 60 | self.next_run = ticks_add(now, self.initial_delay) 61 | 62 | def run(self): 63 | self.callback(self.next_run / 1000, self.data) 64 | self.next_run = ticks_add(self.next_run, self.delay) 65 | 66 | def update(self, now = ticks_ms()): 67 | if ticks_less(self.next_run, now): 68 | self.run() 69 | 70 | all_timers = [] 71 | 72 | def add_timer(delay, initial=None, data=()): 73 | """ 74 | Add a timer with a decorator. 75 | If initial is 0 (default) it will fire once the first time. 76 | Otherwise it will wait that amount of time, regardless of the delay, 77 | to start running every delay seconds. 78 | If initial is None, the delay value is used as the initial delay. 79 | 80 | :param delay: float number of seconds between runs of the timer. 81 | :param initial: float number of seconds before the first run (0). 82 | 83 | Example: 84 | 85 | @timer.add_timer(TIMER_1_DELAY, data=1) 86 | def timer_1_callback(now, data): 87 | print(f"TIMER {data} --> {now:.2f}") 88 | """ 89 | def _inside(callback): 90 | t = Timer(callback, delay, initial, data) 91 | all_timers.append(t) 92 | all_timers.sort(key=Tick) 93 | return t 94 | return _inside 95 | 96 | def restart_timer(timer, initial=0): 97 | """Restart a timer that was stopped""" 98 | if timer not in all_timer: 99 | all_timers.append(t) 100 | all_timers.sort(key=Tick) 101 | 102 | def stop_timer(timer): 103 | """Stop a timer instance from running.""" 104 | if timer in all_timers: 105 | all_timers.remove(timer) 106 | 107 | def stop_all(): 108 | """Stop all timers.""" 109 | all_timers = [] 110 | 111 | def update_all(): 112 | updated = False 113 | now = ticks_ms() 114 | for timer in all_timers: 115 | if ticks_less(timer.next_run, now): 116 | timer.run() 117 | updated = True 118 | else: 119 | break 120 | if updated: 121 | all_timers.sort(key=Tick) 122 | -------------------------------------------------------------------------------- /timings/timer_example.py: -------------------------------------------------------------------------------- 1 | import time 2 | import timer_class as timer 3 | 4 | TIMER_1_DELAY = 5 # every 5 seconds 5 | TIMER_2_DELAY = 2 # every 2 seconds 6 | TIMER_3_DELAY = 7 # every 7 seconds 7 | 8 | # add a timer with a decorator 9 | @timer.add_timer(TIMER_1_DELAY) 10 | def timer_1_callback(now, data): 11 | print(f"TIMER 1 --> {now:.2f}") 12 | 13 | # add timers with a function call 14 | def timer_callback(now, data): 15 | space = " " * (data-1) 16 | print(f"TIMER {data} --> {space}{now:.2f}") 17 | 18 | timer_2 = timer.add_timer(TIMER_2_DELAY, data=2, initial=0)(timer_callback) 19 | timer_3 = timer.add_timer(TIMER_3_DELAY, data=3)(timer_callback) 20 | 21 | while True: 22 | timer.update_all() 23 | # sleep a little bit 24 | time.sleep(0.01) 25 | -------------------------------------------------------------------------------- /timings/timers_with_yield.py: -------------------------------------------------------------------------------- 1 | import time 2 | from adafruit_ticks import ticks_ms, ticks_add, ticks_less 3 | 4 | # every 5 seconds: 5 | TIMER_1_DELAY = 5_000 6 | # every 2 seconds: 7 | TIMER_2_DELAY = 2_000 8 | 9 | # this function is the whole extent of this timer "library" 10 | def setup_timer(callback, delay, initial_delay=0, data=()): 11 | next_run = ticks_add(ticks_ms(), initial_delay) 12 | while True: 13 | now = ticks_ms() 14 | if ticks_less(next_run, now): 15 | callback(now,data) 16 | next_run = ticks_add(next_run, delay) 17 | yield 18 | 19 | # callbacks we want to run on a timer 20 | def timer_1_callback(now,data): 21 | print(f"TIMER {data} --> {time.monotonic():.2f}") 22 | 23 | def timer_2_callback(now,data): 24 | print(f"TIMER {data} --> {time.monotonic():.2f}") 25 | 26 | # setup the timers 27 | timer_1 = setup_timer(timer_1_callback, TIMER_1_DELAY, data=(1)) 28 | timer_2 = setup_timer(timer_2_callback, TIMER_2_DELAY, data=(2)) 29 | 30 | # run the loop 31 | while True: 32 | next(timer_1) 33 | next(timer_2) 34 | # sleep a little bit 35 | time.sleep(0.01) 36 | -------------------------------------------------------------------------------- /usb_serial/README.md: -------------------------------------------------------------------------------- 1 | ## Serial sample codes 2 | 3 | Circuitpython enables communication with the host computer it is connected to via a USB serial port. These examples show how to do it in different basic scenarios on the Circuitpython side and example host-side scripts using python. You can of course use any programming language on the host side. 4 | 5 | 6 | - [The documentation of the module](https://docs.circuitpython.org/en/latest/shared-bindings/usb_cdc/index.html#module-usb_cdc) list all the functions available. 7 | - [This guide uses the usb_cdc module to send sensor data to the PC](https://learn.adafruit.com/diy-trinkey-no-solder-air-quality-monitor/circuitpython). 8 | - [Another guide uses serial to communicate "remote procedure calls"](https://learn.adafruit.com/macropad-remote-procedure-calls-over-usb-to-control-home-assistant). 9 | 10 | 11 | ## The Second Serial Channel 12 | 13 | The default serial channel also hosts the REPL, so you can't communicate through it while looking at the REPL, and some characters can interrupt the code (ctrl-C) so it's not suited for binary data. 14 | 15 | Since Circuitpython 7, it is possible to enable a second serial channel (some chips might be limited) for binary communication without limitations and without losing the REPL, so you can still see debug prints and errors. 16 | 17 | Note that you can use the REPL serial port with `usb_cdc.console`, but that is not available on boards that don't have USB OTG, like the suffixless ESP32 or the ESP32-C3. 18 | 19 | Using the data serial channel requires creating a `boot.py` file containing the following code. The file requires a reset to be active after it's created the first time (it runs whe the board boots). 20 | 21 | - [`boot.py`](boot.py) 22 | 23 | ```py 24 | import usb_cdc 25 | usb_cdc.enable(data=True) 26 | ``` 27 | 28 | ## The Serial Ports 29 | 30 | The serial ports of your boards are available on your computer, depending on the operating system, for example `COM*` on Windows, `/dev/cu.usbmodem*` on MacOS, `/dev/ttyACM0*` on linux. 31 | 32 | To help find the serial ports (REPL or data) on the host side, the `find_serial_port.py` script lists the ports per board and per type (if present). It requires the `adafruit_board_toolkit` module. 33 | ``` 34 | pip3 install adafruit_board_toolkit 35 | ``` 36 | 37 | - [`find_serial_port.py`](find_serial_port.py) 38 | 39 | I also developped a command line python tool to help gather information on the connected devices called [discotool](https://github.com/Neradoc/discotool). It can be used as a library to find a board and its ports by serial number, drive name, and other criteria (see the examples in the discotool repository). 40 | 41 | ## The Example Scripts 42 | 43 | These scripts use a loop in Circuitpython ot communicate with the host computer without blocking. That means that you can run some other code in the same loop, that doesn't have to wait until it receives a message from the host. 44 | 45 | Some parts use the json library, not always available on small SAMD21 (M0) boards, due to lack of space, but they can still be used as templates to insert your own code instead. 46 | 47 | These scripts use `readline()`, assuming that the host always sends entire lines ending in a return character. Of course you don't have to do that and can read N bytes at a time, blocking or not. 48 | 49 | Each example has 3 files. 50 | 51 | - The "host" script that must be run on the computer with python 3, by giving the serial port of the board as argument to the command line. The script requires pyserial to be installed. 52 | ``` 53 | pip3 install pyserial 54 | ``` 55 | - Two board codes that can be renamed to `code.py` or imported from it. 56 | - One that will listen or output to the REPL (or console) serial channel. 57 | - One that will listen or output to the data serial channel. 58 | 59 | ## Serial Send 60 | 61 | Send data from the host computer to the board. 62 | 63 | The host computer sends cycling color data regularly to the board, using JSON encoding to change the color on the board. For testing purposes the code will print out the color to the REPL. 64 | 65 | The board is expected to have a built-in neopixel. If it doesn't, replace it with external neopixels, a dotstar or a screen, etc. It might require to install the `neopixel` library from the library bundle, though it is included in the Circuitpython firmware on some boards. 66 | 67 | - [`serial_send-host.py`](serial_send-host.py) 68 | - [`serial_send_data-code.py`](serial_send_data-code.py) 69 | - [`serial_send_repl-code.py`](serial_send_repl-code.py) 70 | 71 | ## Serial Read 72 | 73 | Read data sent by the board to the host computer. 74 | 75 | The board is sending sensor data to the serial port, trying the CPU temperature first, and sending a randomly fluctuating value if it was not available. You would of course change the `generate_some_data` function to read a sensor connected to or embeded on the board. 76 | 77 | The values are sent in a simple text format that can be displayed in the Mu graphics panel to draw a line graph over time. The host computer code just prints out the values. 78 | 79 | - [`serial_read-host.py`](serial_read-host.py) 80 | - [`serial_read_data-code.py`](serial_read_data-code.py) 81 | - [`serial_read_repl-code.py`](serial_read_repl-code.py) 82 | 83 | ## Serial Exchange 84 | 85 | Bidirectional serial communication. 86 | 87 | This shows a two-way communication using JSON data between the board and the host computer. The computer code uses the `asyncio` module for asynchronous execution and prompts the user for a color by name (list in the code) or `(r,g,b)` values and sends it to the board, which will display the color on it's built-in neopixel. The keyword `blink` will make the on-board monochrome LED (if any) blink once. 88 | 89 | The board sends button presses that the host prints out. It makes a best effort to detect an existing built-in button. For example the A button on the Circuit Playground boards, or the boot button on ESP boards. 90 | 91 | The board is expected to have a built-in neopixel. If it doesn't, replace it with external neopixels, a dotstar, a screen, etc. It might require to install the relevant libraires (`neopixel`, `adafruit_dotstar`, etc.) from the library bundle. 92 | 93 | - [`serial_exchange-host.py`](serial_exchange-host.py) 94 | - [`serial_exchange_data-code.py`](serial_exchange_data-code.py) 95 | - [`serial_exchange_repl-code.py`](serial_exchange_repl-code.py) 96 | -------------------------------------------------------------------------------- /usb_serial/boot.py: -------------------------------------------------------------------------------- 1 | import usb_cdc 2 | usb_cdc.enable(data=True) 3 | -------------------------------------------------------------------------------- /usb_serial/find_serial_port.py: -------------------------------------------------------------------------------- 1 | """ 2 | A simple script listing serial ports on the computer. 3 | This requires to install the adafruit-board-toolkit module. 4 | 5 | pip3 install adafruit-board-toolkit 6 | """ 7 | import adafruit_board_toolkit.circuitpython_serial 8 | 9 | ports = ( 10 | adafruit_board_toolkit.circuitpython_serial.repl_comports() 11 | +adafruit_board_toolkit.circuitpython_serial.data_comports() 12 | ) 13 | 14 | if ports: 15 | print(f"{len(ports)} serial Circuitpython ports found connected to the computer.") 16 | 17 | col1 = max([len(port.device) for port in ports]) + 1 18 | col1 = max(13, col1) 19 | 20 | col3 = max([len(port.product) for port in ports]) + 1 21 | col3 = max(7, col3) 22 | 23 | col4 = max([len(port.product+port.manufacturer) for port in ports]) + 3 24 | col4 = max(10, col4) 25 | 26 | print("") 27 | print(" Port Location".ljust(col1), "Type ", " Device") 28 | print("-" * col1, "-" * 5, "-" * col4) 29 | 30 | for port in adafruit_board_toolkit.circuitpython_serial.repl_comports(): 31 | print(f"{port.device:{col1}s} REPL {port.product} ({port.manufacturer})") 32 | 33 | print("") 34 | 35 | for port in adafruit_board_toolkit.circuitpython_serial.data_comports(): 36 | print(f"{port.device:{col1}s} DATA {port.product} ({port.manufacturer})") 37 | 38 | else: 39 | print("No serial port matching a Circuitpython board was found.") 40 | -------------------------------------------------------------------------------- /usb_serial/serial_exchange-host.py: -------------------------------------------------------------------------------- 1 | """ 2 | This script connects to the provided serial port. 3 | It sends color commands or other commands as a dictionary encoded in json. 4 | It receives button press from the serial port and displays what it gets. 5 | """ 6 | import argparse 7 | import json 8 | import math 9 | import re 10 | import serial 11 | import sys 12 | import time 13 | from aioconsole import ainput 14 | import asyncio 15 | 16 | parser = argparse.ArgumentParser() 17 | parser.add_argument("port", type=str, help="Serial port of the board", nargs=1) 18 | args = parser.parse_args() 19 | 20 | port = args.port 21 | channel = None 22 | 23 | color_names = { 24 | "aqua": (0, 255, 255), 25 | "black": (0, 0, 0), 26 | "blue": (0, 0, 255), 27 | "green": (0, 128, 0), 28 | "orange": (255, 165, 0), 29 | "pink": (240, 32, 128), 30 | "purple": (128, 0, 128), 31 | "red": (255, 0, 0), 32 | "white": (255, 255, 255), 33 | "yellow": (255, 255, 0), 34 | } 35 | 36 | print("Enter a color in the format: (rrr,ggg,bbb)") 37 | print("Example: (255,125,0) is orange") 38 | print(" ".join([name for name in color_names])) 39 | 40 | 41 | def setup_serial(): 42 | """ 43 | Helper to connect and reconnet to the serial channel 44 | """ 45 | global channel 46 | if channel is None: 47 | try: 48 | channel = serial.Serial(args.port[0]) 49 | channel.timeout = 0.05 50 | except Exception as ex: 51 | print(ex) 52 | channel = None 53 | return channel 54 | 55 | 56 | def error_serial(): 57 | """ 58 | Helper to handle serial errors 59 | """ 60 | global channel 61 | if channel != None: 62 | channel.close() 63 | channel = None 64 | print("Exception on read, did the board disconnect ?") 65 | 66 | 67 | async def read_serial(): 68 | """ 69 | Loop reading the serial IN and trea the message with a print 70 | """ 71 | print("read_serial") 72 | while True: 73 | setup_serial() 74 | line = None 75 | try: 76 | line = channel.readline()[:-2] 77 | except KeyboardInterrupt: 78 | print("KeyboardInterrupt - quitting") 79 | exit() 80 | except: 81 | error_serial() 82 | await asyncio.sleep(1) 83 | continue 84 | 85 | data = {} 86 | if line != b"": 87 | try: 88 | data = json.loads(line.decode("utf8")) 89 | except: 90 | data = {"raw": line.decode("utf8")} 91 | 92 | # receive button information and print it out 93 | if "buttons" in data: 94 | for button in data["buttons"]: 95 | if button["status"] == "RELEASED": 96 | print(f"Button {button['id']} clicked") 97 | 98 | # unidentified data sent by the board, helps with testing 99 | if "raw" in data: 100 | print(f"Board sent: {data['raw']}") 101 | 102 | await asyncio.sleep(0.1) 103 | 104 | 105 | async def read_color(): 106 | """ 107 | Single format for a color to send to the neopixel 108 | A color name sends that color to the neopixel 109 | "blink" makes the monochrome LED blink once 110 | """ 111 | data_out = [] 112 | data_in = await ainput("> ") 113 | data_in = data_in.strip() 114 | 115 | if re.match("^\((\d+),(\d+),(\d+)\)$", data_in): 116 | # color formatted as (rrr,ggg,bbb) 117 | m = re.match("^\((\d+),(\d+),(\d+)\)$", data_in) 118 | color = (int(m.group(1)), int(m.group(2)), int(m.group(3))) 119 | return json.dumps({"color": color}) 120 | 121 | elif data_in.lower() in color_names: 122 | # color name for simple tests 123 | color = color_names[data_in.lower()] 124 | return json.dumps({"color": color}) 125 | 126 | elif data_in == "blink": 127 | # simple blink command 128 | return json.dumps({"blink": 1}) 129 | 130 | else: 131 | # send anything anyway, helps testing the board side code 132 | return json.dumps({"raw": data_in}) 133 | 134 | # should not be reached 135 | return "\r\n" 136 | 137 | 138 | async def send_serial(): 139 | """ 140 | Loop on a data provider (here a user prompt) and send the data. 141 | """ 142 | print("send_serial") 143 | while True: 144 | setup_serial() 145 | color_string = await read_color() 146 | try: 147 | if color_string: 148 | channel.write((color_string + "\r\n").encode("utf8")) 149 | except Exception as ex: 150 | print(ex) 151 | error_serial() 152 | await asyncio.sleep(0.1) 153 | 154 | 155 | boo = asyncio.ensure_future(read_serial()) 156 | baa = asyncio.ensure_future(send_serial()) 157 | loop = asyncio.get_event_loop() 158 | loop.run_forever() 159 | -------------------------------------------------------------------------------- /usb_serial/serial_exchange_data-code.py: -------------------------------------------------------------------------------- 1 | """ 2 | Listens to the REPL port. 3 | Receives color information and displays it on the NEOPIXEL. 4 | Receives blink command and blinks once. 5 | Sends button press and release. 6 | 7 | 8 | This uses the optional second serial port available in Circuitpython 7.x 9 | Activate it in the boot.py file with the following code 10 | 11 | import usb_cdc 12 | usb_cdc.enable(console=True, data=True) 13 | 14 | Some boards might require disabling USB endpoints to enable the data port. 15 | """ 16 | 17 | import board 18 | import digitalio 19 | import json 20 | import time 21 | import usb_cdc 22 | 23 | ################################################################ 24 | # init board's LEDs for visual output 25 | # replace with your own pins and stuff 26 | ################################################################ 27 | 28 | pix = None 29 | if hasattr(board, "NEOPIXEL"): 30 | import neopixel 31 | pix = neopixel.NeoPixel(board.NEOPIXEL, 1) 32 | pix.fill((32, 16, 0)) 33 | else: 34 | print("This board is not equipped with a Neopixel.") 35 | 36 | led = None 37 | for ledname in ["LED", "L", "RED_LED", "BLUE_LED"]: 38 | if hasattr(board, ledname): 39 | led = digitalio.DigitalInOut(getattr(board, ledname)) 40 | led.switch_to_output() 41 | led.value = False 42 | print(ledname) 43 | break 44 | 45 | ################################################################ 46 | # init board's button for acknowledging user interaction 47 | # replace with your own pins and stuff 48 | # - the code tries its best to find a default button 49 | # - two fixed default values on some boards (for my tests) 50 | ################################################################ 51 | 52 | # boards with buttons: 53 | BUTTONS_CANDIDATES = [ 54 | "BUTTON", 55 | "BUTTON_USR", 56 | "BUTTON_USER", 57 | "BUTTON_A", 58 | "BUTTON_X", 59 | "BUTTON_UP", 60 | "BUTTON1", 61 | "BUTTON_1", 62 | "BOOT", 63 | "BOOT0", 64 | ] 65 | for btn_pin in BUTTONS_CANDIDATES: 66 | if hasattr(board, btn_pin): 67 | button = digitalio.DigitalInOut(getattr(board, btn_pin)) 68 | button.switch_to_input(digitalio.Pull.UP) 69 | button_id = btn_pin 70 | break 71 | else: # no break 72 | """ 73 | Change the BUTTON pin to match your setup, and button_id 74 | """ 75 | # this is an example for the pico 76 | if hasattr(board, "GP3"): 77 | pin = board.GP3 78 | # this is an example for most boards/feathers 79 | elif hasattr(board, "A2"): 80 | pin = board.A2 81 | # pin = board.SOMEPIN 82 | button = digitalio.DigitalInOut(pin) 83 | button.switch_to_input(digitalio.Pull.UP) 84 | button_id = repr(pin).replace("board.", "") 85 | 86 | ################################################################ 87 | # prepare values for the loop 88 | ################################################################ 89 | 90 | usb_cdc.data.timeout = 0.1 91 | if button: 92 | button_past = button.value 93 | 94 | ################################################################ 95 | # loop-y-loop 96 | ################################################################ 97 | 98 | while True: 99 | # add to that dictionary to send the data at the end of the loop 100 | data_out = {} 101 | 102 | # read the secondary serial line by line when there's data 103 | if usb_cdc.data.in_waiting > 0: 104 | data_in = usb_cdc.data.readline() 105 | 106 | # try to convert the data to a dict (with JSON) 107 | data = None 108 | if len(data_in) > 0: 109 | try: 110 | data = json.loads(data_in) 111 | except ValueError: 112 | data = {"raw": data_in.decode()} 113 | 114 | # interpret 115 | if isinstance(data, dict): 116 | 117 | # change the color of the neopixel 118 | if "color" in data: 119 | print(data["color"]) 120 | if pix is not None: 121 | pix.fill(data["color"]) 122 | 123 | # blinking without sleep is left as an exercise 124 | if "blink" in data and led is not None: 125 | led.value = True 126 | time.sleep(0.25) 127 | led.value = False 128 | time.sleep(0.25) 129 | 130 | # read the buttons and send the info to the serial 131 | if button and button_past != button.value: 132 | button_past = button.value 133 | if not button.value: 134 | data_out["buttons"] = [{"status": "PRESSED", "id": button_id}] 135 | else: 136 | data_out["buttons"] = [{"status": "RELEASED", "id": button_id}] 137 | 138 | # send the data out once everything to be sent is gathered 139 | if data_out: 140 | print(json.dumps(data_out)) 141 | usb_cdc.data.write(json.dumps(data_out).encode() + b"\r\n") 142 | 143 | time.sleep(0.1) 144 | -------------------------------------------------------------------------------- /usb_serial/serial_exchange_repl-code.py: -------------------------------------------------------------------------------- 1 | """ 2 | Listens to the REPL port, without using the usb_cdc module. 3 | Receives color information and displays it on the NEOPIXEL. 4 | Receives blink command and blinks once. 5 | Sends button press and release. 6 | """ 7 | 8 | import board 9 | import digitalio 10 | import json 11 | import time 12 | import supervisor 13 | import sys 14 | 15 | ################################################################ 16 | # select the serial REPL port 17 | ################################################################ 18 | 19 | serial = sys.stdin 20 | 21 | ################################################################ 22 | # init board's LEDs for visual output 23 | # replace with your own pins and stuff 24 | ################################################################ 25 | 26 | pix = None 27 | if hasattr(board, "NEOPIXEL"): 28 | import neopixel 29 | 30 | pix = neopixel.NeoPixel(board.NEOPIXEL, 1) 31 | pix.fill((32, 16, 0)) 32 | else: 33 | print("This board is not equipped with a Neopixel.") 34 | 35 | led = None 36 | for ledname in ["LED", "L", "RED_LED", "BLUE_LED"]: 37 | if hasattr(board, ledname): 38 | led = digitalio.DigitalInOut(getattr(board, ledname)) 39 | led.switch_to_output() 40 | led.value = False 41 | print(ledname) 42 | break 43 | 44 | ################################################################ 45 | # init board's button for acknowledging user interaction 46 | # replace with your own pins and stuff 47 | # - the code tries its best to find a default button 48 | # - two fixed default values on some boards (for my tests) 49 | ################################################################ 50 | 51 | # boards with buttons: 52 | BUTTONS_CANDIDATES = [ 53 | "BUTTON", 54 | "BUTTON_USR", 55 | "BUTTON_USER", 56 | "BUTTON_A", 57 | "BUTTON_X", 58 | "BUTTON_UP", 59 | "BUTTON1", 60 | "BUTTON_1", 61 | "BOOT", 62 | "BOOT0", 63 | ] 64 | for btn_pin in BUTTONS_CANDIDATES: 65 | if hasattr(board, btn_pin): 66 | button = digitalio.DigitalInOut(getattr(board, btn_pin)) 67 | button.switch_to_input(digitalio.Pull.UP) 68 | button_id = btn_pin 69 | break 70 | else: # no break 71 | """ 72 | Change the BUTTON pin to match your setup, and button_id 73 | """ 74 | # this is an example for the pico 75 | if hasattr(board, "GP3"): 76 | pin = board.GP3 77 | # this is an example for most boards/feathers 78 | elif hasattr(board, "A2"): 79 | pin = board.A2 80 | # pin = board.SOMEPIN 81 | button = digitalio.DigitalInOut(pin) 82 | button.switch_to_input(digitalio.Pull.UP) 83 | button_id = repr(pin).replace("board.", "") 84 | 85 | ################################################################ 86 | # prepare values for the loop 87 | ################################################################ 88 | 89 | if button: 90 | button_past = button.value 91 | 92 | ################################################################ 93 | # loop-y-loop 94 | ################################################################ 95 | 96 | while True: 97 | # add to that dictionary to send the data at the end of the loop 98 | data_out = {} 99 | 100 | # read the REPL serial line by line when there's data 101 | if supervisor.runtime.serial_bytes_available: 102 | data_in = serial.readline() 103 | 104 | # try to convert the data to a dict (with JSON) 105 | data = None 106 | if len(data_in) > 0: 107 | try: 108 | data = json.loads(data_in) 109 | except ValueError: 110 | data = {"raw": data_in} 111 | 112 | # interpret 113 | if isinstance(data, dict): 114 | 115 | # change the color of the neopixel 116 | if "color" in data: 117 | print(data["color"]) 118 | if pix is not None: 119 | pix.fill(data["color"]) 120 | 121 | # blinking without sleep is left as an exercise 122 | if "blink" in data and led is not None: 123 | led.value = True 124 | time.sleep(0.25) 125 | led.value = False 126 | time.sleep(0.25) 127 | 128 | # read the buttons and send the info to the serial 129 | if button and button_past != button.value: 130 | button_past = button.value 131 | if not button.value: 132 | data_out["buttons"] = [{"status": "PRESSED", "id": button_id}] 133 | else: 134 | data_out["buttons"] = [{"status": "RELEASED", "id": button_id}] 135 | 136 | # send the data out once everything to be sent is gathered 137 | if data_out: 138 | print(json.dumps(data_out)) 139 | 140 | time.sleep(0.1) 141 | -------------------------------------------------------------------------------- /usb_serial/serial_read-host.py: -------------------------------------------------------------------------------- 1 | """ 2 | A simple, string based (no json), serial read. 3 | Reads the data and interprets it as a float reprensenting the temperature. 4 | """ 5 | import argparse 6 | import re 7 | import serial 8 | import time 9 | 10 | parser = argparse.ArgumentParser() 11 | parser.add_argument("port", type=str, help="Serial port of the board", nargs=1) 12 | args = parser.parse_args() 13 | port = args.port 14 | 15 | channel = serial.Serial(args.port[0]) 16 | channel.timeout = 0.05 17 | 18 | while True: 19 | line = None 20 | try: 21 | line = channel.readline()[:-2] 22 | except KeyboardInterrupt: 23 | print("KeyboardInterrupt - quitting") 24 | exit() 25 | # NOTE: does not catch serial errors 26 | 27 | if line: 28 | try: 29 | # remove the parenthesis 30 | m = line.decode("utf8")[1:-1] 31 | temperature = float(m) 32 | print(f"Temperature: {temperature:.2f}°C") 33 | except ValueError: 34 | # ignore an error if float() fails 35 | # (with the REPL, you get board reload messages and such) 36 | pass 37 | 38 | time.sleep(0.1) 39 | -------------------------------------------------------------------------------- /usb_serial/serial_read_data-code.py: -------------------------------------------------------------------------------- 1 | """ 2 | Print a number to the REPL. 3 | Format that can be read by the Mu grapher. 4 | Using CPU temperature as a sample data. 5 | 6 | 7 | This uses the optional second serial port available in Circuitpython 7.x 8 | Activate it in the boot.py file with the following code 9 | 10 | import usb_cdc 11 | usb_cdc.enable(console=True, data=True) 12 | 13 | Some boards might require disabling USB endpoints to enable the data port. 14 | """ 15 | 16 | import microcontroller 17 | import time 18 | import random 19 | import usb_cdc 20 | 21 | 22 | def generate_some_data(): 23 | """ 24 | dummy data, replace that by reading a sensor 25 | or buttons, or whatever 26 | """ 27 | try: # CPU temperature 28 | return microcontroller.cpu.temperature 29 | except Exception: 30 | pass 31 | # dummy data if temperature not available 32 | past_temp = past_temp + random.random() - 0.5 33 | return random.randint(2000, 2500) / 100 34 | 35 | 36 | while True: 37 | data_out = {} 38 | temperature = generate_some_data() 39 | temperature_str = f"({temperature})\r\n".encode() 40 | # prints with () on a solo line to satisfy the Mu grapher 41 | usb_cdc.data.write(temperature_str) 42 | # optionally print to the REPL channel for debug purposes 43 | # print("DEBUG - sending:", temperature_str) 44 | # change the sleep time to match your needs 45 | time.sleep(1) 46 | -------------------------------------------------------------------------------- /usb_serial/serial_read_repl-code.py: -------------------------------------------------------------------------------- 1 | """ 2 | Print a number to the REPL. 3 | Format that can be read by the Mu grapher. 4 | Using CPU temperature as a sample data. 5 | """ 6 | import json 7 | import microcontroller 8 | import time 9 | import random 10 | 11 | 12 | def generate_some_data(): 13 | """ 14 | dummy data, replace that by reading a sensor 15 | or buttons, or whatever 16 | """ 17 | try: # CPU temperature 18 | return microcontroller.cpu.temperature 19 | except Exception: 20 | pass 21 | # dummy data if temperature not available 22 | past_temp = past_temp + random.random() - 0.5 23 | return random.randint(2000, 2500) / 100 24 | 25 | 26 | while True: 27 | data_out = {} 28 | temperature = generate_some_data() 29 | # prints with () on a solo line to satisfy the Mu grapher 30 | print(f"({temperature})") 31 | # change the sleep time to match your needs 32 | time.sleep(1) 33 | -------------------------------------------------------------------------------- /usb_serial/serial_send-host.py: -------------------------------------------------------------------------------- 1 | """ 2 | A simple, json based, serial write. 3 | Loops on a color wheel to send color info to the board. 4 | """ 5 | import argparse 6 | import json 7 | import serial 8 | import time 9 | 10 | parser = argparse.ArgumentParser() 11 | parser.add_argument("port", type=str, help="Serial port of the board", nargs=1) 12 | args = parser.parse_args() 13 | port = args.port 14 | 15 | channel = serial.Serial(args.port[0]) 16 | channel.timeout = 0.05 17 | 18 | 19 | def wheel(wheel_pos): 20 | """Color wheel to allow for cycling through the rainbow of RGB colors.""" 21 | wheel_pos = wheel_pos % 256 22 | if wheel_pos < 85: 23 | return 255 - wheel_pos * 3, 0, wheel_pos * 3 24 | elif wheel_pos < 170: 25 | wheel_pos -= 85 26 | return 0, wheel_pos * 3, 255 - wheel_pos * 3 27 | else: 28 | wheel_pos -= 170 29 | return wheel_pos * 3, 255 - wheel_pos * 3, 0 30 | 31 | 32 | while True: 33 | for x in range(256): 34 | color = wheel((x * 8) % 256) 35 | 36 | print(color) 37 | channel.write(json.dumps({"color": color}).encode()) 38 | channel.write(b"\r\n") 39 | 40 | time.sleep(0.02) 41 | -------------------------------------------------------------------------------- /usb_serial/serial_send_data-code.py: -------------------------------------------------------------------------------- 1 | """ 2 | Read the Serial port to receive color data for the neopixel. 3 | 4 | 5 | This uses the optional second serial port available in Circuitpython 7.x 6 | Activate it in the boot.py file with the following code 7 | 8 | import usb_cdc 9 | usb_cdc.enable(console=True, data=True) 10 | 11 | Some boards might require disabling USB endpoints to enable the data port. 12 | """ 13 | 14 | import board 15 | import json 16 | import time 17 | import usb_cdc 18 | 19 | ################################################################ 20 | # select the serial Data port 21 | ################################################################ 22 | 23 | serial = usb_cdc.data 24 | 25 | ################################################################ 26 | # init board's LEDs for visual output 27 | # replace with your own pins and stuff 28 | ################################################################ 29 | 30 | pix = None 31 | if hasattr(board, "NEOPIXEL"): 32 | import neopixel 33 | pix = neopixel.NeoPixel(board.NEOPIXEL, 1) 34 | pix.fill((32, 16, 0)) 35 | else: 36 | print("This board is not equipped with a Neopixel.") 37 | 38 | ################################################################ 39 | # loop-y-loop 40 | ################################################################ 41 | 42 | while True: 43 | # read the secondary serial line by line when there's data 44 | # note that this assumes that the host always sends a full line 45 | if serial.in_waiting > 0: 46 | data_in = serial.readline() 47 | 48 | # try to convert the data to a dict (with JSON) 49 | data = None 50 | if data_in: 51 | try: 52 | data = json.loads(data_in) 53 | except ValueError: 54 | data = {"raw": data_in} 55 | 56 | # by using a dictionary, you can add any entry and data into it 57 | # to transmit any command you want and parse it here 58 | if isinstance(data, dict): 59 | 60 | # change the color of the neopixel 61 | if "color" in data: 62 | print("Color received:", data["color"]) 63 | if pix is not None: 64 | pix.fill(data["color"]) 65 | 66 | # this is where the rest of your code goes 67 | # if the code does a lot you don't need a call to sleep, but if possible 68 | # it's good to have the microcontroller sleep from time to time so it's 69 | # not constantly chugging 70 | time.sleep(0.01) 71 | -------------------------------------------------------------------------------- /usb_serial/serial_send_repl-code.py: -------------------------------------------------------------------------------- 1 | """ 2 | Read the REPL to receive color data for the neopixel. 3 | Not using the usb_cdc module. 4 | """ 5 | 6 | import board 7 | import json 8 | import time 9 | import supervisor 10 | import sys 11 | 12 | ################################################################ 13 | # select the serial REPL port 14 | ################################################################ 15 | 16 | serial = sys.stdin 17 | 18 | ################################################################ 19 | # init board's LEDs for visual output 20 | # replace with your own pins and stuff 21 | ################################################################ 22 | 23 | pix = None 24 | if hasattr(board, "NEOPIXEL"): 25 | import neopixel 26 | pix = neopixel.NeoPixel(board.NEOPIXEL, 1) 27 | pix.fill((32, 16, 0)) 28 | else: 29 | print("This board is not equipped with a Neopixel.") 30 | 31 | ################################################################ 32 | # loop-y-loop 33 | ################################################################ 34 | 35 | while True: 36 | # read the REPL serial line by line when there's data 37 | # note that this assumes that the host always sends a full line 38 | if supervisor.runtime.serial_bytes_available: 39 | data_in = serial.readline() 40 | 41 | # try to convert the data to a dict (with JSON) 42 | data = None 43 | if data_in: 44 | try: 45 | data = json.loads(data_in) 46 | except ValueError: 47 | data = {"raw": data_in} 48 | 49 | # by using a dictionary, you can add any entry and data into it 50 | # to transmit any command you want and parse it here 51 | if isinstance(data, dict): 52 | 53 | # change the color of the neopixel 54 | if "color" in data: 55 | print("Color received:", data["color"]) 56 | if pix is not None: 57 | pix.fill(data["color"]) 58 | 59 | # this is where the rest of your code goes 60 | # if the code does a lot you don't need a call to sleep, but if possible 61 | # it's good to have the microcontroller sleep from time to time so it's 62 | # not constantly chugging 63 | time.sleep(0.01) 64 | --------------------------------------------------------------------------------