Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,11 +128,11 @@ iPhone-style dark look (status bar, battery glyph, grouped settings cards).
enabling one frees the other.

### 🔭 Later
- **MeshCore companion bridge** V0 protocol foundation is drafted, and an
initial `companion mc ...` USB serial-console smoke surface can report
snapshots and exercise send boundaries; the formal USB/BLE bridge remains
planned, and it is not official MeshCore app compatible unless the real
MeshCore app protocol is confirmed.
- **MeshCore companion bridge** - V0 protocol foundation is drafted, with
`companion mc ...` diagnostics and formal USB `MC0` mode for snapshots,
public send, private send, and exact public-key DM routing. BLE transport,
live events, and official MeshCore app compatibility remain planned until the
real MeshCore app protocol is confirmed.
- **Roll the iPhone look everywhere** — grouped cards / dividers across Messages, Nodes, Contacts.
- **Local app platform** - scan local app manifests from `/sd/limitlezz/apps`,
`/sd/apps`, `/appfs/apps`, and simulator data dirs, then show accepted apps
Expand Down
2 changes: 1 addition & 1 deletion docs/tdeck-feature-inventory.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ Status labels:
| MeshCore self-advert TX | Partial, needs validation | Ed25519 identity, self-advert builder, serial/UI advert commands | Needs interop proof with real MeshCore nodes. |
| MeshCore public channel / rooms | Planned | README says receive/default Public channel still ahead | V0.6: implement group text decode/send, room model, and split airtime config. |
| MeshCore DMs | Planned | MeshCore contacts are non-messageable while gated | V0.7: implement key/session model, send path, ACKs, and UI routing. |
| MeshCore companion bridge | Planned/In progress | `docs/tdeck-meshcore-companion-protocol.md` drafts the V0 USB serial line protocol; `companion mc ...` provides an initial firmware smoke surface for snapshots, sends, and self-test | V0.8: formalize USB first, mirror to BLE later, and do not claim external MeshCore app compatibility until the real app protocol is confirmed. |
| MeshCore companion bridge | Partial, needs validation | `docs/tdeck-meshcore-companion-protocol.md` defines the V0 USB serial line protocol; `companion mc ...` and formal `MC0` mode provide snapshot, public-send, private-send, exact public-key DM routing, and self-test surfaces | V0.8: hardware-validate USB MC0, add live events, mirror to BLE later, and do not claim external MeshCore app compatibility until the real app protocol is confirmed. |

## User Interface

Expand Down
14 changes: 9 additions & 5 deletions docs/tdeck-firmware-roadmap.md
Original file line number Diff line number Diff line change
Expand Up @@ -190,16 +190,20 @@ Exit criteria:
Goal: expose MeshCore companion functionality only after native MeshCore messaging is stable.

**Status:** protocol foundation in progress with an initial USB serial-console
smoke surface. `companion mc hello|status|nodes|threads|send|dm|test` now
exercises the firmware-owned MeshCore snapshots and send boundaries for COM8
validation, while the formal `MC0` bridge, BLE transport, events, and real
external MeshCore app compatibility are still planned work.
smoke surface plus formal USB `MC0` mode. `companion mc
hello|status|nodes|threads|send|dm|test` and `MC0`
`HELLO|IDENTITY|STATUS|NODES|THREADS|SEND_PUBLIC|SEND_DM|EXIT` exercise the
firmware-owned MeshCore snapshots and send boundaries for COM8 validation.
`MC0` uses full public-key addresses for exact DM routing. BLE transport, live
events, and real external MeshCore app compatibility are still planned work.

Deliverables:

- Define the MeshCore companion protocol surface for node DB, public chat, private chats, and send/receive forwarding. Drafted as `docs/tdeck-meshcore-companion-protocol.md`.
- Add a USB serial-console smoke surface that reports MeshCore companion status, nodes, threads, Public send, DM send, and self-test.
- Implement MeshCore USB companion mode.
- Implement MeshCore USB companion mode. USB `MC0` is implemented for
snapshots, public send, private send, exact full-public-key DM routing, and
explicit exit back to console; live event streaming is still a later step.
- Implement MeshCore BLE companion mode.
- Add UI and serial commands that distinguish Meshtastic companion from MeshCore companion.
- Decide whether one companion session or one network can own the external-app bridge at a time.
Expand Down
37 changes: 22 additions & 15 deletions docs/tdeck-meshcore-companion-protocol.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,14 +140,16 @@ MC0 2 IDENTITY
Response:

