1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
# pyright: reportUndefinedVariable=false, reportAttributeAccessIssue=false, reportMissingImports=false
#
# Pyright type-checks against CPython; on this Jumperless V5 firmware
# 5.6.6.2 MicroPython runtime context the `jumperless` module is
# firmware-injected (not in any CPython search path), `jumperless_mcp.*`
# submodules resolve via the device's sys.path at /python_scripts/lib/,
# `overlay_clear_all` is a firmware builtin (not Python stdlib), and
# `time.sleep_ms` is a MicroPython extension. Suppressing the noise here
# so the LSP isn't loud on what's actually correct device-side code.
"""jumperless_mcp — Jumperless MCP bridge package
Installed at /python_scripts/lib/jumperless_mcp/ by the host-side
jumperless-mcp CLI; importable as `import jumperless_mcp`.
Source of truth for version is the VERSION sibling file (so the host
can read it back via fs without parsing Python). __version__ here
should match.
TODO (future phases):
- set_net_color_hsv(net, h, s, v) install-ack: paint a net green on
connect as a one-liner "the board knows MCP arrived" signal.
- FakeGpioPin-backed effects: digital effects without consuming real
GPIO pins. Requires machine.Pin-compatible wrapper from firmware.
- probe_read_nonblocking() / clickwheel_get_button() tap-to-continue:
elegant ceremony pause waiting for user acknowledgement via two
input methods.
"""
# IDE autocomplete trick (per Kevin's script_template.py convention):
# never executes on device, but tells the IDE what jumperless exposes.
# noqa
=
# Native jumperless module is globally injected by the firmware;
# import explicitly so our submodules can reference it cleanly.
# noqa: E402 (firmware-injected, not in stdlib)
# Pull firmware-injected builtins explicitly into module scope. When this
# package is loaded via `import jumperless_mcp` (as opposed to old-style
# exec(jfs.read(...)) in the global script scope), firmware globals don't
# auto-resolve at module level — they need to be explicitly imported.
# noqa: E402
# Re-export the effects we want callable directly from jumperless_mcp:
# noqa: F401,E402 (loaded for side-effect/availability)
"""Run the MCP connect ceremony. Called by host after import.
Phases:
1. Clear OLED
2. Print "MCP Connected" to OLED
3. Marquee scroll (the marquee IS the banner — no separate wipe preamble)
4. Corner frame settle + OLED clear
Removed in v0.2.3: the wipe_edges preamble + 800ms hold. Reading the two
visuals (edge wipe + then marquee) as "two banners" was disjointed; the
marquee itself contains the orange fill so it reads as a unified
arrival of an MCP banner with text.
Wall-clock: ~3-4s on device.
"""
"""Run the MCP disconnect ceremony. Called by host before disconnect.
Phases (symmetric with connect):
1. Clear OLED
2. Print "MCP Disconnected" to OLED
3. Marquee scroll (R2L, mirror direction of connect's L2R)
4. Corner "unbracket" + OLED clear
Wall-clock: ~3s on device.
**Bracket-unbracket strategy (2026-05-10):** instead
of fighting Kevin's autosave system with overlay_clear_all + nodes_save
(v0.2.1-0.2.3 attempts — never worked reliably because the autosave is
timer-driven and races us, AND it's working-as-designed to preserve user
work), we PAINT the same corner pixels we painted on connect, but with
color=0x000000. The autosave then saves an overlay buffer whose color
array is all-zeros for the bracket positions → on reboot, the overlay
re-renders as invisible. Kevin's autosave still does its job (preserves
overlay state); we just make the saved state visually empty.
"""
# unbracket: same pixels, black color