Badge hardware overview
LEDs
You can use the board's LEDs with the tildagonos package:
Example
This example app lights up the LEDs closest to a button when a button is pressed.
import app
from app_components import clear_background
from events.input import Buttons, BUTTON_TYPES
from tildagonos import tildagonos
from system.eventbus import eventbus
from system.patterndisplay.events import PatternDisable
class LEDExampleApp(app.App):
def __init__(self):
self.button_states = Buttons(self)
# This disables the patterndisplay system module, which does the
# default colour spinny thing
eventbus.emit(PatternDisable())
def update(self, delta):
if self.button_states.get(BUTTON_TYPES["RIGHT"]):
tildagonos.leds[2] = (255, 0, 0)
tildagonos.leds[3] = (255, 0, 0)
elif self.button_states.get(BUTTON_TYPES["LEFT"]):
tildagonos.leds[8] = (0, 255, 0)
tildagonos.leds[9] = (0, 255, 0)
elif self.button_states.get(BUTTON_TYPES["UP"]):
tildagonos.leds[12] = (0, 0, 255)
tildagonos.leds[1] = (0, 0, 255)
elif self.button_states.get(BUTTON_TYPES["DOWN"]):
tildagonos.leds[6] = (255, 255, 0)
tildagonos.leds[7] = (255, 255, 0)
elif self.button_states.get(BUTTON_TYPES["CANCEL"]):
tildagonos.leds[10] = (0, 255, 255)
tildagonos.leds[11] = (0, 255, 255)
elif self.button_states.get(BUTTON_TYPES["CONFIRM"]):
tildagonos.leds[4] = (255, 0, 255)
tildagonos.leds[5] = (255, 0, 255)
else:
for i in range(0, 12):
tildagonos.leds[i+1] = (0, 0, 0)
tildagonos.leds.write()
def draw(self, ctx):
clear_background(ctx)
__app_export__ = LEDExampleApp
You can see a more comprehensive example in the intro_app.py.
Usage
To use the LEDs:
- Import the
tildagonospackage:
from tildagonos import tildagonos
- Enable the LEDs (this step is generally optional, but needed if running outside of an app or from repl):
tildagonos.set_led_power(True)
- Set the LEDs by assigning a colour tuple to one of the 12 LEDs:
tildagonos.leds[2] = (255, 0, 0)
- Write the updated values to the LEDs
tildagonos.leds.write()
Buttons
You can use the board's Buttons with the events.input package:
Example
This example app lights up the LEDs closest to a button when a button is pressed.
import app
from app_components import clear_background
from events.input import Buttons, BUTTON_TYPES
from tildagonos import tildagonos
class LEDExampleApp(app.App):
def __init__(self):
self.button_states = Buttons(self)
def update(self, delta):
if self.button_states.get(BUTTON_TYPES["RIGHT"]):
tildagonos.leds[2] = (255, 0, 0)
tildagonos.leds[3] = (255, 0, 0)
elif self.button_states.get(BUTTON_TYPES["LEFT"]):
tildagonos.leds[8] = (0, 255, 0)
tildagonos.leds[9] = (0, 255, 0)
elif self.button_states.get(BUTTON_TYPES["UP"]):
tildagonos.leds[12] = (0, 0, 255)
tildagonos.leds[1] = (0, 0, 255)
elif self.button_states.get(BUTTON_TYPES["DOWN"]):
tildagonos.leds[6] = (255, 255, 0)
tildagonos.leds[7] = (255, 255, 0)
elif self.button_states.get(BUTTON_TYPES["CANCEL"]):
tildagonos.leds[10] = (0, 255, 255)
tildagonos.leds[11] = (0, 255, 255)
elif self.button_states.get(BUTTON_TYPES["CONFIRM"]):
tildagonos.leds[4] = (255, 0, 255)
tildagonos.leds[5] = (255, 0, 255)
else:
for i in range(0, 12):
tildagonos.leds[i+1] = (0, 0, 0)
tildagonos.leds.write()
def draw(self, ctx):
clear_background(ctx)
__app_export__ = LEDExampleApp
Usage
To use the buttons:
- Import the
events.inputpackage:
from events.input import Buttons, BUTTON_TYPES
- Initialize a variable to hold the
button_statesin the__init__method of your app:
def __init__(self):
self.button_states = Buttons(self)
- Check for
button_state. You can access the differentBUTTON_TYPESwith the names"UP","RIGHT","CONFIRM","DOWN","LEFT","CANCEL":
if self.button_states.get(BUTTON_TYPES["RIGHT"]):
# do something
- The
button_statewill continue returning true while the button is pressed. If you want to only do something once you can clear thebutton_stateonce the event has fired once:
if self.button_states.get(BUTTON_TYPES["RIGHT"]):
self.button_states.clear()
# do something
Custom usage
You can also use the ButtonDownEvent and the ButtonUpEvent directly with an event handler that you register on the eventbus.
-
Import the
events.inputpackage:from events.input import \ Button, BUTTON_TYPES, ButtonDownEvent, ButtonUpEvent from system.eventbus import eventbus -
Add a method to handle the event:
def _handle_buttondown(self, event: ButtonDownEvent): if BUTTON_TYPES["CANCEL"] in event.button: self._cleanup() # perform other actions as needed if BUTTON_TYPES["CONFIRM"] in event.button: self._cleanup() # perform other actions as needed -
Add an event handler in the
__init__method of your app with the event (ButtonDownEventorButtonUpEvent) and a function that should be called when the event happens. Depending on whether the event handler is a synchronous or asynchronous method callon()oron_async:def __init__(self): eventbus.on(ButtonDownEvent, self._handle_buttondown, self.app) # eventbus.on_async(ButtonDownEvent, self._handle_buttondown, self.app) -
Remove the event handler when the app is minimised or closed:
def _cleanup(self): eventbus.remove(ButtonDownEvent, self._handle_buttondown, self.app)Warning
Make sure you remove the event handler when the app is minimised or closed!
You can see a more comprehensive example in dialog.py.
Pins
Each hexpansion has:
- 5 low speed (LS) external GPIO (eGPIO) pins which you can use with the
tildagon.Pin - 4 high speed (HS) GPIO pins which you can use with the
machine.Pinlibrary (12, 13, 18, 19) - 6 GND pins (1, 10, 11, 14, 17, 20)
- 1 pin that detects insertion (6)
- 2 3.3V Power pins (15, 16)
- 1 SDA pin (Data) (4)
- 1 SCL pin (Clock) (5)
Warning
eGPIO does not work correctly in version 1.6.0.
Example
Select a hexpansion port, then press the UP button to toggle the eGPIO value ls_1 or the DOWN button to toggle the GPIO value hs_1. You can see how to access an toggle the Pins in the update methods:
import app
from system.hexpansion.config import HexpansionConfig
from app_components import clear_background, Menu
from app_components.tokens import colors
from events.input import Buttons, BUTTON_TYPES
from math import pi
menu_items = ["1", "2", "3", "4", "5", "6"]
class ExampleApp(app.App):
def __init__(self):
self.menu = Menu(self, menu_items, select_handler=self.select_handler,
back_handler=self.back_handler)
self.hexpansion_config = None
self.button_states = Buttons(self)
self.pins = None
def select_handler(self, item, idx):
self.hexpansion_config = HexpansionConfig(idx+1)
def back_handler(self):
if self.hexpansion_config:
self.hexpansion_config = None
self.pins = None
else:
self.minimise()
def update(self, delta):
if self.hexpansion_config is None:
self.menu.update(delta)
if self.hexpansion_config and not self.pins:
self.pins = {}
# eGPIO pins
self.pins["ls_1"] = self.hexpansion_config.ls_pin[0]
self.pins["ls_1"].init(self.pins["ls_1"].OUT)
# GPIO pins
self.pins["hs_1"] = self.hexpansion_config.pin[0]
# All HS pins start in low mode. Initialize them as follows:
self.pins["hs_1"].init(self.pins["hs_1"].OUT)
if self.pins and self.button_states.get(BUTTON_TYPES["UP"]):
self.button_states.clear()
# Toggle pin ls_1
if self.pins["ls_1"].value():
self.pins["ls_1"].off()
else:
self.pins["ls_1"].on()
if self.pins and self.button_states.get(BUTTON_TYPES["DOWN"]):
self.button_states.clear()
# Toggle pin hs_1
if self.pins["hs_1"].value():
self.pins["hs_1"].off()
else:
self.pins["hs_1"].on()
def draw(self, ctx):
clear_background(ctx)
if self.hexpansion_config is None:
self.menu.draw(ctx)
# Drawing a shape as a port indicator.
ctx.save()
ctx.font_size = 22
ctx.rgb(*colors["dark_green"]).rectangle(
-120, -120, 240, 100).fill()
ctx.rgb(*colors["dark_green"]).rectangle(
-120, 20, 240, 100).fill()
rotation_angle = (self.menu.position - 1) * pi/3
ctx.rgb(*colors["mid_green"]).rotate(rotation_angle).rectangle(
80, -120, 40, 240).fill()
prompt_message = "Select hexpansion port:"
ctx.rgb(1, 1, 1).rotate(-rotation_angle).move_to(
0, -45).text(prompt_message)
ctx.restore()
elif self.pins:
ctx.save()
ctx.font_size = 24
msg = "Hexpansion in port " + str(self.hexpansion_config.port)
msg_width = ctx.text_width(msg)
ctx.rgb(1, 1, 1).move_to(-msg_width/2, 0).text(msg)
# draw pin values
pin_ls_1 = "LS_1: " + str(self.pins["ls_1"].value())
msg_width = ctx.text_width(pin_ls_1)
ctx.rgb(1, 1, 1).move_to(-msg_width/2, -90).text(pin_ls_1)
pin_hs_1 = "HS_1: " + str(self.pins["hs_1"].value())
msg_width = ctx.text_width(pin_hs_1)
ctx.rgb(1, 1, 1).move_to(-msg_width/2, 90).text(pin_hs_1)
ctx.restore()
__app_export__ = ExampleApp
A more elaborate example is this breadboard tester app which allows you to toggle all GPIO and eGPIO pins.
Methods
GPIO pins support the standard machine.Pin methods. These are referred to as high speed pins as they are connected directly to the ESP32.
eGPIO pins are referred to as low speed pins as they are connected to a port expander by I2C and support the following methods:
| Method | Description | Arguments | Returns |
|---|---|---|---|
on() | Drive the pin high. | None | None |
off() | Drive the pin low. | None | None |
value() | If provided with a value, sets the Pin value. If called without value, gets the Pin value. | None | value: The pin value. If called without a value. |
duty() | When in PWM mode, sets the duty cycle of the open collector output | duty: The duty cycle, 0-255 | None |
Usage
To use the Pins:
- Access the pins from the
HexpansionConfigobject and for GPIO pins, initialize the pins:
# eGPIO pins
self.pins["ls_1"] = self.hexpansion_config.ls_pin[0]
self.pins["ls_2"] = self.hexpansion_config.ls_pin[1]
# GPIO pins
self.pins["hs_1"] = self.hexpansion_config.pin[0]
# All pins start in inputs mode. Initialize them as follows:
self.pins["hs_1"].init(self.pins["hs_1"].OUT)
self.pins["ls_1"].init(self.pins["ls_1"].OUT)
# Only LS pins support the PWM function directly.
self.pins["ls_2"].init(self.pins["ls_2"].PWM)
- Call one of the methods, for example
off(),on()or for LS pinsduty().
self.pins["hs_1"].off()
self.pins["ls_1"].on()
self.pins["ls_2"].duty()
IMU
The IMU device is a highly integrated, low power inertial measurement unit (IMU) that combines precise acceleration and angular rate (gyroscopic) measurement. The triple axis device has been configured to measure 2g and 2 degree per second ranges. It also has a step count function intended for wrist mounted applications.
More information about the sensor
For more information see Inertial Measurement Unit BMI270 | Bosch Sensortec (bosch-sensortec.com).
import app
import imu
from events.input import Buttons, BUTTON_TYPES
class ExampleApp(app.App):
def __init__(self):
self.button_states = Buttons(self)
self.acc_read = None
def update(self, delta):
if self.button_states.get(BUTTON_TYPES["CANCEL"]):
self.button_states.clear()
self.minimise()
else:
self.acc_read = imu.acc_read()
def draw(self, ctx):
ctx.save()
ctx.rgb(0.2, 0, 0).rectangle(-120, -120, 240, 240).fill()
if self.acc_read:
ctx.rgb(1, 0, 0).move_to(-80, -40).text(
"accel x,y,z:\n{},\n{},\n{}".format(
self.acc_read[0], self.acc_read[1], self.acc_read[2]))
else:
ctx.rgb(1, 0, 0).move_to(-80, 0).text("no readings yet")
ctx.restore()
__app_export__ = ExampleApp
The following example app measures steps as you walk around with your badge:
import app
import imu
from events.input import Buttons, BUTTON_TYPES
class ExampleApp(app.App):
def __init__(self):
self.button_states = Buttons(self)
self.acc_read = None
self.steps_read = None
def update(self, delta):
if self.button_states.get(BUTTON_TYPES["CANCEL"]):
self.button_states.clear()
self.minimise()
else:
self.steps_read = imu.step_counter_read()
def draw(self, ctx):
ctx.save()
ctx.rgb(0.2, 0, 0).rectangle(-120, -120, 240, 240).fill()
if self.steps_read:
ctx.rgb(1, 0, 0).move_to(-80, -40).text(
"steps:\n{}\n".format(self.steps_read))
else:
ctx.rgb(1, 0, 0).move_to(-80, 0).text("no readings yet")
ctx.restore()
__app_export__ = ExampleApp
Methods
In order to support alternative imu devices a base set of functionality is provided along with i2c access to the device for apps to add functionality. An identify function is provided to determine which device is on the badge. Only the prototype badges have a different imu, but this may change in the future.
| Method | Description | Arguments | Returns |
|---|---|---|---|
acc_read() | Get the accelerometer data. | None | (x,y,z): The accelerometer data as a tuple of floats (m/s^2). |
gyro_read() | Get the gyro data. | None | (x,y,z): The gyro data as a tuple of floats (d/s). |
step_counter_read() | Get the step count | None | count: The step count |
temperature_read() | Get the temperature | None | temerature: Temperature (°). |
id() | Get the device id | None | id: string id of device. |
readfrom() | Read from a device register | (register address, length): address to start read from, length of read | data: bytes of the data or -ve error code. |
writeto() | Write data to a device register | (register address, bytes): address to start write to, data. | error: or None if ok. |
Usage
To use the imu package:
- Import the
powerpackage:
import power
- Call one of the methods, for example
imu.acc_read().
imu.acc_read()
Power
The power package allows you to perform multiple battery related functions, like powering off the badge or getting the battery level.
Example
This example shows you how to power off the badge when the UP button is pressed. Note that your badge will only power off if it is not connected to power through USB. It technically only disconnects the battery.
import power
import app
from events.input import Buttons, BUTTON_TYPES
class ExampleApp(app.App):
def __init__(self):
self.button_states = Buttons(self)
def update(self, delta):
if self.button_states.get(BUTTON_TYPES["CANCEL"]):
self.minimise()
if self.button_states.get(BUTTON_TYPES["UP"]):
power.Off()
def draw(self, ctx):
ctx.save()
ctx.rgb(0.2, 0, 0).rectangle(-120, -120, 240, 240).fill()
ctx.rgb(1, 0, 0).move_to(-80, 0).text("Press up to\npower off")
ctx.restore()
__app_export__ = ExampleApp
Usage
To use the power package:
- Import the
powerpackage:
import power
- Call one of the methods, for example
power.Off().
power.Off()
Methods
| Method | Description | Arguments | Returns |
|---|---|---|---|
Off() | Turn off the battery. When the usb is disconnected the badge will turn off. | None | None |
BatteryChargeState() | Status of the Battery charing cycle. | None | status (string): "Not Charging", "Pre-Charging", "Fast Charging", "Terminated". |
BatteryLevel() | Return the battery charge level. | None. | level (float): Battery charge level as a float representing the charge percentage. |
Enable5V() | Enable the usb out 5V supply. | enable (Boolean): whether to enable or disable the 5V supply. | None. |
Fault() | Get the PMIC fault status. | None. | - fault: The battery fault. Battery: Normal, Over Voltage; Boost: Normal, Overloaded or low battery; Charge: Normal, Input Fault, Safety Timer expired |
SupplyCapabilities() | Read the capabilities of the power supply. | None. | capabilities (List): List of tuples containing supply type, voltage (V) and current (mA). |
Icharge() | Get the battery charge current | None. | current (float): The charge current in A. |
Vbat() | Get the battery voltage. | None. | voltage (float): The battery voltage in V. |
Vin() | Get the input voltage. | None. | voltage (float): The input voltage in V. |
Vsys() (float) | Get the system voltage. | None. | voltage (float): Get the system voltage in V. |
Events
If you want to use the EventBus, you can use it with the following events:
RequestChargeEventRequestBatFaultEventRequestBoostFaultEventRequestChargeFaultEventRequestTimeoutFaultEventRequestHostAttachEventRequestHostDetachEventRequestDeviceAttachEventRequestDeviceDetachEventRequestLanyardAttachEventRequestLanyardDetachEvent
To use events with the EventBus, import the following package:
from system.power import events
You can also use the following hexpansion-related events
HexpansionRemovalEventHexpansionInsertionEventHexpansionFormattedEventHexpansionMountedEvent
To use these events with the EventBus, import the following package:
from system.hexpansion.events import \
HexpansionRemovalEvent, HexpansionInsertionEvent
Then attach a function to the event as following:
class ChargeApp(app.App):
def __init__(self):
eventbus.on(events.RequestChargeEvent, self._handle_charge_event, self)
def _handle_charge_event(self, event: RequestChargeEvent):
# do something
return None
Power Delivery (PD)
The PD module allows sending and receiving PD messages on the USB in and out ports. The message header is handled entirely by the badge for Vendor messages and will automatically fill the fields of the header relating to the physical layer. This leaves the user to only fill the message type and number of objects for the Prime messages used to communicate with the cables plugs. The fusb302b part that the badge uses supports USB PD 2.0, Version 1.1 and uses the number of objects field of the header to determine message length, preventing us from sending extended messages. Each message that it sends must have a 2 byte header followed by 0-7 4 byte data objects. To assist with this the badge will pad the messages sent to the next 4 byte boundary, so messages received from another badge may have additional 0s appended to the end.
Example
import settings
from app import App
from app_components import Menu, clear_background
from system.patterndisplay.events import PatternReload
from system.eventbus import eventbus
from system.power import events
from pd import Host, Device
main_menu_items = ["rainbow", "cylon", "flash", "off"]
class LedSyncApp(App):
def __init__(self):
self.usb_in = Device()
self.usb_out = Host()
self.menu = Menu(
self,
main_menu_items,
select_handler=self.select_handler,
back_handler=self.back_handler,
)
eventbus.on(events.VendorMsgDevRxEvent, self._handle_dev_msg, self)
eventbus.on(events.VendorMsgHostRxEvent, self._handle_host_msg, self)
eventbus.on(
events.BadgeAsHostAttachEvent, self._handle_host_detect, self
)
self.state = main_menu_items[0]
def _handle_dev_msg(self, event: VendorMsgDevRxEvent):
msg = self.usb_in.get_vendor_msg()
if msg is not None:
settings.set("pattern", main_menu_items[msg[0]])
eventbus.emit(PatternReload())
self.state = main_menu_items[msg[0]]
if self.usb_out.badge_connected():
self.usb_out.send_vendor_msg(msg)
def _handle_host_msg(self, event: VendorMsgHostRxEvent):
msg = self.usb_out.get_vendor_msg()
if msg is not None:
settings.set("pattern", main_menu_items[msg[0]])
eventbus.emit(PatternReload())
self.state = main_menu_items[msg[0]]
if self.usb_in.badge_connected():
self.usb_in.send_vendor_msg(msg)
def _handle_host_detect(self, event: BadgeAsHostAttachEvent):
idx = main_menu_items.index(self.state)
self.usb_out.send_vendor_msg(bytearray([idx]))
def select_handler(self, item, item_idx):
self.state = main_menu_items[item_idx]
settings.set("pattern", main_menu_items[item_idx])
eventbus.emit(PatternReload())
if self.usb_in.badge_connected():
self.usb_in.send_vendor_msg(bytearray([item_idx]))
if self.usb_out.badge_connected():
self.usb_out.send_vendor_msg(bytearray([item_idx]))
def back_handler(self):
self.minimise()
def draw(self, ctx):
clear_background(ctx)
self.menu.draw(ctx)
def update(self, delta):
self.menu.update(delta)
__app_export__ = LedSyncApp
Usage
To use either of the USB ports for PD communications, import the Host or Device classes. To assist with the creation of a message import the helper.
from pd import Host, Device
Then create the objects:
usb_in = Device()
usb_out = Host()
Check the connection state and send a message:
if usb_out.pd_enabled():
usb_out.send_vendor_msg(data, length)
Methods
Both the Host and Device ports support the following methods.
| Method | Description | Arguments | Returns |
|---|---|---|---|
connected | Returns whether the badge is connected as a device. | None. | state(bool): Connection state |
pd_enabled | Returns whether PD is enabled on this port | None | state(bool): PD enabled |
badge_connected | Returns whether the badge is connected to another badge as a device | None. | state(bool): Connection state |
get_vendor_msg | Gets vendor message from the in port | None. | message(tuple): vendor header(int) message header, length(int)message length, buffer(list) list of data |
send_vendor_msg | Sends a vendor message on the in port | data(bytearray): data, contents (VDOs) left to the user and the helper. the data will be padded to the 4 byte VDO boundary, max 28 bytes | None. |
The Host port also supports sending messages to the cable plug at either end, known as prime and double prime messages:
| Method | Description | Arguments | Returns |
|---|---|---|---|
send_prime_msg | Sends a prime message | data(bytearray): data, must include 2 byte header | None |
send_dbl_prime_msg | Sends a double prime message | data(bytearray): data, must include 2 byte header | None |
get_prime_msg | Gets a received prime message | None | data(bytearray): data, includes 2 byte header |
get_dbl_prime_msg | Gets a received double prime message | None | data(bytearray): data, includes 2 byte header |
Events
| Method | Description |
|---|---|
BadgeAsDeviceAttachEvent | The badge has detected another badge and connected as a device |
BadgeAsDeviceDetachEvent | The badge has disconnected |
BadgeAsHostAttachEvent | The badge has detected another badge and connected as a host |
BadgeAsHostDetachEvent | The badge has disconnected |
VendorMsgDevRxEvent | The badge has received a message on the USB in port |
VendorMsgHostRxEvent | The badge has received a message on the USB out port |
PrimeMsgHostRxEvent | The badge has received a prime message on the USB out port |
DblPrimeMsgHostRxEvent | The badge has received a double prime message on the USB out port |
PD helper API
You can use the PD helper API to create structured and unstructured vendor headers and send the badge ID message. The badge ID message is sent automatically on connection to the usb out port. If this is detected by another badge on the usb in port it will respond with the same message and set the badge connected state to true. The vendor header is only required when not communicating with another badge.
Example
from system.power.pd_helper import pdHelper
pdh = pdHelper()
pdh.device_send_badge_id()
Usage
You can use the helper as follows to create the headers required for a discover identity command:
from system.power.pd_helper import pdHelper, vdmCmd
pdh = pdHelper()
header = pdh.header(dataType.VENDOR_DEFINED, 1)
vendor_header = pdh.vdm_structured_header(
vdmCmd.DISCOVER_IDENTITY, 0xFF00, 0, 0
)
usb_out = Host()
usb_out.send_prime_msg(
header.to_bytes(2, "little") + vendor_header.to_bytes(4, "little")
)
Methods
| Method | Description | Arguments | Returns |
|---|---|---|---|
device_send_badge_id | Sends the id used to detect another badge on the USB in port | None | None |
host_disc_id_dbl_prime | Sends the discover identity command to a cables plug | None | None |
host_disc_id_prime | Sends the discover identity command to a cables plug | None | None |
host_send_badge_id | Sends the id used to detect another badge on the USB out port | None | None |
pd_header | Create a message header with space for the badge to fill in physical layer info | message_type(int): see dataType and cmdType below, no_objects(int): number of 4 byte objects, optional, default 0 | header(int): 16 bit header |
vdm_structured_header | Creates a structured vendor header | command(int): see vdmCmd below, SVID(int): Standard or Vendor ID, optional, default 0xFF00, obj_pos(int):For the Enter Mode, Exit Mode and Attention Commands, optional, default 0, version(int): Structured VDM Version, optional, default 0, | vendor_header(int): 32 bit vendor header |
vdm_unstructured_header | Creates an unstructured vendor header | SVID(int): optional, default 0xFF00, data(int): 15 bits of vendor defined data | vendor_header(int): 32 bit vendor header |
vdm_header_extract | extract the fields of a vendor header | vendor_header(int): 32 bit vendor header | vendor_header(dict): dictionary containing each field of the header |
Constants
The following constants are available for use in the two headers.
| Class | Constant |
|---|---|
vdmCmd | DISCOVER_IDENTITY |
vdmCmd | DISCOVER_SVIDS |
vdmCmd | DISCOVER_MODES |
vdmCmd | ENTER_MODE |
vdmCmd | EXIT_MODE |
vdmCmd | ATTENTION |
dataType | BIST |
dataType | VENDOR_DEFINED |
cmdType | ACCEPT |
cmdType | REJECT |
cmdType | SOFT_RESET |
I2C
The badge supports the I2C communication protocol on a bus for each hexpansion slot. Hexpansion slots are numbered 1-6, with 1 being the top right slot and the numbers increasing clockwise. To create an I2C bus for a given slot, use:
from machine import I2C
bus = I2C(1)
If your app is loaded from EEPROM on an hexpansion, you can get the slot in the hexpansion config object that is passed to your app to select the correct slot.
Untested
This code is currently untested and may not work. If you work on this, please let us know what you find or what you have to fix at issue.
import app
from app_components import clear_background
from events.input import Buttons, BUTTON_TYPES
class ExampleApp(app.App):
def __init__(self, config=None):
self.button_states = Buttons(self)
self.hexpansion_config = config
def update(self, delta):
if self.button_states.get(BUTTON_TYPES["CANCEL"]):
self.minimise()
if self.hexpansion_config:
print(self.hexpansion_config.i2c)
def draw(self, ctx):
ctx.save()
clear_background(ctx)
ctx.rgb(0, 1, 0).move_to(-90, -40).text("Hello from your\nhexpansion!")
ctx.restore()
return None
__app_export__ = ExampleApp
If it's an app loaded from the badge, you'll need to check each port:
import app
from machine import I2C
from app_components import clear_background
from events.input import Buttons, BUTTON_TYPES
from system.eventbus import eventbus
from system.hexpansion.events import HexpansionRemovalEvent, HexpansionInsertionEvent
from system.hexpansion.config import HexpansionConfig
from system.hexpansion.util import read_hexpansion_header, detect_eeprom_addr
class ExampleApp(app.App):
def __init__(self):
self.button_states = Buttons(self)
self.text = "No hexpansion found."
self.hexpansion_config = self.scan_for_hexpansion()
eventbus.on(HexpansionInsertionEvent, self.handle_hexpansion_insertion, self)
eventbus.on(HexpansionRemovalEvent, self.handle_hexpansion_removal, self)
def handle_hexpansion_insertion(self, event):
self.hexpansion_config = self.scan_for_hexpansion()
def handle_hexpansion_removal(self, event):
self.hexpansion_config = self.scan_for_hexpansion()
def update(self, delta):
if self.button_states.get(BUTTON_TYPES["CANCEL"]):
self.minimise()
if self.hexpansion_config:
print(self.hexpansion_config.i2c)
def draw(self, ctx):
ctx.save()
clear_background(ctx)
ctx.rgb(0, 1, 0).move_to(-90, -40).text(self.text)
ctx.restore()
def scan_for_hexpansion(self):
for port in range(1, 7):
print(f"Searching for hexpansion on port: {port}")
i2c = I2C(port)
addr, addr_len = detect_eeprom_addr(i2c)
if addr is None:
continue
else:
print("Found EEPROM at addr " + hex(addr))
header = read_hexpansion_header(i2c, addr, addr_len=addr_len)
if header is None:
continue
else:
print("Read header: " + str(header))
self.text = "Hexp. found.\nvid: {}\npid: {}\nat port: {}".format(hex(header.vid), hex(header.pid), port)
return HexpansionConfig(port)
self.color = (1, 0, 0)
self.text = "No hexpansion found."
return None
__app_export__ = ExampleApp
You can then use the standard MicroPython I2C API to communicate with devices on the bus.
Example usage from the MicroPython I2C docs:
from machine import I2C
i2c = I2C(freq=400000)
# scan for peripherals, returning a list of 7-bit addresses
i2c.scan()
# write 3 bytes to peripheral with 7-bit address 42
i2c.writeto(42, b'123')
# read 4 bytes from peripheral with 7-bit address 42
i2c.readfrom(42, 4)
# read 3 bytes from memory of peripheral 42, starting at memory-address 8 in
# the peripheral
i2c.readfrom_mem(42, 8, 3)
# write 1 byte to memory of peripheral 42 starting at address 2 in the
# peripheral
i2c.writeto_mem(42, 2, b'\x10')
For more information, see MicroPython I2C docs.
Version
You can use the ota package to obtain the installed TildagonOS version:
Usage
- Import the
otapackage:
import ota
- Use the
get_version()method:
ota.get_version()