```text
MC0 2 OK enabled=1 name=Jess addr=4f8e21a0 role=chat pubkey=6b1d... addr_format=meshcore-hex advert_ready=1
MC0 2 OK enabled=1 name=Jess addr=6b1d000000000000000000000000000000000000000000000000000000000000 short_id=MC-6b1d0000 role=chat pubkey=6b1d000000000000000000000000000000000000000000000000000000000000 addr_format=meshcore-pubkey-hex advert_ready=1
```

Required fields:

- `enabled`: whether MeshCore is currently enabled.
- `name`: percent-encoded local display name.
- `addr`: local MeshCore address, lowercase hex, opaque to the host.
- `addr`: local MeshCore address, lowercase 64-hex MeshCore public key, opaque
to the host except for exact equality.
- `short_id`: optional display-only short identifier. Hosts must not route by it.
- `role`: current local MeshCore role, such as `chat`, `router`, or
`unknown`.
- `addr_format`: the address encoding advertised for this session.
Expand Down Expand Up @@ -197,21 +199,24 @@ Response:

```text
MC0 4 BEGIN type=nodes rev=42 count=2 more=0 cursor=end
MC0 4 NODE addr=4f8e21a0 name=Limitlezz role=chat seen_ms=12000 snr=-9 rssi=-112 public_key=present dm=ready
MC0 4 NODE addr=12ab9001 name=Hilltop role=router seen_ms=180000 snr=-14 rssi=-118 public_key=missing dm=not_messageable
MC0 4 NODE addr=4f8e21a000000000000000000000000000000000000000000000000000000000 short_id=MC-4f8e21a0 name=Limitlezz role=chat seen_ms=12000 snr=-9 rssi=-112 public_key=present dm=ready
MC0 4 NODE addr=- short_id=MC-12ab9001 name=Hilltop role=router seen_ms=180000 snr=-14 rssi=-118 public_key=missing dm=not_messageable
MC0 4 END type=nodes rev=42 count=2 more=0 cursor=end
```

Request fields:

- `since`: last `nodes_rev` known by the host, or `0` for a full snapshot.
- `limit`: maximum rows requested. Firmware may cap this below the requested
value.
value; V0 USB mode caps node snapshots at five rows to keep line responses
bounded.
- `cursor`: optional opaque cursor from a previous `NODES` response.

Node fields:

- `addr`: MeshCore address, lowercase hex, opaque to the host.
- `addr`: MeshCore address, lowercase 64-hex public key when known. `-` means
the firmware has no usable key yet, so `SEND_DM to_addr=...` is impossible.
- `short_id`: display-only short identifier for compact UI labels.
- `name`: percent-encoded display name, if known.
- `role`: `chat`, `router`, `repeater`, `sensor`, or `unknown`.
- `seen_ms`: milliseconds since last heard, or `-1` if unknown.
Expand Down Expand Up @@ -263,7 +268,7 @@ name. Address is preferred because display names can collide.
By address:

```text
MC0 6 SEND_DM to_addr=4f8e21a0 text=Meet%20at%20camp client_mid=pc-0002
MC0 6 SEND_DM to_addr=4f8e21a000000000000000000000000000000000000000000000000000000000 text=Meet%20at%20camp client_mid=pc-0002
```

By known name:
Expand All @@ -275,13 +280,13 @@ MC0 7 SEND_DM to_name=Limitlezz text=Copy%20that client_mid=pc-0003
Immediate response:

```text
MC0 6 OK accepted=1 msg_id=mc-805 to_addr=4f8e21a0 status=queued
MC0 6 OK accepted=1 msg_id=mc-805 to_addr=4f8e21a000000000000000000000000000000000000000000000000000000000 status=queued
```

Later event:

```text
MC0 EVT 123 tx_status client_mid=pc-0002 msg_id=mc-805 kind=dm to_addr=4f8e21a0 status=delivered
MC0 EVT 123 tx_status client_mid=pc-0002 msg_id=mc-805 kind=dm to_addr=4f8e21a000000000000000000000000000000000000000000000000000000000 status=delivered
```

V0 name matching rules:
Expand All @@ -292,6 +297,8 @@ V0 name matching rules:
- If more than one node matches, return `ERR code=ambiguous_name`.
- If the matching node lacks a usable session/key, return `ERR code=no_key`.
- If the node role is not messageable, return `ERR code=not_messageable`.
- If both `to_addr` and `to_name` are supplied, they must identify the same
node or firmware returns `ERR code=target_mismatch`.

The host does not manage MeshCore private keys or sessions in V0.

Expand All @@ -315,10 +322,10 @@ MC0 8 OK events=on types=nodes,messages,tx,status event_seq=123
Supported event types:

