wakemypc 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- wakemypc/__init__.py +0 -0
- wakemypc/flash.py +230 -0
- wakemypc/identify.py +212 -0
- wakemypc/main.py +1011 -0
- wakemypc/provision.py +362 -0
- wakemypc/register.py +395 -0
- wakemypc/restart.py +71 -0
- wakemypc/serial_detect.py +232 -0
- wakemypc/upload.py +434 -0
- wakemypc-1.0.0.dist-info/METADATA +242 -0
- wakemypc-1.0.0.dist-info/RECORD +15 -0
- wakemypc-1.0.0.dist-info/WHEEL +5 -0
- wakemypc-1.0.0.dist-info/entry_points.txt +2 -0
- wakemypc-1.0.0.dist-info/licenses/LICENSE +131 -0
- wakemypc-1.0.0.dist-info/top_level.txt +1 -0
wakemypc/__init__.py
ADDED
|
File without changes
|
wakemypc/flash.py
ADDED
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
"""
|
|
2
|
+
flash.py -- Flash MicroPython firmware (.uf2) onto a Raspberry Pi Pico
|
|
3
|
+
======================================================================
|
|
4
|
+
|
|
5
|
+
WHAT IS FIRMWARE?
|
|
6
|
+
-----------------
|
|
7
|
+
"Firmware" is the base software that runs on a microcontroller. Think of it like
|
|
8
|
+
the operating system on your computer. Without firmware, the Pico is a blank chip
|
|
9
|
+
that cannot do anything.
|
|
10
|
+
|
|
11
|
+
We use MicroPython as our firmware. MicroPython is a tiny version of Python that
|
|
12
|
+
runs directly on microcontrollers. Once MicroPython is installed, you can write
|
|
13
|
+
Python scripts and the Pico will execute them.
|
|
14
|
+
|
|
15
|
+
WHAT IS A .uf2 FILE?
|
|
16
|
+
---------------------
|
|
17
|
+
UF2 stands for "USB Flashing Format". It is a special file format designed by
|
|
18
|
+
Microsoft for flashing microcontrollers. The key insight is:
|
|
19
|
+
|
|
20
|
+
A .uf2 file can be flashed by simply copying it to a USB drive.
|
|
21
|
+
|
|
22
|
+
No special programmer hardware needed. No complex flashing software. Just drag
|
|
23
|
+
and drop (or cp on the command line).
|
|
24
|
+
|
|
25
|
+
HOW BOOTSEL MODE WORKS
|
|
26
|
+
-----------------------
|
|
27
|
+
BOOTSEL = "Boot Select". The Pico has a physical button labeled BOOTSEL on the board.
|
|
28
|
+
|
|
29
|
+
1. Unplug the Pico from USB.
|
|
30
|
+
2. Hold down the BOOTSEL button (it is a small white button on the board).
|
|
31
|
+
3. While holding BOOTSEL, plug the USB cable back in.
|
|
32
|
+
4. Release the BOOTSEL button.
|
|
33
|
+
|
|
34
|
+
Now the Pico appears as a USB mass storage device -- just like a flash drive!
|
|
35
|
+
It will show up as a drive called "RPI-RP2" (for original Pico) or "RP2350"
|
|
36
|
+
(for Pico 2 / Pico W 2).
|
|
37
|
+
|
|
38
|
+
The drive is tiny (only a few hundred KB) and contains two files:
|
|
39
|
+
- INFO_UF2.TXT (information about the bootloader)
|
|
40
|
+
- INDEX.HTM (a redirect to the Raspberry Pi documentation)
|
|
41
|
+
|
|
42
|
+
To flash new firmware, you just copy a .uf2 file onto this drive. The Pico
|
|
43
|
+
detects the new file, writes it to its internal flash memory, and automatically
|
|
44
|
+
reboots. After reboot, the Pico is no longer a USB drive -- it is now running
|
|
45
|
+
whatever firmware was in the .uf2 file.
|
|
46
|
+
|
|
47
|
+
AFTER FLASHING
|
|
48
|
+
--------------
|
|
49
|
+
Once MicroPython is flashed, the Pico reboots and appears as a USB serial device.
|
|
50
|
+
You can then connect to it via serial port (e.g. /dev/ttyACM0) and type Python
|
|
51
|
+
commands interactively, or upload .py files for it to run.
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
import platform
|
|
55
|
+
import shutil
|
|
56
|
+
import time
|
|
57
|
+
from pathlib import Path
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
# Known mount point names for Pico in BOOTSEL mode.
|
|
61
|
+
# These are the "drive names" that appear when the Pico is in BOOTSEL.
|
|
62
|
+
BOOTSEL_DRIVE_NAMES = {"RPI-RP2", "RP2350"}
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def find_bootsel_drive():
|
|
66
|
+
"""
|
|
67
|
+
Look for a Pico in BOOTSEL mode by searching for its USB mass storage mount point.
|
|
68
|
+
|
|
69
|
+
On Linux: typically mounted at /media/<username>/RPI-RP2 or /run/media/...
|
|
70
|
+
On macOS: typically mounted at /Volumes/RPI-RP2
|
|
71
|
+
On Windows: appears as a new drive letter like E:\\
|
|
72
|
+
|
|
73
|
+
Returns the path to the mounted drive, or None if not found.
|
|
74
|
+
"""
|
|
75
|
+
system = platform.system()
|
|
76
|
+
|
|
77
|
+
if system == "Linux":
|
|
78
|
+
# On Linux, removable drives are usually auto-mounted under /media/<user>/
|
|
79
|
+
# or /run/media/<user>/ depending on the desktop environment.
|
|
80
|
+
search_dirs = []
|
|
81
|
+
|
|
82
|
+
# Check /media/<username>/
|
|
83
|
+
media_path = Path("/media")
|
|
84
|
+
if media_path.exists():
|
|
85
|
+
for user_dir in media_path.iterdir():
|
|
86
|
+
if user_dir.is_dir():
|
|
87
|
+
search_dirs.append(user_dir)
|
|
88
|
+
|
|
89
|
+
# Check /run/media/<username>/
|
|
90
|
+
run_media_path = Path("/run/media")
|
|
91
|
+
if run_media_path.exists():
|
|
92
|
+
for user_dir in run_media_path.iterdir():
|
|
93
|
+
if user_dir.is_dir():
|
|
94
|
+
search_dirs.append(user_dir)
|
|
95
|
+
|
|
96
|
+
for search_dir in search_dirs:
|
|
97
|
+
for drive_name in BOOTSEL_DRIVE_NAMES:
|
|
98
|
+
candidate = search_dir / drive_name
|
|
99
|
+
if candidate.is_dir() and (candidate / "INFO_UF2.TXT").exists():
|
|
100
|
+
return str(candidate)
|
|
101
|
+
|
|
102
|
+
elif system == "Darwin":
|
|
103
|
+
# macOS mounts volumes under /Volumes/
|
|
104
|
+
for drive_name in BOOTSEL_DRIVE_NAMES:
|
|
105
|
+
candidate = Path("/Volumes") / drive_name
|
|
106
|
+
if candidate.is_dir() and (candidate / "INFO_UF2.TXT").exists():
|
|
107
|
+
return str(candidate)
|
|
108
|
+
|
|
109
|
+
elif system == "Windows":
|
|
110
|
+
# On Windows, check all drive letters for the BOOTSEL drive.
|
|
111
|
+
# The drive will have INFO_UF2.TXT in its root.
|
|
112
|
+
import string
|
|
113
|
+
|
|
114
|
+
for letter in string.ascii_uppercase:
|
|
115
|
+
candidate = Path(f"{letter}:\\")
|
|
116
|
+
if candidate.exists() and (candidate / "INFO_UF2.TXT").exists():
|
|
117
|
+
return str(candidate)
|
|
118
|
+
|
|
119
|
+
return None
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def read_bootsel_info(drive_path):
|
|
123
|
+
"""
|
|
124
|
+
Read the INFO_UF2.TXT file on the BOOTSEL drive to get board information.
|
|
125
|
+
|
|
126
|
+
This file contains lines like:
|
|
127
|
+
UF2 Bootloader v1.0
|
|
128
|
+
Model: Raspberry Pi RP2350
|
|
129
|
+
Board-ID: RPI-RP2350
|
|
130
|
+
"""
|
|
131
|
+
info_file = Path(drive_path) / "INFO_UF2.TXT"
|
|
132
|
+
if info_file.exists():
|
|
133
|
+
return info_file.read_text()
|
|
134
|
+
return "INFO_UF2.TXT not found"
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def flash_uf2(uf2_path, drive_path=None):
|
|
138
|
+
"""
|
|
139
|
+
Flash a .uf2 firmware file to a Pico in BOOTSEL mode.
|
|
140
|
+
|
|
141
|
+
This is literally just copying a file to a USB drive. That is all flashing is!
|
|
142
|
+
The Pico's bootloader detects the .uf2 file, writes it to internal flash,
|
|
143
|
+
and reboots automatically.
|
|
144
|
+
|
|
145
|
+
Parameters:
|
|
146
|
+
uf2_path: Path to the .uf2 firmware file (e.g. "micropython-pico-w2.uf2")
|
|
147
|
+
drive_path: Path to the BOOTSEL drive. If None, auto-detect.
|
|
148
|
+
|
|
149
|
+
Returns:
|
|
150
|
+
True on success, raises on failure.
|
|
151
|
+
"""
|
|
152
|
+
uf2_path = Path(uf2_path)
|
|
153
|
+
|
|
154
|
+
# Validate the .uf2 file exists
|
|
155
|
+
if not uf2_path.exists():
|
|
156
|
+
raise FileNotFoundError(
|
|
157
|
+
f"UF2 file not found: {uf2_path}\n"
|
|
158
|
+
f"\n"
|
|
159
|
+
f"You need to download the MicroPython firmware for your Pico variant.\n"
|
|
160
|
+
f"Get it from: https://micropython.org/download/\n"
|
|
161
|
+
f" - For Pico W 2: look for 'RPI_PICO2_W' or 'PICO2-W'\n"
|
|
162
|
+
f" - The file will be named something like: RPI_PICO2_W-v1.xx.x.uf2"
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
if not uf2_path.suffix.lower() == ".uf2":
|
|
166
|
+
raise ValueError(
|
|
167
|
+
f"File does not have .uf2 extension: {uf2_path}\n"
|
|
168
|
+
f"Make sure you downloaded the correct firmware file."
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
# Find the BOOTSEL drive
|
|
172
|
+
if drive_path is None:
|
|
173
|
+
drive_path = find_bootsel_drive()
|
|
174
|
+
if drive_path is None:
|
|
175
|
+
raise RuntimeError(
|
|
176
|
+
"No Pico in BOOTSEL mode detected.\n"
|
|
177
|
+
"\n"
|
|
178
|
+
"To put the Pico in BOOTSEL mode:\n"
|
|
179
|
+
" 1. Unplug the Pico from USB.\n"
|
|
180
|
+
" 2. Hold down the BOOTSEL button (small white button on the board).\n"
|
|
181
|
+
" 3. While holding BOOTSEL, plug the USB cable back in.\n"
|
|
182
|
+
" 4. Release the BOOTSEL button.\n"
|
|
183
|
+
"\n"
|
|
184
|
+
"The Pico should appear as a USB drive named 'RPI-RP2' or 'RP2350'.\n"
|
|
185
|
+
"If it does not appear, try a different USB cable -- some cables are\n"
|
|
186
|
+
"charge-only and do not carry data."
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
# Read board info before flashing (for display purposes)
|
|
190
|
+
board_info = read_bootsel_info(drive_path)
|
|
191
|
+
|
|
192
|
+
# The actual flash: copy the .uf2 file to the BOOTSEL drive.
|
|
193
|
+
# shutil.copy2 preserves file metadata. The Pico's bootloader will
|
|
194
|
+
# detect the new file and start writing it to flash memory.
|
|
195
|
+
dest = Path(drive_path) / uf2_path.name
|
|
196
|
+
shutil.copy2(str(uf2_path), str(dest))
|
|
197
|
+
|
|
198
|
+
# After the copy completes, the Pico will automatically reboot.
|
|
199
|
+
# The BOOTSEL drive will disappear (unmount) as the Pico restarts.
|
|
200
|
+
# Give it a moment to start the reboot process.
|
|
201
|
+
return {
|
|
202
|
+
"uf2_file": str(uf2_path),
|
|
203
|
+
"drive_path": drive_path,
|
|
204
|
+
"board_info": board_info,
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def wait_for_serial_after_flash(timeout=15):
|
|
209
|
+
"""
|
|
210
|
+
After flashing, wait for the Pico to reboot and appear as a serial device.
|
|
211
|
+
|
|
212
|
+
The typical sequence after flashing MicroPython:
|
|
213
|
+
1. .uf2 is copied to BOOTSEL drive (~2 seconds)
|
|
214
|
+
2. Pico writes firmware to flash memory (~3 seconds)
|
|
215
|
+
3. Pico reboots (~1 second)
|
|
216
|
+
4. MicroPython starts and USB serial device appears (~2 seconds)
|
|
217
|
+
|
|
218
|
+
Total: about 5-10 seconds.
|
|
219
|
+
"""
|
|
220
|
+
# Import here to avoid circular imports
|
|
221
|
+
from .serial_detect import list_pico_serial_ports
|
|
222
|
+
|
|
223
|
+
start = time.time()
|
|
224
|
+
while time.time() - start < timeout:
|
|
225
|
+
picos = list_pico_serial_ports()
|
|
226
|
+
if picos:
|
|
227
|
+
return picos[0]["port"]
|
|
228
|
+
time.sleep(1)
|
|
229
|
+
|
|
230
|
+
return None
|
wakemypc/identify.py
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
"""
|
|
2
|
+
identify.py -- Blink the Pico's LED for physical identification
|
|
3
|
+
================================================================
|
|
4
|
+
|
|
5
|
+
WHY YOU NEED THIS
|
|
6
|
+
-----------------
|
|
7
|
+
Imagine you have 5 Picos plugged into a USB hub, and your computer shows them as:
|
|
8
|
+
/dev/ttyACM0
|
|
9
|
+
/dev/ttyACM1
|
|
10
|
+
/dev/ttyACM2
|
|
11
|
+
/dev/ttyACM3
|
|
12
|
+
/dev/ttyACM4
|
|
13
|
+
|
|
14
|
+
Which physical Pico is /dev/ttyACM2? They all look the same! You cannot tell
|
|
15
|
+
just by looking at them.
|
|
16
|
+
|
|
17
|
+
The "identify" command solves this: it tells a specific Pico to blink its LED
|
|
18
|
+
rapidly. Now you can look at your pile of Picos and see which one is blinking.
|
|
19
|
+
Then you can label it with a sticker or note its position.
|
|
20
|
+
|
|
21
|
+
HOW IT WORKS
|
|
22
|
+
------------
|
|
23
|
+
We send a small Python script to the Pico via the serial REPL (the interactive
|
|
24
|
+
Python prompt accessible over USB). The script:
|
|
25
|
+
|
|
26
|
+
1. Imports the 'machine' module (MicroPython's hardware control library).
|
|
27
|
+
2. Gets a reference to the onboard LED pin.
|
|
28
|
+
- On Pico W / Pico W 2, the LED is connected to the WiFi chip, so we
|
|
29
|
+
use the string "LED" instead of a pin number.
|
|
30
|
+
3. Toggles the LED on and off rapidly in a loop.
|
|
31
|
+
|
|
32
|
+
The Pico W 2's onboard LED is a small green LED next to the USB connector.
|
|
33
|
+
It is not super bright, but it is visible enough to identify the device.
|
|
34
|
+
|
|
35
|
+
SERIAL REPL COMMUNICATION
|
|
36
|
+
--------------------------
|
|
37
|
+
The serial REPL is like having a Python terminal running on the Pico. When you
|
|
38
|
+
open a serial connection (like we do here), you can type Python code and the
|
|
39
|
+
Pico executes it immediately. This is the same thing that happens when you use
|
|
40
|
+
tools like Thonny, PuTTY, or 'screen' to talk to the Pico.
|
|
41
|
+
|
|
42
|
+
We use "raw REPL" mode (entered with Ctrl+A) for sending multi-line scripts.
|
|
43
|
+
In raw mode, there is no echo or prompt, making it easier for programs (vs humans)
|
|
44
|
+
to communicate with the Pico.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
import time
|
|
48
|
+
|
|
49
|
+
import serial
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
# The MicroPython script that runs ON THE PICO (not on your computer!).
|
|
53
|
+
# This script is sent over USB serial and executed by the Pico's MicroPython
|
|
54
|
+
# interpreter.
|
|
55
|
+
#
|
|
56
|
+
# Note: This code uses MicroPython APIs (machine.Pin, time.sleep) which are
|
|
57
|
+
# different from regular Python. These only work on the Pico, not on your computer.
|
|
58
|
+
BLINK_SCRIPT = """\
|
|
59
|
+
import machine
|
|
60
|
+
import time
|
|
61
|
+
|
|
62
|
+
# On Pico W and Pico W 2, the onboard LED is controlled via the WiFi chip,
|
|
63
|
+
# so we reference it by the string "LED" rather than a GPIO pin number.
|
|
64
|
+
# On the original Pico (non-W), the LED is on GPIO 25: machine.Pin(25, machine.Pin.OUT)
|
|
65
|
+
led = machine.Pin("LED", machine.Pin.OUT)
|
|
66
|
+
|
|
67
|
+
# Blink rapidly 20 times (takes about 4 seconds total).
|
|
68
|
+
# Each cycle: 100ms on + 100ms off = 200ms per blink.
|
|
69
|
+
for i in range(20):
|
|
70
|
+
led.on()
|
|
71
|
+
time.sleep(0.1)
|
|
72
|
+
led.off()
|
|
73
|
+
time.sleep(0.1)
|
|
74
|
+
|
|
75
|
+
# Leave the LED off when done
|
|
76
|
+
led.off()
|
|
77
|
+
print("IDENTIFY_DONE")
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def blink_led(port, duration_seconds=4, baudrate=115200):
|
|
82
|
+
"""
|
|
83
|
+
Make a Pico's onboard LED blink rapidly for physical identification.
|
|
84
|
+
|
|
85
|
+
Parameters:
|
|
86
|
+
port: Serial port path, e.g. "/dev/ttyACM0"
|
|
87
|
+
duration_seconds: Approximate duration of blinking (not precise)
|
|
88
|
+
baudrate: Serial speed (115200 is standard for MicroPython)
|
|
89
|
+
|
|
90
|
+
How it works:
|
|
91
|
+
1. Open a serial connection to the Pico.
|
|
92
|
+
2. Enter raw REPL mode (Ctrl+A) for clean script execution.
|
|
93
|
+
3. Send the blink script.
|
|
94
|
+
4. Wait for the script to finish.
|
|
95
|
+
5. Return to normal REPL mode (Ctrl+B).
|
|
96
|
+
|
|
97
|
+
The raw REPL protocol:
|
|
98
|
+
- Ctrl+A (0x01): Enter raw REPL mode. Pico responds with "raw REPL; CTRL-B to exit"
|
|
99
|
+
- Send Python code as plain text.
|
|
100
|
+
- Ctrl+D (0x04): Execute the code. Pico responds with "OK" then output then Ctrl+D.
|
|
101
|
+
- Ctrl+B (0x02): Exit raw REPL, return to normal interactive mode.
|
|
102
|
+
"""
|
|
103
|
+
try:
|
|
104
|
+
ser = serial.Serial(port, baudrate, timeout=2)
|
|
105
|
+
except serial.SerialException as e:
|
|
106
|
+
raise RuntimeError(
|
|
107
|
+
f"Could not open serial port {port}: {e}\n"
|
|
108
|
+
f"\n"
|
|
109
|
+
f"Make sure:\n"
|
|
110
|
+
f" - The Pico is plugged in and has MicroPython installed\n"
|
|
111
|
+
f" - No other program (Thonny, screen, etc.) is using the port\n"
|
|
112
|
+
f" - You have permission to access the port (Linux: add yourself to 'dialout' group)"
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
time.sleep(0.5)
|
|
116
|
+
|
|
117
|
+
# Interrupt any currently running program on the Pico.
|
|
118
|
+
# Sending Ctrl+C (0x03) twice is the standard way to get back to the REPL
|
|
119
|
+
# prompt, even if a program is in the middle of a time.sleep() or a loop.
|
|
120
|
+
ser.write(b"\r\x03\x03")
|
|
121
|
+
time.sleep(0.5)
|
|
122
|
+
ser.read(ser.in_waiting) # Discard any buffered output
|
|
123
|
+
|
|
124
|
+
# Enter raw REPL mode.
|
|
125
|
+
# Normal REPL: echoes what you type, has ">>> " prompt, auto-indents.
|
|
126
|
+
# Raw REPL: no echo, no prompt, executes code blocks terminated by Ctrl+D.
|
|
127
|
+
# Raw mode is better for programmatic use because we do not have to parse prompts.
|
|
128
|
+
ser.write(b"\x01") # Ctrl+A = enter raw REPL
|
|
129
|
+
time.sleep(0.3)
|
|
130
|
+
ser.read(ser.in_waiting) # Read and discard the "raw REPL" banner
|
|
131
|
+
|
|
132
|
+
# Calculate blink count based on desired duration.
|
|
133
|
+
# Each blink cycle is ~200ms (100ms on + 100ms off).
|
|
134
|
+
blink_count = max(5, int(duration_seconds / 0.2))
|
|
135
|
+
|
|
136
|
+
# Build the script with the custom blink count
|
|
137
|
+
script = (
|
|
138
|
+
"import machine\n"
|
|
139
|
+
"import time\n"
|
|
140
|
+
'led = machine.Pin("LED", machine.Pin.OUT)\n'
|
|
141
|
+
f"for i in range({blink_count}):\n"
|
|
142
|
+
" led.on()\n"
|
|
143
|
+
" time.sleep(0.1)\n"
|
|
144
|
+
" led.off()\n"
|
|
145
|
+
" time.sleep(0.1)\n"
|
|
146
|
+
"led.off()\n"
|
|
147
|
+
'print("IDENTIFY_DONE")\n'
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
# Send the script and execute it
|
|
151
|
+
ser.write(script.encode())
|
|
152
|
+
ser.write(b"\x04") # Ctrl+D = execute the code
|
|
153
|
+
|
|
154
|
+
# Wait for the blinking to finish.
|
|
155
|
+
# The script takes approximately `duration_seconds` to complete.
|
|
156
|
+
timeout = duration_seconds + 5 # Extra buffer for slow serial
|
|
157
|
+
start = time.time()
|
|
158
|
+
response = b""
|
|
159
|
+
|
|
160
|
+
while time.time() - start < timeout:
|
|
161
|
+
if ser.in_waiting:
|
|
162
|
+
response += ser.read(ser.in_waiting)
|
|
163
|
+
if b"IDENTIFY_DONE" in response:
|
|
164
|
+
break
|
|
165
|
+
time.sleep(0.1)
|
|
166
|
+
|
|
167
|
+
# Exit raw REPL mode and return to normal mode
|
|
168
|
+
ser.write(b"\x02") # Ctrl+B = exit raw REPL
|
|
169
|
+
time.sleep(0.2)
|
|
170
|
+
ser.close()
|
|
171
|
+
|
|
172
|
+
success = b"IDENTIFY_DONE" in response
|
|
173
|
+
return {
|
|
174
|
+
"port": port,
|
|
175
|
+
"success": success,
|
|
176
|
+
"duration": duration_seconds,
|
|
177
|
+
"message": (
|
|
178
|
+
f"LED on {port} blinked for ~{duration_seconds} seconds."
|
|
179
|
+
if success
|
|
180
|
+
else f"Blink command was sent to {port} but completion was not confirmed."
|
|
181
|
+
),
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def read_device_id_and_blink(port, baudrate=115200):
|
|
186
|
+
"""
|
|
187
|
+
Read the device ID AND blink the LED, so the user can match ID to physical device.
|
|
188
|
+
|
|
189
|
+
This is a convenience function that:
|
|
190
|
+
1. Reads the Pico's unique hardware ID.
|
|
191
|
+
2. Blinks the LED so you can see which physical Pico it is.
|
|
192
|
+
3. Returns both the device ID and the port, so you can label the device.
|
|
193
|
+
|
|
194
|
+
Typical usage:
|
|
195
|
+
"I have 3 Picos. Let me identify each one."
|
|
196
|
+
For each port, call this function. It will tell you:
|
|
197
|
+
"Port /dev/ttyACM0 has device ID e660583883724a32 -- that's the one blinking now."
|
|
198
|
+
"""
|
|
199
|
+
from .provision import read_device_id
|
|
200
|
+
|
|
201
|
+
device_id = read_device_id(port)
|
|
202
|
+
blink_result = blink_led(port)
|
|
203
|
+
|
|
204
|
+
return {
|
|
205
|
+
"port": port,
|
|
206
|
+
"device_id": device_id,
|
|
207
|
+
"blink_success": blink_result["success"],
|
|
208
|
+
"message": (
|
|
209
|
+
f"Device on {port} has ID: {device_id}\n"
|
|
210
|
+
f"The LED is blinking now -- look for the flashing green light!"
|
|
211
|
+
),
|
|
212
|
+
}
|