Examples
All code examples can be found in the examples directory of the repository.
Find all devices
Find and display all Free-Wili devices.
1"""Example to find all Free-Wilis connected over USB."""
2
3import result
4
5from freewili import FreeWili
6
7# Find the first device and raise an exception otherwise
8try:
9 device = FreeWili.find_first().expect("Failed to find a FreeWili")
10except result.UnwrapError as ex:
11 print(ex)
12
13# Find all and display information about it.
14devices = FreeWili.find_all()
15print(f"Found {len(devices)} FreeWili(s)")
16for i, free_wili in enumerate(devices, start=1):
17 print(f"{i}. {free_wili}")
18 print(f"\t{free_wili.main}")
19 print(f"\t{free_wili.display}")
20 print(f"\t{free_wili.ftdi}")
Upload files
Upload files to the first Free-Wili device.
1"""Upload a wasm file to a Free-Wili."""
2
3import pathlib
4
5from freewili import FreeWili
6
7# The fwi file to upload
8my_fwi_file = pathlib.Path(r"~/path/to/MyFile.fwi")
9# The wasm file to upload
10my_wasm_file = pathlib.Path(r"~/path/to/MyFile.wasm")
11
12# Find connected Free-Wilis
13devices = FreeWili.find_all()
14if not devices:
15 print("No Free-Wili devices found!")
16 exit(1)
17
18# Pick the first Free-Wili
19device = devices[0]
20
21# We can leave target_name and processor None and it will automatically
22# figure out where to send the file.
23device.send_file(my_fwi_file, None, None).expect("Failed to upload file")
24device.send_file(my_wasm_file, None, None).expect("Failed to upload file")
25device.run_script(my_wasm_file.name).expect("Failed to run script")
Device Settings
Configure FreeWili device settings including system sounds, default configurations, and real-time clock.
1"""Example script to set FreeWili settings. Currently unstable and subject to API changes."""
2
3from datetime import datetime
4
5from freewili import FreeWili
6from freewili.types import FreeWiliProcessorType
7
8with FreeWili.find_first().expect("Failed to find FreeWili") as fw:
9 print(f"Connected to {fw}")
10 # Set default settings for both processors
11 for processor in (FreeWiliProcessorType.Main, FreeWiliProcessorType.Display):
12 print("Setting defaulting settings for", processor.name)
13 fw.set_settings_to_default(processor).expect(
14 f"Failed to set {processor.name.lower()} processor settings to default"
15 )
16 # Set system sounds to off
17 print("Disabling system sounds...")
18 fw.set_system_sounds(False).expect("Failed to disable system sounds")
19 # Save these settings for both processors on startup
20 for processor in (FreeWiliProcessorType.Main, FreeWiliProcessorType.Display):
21 print("Setting", processor.name, "processor settings on startup...")
22 fw.set_settings_as_startup(processor).expect(
23 f"Failed to set {processor.name.lower()} processor settings on startup"
24 )
25 # Set the current RTC time
26 print("Setting RTC to current time...")
27 fw.set_rtc(datetime.now()).expect("Failed to set RTC")
28 print("RTC set successfully.")
29print("Goodbye!")
FPGA Configuration
Load FPGA configuration files on the FreeWili device.
1"""Example script to load a FPGA file using FreeWili."""
2
3from freewili import FreeWili
4
5with FreeWili.find_first().expect("Failed to find FreeWili") as fw:
6 print(f"Connected to {fw}")
7 fw.load_fpga_from_file("i2c").expect("Failed to load i2c FPGA from file")
8 print("FPGA loaded successfully.")
9print("Goodbye!")
Toggle IO
Toggles IO on the first Free-Wili device.
1"""Toggle IO on a Free-Wili."""
2
3from freewili import FreeWili
4from freewili.types import IOMenuCommand
5
6if __name__ == "__main__":
7 try:
8 with FreeWili.find_first().expect("Failed to find FreeWili") as fw:
9 print(f"Connected to {fw}")
10 # Set IO 25 high
11 fw.set_io(25, IOMenuCommand.High).expect("Failed to set IO high")
12 # Set IO 25 Low
13 fw.set_io(25, IOMenuCommand.Low).expect("Failed to set IO low")
14 # Toggle IO 25 Low
15 fw.set_io(25, IOMenuCommand.Toggle).expect("Failed to toggle IO")
16 # PWM IO 25
17 fw.set_io(25, IOMenuCommand.Pwm, 10, 50).expect("Failed to toggle IO")
18 # Toggle high-speed IO
19 fw.toggle_high_speed_io(True).expect("Failed to toggle high-speed IO")
20 fw.toggle_high_speed_io(False).expect("Failed to toggle high-speed IO off")
21 except Exception as ex:
22 print(f"Error: {ex}")
23
24 print("Done.")
Set Board LEDs
Set Board LEDs on the first Free-Wili device.
1"""Set Board RGB on a Free-Wili."""
2
3import time
4
5from freewili import FreeWili
6
7# find a FreeWili device and open it
8device = FreeWili.find_first().expect("Failed to find a FreeWili")
9device.open().expect("Failed to open")
10
11while True:
12 # Turn the LEDs on
13 for led_num in range(7):
14 print(device.set_board_leds(led_num, 10, 10, led_num * 2).expect("Failed to set LED"))
15 # Wait so we can see them
16 time.sleep(0.3)
17 # Turn the LEDS off
18 for led_num in reversed(range(7)):
19 print(device.set_board_leds(led_num, 0, 0, 0).expect("Failed to set LED"))
20
21device.close()
SparkFun 9DoF IMU Breakout - ISM330DHCX, MMC5983MA (Qwiic)
SparkFun 9DoF IMU Breakout example over I2C.
1"""Example I2C on a FreeWili with SparkFun 9DoF IMU Breakout - ISM330DHCX, MMC5983MA (Qwiic) attached.
2
3https://www.sparkfun.com/sparkfun-9dof-imu-breakout-ism330dhcx-mmc5983ma-qwiic.html
4ISM330DHCX I2C Address: 0x6B (Default)
5MMC5983MA Magnetometer I2C Address: 0x30
6"""
7
8import enum
9from time import time
10
11from freewili import FreeWili
12
13MMC5983MA_ADDR = 0x30
14ISM330DHCX_ADDR = 0x6B
15
16
17class MMC5983MA(enum.Enum):
18 """Register list for MMC5983MA Magnetometer."""
19
20 Xout0 = 0x00 # Xout [17:10]
21 Xout1 = 0x01 # Xout [9:2]
22 Yout0 = 0x02 # Yout [17:10]
23 Yout1 = 0x03 # Yout [9:2]
24 Zout0 = 0x04 # Zout [17:10]
25 Zout1 = 0x05 # Zout [9:2]
26 XYZout2 = 0x06 # Xout[1:0], Yout[1:0], Zout[1:0]
27 Tout = 0x07 # Temperature output
28 Status = 0x08 # Device status
29 Control0 = 0x09 # Control register 0
30 Control1 = 0x0A # Control register 1
31 Control2 = 0x0B # Control register 2
32 Control3 = 0x0C # Control register 3
33 ProductID = 0x2F # Product ID
34
35
36class ISM330DHCX(enum.Enum):
37 """Register list for ISM330DHCX."""
38
39 FUNC_CFG_ACCESS = 0x01
40 PIN_CTRL = 0x02
41 FIFO_CTRL1 = 0x07
42 FIFO_CTRL2 = 0x08
43 FIFO_CTRL3 = 0x09
44 FIFO_CTRL4 = 0x0A
45 COUNTER_BDR_REG1 = 0x0B
46 COUNTER_BDR_REG2 = 0x0C
47 INT1_CTRL = 0x0D
48 INT2_CTRL = 0x0E
49 WHO_AM_I = 0x0F
50 CTRL1_XL = 0x10
51 CTRL2_G = 0x11
52 CTRL3_C = 0x12
53 CTRL4_C = 0x13
54 CTRL5_C = 0x14
55 CTRL6_C = 0x15
56 CTRL7_G = 0x16
57 CTRL8_XL = 0x17
58 CTRL9_XL = 0x18
59 CTRL10_C = 0x19
60 ALL_INT_SRC = 0x1
61 WAKE_UP_SRC = 0x1B
62 TAP_SRC = 0x1C
63 DRD_SRC = 0x1D
64 STATUS_REG = 0x1E
65 STATUS_SPIAux = 0x1E
66 OUT_TEMP_L = 0x20
67 OUT_TEMP_H = 0x21
68 OUTX_L_G = 0x22
69 OUTX_H_G = 0x23
70 OUTY_L_G = 0x24
71 OUTY_H_G = 0x25
72 OUTZ_L_G = 0x26
73 OUTZ_H_G = 0x27
74 OUTX_L_A = 0x28
75 OUTX_H_A = 0x29
76 OUTY_L_A = 0x2A
77 OUTY_H_A = 0x2B
78 OUTZ_L_A = 0x2C
79 OUTZ_H_A = 0x2D
80 EMB_FUNC_STATUS_MAINPAGE = 0x35
81 FSM_STATUS_A_MAINPAGE = 0x36
82 FSM_STATUS_B_MAINPAGE = 0x37
83 MLC_STATUS_MAINPAGE = 0x38
84 STATUS_MASTER_MAINPAGE = 0x39
85 FIFO_STATUS1 = 0x3A
86 FIFO_STATUS2 = 0x3B
87 TIMESTAMP0 = 0x40
88 TIMESTAMP1 = 0x41
89 TIMESTAMP2 = 0x42
90 TIMESTAMP3 = 0x43
91 TAP_CFG0 = 0x56
92 TAP_CFG1 = 0x57
93 TAP_CFG2 = 0x58
94 TAP_THS_6D = 0x59
95 INT_DUR2 = 0x5A
96 WAKE_UP_THS = 0x5B
97 WAKE_UP_DUR = 0x5C
98 FREE_FALL = 0x5D
99 MD1_CFG = 0x5E
100 MD2_CFG = 0x5F
101 INTERNAL_FREQ_FINE = 0x63
102 INT_OIS = 0x6F
103 CTRL1_OIS = 0x70
104 CTRL2_OIS = 0x71
105 CTRL3_OIS = 0x72
106 X_OFS_USR = 0x73
107 Y_OFS_USR = 0x74
108 Z_OFS_USR = 0x75
109 FIFO_DATA_OUT_TAG = 0x78
110 FIFO_DATA_OUT_X_L = 0x79
111 FIFO_DATA_OUT_X_H = 0x7A
112 FIFO_DATA_OUT_Y_L = 0x7B
113 FIFO_DATA_OUT_Y_H = 0x7C
114 FIFO_DATA_OUT_Z_L = 0x7D
115 FIFO_DATA_OUT_Z_H = 0x7E
116
117
118def get_mmc5983ma_temperature(device: FreeWili) -> float:
119 """Get temperature on MMC5983MA.
120
121 Arguments:
122 ----------
123 device: FreeWili
124 FreeWili device to use.
125
126 Returns:
127 ---------
128 tuple[float, int]:
129 (Temperature in degrees Celcius, timestamp in ns).
130 """
131 _ = device.write_i2c(MMC5983MA_ADDR, MMC5983MA.Control0.value, bytes([0x2A])).expect("Enable temp measure")
132 while True:
133 response = device.read_i2c(MMC5983MA_ADDR, MMC5983MA.Status.value, 1).expect("Reading Status failed")
134 status = response[0]
135 if (status & 0x2) == 0x2:
136 # print("Temperature measurement done!")
137 break
138 response = device.read_i2c(MMC5983MA_ADDR, MMC5983MA.Tout.value, 1).expect("Reading Tout failed")
139 # Temperature output, unsigned format. The range is -75~125°C, about 0.8°C/LSB, 00000000 stands for -75°C
140 temp = response
141 temp_int = int.from_bytes(temp, byteorder="little", signed=False)
142 converted = temp_int * 0.8 - 75
143 # print(resp._raw.strip())
144 return converted
145
146
147def get_mmc5983ma_magnetic_sensor(device: FreeWili) -> tuple[int, int, int]:
148 """Get magnetic field data on MMC5983MA.
149
150 Arguments:
151 ----------
152 device: FreeWili
153 FreeWili device to use.
154
155 Returns:
156 ---------
157 tuple[int, int, int]:
158 (x, y, z).
159 """
160 _ = device.write_i2c(MMC5983MA_ADDR, MMC5983MA.Control0.value, bytes([0x29])).expect("Enable magnetic field")
161 while True:
162 response = device.read_i2c(MMC5983MA_ADDR, MMC5983MA.Status.value, 1).expect("Reading Status failed")
163 status = response[0]
164 if (status & 0x1) == 0x1:
165 # print("Temperature measurement done!")
166 break
167 data = device.read_i2c(MMC5983MA_ADDR, MMC5983MA.Xout0.value, 6).expect("Reading Tout failed")
168 x = int.from_bytes(data[0:2], byteorder="little", signed=False) << 2
169 y = int.from_bytes(data[2:4], byteorder="little", signed=False) << 2
170 z = int.from_bytes(data[4:6], byteorder="little", signed=False) << 2
171 return (x, y, z)
172
173
174# find a FreeWili device
175device = FreeWili.find_first().expect("Failed to find a FreeWili")
176device.open().expect("Failed to open FreeWili")
177
178try:
179 # Poll the I2C to make sure we can read the breakout board
180 print("Polling I2C...")
181 addresses = device.poll_i2c().expect("Failed to poll I2C")
182 if MMC5983MA_ADDR not in addresses or ISM330DHCX_ADDR not in addresses:
183 print(f"Expected I2C addresses {MMC5983MA_ADDR} and {ISM330DHCX_ADDR} not found. Got {addresses}!")
184 exit(1)
185
186 start = time()
187 while True:
188 try:
189 temp_c = get_mmc5983ma_temperature(device)
190 temp_f = temp_c * 1.8 + 32
191 print(f"[{time() - start:.3f}] Temperature: {temp_c:.1f}C ({temp_f:.1f}F)")
192 magnetic_data = get_mmc5983ma_magnetic_sensor(device)
193 print(f"[{time() - start:.3f}] Magnetic Field: {magnetic_data}")
194 except KeyboardInterrupt:
195 break
196finally:
197 device.close()
198
199print("Done.")
Event Handling (Console)
Console-based example for handling real-time events from FreeWili devices including accelerometer, button, IR, and battery events.
1"""Example script to handle events from FreeWili."""
2
3from freewili import FreeWili
4from freewili.framing import ResponseFrame
5from freewili.types import AccelData, EventDataType, EventType, GPIOData
6
7
8def event_handler(event_type: EventType, frame: ResponseFrame, data: EventDataType) -> None:
9 """Handle events from FreeWili."""
10 match event_type:
11 case EventType.Accel:
12 data: AccelData = data # type:ignore
13 print(f"Accel Event: {data}")
14 case EventType.GPIO:
15 data: GPIOData = data # type: ignore
16 print(f"GPIO Event: {data.raw}") # type: ignore
17 case _:
18 # Handle other event types as needed
19 if data:
20 print(f"{event_type}: Event Data: {data}")
21 else:
22 print(f"No data for this {event_type}.")
23
24
25with FreeWili.find_first().expect("Failed to find FreeWili") as fw:
26 print(f"Connected to FreeWili {fw}")
27 fw.set_event_callback(event_handler)
28
29 print("Enabling events...")
30 fw.enable_gpio_events(True).expect("Failed to enable GPIO events")
31 fw.enable_accel_events(True, 33).expect("Failed to enable accel events")
32 fw.enable_button_events(True, 33).expect("Failed to enable button events")
33 fw.enable_ir_events(True).expect("Failed to enable IR events")
34 fw.enable_battery_events(True).expect("Failed to enable battery events")
35 # fw.enable_audio_events(True).expect("Failed to enable audio events")
36 print("Listening for events...")
37 while True:
38 try:
39 fw.process_events()
40 except KeyboardInterrupt:
41 break
42 # Disable events before exiting
43 print("Disabling events...")
44 fw.enable_gpio_events(False).expect("Failed to disable GPIO events")
45 fw.enable_accel_events(False).expect("Failed to disable accel events")
46 fw.enable_button_events(False).expect("Failed to disable button events")
47 fw.enable_ir_events(False).expect("Failed to disable IR events")
48 fw.enable_battery_events(False).expect("Failed to disable battery events")
49 # fw.enable_audio_events(False).expect("Failed to disable audio events")
50 print("Exiting event loop")
Event Handling (GUI)
PySide6 GUI application for monitoring real-time FreeWili events with visual displays and controls.
1# type: ignore
2"""PySide6 GUI example to display FreeWili events in real-time."""
3
4import sys
5from typing import Any
6
7from PySide6.QtCore import QObject, QTimer, Signal
8from PySide6.QtWidgets import (
9 QApplication,
10 QCheckBox,
11 QGridLayout,
12 QGroupBox,
13 QHBoxLayout,
14 QLabel,
15 QMainWindow,
16 QPushButton,
17 QTextEdit,
18 QVBoxLayout,
19 QWidget,
20)
21
22from freewili import FreeWili
23from freewili.framing import ResponseFrame
24from freewili.types import AccelData, BatteryData, ButtonData, EventType, GPIOData, IRData
25
26
27class EventSignals(QObject):
28 """Signals for thread-safe GUI updates."""
29
30 accel_updated = Signal(AccelData)
31 button_updated = Signal(ButtonData)
32 ir_updated = Signal(IRData)
33 battery_updated = Signal(BatteryData)
34 gpio_updated = Signal(GPIOData)
35 raw_event = Signal(str)
36
37
38class FreeWiliEventsGUI(QMainWindow):
39 """Main window for FreeWili events display."""
40
41 def __init__(self):
42 super().__init__()
43 self.freewili = None
44 self.event_timer = QTimer()
45 self.signals = EventSignals()
46
47 # Connect signals
48 self.signals.accel_updated.connect(self.update_accel_display)
49 self.signals.button_updated.connect(self.update_button_display)
50 self.signals.ir_updated.connect(self.update_ir_display)
51 self.signals.battery_updated.connect(self.update_battery_display)
52 self.signals.gpio_updated.connect(self.update_gpio_display)
53 self.signals.raw_event.connect(self.update_raw_events)
54
55 self.setup_ui()
56 self.setup_freewili()
57
58 def setup_ui(self):
59 """Set up the user interface."""
60 self.setWindowTitle("FreeWili Events Monitor")
61 self.setGeometry(100, 100, 800, 600)
62
63 central_widget = QWidget()
64 self.setCentralWidget(central_widget)
65
66 main_layout = QVBoxLayout(central_widget)
67
68 # Control buttons
69 control_layout = QHBoxLayout()
70
71 self.connect_btn = QPushButton("Connect")
72 self.connect_btn.clicked.connect(self.toggle_connection)
73 control_layout.addWidget(self.connect_btn)
74
75 # Event enable checkboxes
76 self.accel_cb = QCheckBox("Accelerometer")
77 self.button_cb = QCheckBox("Buttons")
78 self.ir_cb = QCheckBox("IR")
79 self.battery_cb = QCheckBox("Battery")
80 self.gpio_cb = QCheckBox("GPIO")
81
82 for cb in [self.accel_cb, self.button_cb, self.ir_cb, self.battery_cb, self.gpio_cb]:
83 cb.stateChanged.connect(self.update_event_subscriptions)
84 control_layout.addWidget(cb)
85
86 control_layout.addStretch()
87 main_layout.addLayout(control_layout)
88
89 # Data display areas
90 data_layout = QHBoxLayout()
91
92 # Left column - Sensor data
93 left_layout = QVBoxLayout()
94
95 # Accelerometer group
96 self.accel_group = self.create_accel_group()
97 left_layout.addWidget(self.accel_group)
98
99 # Battery group
100 self.battery_group = self.create_battery_group()
101 left_layout.addWidget(self.battery_group)
102
103 data_layout.addLayout(left_layout)
104
105 # Right column - Digital inputs
106 right_layout = QVBoxLayout()
107
108 # Button group
109 self.button_group = self.create_button_group()
110 right_layout.addWidget(self.button_group)
111
112 # IR group
113 self.ir_group = self.create_ir_group()
114 right_layout.addWidget(self.ir_group)
115
116 # GPIO group
117 self.gpio_group = self.create_gpio_group()
118 right_layout.addWidget(self.gpio_group)
119
120 # Raw events log
121 self.raw_events_group = self.create_raw_events_group()
122 right_layout.addWidget(self.raw_events_group)
123
124 data_layout.addLayout(right_layout)
125 main_layout.addLayout(data_layout)
126
127 def create_accel_group(self):
128 """Create accelerometer data display group."""
129 group = QGroupBox("Accelerometer")
130 layout = QGridLayout(group)
131
132 # Labels and value displays
133 self.accel_labels = {}
134 fields = [
135 ("G-Force:", "g"),
136 ("X:", "x"),
137 ("Y:", "y"),
138 ("Z:", "z"),
139 ("Temp °C:", "temp_c"),
140 ("Temp °F:", "temp_f"),
141 ]
142
143 for i, (label_text, key) in enumerate(fields):
144 label = QLabel(label_text)
145 value_label = QLabel("--")
146 value_label.setStyleSheet("font-weight: bold; color: blue;")
147
148 layout.addWidget(label, i, 0)
149 layout.addWidget(value_label, i, 1)
150
151 self.accel_labels[key] = value_label
152
153 return group
154
155 def create_battery_group(self):
156 """Create battery data display group."""
157 group = QGroupBox("Battery")
158 layout = QGridLayout(group)
159
160 self.battery_labels = {}
161 fields = [
162 ("VBUS:", "vbus"),
163 ("VSYS:", "vsys"),
164 ("VBATT:", "vbatt"),
165 ("ICHG:", "ichg"),
166 ("Charging:", "charging"),
167 ("Complete:", "charge_complete"),
168 ]
169
170 for i, (label_text, key) in enumerate(fields):
171 label = QLabel(label_text)
172 value_label = QLabel("--")
173 value_label.setStyleSheet("font-weight: bold; color: green;")
174
175 layout.addWidget(label, i, 0)
176 layout.addWidget(value_label, i, 1)
177
178 self.battery_labels[key] = value_label
179
180 return group
181
182 def create_button_group(self):
183 """Create button state display group."""
184 group = QGroupBox("Buttons")
185 layout = QGridLayout(group)
186
187 self.button_labels = {}
188 buttons = [("Gray:", "gray"), ("Yellow:", "yellow"), ("Green:", "green"), ("Blue:", "blue"), ("Red:", "red")]
189
190 for i, (label_text, key) in enumerate(buttons):
191 label = QLabel(label_text)
192 value_label = QLabel("Released")
193 value_label.setStyleSheet("font-weight: bold;")
194
195 layout.addWidget(label, i, 0)
196 layout.addWidget(value_label, i, 1)
197
198 self.button_labels[key] = value_label
199
200 return group
201
202 def create_ir_group(self):
203 """Create IR data display group."""
204 group = QGroupBox("IR Data")
205 layout = QVBoxLayout(group)
206
207 self.ir_value_label = QLabel("No data")
208 self.ir_value_label.setStyleSheet("font-weight: bold; color: purple; font-family: monospace;")
209 layout.addWidget(self.ir_value_label)
210
211 return group
212
213 def create_gpio_group(self):
214 """Create GPIO state display group."""
215 group = QGroupBox("GPIO States")
216 layout = QGridLayout(group)
217
218 self.gpio_labels = {}
219
220 # Get GPIO mappings from types.py
221 from freewili.types import GPIO_MAP
222
223 # Create labels for each GPIO pin
224 row = 0
225 for _gpio_num, gpio_name in GPIO_MAP.items():
226 # Show just the GPIO number and main function
227 display_name = gpio_name.replace("/", " / ")
228
229 label = QLabel(f"{display_name}:")
230 value_label = QLabel("--")
231 value_label.setStyleSheet("font-weight: bold; font-family: monospace;")
232
233 layout.addWidget(label, row, 0)
234 layout.addWidget(value_label, row, 1)
235
236 self.gpio_labels[gpio_name] = value_label
237 row += 1
238
239 return group
240
241 def create_raw_events_group(self):
242 """Create raw events log group."""
243 group = QGroupBox("Raw Events Log")
244 layout = QVBoxLayout(group)
245
246 self.raw_events_text = QTextEdit()
247 # self.raw_events_text.setMaximumHeight(150)
248 self.raw_events_text.setStyleSheet("font-family: monospace; font-size: 10px;")
249 layout.addWidget(self.raw_events_text)
250
251 clear_btn = QPushButton("Clear Log")
252 clear_btn.clicked.connect(self.raw_events_text.clear)
253 layout.addWidget(clear_btn)
254
255 return group
256
257 def setup_freewili(self):
258 """Set up FreeWili connection and event processing."""
259 self.event_timer.timeout.connect(self.process_events)
260 self.event_timer.setInterval(50) # Process events every 50ms
261
262 def event_handler(self, event_type: EventType, frame: ResponseFrame, data: Any) -> None:
263 """Handle events from FreeWili (runs in background thread)."""
264 # Convert the data to the appropriate type and emit signals for thread-safe GUI updates
265 try:
266 if event_type == EventType.Accel and isinstance(data, AccelData):
267 self.signals.accel_updated.emit(data)
268 elif event_type == EventType.Button and isinstance(data, ButtonData):
269 self.signals.button_updated.emit(data)
270 elif event_type == EventType.IR and isinstance(data, IRData):
271 self.signals.ir_updated.emit(data)
272 elif event_type == EventType.Battery and isinstance(data, BatteryData):
273 self.signals.battery_updated.emit(data)
274 elif event_type == EventType.GPIO and isinstance(data, GPIOData):
275 self.signals.gpio_updated.emit(data)
276
277 # Also log to raw events
278 self.signals.raw_event.emit(f"{event_type.name}: {data}")
279
280 except Exception as e:
281 self.signals.raw_event.emit(f"Error processing {event_type.name}: {e}")
282
283 def update_accel_display(self, data: AccelData):
284 """Update accelerometer display."""
285 self.accel_labels["g"].setText(f"{data.g:.2f}g")
286 self.accel_labels["x"].setText(f"{data.x:.1f}")
287 self.accel_labels["y"].setText(f"{data.y:.1f}")
288 self.accel_labels["z"].setText(f"{data.z:.1f}")
289 self.accel_labels["temp_c"].setText(f"{data.temp_c:.1f}°C")
290 self.accel_labels["temp_f"].setText(f"{data.temp_f:.1f}°F")
291
292 def update_button_display(self, data: ButtonData):
293 """Update button display."""
294 buttons = {"gray": data.gray, "yellow": data.yellow, "green": data.green, "blue": data.blue, "red": data.red}
295
296 for key, pressed in buttons.items():
297 label = self.button_labels[key]
298 if pressed:
299 label.setText("PRESSED")
300 label.setStyleSheet("font-weight: bold; color: green;")
301 else:
302 label.setText("Released")
303 label.setStyleSheet("font-weight: bold; color: red;")
304
305 def update_ir_display(self, data: IRData):
306 """Update IR display."""
307 hex_value = data.value.hex().upper()
308 self.ir_value_label.setText(f"0x{hex_value}" if hex_value else "No data")
309
310 def update_battery_display(self, data: BatteryData):
311 """Update battery display."""
312 self.battery_labels["vbus"].setText(f"{data.vbus:.1f}mV")
313 self.battery_labels["vsys"].setText(f"{data.vsys:.1f}mV")
314 self.battery_labels["vbatt"].setText(f"{data.vbatt:.1f}mV")
315 self.battery_labels["ichg"].setText(f"{data.ichg}mA")
316 self.battery_labels["charging"].setText("YES" if data.charging else "NO")
317 self.battery_labels["charge_complete"].setText("YES" if data.charge_complete else "NO")
318
319 def update_gpio_display(self, data: GPIOData):
320 """Update GPIO display."""
321 for gpio_name, value in data.pin.items():
322 if gpio_name in self.gpio_labels:
323 label = self.gpio_labels[gpio_name]
324 if value == 1:
325 label.setText("HIGH")
326 label.setStyleSheet("font-weight: bold; color: red; font-family: monospace;")
327 else:
328 label.setText("LOW")
329 label.setStyleSheet("font-weight: bold; color: blue; font-family: monospace;")
330
331 def update_raw_events(self, event_text: str):
332 """Update raw events log."""
333 self.raw_events_text.append(event_text)
334
335 # Keep log size manageable
336 if self.raw_events_text.document().blockCount() > 100:
337 cursor = self.raw_events_text.textCursor()
338 cursor.movePosition(cursor.MoveOperation.Start)
339 cursor.select(cursor.SelectionType.BlockUnderCursor)
340 cursor.removeSelectedText()
341 cursor.deleteChar() # Remove the newline
342
343 def toggle_connection(self):
344 """Connect or disconnect from FreeWili."""
345 if self.freewili is None:
346 try:
347 result = FreeWili.find_first()
348 if result.is_err():
349 self.signals.raw_event.emit(f"Error: {result.unwrap_err()}")
350 return
351
352 self.freewili = result.unwrap()
353 self.freewili.set_event_callback(self.event_handler)
354
355 # Open connection
356 open_result = self.freewili.open()
357 if open_result.is_err():
358 self.signals.raw_event.emit(f"Error opening connection: {open_result.unwrap_err()}")
359 self.freewili = None
360 return
361
362 self.connect_btn.setText("Disconnect")
363 self.event_timer.start()
364 self.signals.raw_event.emit("Connected to FreeWili")
365
366 # Enable default events
367 self.accel_cb.setChecked(True)
368 self.button_cb.setChecked(True)
369 self.ir_cb.setChecked(True)
370 self.battery_cb.setChecked(True)
371 self.gpio_cb.setChecked(True)
372
373 except Exception as e:
374 self.signals.raw_event.emit(f"Connection error: {e}")
375
376 else:
377 # Disconnect
378 self.event_timer.stop()
379
380 # Disable all events
381 for cb in [self.accel_cb, self.button_cb, self.ir_cb, self.battery_cb, self.gpio_cb]:
382 cb.setChecked(False)
383
384 if hasattr(self.freewili, "close"):
385 self.freewili.close()
386
387 self.freewili = None
388 self.connect_btn.setText("Connect")
389 self.signals.raw_event.emit("Disconnected from FreeWili")
390
391 def update_event_subscriptions(self):
392 """Update event subscriptions based on checkboxes."""
393 if self.freewili is None:
394 return
395
396 try:
397 # Update accelerometer events
398 if self.accel_cb.isChecked():
399 result = self.freewili.enable_accel_events(True, 100)
400 if result.is_err():
401 self.signals.raw_event.emit(f"Error enabling accel: {result.unwrap_err()}")
402 else:
403 result = self.freewili.enable_accel_events(False)
404 if result.is_err():
405 self.signals.raw_event.emit(f"Error disabling accel: {result.unwrap_err()}")
406
407 # Update button events (assuming these methods exist)
408 if hasattr(self.freewili, "enable_button_events"):
409 if self.button_cb.isChecked():
410 result = self.freewili.enable_button_events(True, 100)
411 if result.is_err():
412 self.signals.raw_event.emit(f"Error enabling buttons: {result.unwrap_err()}")
413 else:
414 result = self.freewili.enable_button_events(False)
415 if result.is_err():
416 self.signals.raw_event.emit(f"Error disabling buttons: {result.unwrap_err()}")
417
418 # Update IR events
419 if hasattr(self.freewili, "enable_ir_events"):
420 if self.ir_cb.isChecked():
421 result = self.freewili.enable_ir_events(True)
422 if result.is_err():
423 self.signals.raw_event.emit(f"Error enabling IR: {result.unwrap_err()}")
424 else:
425 result = self.freewili.enable_ir_events(False)
426 if result.is_err():
427 self.signals.raw_event.emit(f"Error disabling IR: {result.unwrap_err()}")
428
429 # Update battery events
430 if hasattr(self.freewili, "enable_battery_events"):
431 if self.battery_cb.isChecked():
432 result = self.freewili.enable_battery_events(True)
433 if result.is_err():
434 self.signals.raw_event.emit(f"Error enabling battery: {result.unwrap_err()}")
435 else:
436 result = self.freewili.enable_battery_events(False)
437 if result.is_err():
438 self.signals.raw_event.emit(f"Error disabling battery: {result.unwrap_err()}")
439
440 # Update GPIO events
441 if hasattr(self.freewili, "enable_gpio_events"):
442 if self.gpio_cb.isChecked():
443 result = self.freewili.enable_gpio_events(True)
444 if result.is_err():
445 self.signals.raw_event.emit(f"Error enabling GPIO: {result.unwrap_err()}")
446 else:
447 result = self.freewili.enable_gpio_events(False)
448 if result.is_err():
449 self.signals.raw_event.emit(f"Error disabling GPIO: {result.unwrap_err()}")
450
451 except Exception as e:
452 self.signals.raw_event.emit(f"Error updating subscriptions: {e}")
453
454 def process_events(self):
455 """Process FreeWili events (called by timer)."""
456 if self.freewili:
457 try:
458 self.freewili.process_events()
459 except Exception as e:
460 self.signals.raw_event.emit(f"Event processing error: {e}")
461
462 def closeEvent(self, event): # noqa: N802
463 """Handle window close event."""
464 if self.freewili:
465 self.toggle_connection() # Disconnect cleanly
466 event.accept()
467
468
469def main():
470 """Main function to run the GUI application."""
471 app = QApplication(sys.argv)
472
473 # Set application properties
474 app.setApplicationName("FreeWili Events Monitor")
475 app.setApplicationVersion("1.0")
476
477 # Create and show the main window
478 window = FreeWiliEventsGUI()
479 window.show()
480
481 # Run the application
482 sys.exit(app.exec())
483
484
485if __name__ == "__main__":
486 main()
Audio Recording
Record audio from FreeWili and save it to a WAV file.
1"""Example script to record audio from FreeWili and save it to a WAV file."""
2
3import struct
4import time
5import wave
6
7from freewili import FreeWili
8from freewili.framing import ResponseFrame
9from freewili.types import AudioData, EventType
10
11audio_data = []
12
13
14def event_handler(event_type: EventType, frame: ResponseFrame, data: AudioData) -> None:
15 """Handle events from FreeWili."""
16 if event_type != EventType.Audio:
17 return
18 # print(f"Audio Event: {data.data}")
19 audio_data.append(data.data)
20
21
22with FreeWili.find_first().expect("Failed to find FreeWili") as fw, wave.open("test.wav", "wb") as wav_file:
23 print(f"Connected to {fw}")
24 wav_file.setnchannels(1) # Mono
25 wav_file.setsampwidth(2) # 2 bytes per sample (16-bit)
26 wav_file.setframerate(8000) # 4kHz sample rate
27
28 fw.set_event_callback(event_handler)
29 fw.enable_audio_events(True).expect("Failed to enable audio events")
30 start_time = time.time()
31 print("Listening for audio events... Press Ctrl+C to stop recording...")
32 while True:
33 try:
34 fw.process_events()
35 # Clear the first 2 seconds of audio data to avoid initial noise
36 if time.time() - start_time < 2.0:
37 audio_data.clear()
38 if audio_data:
39 # Write the audio data to the WAV file
40 for data in audio_data:
41 print(f"\tWriting audio data: {data!r}" + " " * 30, end="\r")
42 # Convert data to bytes (little-endian 16-bit signed)
43 audio_bytes = b"".join(struct.pack("<h", sample) for sample in data)
44 wav_file.writeframes(audio_bytes)
45 audio_data.clear() # Clear the list after writing
46 except KeyboardInterrupt:
47 print("\nStopping audio recording...")
48 break
49 # Disable audio events before exiting
50 print("Disabling audio events...")
51 fw.enable_audio_events(False).expect("Failed to disable audio events")
52 print(f"Audio saved to {wav_file._file.name}") # type:ignore
53print("Done.")
Audio Playback - Tone
Play audio tones on the FreeWili device.
1"""Example script to demonstrate playing audio tones with Free-WiLi."""
2
3import time
4
5from freewili import FreeWili
6
7with FreeWili.find_first().expect("Failed to find FreeWili") as fw:
8 print(f"Connected to FreeWili {fw}")
9 print("type 'list' to see audio assets or 'exit' to quit.")
10 while True:
11 try:
12 user_input = input("Enter to play audio tone [hz duration_sec amplitude]: ")
13 frequency_hz, duration_sec, amplitude = map(float, user_input.split())
14 frequency_hz = int(frequency_hz) # Convert to int for frequency
15 # v54 firmware: Response frame always returns failure
16 fw.play_audio_tone(frequency_hz, duration_sec, amplitude) # .expect("Failed to play audio tone")
17 time.sleep(duration_sec) # Give some time for the audio to play, playing is async
18 except (ValueError, KeyboardInterrupt):
19 print("Goodbye!")
20 break
21 print("Done.")
Audio Playback - Number
Play audio number announcements on the FreeWili device.
1"""Example script to demonstrate playing audio numbers with Free-WiLi."""
2
3import time
4
5from freewili import FreeWili
6
7with FreeWili.find_first().expect("Failed to find FreeWili") as fw:
8 print(f"Connected to FreeWili {fw}")
9 while True:
10 try:
11 user_input = int(input("Enter number: "))
12 fw.play_audio_number_as_speech(user_input).expect("Failed to play audio number as speech")
13 time.sleep(len(str(user_input)) * 0.8) # Give some time for the audio to play, playing is async
14 except (ValueError, KeyboardInterrupt):
15 print("Goodbye!")
16 break
17 print("Done.")
Audio Playback - Assets
Play audio assets/files on the FreeWili device.
1"""Example script to play audio on the FreeWili."""
2
3import time
4
5from freewili import FreeWili
6
7# https://docs.freewili.com/downloads/FwROMAudioAssets.pdf
8AUDIO_NAMES = (
9 "1",
10 "10",
11 "2",
12 "3",
13 "4",
14 "42",
15 "5",
16 "6",
17 "7",
18 "8",
19 "9",
20 "About",
21 "AllYourBases",
22 "beephigh",
23 "ChooseYourDestiny",
24 "click",
25 "daisy",
26 "DaveICantDoThat",
27 "DefCon",
28 "Dominating",
29 "DoOrDoNot",
30 "DoubleKill",
31 "EndOfLine",
32 "Engage",
33 "Excellent",
34 "ExcuseMe",
35 "FearIsTheMindKiller",
36 "FreeWilli",
37 "GodLike",
38 "GoodJob",
39 "GPIO",
40 "GUI",
41 "GUISettings",
42 "GUITerminal",
43 "HackMe",
44 "HangTough",
45 "HaveACupcake",
46 "HolyShit",
47 "I2C",
48 "IllBeBack",
49 "ImThinking",
50 "Infinity",
51 "Invalid",
52 "IR",
53 "ItsTime",
54 "KillingSpree",
55 "LudacrisKill",
56 "MegaKill",
57 "Minus",
58 "MonsterKill",
59 "MultiKill",
60 "No",
61 "NoWay",
62 "NoWayWithLaugh",
63 "Orca",
64 "palebluedot",
65 "PinOut",
66 "PlansWithinPlans",
67 "Plus",
68 "Point",
69 "PorqueNo",
70 "Radios",
71 "Rampage",
72 "Revenge",
73 "ScanMeHarder",
74 "Scripts",
75 "Settings",
76 "shame",
77 "ShutUpWesely",
78 "SmileAndBeHappy",
79 "Sorry",
80 "SoSayWeAll",
81 "SPI",
82 "Sweet",
83 "Tasty",
84 "Terminal",
85 "ThanksForAllTheFish",
86 "tom",
87 "UART",
88 "UltraKill",
89 "UnstopableWind",
90 "unstoppable",
91 "UserError",
92 "VivaLosVegas",
93 "WarWarNeverChanges",
94 "Welcome",
95 "WhaleEver",
96 "WhaleThankU",
97 "What",
98 "WickedSick",
99 "Yes",
100)
101
102with FreeWili.find_first().expect("Failed to find FreeWili") as fw:
103 print(f"Connected to FreeWili {fw}")
104 print("type 'list' to see audio assets or 'exit' to quit.")
105 while True:
106 try:
107 user_input = input("Enter to play audio asset (1-{}): ".format(len(AUDIO_NAMES)))
108 value = int(user_input) - 1 # Convert to zero-based index
109 print(f"\tPlaying: {AUDIO_NAMES[value]}") # Fixed index here
110 fw.play_audio_asset(value).expect("Failed to play audio asset")
111 time.sleep(1) # Give some time for the audio to play, playing is async
112 except ValueError:
113 if user_input.lower() == "list":
114 for i, name in enumerate(AUDIO_NAMES, start=1):
115 print(f"\t{i}: {name}")
116 continue
117 print("Goodbye!")
118 break
119 print("Done.")
IR Communication - Send
Send infrared signals using the FreeWili device.
1"""Example script to demonstrate IR NEC communication with Free-WiLi."""
2
3from freewili import FreeWili
4
5with FreeWili.find_first().expect("Failed to find FreeWili") as fw:
6 roku_keyhome = bytes([0xBE, 0xEF, 00, 0xFF])
7 print("Sending Roku Key Home IR command:", roku_keyhome)
8 fw.send_ir(roku_keyhome).expect("Failed to send IR command")
9print("Done.")
IR Communication - Read
Read and decode infrared signals using the FreeWili device.
1"""Example script to demonstrate IR NEC communication with Free-WiLi."""
2
3from freewili import FreeWili
4from freewili.framing import ResponseFrame
5from freewili.types import EventDataType, EventType, IRData
6
7
8def event_callback(event_type: EventType, response_frame: ResponseFrame, event_data: EventDataType) -> None:
9 """Callback function to handle events from FreeWili."""
10 if isinstance(event_data, IRData):
11 print(f"IR RX {len(event_data.value)}: {event_data.value!r}")
12
13
14with FreeWili.find_first().expect("Failed to find FreeWili") as fw:
15 fw.set_event_callback(event_callback)
16 print("Enabling IR events")
17 fw.enable_ir_events(True).expect("Failed to enable IR events")
18 print("Waiting for IR events... Press Ctrl+C to exit.")
19 while True:
20 try:
21 fw.process_events()
22 except KeyboardInterrupt:
23 print("Exiting IR event loop")
24 break
25 fw.enable_ir_events(False).expect("Failed to disable IR events")
26print("Done.")
UART Communication
UART serial communication example for the FreeWili device.
1"""Example script to demonstrate UART communication with Free-WiLi."""
2
3# Note: v54 firmware has a limitation of 22 bytes per UART message, so we use 16 to be safe.
4# Note: v54 firmware enable_uart_events is a toggle only, so we enable it once and disable it at the end.
5
6import time
7
8from freewili import FreeWili
9from freewili.framing import ResponseFrame
10from freewili.types import EventDataType, EventType, UART1Data
11
12
13def event_callback(event_type: EventType, response_frame: ResponseFrame, event_data: EventDataType) -> None:
14 """Callback function to handle events from FreeWili."""
15 if isinstance(event_data, UART1Data):
16 print(f"UART1 RX {len(event_data.data)}: {event_data.data!r}")
17
18
19with FreeWili.find_first().expect("Failed to find FreeWili") as fw:
20 fw.set_event_callback(event_callback)
21 fw.enable_uart_events(True).expect("Failed to enable UART events")
22 data = b"Hello Free-WiLi from UART1!"
23 chunk_size = 16 # v54 firmware has a limitation of 22 bytes per UART message, so we use 16 to be safe.
24 for i in range(0, len(data), chunk_size):
25 chunk = data[i : i + chunk_size]
26 print(f"Sending UART Data: {chunk!r}")
27 fw.write_uart(chunk).expect("Failed to send UART message")
28 # Wait for the device to process and send back any events. If we close too soon, we won't see the events.
29 time.sleep(0.5)
30 try:
31 fw.process_events()
32 except KeyboardInterrupt:
33 print("Exiting UART event loop")
34 fw.enable_uart_events(False).expect("Failed to disable UART events")
35print("Done.")
SPI Communication
SPI read/write communication example for the FreeWili device.
1"""Example script to read and write SPI data using FreeWili."""
2
3from freewili import FreeWili
4
5with FreeWili.find_first().expect("Failed to find FreeWili") as fw:
6 print(f"Connected to {fw}")
7 while True:
8 try:
9 user_input = input("Enter data bytes seperated by spaces: ").strip().split(" ")
10 data = bytes(int(x, 16) for x in user_input)
11 print(f"Sending {len(data)} bytes: {data!r}")
12 rx_data = fw.read_write_spi_data(data).expect("Failed to send SPI data")
13 print(f"Received {len(rx_data)} bytes: {rx_data!r}")
14 except (ValueError, KeyboardInterrupt):
15 break
16print("Goodbye!")
Filesystem Explorer
Recursively explore and list the filesystem contents on both Display and Main processors of the FreeWili device.
1"""This script explores the FreeWili filesystem, listing directories and files recursively."""
2
3from freewili import FreeWili
4from freewili.fw import FreeWiliProcessorType as FwProcessor
5from freewili.types import FileType
6
7
8def explore_directory(
9 fw: FreeWili,
10 processor: FwProcessor,
11 path: str = "/",
12 indent: int = 0,
13 max_depth: int = 3,
14 visited: None | set[str] = None,
15) -> None:
16 """Recursively explore directories on the FreeWili filesystem."""
17 if visited is None:
18 visited = set()
19
20 # Normalize the path to avoid double slashes
21 path = "/" if path in ["/", "//"] else path
22
23 # Prevent infinite recursion
24 if indent > max_depth:
25 print(" " * indent + f"⚠️ Max depth reached for {path}")
26 return
27
28 # Prevent visiting the same path twice
29 if path in visited:
30 print(" " * indent + f"🔄 Already visited {path}")
31 return
32
33 visited.add(path)
34
35 # Change to the directory
36 if path != "/":
37 change_result = fw.change_directory(path, processor)
38 if change_result.is_err():
39 print(" " * indent + f"❌ Failed to change to {path}: {change_result.unwrap_err()}")
40 return
41
42 # List current directory contents
43 result = fw.list_current_directory(processor)
44 if result.is_err():
45 print(" " * indent + f"❌ Failed to list directory {path}: {result.unwrap_err()}")
46 return
47
48 fs_contents = result.unwrap()
49
50 # Calculate actual item count (excluding parent/current directory references)
51 actual_items = [item for item in fs_contents.contents if item.name not in ["..", "."]]
52
53 # Print current directory info
54 prefix = " " * indent
55 # Normalize the path display to avoid double slashes
56 if fs_contents.cwd == "/":
57 display_path = "/"
58 else:
59 display_path = f"{fs_contents.cwd}/"
60 print(f"{prefix}📁 {display_path} ({len(actual_items)} items)")
61
62 # Print files only (we'll show directories when we explore them)
63 for item in fs_contents.contents:
64 if item.name == ".." or item.name == ".":
65 continue # Skip parent/current directory references
66
67 if item.file_type == FileType.File:
68 print(f"{prefix} 📄 {item.name} ({item.size} bytes)")
69
70 # Recursively explore subdirectories (excluding parent directory references)
71 for item in fs_contents.contents:
72 if item.file_type == FileType.Directory and item.name not in ["..", "."]:
73 # Build the new path, handling root directory properly
74 current_dir = fs_contents.cwd if fs_contents.cwd not in ["/", "//"] else ""
75 if current_dir:
76 new_path = f"{current_dir}/{item.name}"
77 else:
78 new_path = f"/{item.name}"
79
80 explore_directory(fw, processor, new_path, indent + 1, max_depth, visited)
81
82 # Change back to the original directory after exploring subdirectory
83 # Normalize the path to avoid double slashes
84 return_path = fs_contents.cwd if fs_contents.cwd not in ["//"] else "/"
85 fw.change_directory(return_path, processor)
86
87
88with FreeWili.find_first().expect("Failed to find FreeWili") as fw:
89 print(f"Connected to {fw}")
90 print("\n=== Exploring Display Processor Filesystem ===")
91 fw.change_directory("/", FwProcessor.Display).expect("Failed to change to root directory on Display processor")
92 explore_directory(fw, FwProcessor.Display)
93
94 print("\n=== Exploring Main Processor Filesystem ===")
95 fw.change_directory("/", FwProcessor.Main).expect("Failed to change to root directory on Main processor")
96 explore_directory(fw, FwProcessor.Main)