```text
MC0 EVT 124 node_upsert addr=4f8e21a0 nodes_rev=43
MC0 EVT 124 node_upsert addr=4f8e21a000000000000000000000000000000000000000000000000000000000 nodes_rev=43
MC0 EVT 125 snapshot_dirty type=nodes rev=43 reason=node_upsert
MC0 EVT 126 rx_public msg_id=mc-806 from_addr=4f8e21a0 from_name=Limitlezz room=public text=Copy%20CH0.
MC0 EVT 127 rx_dm msg_id=mc-807 from_addr=4f8e21a0 from_name=Limitlezz text=Direct%20copy.
MC0 EVT 126 rx_public msg_id=mc-806 from_addr=4f8e21a000000000000000000000000000000000000000000000000000000000 from_name=Limitlezz room=public text=Copy%20CH0.
MC0 EVT 127 rx_dm msg_id=mc-807 from_addr=4f8e21a000000000000000000000000000000000000000000000000000000000 from_name=Limitlezz text=Direct%20copy.
MC0 EVT 128 tx_status client_mid=pc-0002 msg_id=mc-805 kind=dm status=failed reason=ack_timeout retry=1
MC0 EVT 129 status mc=on tdm=active airtime=balanced queue=0
```
Expand Down Expand Up @@ -390,9 +397,9 @@ Rules:
`companion mc test`.
- Formal USB MC0 smoke is opt-in:
`python scripts/mc_companion_usb_smoke.py --mc0-usb` enters the configured
USB mode, sends `HELLO`, `STATUS`, and `NODES`, asserts `MC0 ... OK`,
`BEGIN`, and `END` response markers, then exits through the configured
`MC0 <id> EXIT` line.
USB mode, sends `HELLO`, `IDENTITY`, `STATUS`, and `NODES`, asserts
`MC0 ... OK`, the public-key address format, `BEGIN`, and `END` response
markers, then exits through the configured `MC0 <id> EXIT` line.
- If firmware lands different command names, use the smoke helper's
`--mc0-enter-command`, `--mc0-*-template`, `--mc0-*-marker`, and
`--mc0-exit-template` flags instead of editing firmware or weakening the
Expand Down
35 changes: 33 additions & 2 deletions scripts/mc_companion_usb_smoke.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,12 @@
DEFAULT_PUBLIC_TEMPLATE = "companion mc send {text}"
DEFAULT_MC0_ENTER_COMMAND = "companion mc usb on"
DEFAULT_MC0_HELLO_ID = "1"
DEFAULT_MC0_STATUS_ID = "2"
DEFAULT_MC0_NODES_ID = "3"
DEFAULT_MC0_IDENTITY_ID = "2"
DEFAULT_MC0_STATUS_ID = "3"
DEFAULT_MC0_NODES_ID = "4"
DEFAULT_MC0_EXIT_ID = "99"
DEFAULT_MC0_HELLO_TEMPLATE = "MC0 {id} HELLO proto=0 app=limitlezz-smoke host=windows want=none"
DEFAULT_MC0_IDENTITY_TEMPLATE = "MC0 {id} IDENTITY"
DEFAULT_MC0_STATUS_TEMPLATE = "MC0 {id} STATUS"
DEFAULT_MC0_NODES_TEMPLATE = "MC0 {id} NODES since=0 limit=5"
DEFAULT_MC0_EXIT_TEMPLATE = "MC0 {id} EXIT"
Expand Down Expand Up @@ -180,6 +182,9 @@ def build_mc0_specs(args: argparse.Namespace) -> list[CommandSpec]:
hello_command = format_mc0_template(
"--mc0-hello-template", args.mc0_hello_template, args.mc0_hello_id
)
identity_command = format_mc0_template(
"--mc0-identity-template", args.mc0_identity_template, args.mc0_identity_id
)
status_command = format_mc0_template(
"--mc0-status-template", args.mc0_status_template, args.mc0_status_id
)
Expand All @@ -195,6 +200,18 @@ def build_mc0_specs(args: argparse.Namespace) -> list[CommandSpec]:
[f"MC0 {args.mc0_hello_id} OK"],
),
),
CommandSpec(
"mc0 identity",
identity_command,
default_marker_override(
args.mc0_identity_marker,
[
f"MC0 {args.mc0_identity_id} OK",
"addr_format=meshcore-pubkey-hex",
"pubkey=",
],
),
),
CommandSpec(
"mc0 status",
status_command,
Expand Down Expand Up @@ -549,6 +566,7 @@ def main() -> int:
help="Seconds of quiet serial input that ends an optional MC0 read.",
)
mc0.add_argument("--mc0-hello-id", default=DEFAULT_MC0_HELLO_ID)
mc0.add_argument("--mc0-identity-id", default=DEFAULT_MC0_IDENTITY_ID)
mc0.add_argument("--mc0-status-id", default=DEFAULT_MC0_STATUS_ID)
mc0.add_argument("--mc0-nodes-id", default=DEFAULT_MC0_NODES_ID)
mc0.add_argument("--mc0-exit-id", default=DEFAULT_MC0_EXIT_ID)
Expand All @@ -557,6 +575,11 @@ def main() -> int:
default=DEFAULT_MC0_HELLO_TEMPLATE,
help="MC0 HELLO line template; supports {id}.",
)
mc0.add_argument(
"--mc0-identity-template",
default=DEFAULT_MC0_IDENTITY_TEMPLATE,
help="MC0 IDENTITY line template; supports {id}.",
)
mc0.add_argument(
"--mc0-status-template",
default=DEFAULT_MC0_STATUS_TEMPLATE,
Expand All @@ -572,6 +595,14 @@ def main() -> int:
action="append",
help="Override HELLO expected marker(s). Defaults to 'MC0 <hello-id> OK'.",
)
mc0.add_argument(
"--mc0-identity-marker",
action="append",
help=(
"Override IDENTITY expected marker(s). Defaults to OK, "
"addr_format=meshcore-pubkey-hex, and pubkey=."
),
)
mc0.add_argument(
"--mc0-status-marker",
action="append",
Expand Down
12 changes: 6 additions & 6 deletions scripts/tdeck_smoke.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,16 +115,16 @@ def nostub_upload(project_dir: Path, env_name: str, port: str, baud: int, artifa
"--baud",
str(baud),
"--before",
"default-reset",
"default_reset",
"--after",
"hard-reset",
"hard_reset",
"--no-stub",
"write-flash",
"--flash-mode",
"write_flash",
"--flash_mode",
"dio",
"--flash-freq",
"--flash_freq",
"80m",
"--flash-size",
"--flash_size",
"16MB",
"0x0",
str(bootloader),
Expand Down
46 changes: 45 additions & 1 deletion sim/backend_sim.c
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include "sim_radio.h"
#include <stdio.h>
#include <stdint.h>
#include <string.h>

void lz_backend_init(void) { sim_radio_init(); }

Expand All @@ -36,11 +37,54 @@ bool lz_backend_mc_advert_now(bool flood) { return sim_radio_mc_advert_now(flood

void lz_backend_mc_addr(char *buf, int n) { snprintf(buf, n, "MC-1ec77175"); }

bool lz_backend_mc_pubkey(uint8_t out32[32])
{
if(!out32) return false;
for(int i = 0; i < 32; i++) out32[i] = (uint8_t)(0x10 + i);
return true;
}

/* MeshCore outbound API. sim_radio models inbound MeshCore (sim_inject_mc_*);
* the outbound contract still needs these symbols defined or the native build
* fails to link on macOS (weak externs in mesh_core.c don't resolve to NULL). */
bool lz_backend_mc_send_public(const char *text) { (void)text; return true; }
bool lz_backend_mc_dm(const char *name, const char *text) { (void)name; (void)text; return true; }

static bool g_last_mc_dm;
static uint8_t g_last_mc_dm_key[32];
static char g_last_mc_dm_name[28];
static char g_last_mc_dm_text[160];

bool lz_backend_mc_dm_key(const uint8_t peer_pub[32], const char *name_hint, const char *text)
{
if(!peer_pub || !text || !text[0]) return false;
g_last_mc_dm = true;
memcpy(g_last_mc_dm_key, peer_pub, 32);
snprintf(g_last_mc_dm_name, sizeof g_last_mc_dm_name, "%s",
name_hint ? name_hint : "");
snprintf(g_last_mc_dm_text, sizeof g_last_mc_dm_text, "%s", text);
return true;
}

bool lz_backend_mc_dm(const char *name, const char *text) { (void)name; (void)text; return false; }

void sim_backend_mc_clear_last_dm(void)
{
g_last_mc_dm = false;
memset(g_last_mc_dm_key, 0, sizeof g_last_mc_dm_key);
g_last_mc_dm_name[0] = 0;
g_last_mc_dm_text[0] = 0;
}

bool sim_backend_mc_last_dm(uint8_t out32[32], char *name, int name_cap,
char *text, int text_cap)
{
if(!g_last_mc_dm) return false;
if(out32) memcpy(out32, g_last_mc_dm_key, 32);
if(name && name_cap > 0) snprintf(name, (size_t)name_cap, "%s", g_last_mc_dm_name);
if(text && text_cap > 0) snprintf(text, (size_t)text_cap, "%s", g_last_mc_dm_text);
return true;
}

int lz_backend_mc_peers(char *buf, int n) { snprintf(buf, n, "no MeshCore peers (sim)\n"); return 0; }

static bool g_sim_companion;
Expand Down
Loading