The README and the project documents are in ASD-STE100
The README, CONTRIBUTING, SECURITY, the docs index, the development documents, the script READMEs, the comments of config.example.toml and pyproject.toml, and the issue and pull request forms. The legal sections of the README (attribution and licence) stay exactly as they were, between ste: off and ste: on markers. The SignPath attribution line, the one-line description of MeshTerm, every code block, and every URL do not change. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
962d96f8d3
commit
e6f6cf2621
14 files changed
+690
-618
No files matched your search
@@ -5,30 +5,30 @@ body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Hopefully you never need this form.
|
||||
We hope that you never need this form.
|
||||
|
||||
If you do: start by running this, and attach the file it writes.
|
||||
If you do need it, first run this command and attach the file that it writes.
|
||||
|
||||
```
|
||||
meshterm diagnostics --out bug.md
|
||||
```
|
||||
|
||||
It answers which MeshTerm, on which machine, in which terminal, talking to which
|
||||
radio over which transport and firmware — most of what I would otherwise ask one
|
||||
question at a time, and the details people most often end up guessing at. Nothing
|
||||
in it is private: no keys, no pairing PINs, no positions, no contacts. That is a
|
||||
tested property, not a promise.
|
||||
The file says which MeshTerm you use, on which machine, in which terminal, and with
|
||||
which radio, transport, and firmware. This is most of the information that I would
|
||||
otherwise ask for in single questions. It is also the information that people most
|
||||
often guess. The file has no private information: no keys, no pairing PINs, no
|
||||
positions, and no contacts. A test checks this. It is not only a promise.
|
||||
|
||||
The **Diagnostics** page in the menu saves the same file (with `s`, or F3 on the
|
||||
PicoCalc), next to the log.
|
||||
The **Diagnostics** page in the menu saves the same file next to the log. Press `s`,
|
||||
or F3 on the PicoCalc.
|
||||
|
||||
The rest of this form asks only for what that file cannot know.
|
||||
The rest of this form asks only for the information that the file cannot know.
|
||||
|
||||
- type: textarea
|
||||
id: problem
|
||||
attributes:
|
||||
label: What's the problem
|
||||
description: What happens, and what you expected to happen instead.
|
||||
label: What is the problem
|
||||
description: What happens, and what you expected to happen.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
@@ -47,11 +47,12 @@ body:
|
||||
- type: textarea
|
||||
id: repro
|
||||
attributes:
|
||||
label: How do I make it happen
|
||||
label: How can I make it happen
|
||||
description: >
|
||||
The steps, in order, starting from launching MeshTerm. Screens and keys beat
|
||||
descriptions — "Contacts, Enter on a node, Routes tab, Enter on the second route"
|
||||
is worth more than "open a trace".
|
||||
Write the steps in order. Start from the time that you start MeshTerm. Name the
|
||||
screens and the keys. This is more useful than a description. For example,
|
||||
"Contacts, Enter on a node, Routes tab, Enter on the second route" is better than
|
||||
"open a trace".
|
||||
placeholder: |
|
||||
1.
|
||||
2.
|
||||
@@ -64,15 +65,16 @@ body:
|
||||
attributes:
|
||||
label: Does it still happen with `meshterm --mock`
|
||||
description: >
|
||||
`--mock` runs the whole app against a fake radio. If the bug survives that, it is
|
||||
in MeshTerm and I can chase it without owning your hardware. If it doesn't, it is
|
||||
in the link or the radio, which is a different hunt. This one question decides
|
||||
which of those two jobs this is, so it is worth the minute it takes.
|
||||
`--mock` runs the complete app with a fake radio. If the bug also occurs with
|
||||
`--mock`, the bug is in MeshTerm, and I can look for it without your hardware. If
|
||||
the bug does not occur with `--mock`, it is in the link or in the radio. This is a
|
||||
different search. This one question tells me which of the two jobs this is, so
|
||||
please take the minute to answer it.
|
||||
options:
|
||||
- "Yes, still happens with --mock"
|
||||
- "No, --mock is fine"
|
||||
- "Haven't tried"
|
||||
- "Doesn't apply — the problem is connecting"
|
||||
- "I have not tried it"
|
||||
- "Does not apply: the problem is the connection"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
@@ -81,9 +83,9 @@ body:
|
||||
attributes:
|
||||
label: The diagnostics block
|
||||
description: >
|
||||
Attach `bug.md` by dragging it in, or paste its contents here. If you genuinely
|
||||
cannot produce one, because MeshTerm will not start at all, write down why instead,
|
||||
along with your version, and I will work with that.
|
||||
Drag `bug.md` into this field, or paste its contents here. If you cannot make the
|
||||
file, because MeshTerm does not start at all, write the reason instead. Also write
|
||||
your version. I will work with that information.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
@@ -92,9 +94,9 @@ body:
|
||||
attributes:
|
||||
label: Which handheld, if it is a handheld
|
||||
description: >
|
||||
Only if you are running on something the diagnostics file cannot name. It knows a
|
||||
PicoCalc from a regular terminal, but a uConsole and a laptop look much the same to
|
||||
it. Leave blank on a desktop, laptop, or Mac.
|
||||
Answer this only if you use a device that the diagnostics file cannot name. The file
|
||||
can tell a PicoCalc from a regular terminal. But a uConsole and a laptop look almost
|
||||
the same to it. Leave this blank on a desktop, a laptop, or a Mac.
|
||||
placeholder: "ClockworkPi uConsole (CM4)"
|
||||
|
||||
- type: input
|
||||
@@ -102,10 +104,10 @@ body:
|
||||
attributes:
|
||||
label: Which radio and firmware, if MeshTerm could not reach it
|
||||
description: >
|
||||
The diagnostics file reads the model and firmware off the radio itself, so when the
|
||||
problem *is* connecting, it has nothing to read. That is the one case where I need
|
||||
you to tell me. Be precise — "a Xiao" covers several boards that do not behave
|
||||
alike.
|
||||
The diagnostics file reads the model and the firmware from the radio itself. When
|
||||
the problem is the connection, the file has nothing to read. In this one case, I
|
||||
need you to tell me. Be exact. "A Xiao" can mean several boards that do not behave
|
||||
in the same way.
|
||||
placeholder: "Seeed XIAO nRF52840 + Wio-SX1262, MeshCore 1.15.0"
|
||||
|
||||
- type: textarea
|
||||
@@ -113,8 +115,9 @@ body:
|
||||
attributes:
|
||||
label: The exact error, if there was one
|
||||
description: >
|
||||
Copy it as text rather than photographing the screen — text can be searched, and a
|
||||
photo of a terminal is hard to read. Paste the whole traceback if it crashed.
|
||||
Copy it as text. Do not take a photograph of the screen. A search can find text, and
|
||||
a photograph of a terminal is difficult to read. If MeshTerm crashed, paste the
|
||||
complete traceback.
|
||||
render: text
|
||||
|
||||
- type: textarea
|
||||
@@ -122,22 +125,22 @@ body:
|
||||
attributes:
|
||||
label: The log file
|
||||
description: >
|
||||
MeshTerm keeps one at `~/.meshterm/meshterm.log` (or wherever `MESHTERM_HOME`
|
||||
points). Attach the file, or paste the last twenty lines inside a ``` fence. If the
|
||||
problem is hard to catch, set **Log detail** to Debug on the Preferences page,
|
||||
restart MeshTerm, do it once more, and send that instead. It records a great deal
|
||||
more.
|
||||
MeshTerm keeps a log at `~/.meshterm/meshterm.log` (or at the place that
|
||||
`MESHTERM_HOME` points to). Attach the file, or paste the last twenty lines inside a
|
||||
``` fence. If the problem is difficult to catch, set **Log detail** to Debug on the
|
||||
Preferences page and restart MeshTerm. Then do the action one more time, and send
|
||||
that log instead. It records much more information.
|
||||
|
||||
- type: textarea
|
||||
id: workaround
|
||||
attributes:
|
||||
label: Is there a way around it
|
||||
description: Anything that avoids it, or gets you unstuck. Say so even if it's ugly.
|
||||
description: Write anything that avoids the problem or lets you continue. Tell us also when the way around is not good.
|
||||
|
||||
- type: textarea
|
||||
id: extra
|
||||
attributes:
|
||||
label: Anything else
|
||||
description: >
|
||||
Did it used to work? Did it start after an update? Screenshots of a wrong-looking
|
||||
screen are genuinely useful here, even though text is better for errors.
|
||||
Did it work before? Did the problem start after an update? Screenshots of a screen
|
||||
that looks wrong are very useful here. For errors, text is better.
|
||||
@@ -1,15 +1,16 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Not sure it's a bug? Ask on Discord
|
||||
- name: Not sure it is a bug? Ask on Discord
|
||||
url: https://discord.gg/AZwe5Uvb3S
|
||||
about: >
|
||||
Setup trouble, "is it supposed to do this", or anything you want a second opinion
|
||||
on. The #support forum is the place. Someone running your radio has probably hit it.
|
||||
Use Discord for a problem with the setup, for the question "is it correct that it does
|
||||
this", or when you want a second opinion. The #support forum is the correct place.
|
||||
Probably someone who runs your radio had the same problem.
|
||||
|
||||
- name: "No GitHub account? Report it in the #bugs forum instead"
|
||||
url: https://discord.gg/AZwe5Uvb3S
|
||||
about: >
|
||||
One post per bug, tagged, with the diagnostics file attached. Real ones get opened
|
||||
here and your post is renamed with the issue number, so you can follow it without an
|
||||
account — and something comes back to tell you when it ships. Being told beats
|
||||
filing it tidily.
|
||||
Make one post for each bug. Add a tag, and attach the diagnostics file. I open an issue
|
||||
here for each real bug, and I rename your post with the number of the issue. Thus you can
|
||||
follow it without an account. You also get a message when the correction is released.
|
||||
It is better that you tell us about a bug than that you file it in a tidy way.
|
||||
@@ -5,15 +5,15 @@ body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
MeshTerm is one person's spare time, so the honest answer to a good idea is often
|
||||
"yes, eventually". Saying what you are actually trying to do — rather than the
|
||||
feature you have in mind — often finds something that already works.
|
||||
One person works on MeshTerm in his spare time. For this reason, the honest answer to a
|
||||
good idea is often "yes, but not soon". Tell us what you want to do, and not the
|
||||
feature that you have in mind. Then we can often find a function that already works.
|
||||
|
||||
- type: textarea
|
||||
id: problem
|
||||
attributes:
|
||||
label: What are you trying to do
|
||||
description: The situation you are in, not the feature. What are you trying to find out about your mesh?
|
||||
label: What do you want to do
|
||||
description: Describe your situation, and not the feature. What do you want to find out about your mesh?
|
||||
validations:
|
||||
required: true
|
||||
|
||||
@@ -21,10 +21,10 @@ body:
|
||||
id: idea
|
||||
attributes:
|
||||
label: What you had in mind
|
||||
description: If you have a shape in mind — a screen, a key, a subcommand — describe it.
|
||||
description: If you have an idea of the form, such as a screen, a key, or a subcommand, describe it.
|
||||
|
||||
- type: textarea
|
||||
id: today
|
||||
attributes:
|
||||
label: What you do today instead
|
||||
description: The workaround, if there is one. This says more about how much the feature is worth than anything else.
|
||||
description: Describe the way around the problem, if there is one. This tells us more than anything else about how much the feature is worth.
|
||||
@@ -1,30 +1,31 @@
|
||||
## What this changes, and why
|
||||
|
||||
<!-- The why matters more than the what — the diff already says the what. -->
|
||||
<!-- The reason is more important than the description of the change, because the diff already shows the change. -->
|
||||
|
||||
## Discord
|
||||
|
||||
<!-- Your Discord username on the MeshTerm server, and a link to the message in
|
||||
#contributing where you got a yes. A pull request without them is closed without
|
||||
a review. -->
|
||||
<!-- Write your Discord username on the MeshTerm server. Also write a link to the message
|
||||
in #contributing where you got a yes. We close a pull request without these items
|
||||
and do not review it. -->
|
||||
|
||||
Username:
|
||||
Link to the yes:
|
||||
|
||||
## AI use
|
||||
|
||||
<!-- Required. Did an AI tool write any part of this change? If yes: which tool, what it
|
||||
did, and what you checked yourself. Each commit it helped with also needs a line at
|
||||
the end naming it, like `Co-Authored-By:` or `Assisted-by:`. If you used AI and don't
|
||||
say so, this pull request is treated as a bot's and closed. -->
|
||||
<!-- This is necessary. Did an AI tool write any part of this change? If yes, write which
|
||||
tool, what it did, and what you checked yourself. Each commit that it helped with also
|
||||
needs a line at the end that names the tool, for example `Co-Authored-By:` or
|
||||
`Assisted-by:`. If you used AI and you do not say so, we treat this pull request as a
|
||||
bot's pull request and close it. -->
|
||||
|
||||
## Before you mark it ready
|
||||
|
||||
- [ ] I've read [CONTRIBUTING.md](../blob/main/CONTRIBUTING.md) and agree its licensing terms apply to this contribution
|
||||
- [ ] I've put my Discord username and a link to the yes above
|
||||
- [ ] If I used AI, I said so above and in the commits it helped with
|
||||
- [ ] I have read [CONTRIBUTING.md](../blob/main/CONTRIBUTING.md) and I agree that its licensing terms apply to this contribution
|
||||
- [ ] I have written my Discord username and a link to the yes above
|
||||
- [ ] If I used AI, I said so above and in the commits that it helped with
|
||||
- [ ] `python -m pytest -q` passes, including the dual-platform gallery gate
|
||||
- [ ] `ruff check .` and `ruff format --check .` are clean
|
||||
- [ ] New screens, rows, and hints go through the helpers in `ui/menus.py`, and follow the
|
||||
- [ ] `ruff check .` and `ruff format --check .` have no errors
|
||||
- [ ] New screens, rows, and hints use the helpers in `ui/menus.py`, and follow the
|
||||
lexicon and UX standards in `CLAUDE.md`
|
||||
- [ ] Anything user-visible has a line in `CHANGELOG.md` under `[Unreleased]`
|
||||
- [ ] Anything that the user can see has a line in `CHANGELOG.md` under `[Unreleased]`
|
||||
+80
-68
@@ -1,100 +1,104 @@
|
||||
# Contributing to MeshTerm
|
||||
|
||||
Thanks for wanting to work on MeshTerm. A few things to know before you open a pull
|
||||
Thank you for your wish to work on MeshTerm. Read these notes before you open a pull
|
||||
request.
|
||||
|
||||
## Licensing — what happens to your contribution
|
||||
## Licensing: what happens to your contribution
|
||||
|
||||
MeshTerm is licensed under the [Apache License, Version 2.0](LICENSE). By opening a
|
||||
pull request, you agree to license your contribution under those same terms — and,
|
||||
going a step beyond what Apache-2.0 alone requires, you additionally grant Jean-Pierre
|
||||
Martineau (the project's copyright holder) the right to relicense your contribution as
|
||||
part of the MeshTerm project, should the project's license ever change in the future.
|
||||
You keep copyright over what you write; you're not signing it away, just granting these
|
||||
usage rights alongside it. No separate form, bot, or signature is needed — opening the
|
||||
PR is how you agree.
|
||||
MeshTerm is licensed under the [Apache License, Version 2.0](LICENSE). When you open a
|
||||
pull request, you agree to license your contribution under the same terms. You also agree
|
||||
to one more term that Apache-2.0 alone does not require. You grant Jean-Pierre Martineau
|
||||
(the project's copyright holder) the right to relicense your contribution as part of
|
||||
the MeshTerm project, if the project's license changes in the future.
|
||||
You keep the copyright of what you write. You do not give it away. You only grant these
|
||||
rights of use in addition to it. You do not need a separate form, bot, or signature. When
|
||||
you open the pull request, you agree.
|
||||
|
||||
If that's not something you're comfortable granting, say so on the PR before it's
|
||||
merged and we'll talk about it, but by default that's the deal for anything merged into
|
||||
this repository.
|
||||
If you are not comfortable with this grant, say so on the pull request before we merge it,
|
||||
and we will talk about it. If you do not say so, these are the terms for each contribution
|
||||
that we merge into this repository.
|
||||
|
||||
## Before you start
|
||||
|
||||
- Read [CLAUDE.md](CLAUDE.md). It isn't just instructions for AI assistants — it's the
|
||||
actual style guide for this codebase: the terminology (node vs. contact, heard vs.
|
||||
seen, etc.), UX standards for screens/dialogs/menus, and where the reusable building
|
||||
blocks live in code (`ui/menus.py`, `ui/markdown.py`, `ui/widgets.py`, `ui/theme.py`).
|
||||
New code should read like it always belonged here.
|
||||
- Tell us before you start working on something (see below). For anything bigger than a
|
||||
small fix, expect to talk it through first. That saves everyone a rewrite.
|
||||
- Read [CLAUDE.md](CLAUDE.md). It is not only instructions for AI assistants. It is the
|
||||
real style guide for this codebase. It gives the terms (node and contact, heard and
|
||||
seen, and other terms), the UX standards for screens, dialogs, and menus, and the place
|
||||
in the code of each building block that you can use again (`ui/menus.py`,
|
||||
`ui/markdown.py`, `ui/widgets.py`, `ui/theme.py`). New code must look like it was always
|
||||
part of the project.
|
||||
- Tell us before you start work on a change (refer to the sections below). If a change is
|
||||
bigger than a small correction, expect to talk about it first. This saves everyone from
|
||||
a rewrite.
|
||||
|
||||
## Who can contribute
|
||||
|
||||
If you use MeshTerm, you're welcome here. Maybe something broke for you, or you wished
|
||||
it could do one more thing, or a screen looked wrong on your radio. Those are exactly
|
||||
the changes we want.
|
||||
If you use MeshTerm, you are welcome here. Maybe something did not work for you. Maybe you
|
||||
wanted one more function. Maybe a screen looked wrong on your radio. We want exactly these
|
||||
changes.
|
||||
|
||||
### How MeshTerm was made
|
||||
|
||||
Some people in the MeshCore community don't trust code written by AI. After some of
|
||||
what has happened lately, that's easy to understand, and it deserves a straight answer.
|
||||
Some persons in the MeshCore community do not trust code that AI wrote. After some recent
|
||||
events, we understand this, and it needs a direct answer.
|
||||
|
||||
Jean-Pierre Martineau, who started MeshTerm, sees it differently. AI was a big help on
|
||||
this project. Honestly, MeshTerm couldn't have been built without it. But the choices
|
||||
were his. He decided what MeshTerm should be, what it should do, and how it should
|
||||
look. He uses it every day. It's handmade, with AI's help.
|
||||
Jean-Pierre Martineau started MeshTerm. He has a different opinion. AI helped much with
|
||||
this project. Honestly, it was not possible to build MeshTerm without it. But he made the
|
||||
choices. He decided what MeshTerm must be, what it must do, and how it must look. He uses
|
||||
it each day. It is handmade, with the help of AI.
|
||||
|
||||
The project wants to stay that way. That's what the rules below are for.
|
||||
The project must stay this way. The rules below are for this purpose.
|
||||
|
||||
### Come say hello first
|
||||
|
||||
Using AI tools to help you work is fine. What matters is that a real person who cares
|
||||
about MeshTerm is behind the change.
|
||||
It is permitted to use AI tools to help you work. What is important is that a real person
|
||||
who cares about MeshTerm is behind the change.
|
||||
|
||||
The MeshTerm community lives on [Discord](https://discord.gg/AZwe5Uvb3S), and everyone
|
||||
is welcome to join. But please join before you change things. Before you start writing
|
||||
code, say hello in the **#contributing** channel and tell us what you'd like to work on.
|
||||
Then wait until we say yes. This goes for small fixes too.
|
||||
The MeshTerm community is on [Discord](https://discord.gg/AZwe5Uvb3S), and everyone is
|
||||
welcome to join. But join before you change anything. Before you start to write code,
|
||||
say hello in the **#contributing** channel and tell us what you want to work on. Then wait
|
||||
until we say yes. This is also the rule for small corrections.
|
||||
|
||||
When you open your pull request, include two things:
|
||||
When you open your pull request, include these two items:
|
||||
|
||||
- **Your Discord username**, the one you use on the MeshTerm server.
|
||||
- **Your Discord username.** Use the one that you use on the MeshTerm server.
|
||||
- **A link to the message where we said yes.** In Discord, right-click the message and
|
||||
choose "Copy Message Link."
|
||||
|
||||
If you haven't joined the Discord, or nobody said yes to your idea, your pull request
|
||||
will be closed without a review.
|
||||
If you did not join the Discord, or if nobody said yes to your idea, we will close your
|
||||
pull request without a review.
|
||||
|
||||
### No bots
|
||||
|
||||
Some accounts use AI to find open issues, write a fix, and open a pull request, all on
|
||||
their own. There's no person behind it who read the change or can answer questions
|
||||
about it. People call this "AI slop." We close those pull requests without a review,
|
||||
even if the code looks fine. The same goes for issues and bug reports written that way.
|
||||
Some accounts use AI to find open issues, write a correction, and open a pull request, all
|
||||
by themselves. No person read the change, and no person can answer questions about it.
|
||||
People call this "AI slop." We close these pull requests without a review, also when the
|
||||
code looks correct. The same rule is true for issues and bug reports that are written in
|
||||
this way.
|
||||
|
||||
### Tell us if you used AI
|
||||
|
||||
This part is required. If an AI tool wrote any part of your change, say so in two
|
||||
This part is necessary. If an AI tool wrote any part of your change, you must say so in two
|
||||
places:
|
||||
|
||||
- **In each commit it helped with.** Add a line at the end of the commit message naming
|
||||
the tool, like `Co-Authored-By: Claude <noreply@anthropic.com>` or
|
||||
- **In each commit that it helped with.** Add a line at the end of the commit message with
|
||||
the name of the tool, for example `Co-Authored-By: Claude <noreply@anthropic.com>` or
|
||||
`Assisted-by: GitHub Copilot`.
|
||||
- **In your pull request.** Write a sentence about what the tool did and what you
|
||||
checked yourself.
|
||||
- **In your pull request.** Write a sentence about what the tool did and what you checked
|
||||
yourself.
|
||||
|
||||
If you used AI and didn't say so, we'll treat your pull request as a bot's. Saying so
|
||||
costs you nothing. Either way, you're the author, so be ready to explain your change.
|
||||
If you used AI and you did not say so, we will treat your pull request as a bot's pull
|
||||
request. It costs you nothing to say so. In all cases, you are the author, so be ready to
|
||||
explain your change.
|
||||
|
||||
## Making a change
|
||||
|
||||
1. Fork the repo and branch off `main`.
|
||||
1. Fork the repository and make a branch from `main`.
|
||||
2. Set up a dev install:
|
||||
```bash
|
||||
pip install -e ".[dev]"
|
||||
git config blame.ignoreRevsFile .git-blame-ignore-revs # skip the formatting sweeps
|
||||
```
|
||||
3. Before opening the PR, make sure these all pass:
|
||||
3. Before you open the pull request, make sure that all these checks pass:
|
||||
```bash
|
||||
python -m pytest -q # tests, including tests/test_gallery.py — the dual-platform
|
||||
# readability gate (screens must stay readable at 72 cols
|
||||
@@ -102,29 +106,37 @@ costs you nothing. Either way, you're the author, so be ready to explain your ch
|
||||
ruff check . # lint
|
||||
ruff format --check . # formatting
|
||||
```
|
||||
4. Keep commits focused; write imperative-mood messages and explain *why* when it isn't
|
||||
obvious from the diff.
|
||||
5. Open the PR against `main` with a clear description of what changed and why.
|
||||
The tests include `tests/test_writing_style.py`. It checks the docstrings and the
|
||||
comments for the marks that ASD-STE100 does not use. Refer to
|
||||
[How we write](docs/development/writing-style.md).
|
||||
4. Keep each commit to one subject. Write the commit message in the imperative mood. Explain
|
||||
*why* when the diff does not show the reason.
|
||||
5. Open the pull request against `main`. Write a clear description of what changed and why.
|
||||
|
||||
## Code style
|
||||
|
||||
- Match the surrounding file: naming, comment density, idiom.
|
||||
- Follow the lexicon and UX standards in [CLAUDE.md](CLAUDE.md) — one term per concept;
|
||||
don't introduce a synonym for something that already has a name.
|
||||
- New screens, dialogs, and rows go through the existing helpers rather than hand-rolled
|
||||
layout — CLAUDE.md names the enforcement points.
|
||||
- Make your change match the file around it: the names, the amount of comments, and the
|
||||
idioms.
|
||||
- Follow the terms and the UX standards in [CLAUDE.md](CLAUDE.md). Use one term for each
|
||||
concept. Do not add a synonym for something that already has a name.
|
||||
- Build new screens, dialogs, and rows with the existing helpers. Do not make the layout by
|
||||
hand. CLAUDE.md names the places where the code enforces these standards.
|
||||
- Write docstrings, comments, and documents in ASD-STE100 Simplified Technical English,
|
||||
with Canadian spelling. [How we write](docs/development/writing-style.md) gives the
|
||||
rules and the glossary.
|
||||
|
||||
## Reporting bugs / requesting features
|
||||
|
||||
Open a GitHub issue. For bugs, include what you expected, what happened, and enough to
|
||||
reproduce it — device/platform, MeshCore firmware version if relevant, and steps.
|
||||
Open a GitHub issue. For a bug, include what you expected, what happened, and enough
|
||||
information for us to reproduce it. This is the device and platform, the MeshCore firmware
|
||||
version if it is relevant, and the steps.
|
||||
|
||||
The [bug form](https://github.com/jpmartineau/MeshTerm/issues/new?template=bug.yml) asks
|
||||
for all of this. Attach the file that `meshterm diagnostics --out bug.md` writes. No
|
||||
GitHub account? Post it in the #bugs forum on [Discord](https://discord.gg/AZwe5Uvb3S)
|
||||
instead.
|
||||
for all of this. Attach the file that `meshterm diagnostics --out bug.md` writes. If you do
|
||||
not have a GitHub account, post the report in the #bugs forum on
|
||||
[Discord](https://discord.gg/AZwe5Uvb3S) instead.
|
||||
|
||||
---
|
||||
|
||||
Questions about any of this, licensing included, are welcome as a GitHub issue before you
|
||||
put work in.
|
||||
You can ask a question about any of this, and this includes licensing, as a GitHub issue
|
||||
before you start work.
|
||||
@@ -1,17 +1,17 @@
|
||||
# MeshTerm
|
||||
|
||||
**A full-featured TUI [MeshCore](https://meshcore.io/) client for your terminal** — tune
|
||||
your radio, chat across the mesh, watch the network live, and keep a longitudinal record
|
||||
of everything you overhear, all from a connected serial, Bluetooth, or network (TCP)
|
||||
companion.
|
||||
**A full-featured TUI [MeshCore](https://meshcore.io/) client for your terminal.** Use it
|
||||
to tune your radio, chat across the mesh, and watch the network live. It also keeps a
|
||||
long-term record of everything that you overhear. It works with a companion that is
|
||||
connected by serial, Bluetooth, or network (TCP).
|
||||
|
||||
MeshTerm is interactive by default: a modern, keyboard-driven TUI built on
|
||||
[Rich](https://github.com/Textualize/rich) and
|
||||
[prompt_toolkit](https://github.com/prompt-toolkit/python-prompt-toolkit). It is also
|
||||
fully scriptable — **most screens have a mirror `meshterm` subcommand** — so the same
|
||||
capabilities drive both an evening of exploring the mesh and a cron job. Every packet the
|
||||
radio overhears is recorded to a local SQLite database, so the longer you run it, the more
|
||||
your mesh's history is worth.
|
||||
MeshTerm is interactive by default. It is a modern TUI that you operate with the keyboard,
|
||||
built on [Rich](https://github.com/Textualize/rich) and
|
||||
[prompt_toolkit](https://github.com/prompt-toolkit/python-prompt-toolkit). You can also
|
||||
use it in scripts. **Most screens have a mirror `meshterm` subcommand.** Thus the same
|
||||
functions work for an evening of exploration on the mesh and for a cron job. MeshTerm
|
||||
stores each packet that the radio overhears in a local SQLite database. The longer you
|
||||
run MeshTerm, the more the history of your mesh is worth.
|
||||
|
||||
<p>
|
||||
<a href="https://github.com/jpmartineau/MeshTerm/actions/workflows/ci.yml"><img alt="CI" src="https://github.com/jpmartineau/MeshTerm/actions/workflows/ci.yml/badge.svg"></a>
|
||||
@@ -23,69 +23,74 @@ your mesh's history is worth.
|
||||
<a href="https://discord.gg/AZwe5Uvb3S"><img alt="Discord" src="https://img.shields.io/badge/chat-Discord-5865F2"></a>
|
||||
</p>
|
||||
|
||||
> MeshTerm is a side project, run by one person. Bug reports are very welcome. Want to
|
||||
> change something? Say hello on [Discord](https://discord.gg/AZwe5Uvb3S) first.
|
||||
> [More on how it's run](#how-this-project-is-run).
|
||||
> MeshTerm is a side project, and one person runs it. We welcome bug reports. If you want to
|
||||
> change something, first say hello on [Discord](https://discord.gg/AZwe5Uvb3S).
|
||||
> [Read more about how the project is run](#how-this-project-is-run).
|
||||
|
||||
https://github.com/user-attachments/assets/f91a6695-14d5-4ae0-8fdc-e65492d0955d
|
||||
|
||||
<p align="center"><sub>Under two minutes on a real mesh.</sub></p>
|
||||
<p align="center"><sub>A video of less than two minutes, made on a real mesh.</sub></p>
|
||||
|
||||
<p align="center">
|
||||
<img src="docs/screenshots/dashboard.png" alt="The dashboard: a packets-per-minute timeline over the last two hours, and every packet class counted" width="49%">
|
||||
<img src="docs/screenshots/map.png" alt="The map: repeaters named over an OpenStreetMap basemap of the Lachine Canal" width="49%">
|
||||
<img src="docs/screenshots/dashboard.png" alt="The dashboard: a timeline of the packets for each minute over the last two hours, and a count for each class of packet" width="49%">
|
||||
<img src="docs/screenshots/map.png" alt="The map: repeaters with their names, on an OpenStreetMap basemap of the Lachine Canal" width="49%">
|
||||
</p>
|
||||
<p align="center">
|
||||
<img src="docs/screenshots/trace.png" alt="A trace: the route it walked drawn as chips, then the SNR of every hop" width="49%">
|
||||
<img src="docs/screenshots/path.png" alt="Message paths: every route a channel message was heard over, drawn on the ground" width="49%">
|
||||
<img src="docs/screenshots/trace.png" alt="A trace: the route that the trace went through, drawn as chips, then the SNR of each hop" width="49%">
|
||||
<img src="docs/screenshots/path.png" alt="Message paths: each route over which a channel message was heard, drawn on the map" width="49%">
|
||||
</p>
|
||||
<p align="center">
|
||||
<img src="docs/screenshots/picocalc.png" alt="MeshTerm on the PicoCalc, a 53-column console with a radio wired inside" width="38%">
|
||||
<img src="docs/screenshots/picocalc.png" alt="MeshTerm on the PicoCalc, a console that is 53 columns wide, with a radio installed inside it" width="38%">
|
||||
</p>
|
||||
<p align="center"><sub>The dashboard, the map, a trace, the paths a message took — and the whole thing on a PicoCalc.</sub></p>
|
||||
<p align="center"><sub>The dashboard, the map, a trace, the paths of a message, and the whole app on a PicoCalc.</sub></p>
|
||||
|
||||
---
|
||||
|
||||
## Why MeshTerm
|
||||
|
||||
- **One tool, two faces.** Launch `meshterm` for a full-screen menu; pass a subcommand for
|
||||
a scripted one-shot. The registry that builds the menu builds the CLI, so they never
|
||||
drift apart.
|
||||
- **It remembers.** A passive monitor logs every overheard advert and telemetry frame —
|
||||
SNR, RSSI, shared location — to SQLite from the moment the radio opens. Maps, contact lists,
|
||||
charts, and the Time Machine all read back that history.
|
||||
- **It measures.** Live trace with per-hop reliability, a coarse→refine→verify TX-power
|
||||
sweep, and a walkable graph of how the mesh actually hangs together.
|
||||
- **It talks.** Live channel and direct messaging, room servers' message boards,
|
||||
store-and-forward courier delivery for contacts that aren't there yet, and shareable
|
||||
channels as QR codes and `meshcore://` links.
|
||||
- **It connects however your companion does.** Serial (USB), Bluetooth LE, or TCP (a Wi-Fi
|
||||
board or a network proxy) — the discoverable transports are auto-found and picked
|
||||
interactively, a network device is named by hand, and every kind reconnects live when a link
|
||||
drops. It pairs a PIN-protected Bluetooth companion itself on Windows and Linux, and when a
|
||||
connection fails it says which step failed and what to do. No radio? A built-in simulator
|
||||
covers development.
|
||||
- **It works offline.** The street map caches OpenStreetMap tiles to disk and falls back to
|
||||
a blank grid when there's no network — it never needs the internet to run.
|
||||
- **One tool, two faces.** To open a full-screen menu, start `meshterm`. To run one scripted
|
||||
task, give a subcommand. The same registry builds the menu and the CLI, so they do not
|
||||
become different from one another.
|
||||
- **It remembers.** A passive monitor stores each advert and each telemetry packet that it
|
||||
overhears. This includes the SNR, the RSSI, and the shared position. The monitor starts
|
||||
when the radio opens. The maps, the contact lists, the charts, and the Time Machine all
|
||||
read this history.
|
||||
- **It measures.** It has a live trace with the reliability of each hop. It has a TX-power
|
||||
sweep that goes from coarse to fine and then verifies the result. It also has a graph
|
||||
that you can walk through, which shows how the mesh is really connected.
|
||||
- **It talks.** You can send live messages on a channel and direct messages. You can use the
|
||||
message boards of room servers. A courier stores messages and sends them when a contact
|
||||
is not yet there. You can share a channel as a QR code or as a `meshcore://` link.
|
||||
- **It connects in the same way as your companion.** It supports serial (USB), Bluetooth
|
||||
LE, and TCP (a Wi-Fi board or a network proxy). It finds the serial and Bluetooth
|
||||
devices automatically, and you select one from a list. You type the name of a network
|
||||
device. MeshTerm reconnects live when a link of any kind drops. On Windows and Linux,
|
||||
MeshTerm pairs with a Bluetooth companion that has a PIN. When a connection fails,
|
||||
MeshTerm says which step failed and what to do. If you have no radio, a built-in
|
||||
simulator is available for development.
|
||||
- **It works offline.** The street map caches OpenStreetMap tiles on disk. When there is no
|
||||
network, it shows a blank grid. MeshTerm never needs the internet to run.
|
||||
|
||||
## Install
|
||||
|
||||
### Download a build
|
||||
|
||||
One file, no Python needed — Windows, macOS (Intel and Apple silicon), and Linux (x64 and
|
||||
ARM64, so the uConsole is covered). Every command below fetches the
|
||||
A build is one file. You do not need Python. Builds are available for Windows, macOS (Intel
|
||||
and Apple silicon), and Linux (x64 and ARM64, so the uConsole is included). Each command
|
||||
below gets the
|
||||
**[⬇ latest release](https://github.com/jpmartineau/MeshTerm/releases/latest)**, whichever
|
||||
one that is, so none of them goes stale.
|
||||
release that is. Thus the commands do not become old.
|
||||
|
||||
MeshTerm is a terminal program, so **open a terminal and run it from there.** You can
|
||||
double-click it and it will work, but you'll get whatever console your system picks, and
|
||||
if anything goes wrong at startup the window closes before you can read why.
|
||||
double-click the file and it will work. But then your system selects the console, and you
|
||||
cannot choose it. If an error occurs at startup, the window closes before you can read the
|
||||
error.
|
||||
|
||||
#### macOS — download it with `curl`
|
||||
#### macOS: download it with `curl`
|
||||
|
||||
**Don't use your browser.** A browser flags the file as downloaded, and macOS then refuses
|
||||
to open it at all — on Sequoia and later, right-click → Open no longer gets you past that
|
||||
either. `curl` flags nothing, so what it hands you simply runs. Open Terminal and paste:
|
||||
**Do not use your browser.** A browser marks the file as downloaded, and then macOS does not
|
||||
let you open it. On Sequoia and later, right-click → Open does not help. `curl` does not
|
||||
mark the file, so the file runs. Open Terminal and paste:
|
||||
|
||||
```bash
|
||||
curl -fL -o meshterm https://github.com/jpmartineau/MeshTerm/releases/latest/download/meshterm-macos-arm64
|
||||
@@ -93,13 +98,13 @@ chmod +x meshterm
|
||||
./meshterm
|
||||
```
|
||||
|
||||
That's the build for Apple silicon Macs (M1 and later). On an **Intel** Mac, swap `arm64`
|
||||
for `x64`. And if you already downloaded it with a browser, you don't have to start over:
|
||||
`xattr -d com.apple.quarantine meshterm` clears the flag.
|
||||
This is the build for Apple silicon Macs (M1 and later). On an **Intel** Mac, replace
|
||||
`arm64` with `x64`. If you already downloaded the file with a browser, you do not have to
|
||||
start again. Run `xattr -d com.apple.quarantine meshterm` to clear the mark.
|
||||
|
||||
#### Linux — the same three lines
|
||||
#### Linux: the same three lines
|
||||
|
||||
`linux-x64` on a PC, `linux-arm64` on a uConsole or another 64-bit ARM handheld.
|
||||
Use `linux-x64` on a PC. Use `linux-arm64` on a uConsole or another 64-bit ARM handheld.
|
||||
|
||||
```bash
|
||||
curl -fL -o meshterm https://github.com/jpmartineau/MeshTerm/releases/latest/download/meshterm-linux-x64
|
||||
@@ -110,52 +115,54 @@ chmod +x meshterm
|
||||
#### Windows
|
||||
|
||||
Download
|
||||
**[meshterm-windows-x64.exe](https://github.com/jpmartineau/MeshTerm/releases/latest/download/meshterm-windows-x64.exe)**,
|
||||
then open Windows Terminal or PowerShell, `cd` to your downloads, and run it:
|
||||
**[meshterm-windows-x64.exe](https://github.com/jpmartineau/MeshTerm/releases/latest/download/meshterm-windows-x64.exe)**.
|
||||
Then open Windows Terminal or PowerShell, use `cd` to go to your downloads folder, and run
|
||||
the file:
|
||||
|
||||
```powershell
|
||||
.\meshterm-windows-x64.exe
|
||||
```
|
||||
|
||||
Windows says *"Windows protected your PC"* the first time. Click **More info → Run anyway**.
|
||||
The first time, Windows says *"Windows protected your PC"*. Click **More info → Run anyway**.
|
||||
|
||||
Whichever you took, you now have a single file: `meshterm` on macOS and Linux,
|
||||
`meshterm-windows-x64.exe` on Windows. Put it somewhere on your `PATH` (renamed to
|
||||
`meshterm.exe` on Windows) and it's just `meshterm` from anywhere.
|
||||
After these steps, you have one file: `meshterm` on macOS and Linux, or
|
||||
`meshterm-windows-x64.exe` on Windows. Put the file in a folder that is in your `PATH`. On
|
||||
Windows, rename it to `meshterm.exe`. Then you can run `meshterm` from any folder.
|
||||
|
||||
> **These builds aren't code-signed yet**, which is why macOS and Windows both push back
|
||||
> the first time. **Windows signing is on its way.** MeshTerm has applied to the
|
||||
> **These builds are not code-signed yet.** For this reason, macOS and Windows both show a
|
||||
> warning the first time. **Windows signing will come.** MeshTerm applied to the
|
||||
> [SignPath Foundation](https://signpath.org), which signs open-source projects for free.
|
||||
> Once that's set up, every Windows download will be signed. The
|
||||
> [code signing policy](#code-signing-policy) below explains how. Mac builds will stay
|
||||
> unsigned, because Apple charges for signing and this is a free side project.
|
||||
> Every release ships a `SHA256SUMS` file if you'd rather check what you got, and
|
||||
> [installing with pip or pipx](#or-install-with-pip) sidesteps the whole business, since
|
||||
> nothing arrives as a downloaded binary.
|
||||
> After the setup is complete, each Windows download will be signed. The
|
||||
> [code signing policy](#code-signing-policy) below gives the details. The Mac builds will
|
||||
> stay unsigned, because Apple charges a fee for signing and this is a free side project.
|
||||
> Each release has a `SHA256SUMS` file. Use it to check the file that you got.
|
||||
> [Installing with pip or pipx](#or-install-with-pip) avoids the problem, because you do not
|
||||
> download a binary.
|
||||
>
|
||||
> Each release page also carries these commands written out for **that exact version**,
|
||||
> next to its own downloads.
|
||||
> The page of each release also has these commands, written for **that exact version**, next
|
||||
> to its own downloads.
|
||||
|
||||
### If your terminal draws empty boxes
|
||||
|
||||
MeshTerm is drawn with emoji icons, braille charts, and powerline path chips, and whether
|
||||
you see them is up to your *terminal* rather than your font. Windows Terminal, VS Code's
|
||||
terminal, and anything modern on macOS and Linux all draw them correctly.
|
||||
MeshTerm draws emoji icons, braille charts, and powerline path chips. Your *terminal*
|
||||
decides if you see them, not your font. Windows Terminal, the terminal of VS Code, and each
|
||||
modern terminal on macOS and Linux draw them correctly.
|
||||
|
||||
The classic Windows console — the black `cmd.exe` window you get from a double-click —
|
||||
does not. So when MeshTerm lands there it **moves to Windows Terminal**, says so, and
|
||||
opens there; nothing is installed and nothing is changed. Where there's no Windows
|
||||
Terminal to move to, it offers the charts and marks without the icons instead, and will
|
||||
install Microsoft's [Cascadia Mono PL](https://github.com/microsoft/cascadia-code) just
|
||||
for you — no administrator rights, nothing downloaded.
|
||||
The classic Windows console does not draw them. This is the black `cmd.exe` window that
|
||||
opens when you double-click the file. When MeshTerm starts there, it **moves to Windows
|
||||
Terminal**, tells you, and opens there. It does not install anything and does not change
|
||||
anything. If Windows Terminal is not available, MeshTerm offers the charts and marks without
|
||||
the icons. It also installs Microsoft's [Cascadia Mono PL](https://github.com/microsoft/cascadia-code)
|
||||
for you. This needs no administrator rights, and it downloads nothing.
|
||||
|
||||
Preferences → Display → Console setup turns all of this off if you'd rather stay put.
|
||||
[Terminals, icons, and the Windows console](docs/guide/terminals.md) explains why any of it is
|
||||
necessary.
|
||||
To stay in the same console, turn off these actions in Preferences → Display → Console
|
||||
setup. [Terminals, icons, and the Windows console](docs/guide/terminals.md) explains why
|
||||
they are necessary.
|
||||
|
||||
> **Trying a build without touching your real data.** MeshTerm keeps everything in
|
||||
> `~/.meshterm` — your history database, contacts, channel keys — and *every* copy of
|
||||
> MeshTerm uses that same folder. Set `MESHTERM_HOME` to try one in isolation:
|
||||
> **Try a build without a change to your real data.** MeshTerm keeps all its data in
|
||||
> `~/.meshterm`. This includes your history database, your contacts, and your channel
|
||||
> keys. *Each* copy of MeshTerm uses this same folder. To try a copy in isolation, set
|
||||
> `MESHTERM_HOME`:
|
||||
>
|
||||
> ```bash
|
||||
> MESHTERM_HOME=~/meshterm-test ./meshterm # macOS, Linux
|
||||
@@ -164,53 +171,54 @@ necessary.
|
||||
> $env:MESHTERM_HOME = "$HOME\meshterm-test"; .\meshterm-windows-x64.exe
|
||||
> ```
|
||||
>
|
||||
> Same variable if you run two radios and want them kept apart.
|
||||
> Use the same variable if you run two radios and you want to keep them separate.
|
||||
|
||||
### Or install with pip
|
||||
|
||||
You need **Python 3.10 or newer**. That's the only requirement — MeshTerm pulls in
|
||||
everything else itself.
|
||||
You need **Python 3.10 or newer**. This is the only requirement. MeshTerm installs all
|
||||
other parts by itself.
|
||||
|
||||
```bash
|
||||
pip install git+https://github.com/jpmartineau/MeshTerm
|
||||
meshterm
|
||||
```
|
||||
|
||||
`meshterm` opens the full-screen menu. That's the whole install.
|
||||
`meshterm` opens the full-screen menu. This is the complete installation.
|
||||
|
||||
**Rather not touch your system Python?** [pipx](https://pipx.pypa.io/) puts the app in its
|
||||
own private environment and still gives you a plain `meshterm` command:
|
||||
**Do you not want to change your system Python?** [pipx](https://pipx.pypa.io/) puts the
|
||||
app in its own private environment. It also gives you a normal `meshterm` command:
|
||||
|
||||
```bash
|
||||
pipx install git+https://github.com/jpmartineau/MeshTerm
|
||||
```
|
||||
|
||||
**No radio yet?** `meshterm --mock` runs the entire app against a simulated mesh, so you
|
||||
can have a look around before you buy anything.
|
||||
**Do you not have a radio yet?** `meshterm --mock` runs the complete app on a simulated
|
||||
mesh. You can look at the app before you buy anything.
|
||||
|
||||
### Coming
|
||||
|
||||
**`pip install mesh-term`**, once the package is published. It isn't yet — the name
|
||||
question is still being sorted out ([why](https://github.com/pypi/support/issues/12162)).
|
||||
**`pip install mesh-term`** will work after the package is published. It is not published
|
||||
yet, because the question of the name is not resolved
|
||||
([read why](https://github.com/pypi/support/issues/12162)).
|
||||
|
||||
Setting up for development instead? That's in [CONTRIBUTING.md](CONTRIBUTING.md).
|
||||
To set up a development environment, refer to [CONTRIBUTING.md](CONTRIBUTING.md).
|
||||
|
||||
## Documentation
|
||||
|
||||
**[The documentation index](docs/README.md)** lists everything, including the code survey
|
||||
kept as a historical record. The short version:
|
||||
**[The documentation index](docs/README.md)** lists all the documents. This includes the
|
||||
code survey, which we keep as a historical record. This table is a short version:
|
||||
|
||||
| Read | For |
|
||||
| --- | --- |
|
||||
| **[The command line](docs/cli/README.md)** | Every subcommand and option, what each prints on the plain and JSON faces, the exit statuses, recipes. |
|
||||
| **[What MeshTerm does](docs/guide/features.md)** | Every screen the menu offers, and the command that does the same job without it. |
|
||||
| [The CLI cookbook](docs/cli/cookbook.md) | Common one-liners, by the thing you're trying to do. |
|
||||
| [Configuring MeshTerm](docs/guide/configuration.md) | Preferences, device profiles, and everything kept under `~/.meshterm`. |
|
||||
| [When a companion won't connect](docs/guide/connecting.md) | Bluetooth pairing on each system, finding the PIN, USB permissions, and every connection error, with what to do about it. |
|
||||
| [MeshTerm on hardware](docs/devices/README.md) | Which handheld manual is yours — the [PicoCalc](docs/devices/picocalc-lyra.md) build, or the [uConsole](docs/devices/uconsole.md), whose LoRa chip MeshTerm can drive directly. The [Cardputer Zero](docs/devices/cardputer-zero.md) is **not supported yet**. |
|
||||
| [How the code is laid out](docs/development/architecture.md) | The layering, and where a new feature goes. |
|
||||
| **[The command line](docs/cli/README.md)** | Each subcommand and option, what each prints on the plain face and on the JSON face, the exit statuses, and recipes. |
|
||||
| **[What MeshTerm does](docs/guide/features.md)** | Each screen that the menu has, and the command that does the same job without the menu. |
|
||||
| [The CLI cookbook](docs/cli/cookbook.md) | Common one-line commands, in groups by the task that you want to do. |
|
||||
| [Configuring MeshTerm](docs/guide/configuration.md) | The preferences, the device profiles, and all the files that MeshTerm keeps in `~/.meshterm`. |
|
||||
| [When a companion does not connect](docs/guide/connecting.md) | Bluetooth pairing on each system, how to find the PIN, USB permissions, and each connection error, with the action to take. |
|
||||
| [MeshTerm on hardware](docs/devices/README.md) | Which handheld manual is the correct one for you: the [PicoCalc](docs/devices/picocalc-lyra.md) build, or the [uConsole](docs/devices/uconsole.md), whose LoRa chip MeshTerm can control directly. The [Cardputer Zero](docs/devices/cardputer-zero.md) is **not supported yet**. |
|
||||
| [How the code is laid out](docs/development/architecture.md) | The layers of the code, and where to put a new feature. |
|
||||
| [CONTRIBUTING.md](CONTRIBUTING.md) | The development setup and the house rules. |
|
||||
| [CHANGELOG.md](CHANGELOG.md) | What changed, newest first. |
|
||||
| [CHANGELOG.md](CHANGELOG.md) | What changed, with the newest change first. |
|
||||
|
||||
## Quick start
|
||||
|
||||
@@ -237,40 +245,43 @@ meshterm --mock --platform picocalc-lyra
|
||||
meshterm specimen
|
||||
```
|
||||
|
||||
Putting MeshTerm on a handheld has its own manuals. **[MeshTerm on the
|
||||
PicoCalc](docs/devices/picocalc-lyra.md)** takes a stock PicoCalc from the shopping list to a
|
||||
Linux handheld with a LoRa radio soldered inside; **[MeshTerm on the
|
||||
uConsole](docs/devices/uconsole.md)** drives a uConsole's SPI LoRa board directly with
|
||||
`--spi`, or through a small bridge if you want the node on the mesh all the time. M5Stack's
|
||||
Cardputer Zero has a page too, but it is **[not supported yet](docs/devices/cardputer-zero.md)**.
|
||||
The device-side scripts the manuals run are in [`scripts/`](scripts/); not sure which
|
||||
manual is yours? [`docs/devices/README.md`](docs/devices/README.md) says.
|
||||
MeshTerm has its own manuals for handhelds. **[MeshTerm on the
|
||||
PicoCalc](docs/devices/picocalc-lyra.md)** goes from the shopping list for a stock PicoCalc
|
||||
to a Linux handheld that has a LoRa radio soldered inside. **[MeshTerm on the
|
||||
uConsole](docs/devices/uconsole.md)** shows how to control the SPI LoRa board of a uConsole
|
||||
directly with `--spi`. It also shows how to use a small bridge, if you want the node to be
|
||||
on the mesh at all times. M5Stack's Cardputer Zero also has a page, but it is **[not
|
||||
supported yet](docs/devices/cardputer-zero.md)**. The scripts that the manuals run on the
|
||||
device are in [`scripts/`](scripts/). If you do not know which manual is correct for you,
|
||||
refer to [`docs/devices/README.md`](docs/devices/README.md).
|
||||
|
||||
A companion that won't connect? **[When a companion won't
|
||||
connect](docs/guide/connecting.md)** covers Bluetooth pairing on Windows, Linux, and macOS, the
|
||||
`dialout` group and ModemManager for USB on Linux, and every error message MeshTerm gives.
|
||||
Does a companion not connect? **[When a companion does not
|
||||
connect](docs/guide/connecting.md)** explains Bluetooth pairing on Windows, Linux, and
|
||||
macOS. It also explains the `dialout` group and ModemManager for USB on Linux, and each
|
||||
error message that MeshTerm gives.
|
||||
|
||||
## What it does
|
||||
|
||||
The menu asks one question — *what would you like to do?* — and answers it in two halves.
|
||||
Three sections name a doing: **Message** is chat, channels, the courier outbox, and
|
||||
contacts; **Watch** is the dashboard, the live feed, the watchtower, and the time machine;
|
||||
**Explore** is the map, the mesh walk, tracing, and the trophy case. Three name whose it is:
|
||||
**This node** is the radio in your hand, **Other nodes** is someone else's over the mesh,
|
||||
and **This app** is MeshTerm itself — preferences, diagnostics, and the written pages.
|
||||
The menu asks one question: *what would you like to do?* It gives the answer in two halves.
|
||||
Three sections name an activity. **Message** has chat, channels, the courier outbox, and
|
||||
contacts. **Watch** has the dashboard, the live feed, the watchtower, and the time machine.
|
||||
**Explore** has the map, the mesh walk, tracing, and the trophy case. The other three
|
||||
sections name the owner. **This node** is the radio in your hand. **Other nodes** is the
|
||||
someone else's radio, which you reach over the mesh. **This app** is MeshTerm itself, with
|
||||
the preferences, the diagnostics, and the written pages.
|
||||
|
||||
Nearly every one of them has a mirror `meshterm` subcommand — the same registry builds
|
||||
the menu and the CLI, so they can't drift apart. The live screens, such as the map and
|
||||
the dashboard, are only in the menu.
|
||||
Almost all of these have a mirror `meshterm` subcommand. The same registry builds the menu
|
||||
and the CLI, so they do not become different from one another. The live screens, such as the
|
||||
map and the dashboard, are only in the menu.
|
||||
|
||||
**[What MeshTerm does](docs/guide/features.md)** is the full catalogue: every screen, what it
|
||||
does, and its scripted equivalent where one exists.
|
||||
**[What MeshTerm does](docs/guide/features.md)** is the complete catalogue. It shows each
|
||||
screen, what the screen does, and the scripted equivalent where there is one.
|
||||
|
||||
## Scripting it
|
||||
|
||||
Nearly every command speaks `--json`, and the global options — `--profile/-p`, `--port`,
|
||||
`--ble`, `--ble-pin`, `--tcp`, `--spi`, `--mock`, `--db`, `--json`, `--absolute`, `--quiet/-q`,
|
||||
`--platform` — may be typed before or after the subcommand.
|
||||
Almost every command accepts `--json`. The global options are `--profile/-p`, `--port`,
|
||||
`--ble`, `--ble-pin`, `--tcp`, `--spi`, `--mock`, `--db`, `--json`, `--absolute`,
|
||||
`--quiet/-q`, and `--platform`. You can type them before or after the subcommand.
|
||||
|
||||
```bash
|
||||
# Every repeater's public key, for a script
|
||||
@@ -288,44 +299,46 @@ meshterm config set radio_sf 9
|
||||
meshterm monitor --seconds 60
|
||||
```
|
||||
|
||||
**[The command line](docs/cli/README.md)** is the manual — every command and option, what each
|
||||
prints on both faces, and the exit statuses.
|
||||
**[The CLI cookbook](docs/cli/cookbook.md)** has more one-liners like these.
|
||||
**[The command line](docs/cli/README.md)** is the manual. It shows each command and option,
|
||||
what each prints on both faces, and the exit statuses.
|
||||
**[The CLI cookbook](docs/cli/cookbook.md)** has more one-line commands like these.
|
||||
|
||||
## How this project is run
|
||||
|
||||
MeshTerm is one person working evenings and weekends. I'd rather tell you that up front
|
||||
than have you guess from how long things take.
|
||||
One person works on MeshTerm, in the evenings and at weekends. I want to tell you this at
|
||||
the start, so that you do not have to guess from the time that things take.
|
||||
|
||||
**Issues are welcome — all of them.** Bugs, questions, "is this supposed to do that". The
|
||||
[bug form](https://github.com/jpmartineau/MeshTerm/issues/new?template=bug.yml) asks for a
|
||||
fair bit, and that's on purpose: how well a problem is described really does decide whether
|
||||
I can do anything with it. If I can reproduce it, I'll usually chase it. If I can't, I'm
|
||||
mostly guessing.
|
||||
**We welcome all issues.** This includes bugs, questions, and "is this a fault or is it
|
||||
correct". The
|
||||
[bug form](https://github.com/jpmartineau/MeshTerm/issues/new?template=bug.yml) asks for
|
||||
much information. This is intentional. The quality of the description of a problem
|
||||
decides if I can do something about it. If I can reproduce a problem, I will usually work
|
||||
on it. If I cannot reproduce it, I can only guess.
|
||||
|
||||
**Ask before you write a pull request.** Join the [Discord](https://discord.gg/AZwe5Uvb3S)
|
||||
and say hello in `#contributing`. Tell us what you'd like to change. Then wait for a yes.
|
||||
That goes for small fixes too. I'm not being precious. MeshTerm has firm house rules
|
||||
about how screens get built (they're in [CLAUDE.md](CLAUDE.md)), and I'd hate for you to
|
||||
spend a weekend on something I then ask you to rewrite. A quick conversation first saves
|
||||
us both. AI tools are fine, as long as you say you used them.
|
||||
[CONTRIBUTING.md](CONTRIBUTING.md) has the details.
|
||||
and say hello in `#contributing`. Tell us what you want to change. Then wait for a yes.
|
||||
This applies to small corrections also. This is not because I am too careful. MeshTerm has
|
||||
firm house rules for how to build screens (they are in [CLAUDE.md](CLAUDE.md)). I do not
|
||||
want you to spend a weekend on a change that I then ask you to write again. A short
|
||||
conversation at the start saves time for both of us. You can use AI tools, but you must say
|
||||
that you used them. [CONTRIBUTING.md](CONTRIBUTING.md) has the details.
|
||||
|
||||
**I can't promise timelines.** Some things get fixed the same night. Some sit for a month
|
||||
because life happened. If your issue goes quiet, please give it a nudge. That helps me.
|
||||
**I cannot promise a time.** Some problems are corrected the same night. Some wait for a
|
||||
month, because I had other tasks in my life. If your issue is quiet for a long time, please
|
||||
remind me. This helps me.
|
||||
|
||||
**Where to report things.** Either works:
|
||||
**Where to report a problem.** You can use one of these two places:
|
||||
|
||||
- **[GitHub issues](https://github.com/jpmartineau/MeshTerm/issues)** if you have an
|
||||
account. This is where things get tracked and fixed, so it's the shortest path.
|
||||
- **[Discord](https://discord.gg/AZwe5Uvb3S)** if you don't, or if you're not sure it's a
|
||||
bug yet. Ask in the support forum, or drop reproducible defects in `#bugs`. I move the
|
||||
real ones over to GitHub myself.
|
||||
- **[GitHub issues](https://github.com/jpmartineau/MeshTerm/issues)**, if you have an
|
||||
account. We track and correct problems here, so this is the shortest path.
|
||||
- **[Discord](https://discord.gg/AZwe5Uvb3S)**, if you do not have an account, or if you
|
||||
are not sure that the problem is a bug. Ask in the support forum, or put defects that you
|
||||
can reproduce in `#bugs`. I move the real bugs to GitHub myself.
|
||||
|
||||
Don't worry about picking the wrong one. Getting told about a problem beats filing it
|
||||
tidily.
|
||||
Do not worry about the choice of the wrong place. It is better that you tell us about a
|
||||
problem than that you file it in a tidy way.
|
||||
|
||||
Also here: [CONTRIBUTING.md](CONTRIBUTING.md) ·
|
||||
More documents: [CONTRIBUTING.md](CONTRIBUTING.md) ·
|
||||
[SECURITY.md](SECURITY.md) ·
|
||||
[CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) ·
|
||||
[CHANGELOG.md](CHANGELOG.md)
|
||||
@@ -335,29 +348,33 @@ Also here: [CONTRIBUTING.md](CONTRIBUTING.md) ·
|
||||
Free code signing provided by [SignPath.io](https://signpath.io), certificate by
|
||||
[SignPath Foundation](https://signpath.org).
|
||||
|
||||
This is for the Windows download. It's being set up now, so releases up to and including
|
||||
0.10.2 are still unsigned.
|
||||
This policy is for the Windows download. The setup is in progress, so the releases up to
|
||||
and including 0.10.2 are still unsigned.
|
||||
|
||||
**How a build gets signed.** Every Windows build is made by GitHub Actions, straight from
|
||||
the public source code in this repository. SignPath only signs a file it can trace back to
|
||||
one of those builds, and only after the maintainer approves that release. A file built on
|
||||
anyone's own computer can't be signed this way, not even the maintainer's.
|
||||
**How a build is signed.** GitHub Actions makes each Windows build, directly from the public
|
||||
source code in this repository. SignPath signs only a file that it can trace to one of
|
||||
these builds, and only after the maintainer approves that release. This process cannot sign
|
||||
a file that was built on any person's own computer, and this includes the maintainer's
|
||||
computer.
|
||||
|
||||
**Who does what.** MeshTerm has one maintainer,
|
||||
[Jean-Pierre Martineau](https://github.com/jpmartineau), who holds all three roles:
|
||||
[Jean-Pierre Martineau](https://github.com/jpmartineau), who has all three roles:
|
||||
|
||||
- **Author:** writes the code and commits it.
|
||||
- **Reviewer:** reviews every change from anyone else before it goes in.
|
||||
- **Approver:** approves each release before it gets signed.
|
||||
- **Reviewer:** reviews each change from anyone else before it goes in.
|
||||
- **Approver:** approves each release before it is signed.
|
||||
|
||||
**Privacy.** This program will not transfer any information to other networked systems
|
||||
unless specifically requested by the user or the person installing or operating it.
|
||||
|
||||
In practice, MeshTerm goes online for one thing: map tiles. It downloads them from
|
||||
[OpenFreeMap](https://openfreemap.org/) when you open a screen that shows a map, and
|
||||
OpenFreeMap's [privacy policy](https://openfreemap.org/privacy) covers those downloads. It
|
||||
collects nothing about you and doesn't check for updates. Over the radio, it sends only
|
||||
what you ask it to send.
|
||||
In practice, MeshTerm goes online for only one purpose: map tiles. It downloads them from
|
||||
[OpenFreeMap](https://openfreemap.org/) when you open a screen that shows a map. The
|
||||
[privacy policy](https://openfreemap.org/privacy) of OpenFreeMap applies to these
|
||||
downloads. MeshTerm collects no information about you, and it does not check for updates.
|
||||
Over the radio, MeshTerm sends only what you tell it to send.
|
||||
|
||||
<!-- ste: off -->
|
||||
<!-- Legal text: attribution and licence. It is not in ASD-STE100, on purpose. -->
|
||||
|
||||
## Acknowledgements & attribution
|
||||
|
||||
@@ -430,3 +447,5 @@ Each standalone build is a single file with `LICENSE`, `NOTICE`, and a generated
|
||||
three files are also attached to each release on the
|
||||
[releases page](https://github.com/jpmartineau/MeshTerm/releases), so you can read them
|
||||
without running anything.
|
||||
|
||||
<!-- ste: on -->
|
||||
+37
-37
@@ -2,61 +2,61 @@
|
||||
|
||||
## Reporting a vulnerability
|
||||
|
||||
Use GitHub's private vulnerability reporting: go to the **Security** tab of this
|
||||
repository and choose **Report a vulnerability**. That opens a private thread visible only
|
||||
to you and the maintainer, so nothing is disclosed while it is being fixed.
|
||||
Use the private vulnerability reporting of GitHub. Go to the **Security** tab of this
|
||||
repository and choose **Report a vulnerability**. This opens a private thread. Only you and
|
||||
the maintainer can see it, so nothing is disclosed while we correct the problem.
|
||||
|
||||
If that is unavailable to you for any reason, email <johnputer@meshterm.net> instead.
|
||||
If you cannot use this for any reason, send an email to <johnputer@meshterm.net> instead.
|
||||
|
||||
**Please do not open a public issue for a security problem.**
|
||||
**Do not open a public issue for a security problem.**
|
||||
|
||||
## What to expect
|
||||
|
||||
MeshTerm is maintained by one person in his spare time, so this promises only what can
|
||||
actually be kept:
|
||||
One person maintains MeshTerm in his spare time. For this reason, this policy promises only
|
||||
what he can keep:
|
||||
|
||||
- **An acknowledgement within a week.** Not a fix within a week — an answer saying the
|
||||
report was read and what happens next.
|
||||
- **A fix or a decision within 90 days** for anything confirmed. If it will take longer,
|
||||
you will be told why.
|
||||
- **Credit in the release notes**, unless you would rather not be named.
|
||||
- **An acknowledgement within a week.** This is not a correction within a week. It is an
|
||||
answer that says that he read the report and what happens next.
|
||||
- **A correction or a decision within 90 days** for each confirmed problem. If it will take
|
||||
more time, you will be told why.
|
||||
- **Credit in the release notes**, unless you prefer that we do not use your name.
|
||||
|
||||
A slow reply is not a dismissal. If a week passes with silence, a nudge on the same thread
|
||||
is welcome.
|
||||
A slow reply is not a rejection. If you hear nothing for a week, you are welcome to remind
|
||||
us on the same thread.
|
||||
|
||||
## Supported versions
|
||||
|
||||
While the major version is `0`, only the **latest release** is supported. There are no
|
||||
backports; a fix ships in the next version.
|
||||
While the major version is `0`, only the **latest release** is supported. We do not make
|
||||
backports. A correction is in the next version.
|
||||
|
||||
## Scope
|
||||
|
||||
In scope — anything in this repository:
|
||||
In scope: anything in this repository.
|
||||
|
||||
- Code execution, path traversal, or file overwrite triggered by data MeshTerm reads:
|
||||
packets overheard from the mesh, a companion's responses, a config or preferences file,
|
||||
a downloaded map tile.
|
||||
- Anything that discloses a **channel key**, a **repeater admin password**, or a node's
|
||||
private key material — including into the local database, a log, an export, or the
|
||||
screen when it should not be there.
|
||||
- Code execution, path traversal, or file overwrite that data which MeshTerm reads can
|
||||
cause. This data includes packets overheard from the mesh, the responses of a companion,
|
||||
a config file or a preferences file, and a downloaded map tile.
|
||||
- Anything that discloses a **channel key**, a **repeater admin password**, or the private
|
||||
key material of a node. This includes disclosure into the local database, a log, an
|
||||
export, or the screen, when the material must not be there.
|
||||
- Anything that lets a crafted packet corrupt or delete the observation database.
|
||||
- Weaknesses in how MeshTerm stores credentials on disk.
|
||||
|
||||
Out of scope, though still worth telling us about somewhere:
|
||||
Out of scope, but we still want you to tell us somewhere:
|
||||
|
||||
- Vulnerabilities in the **MeshCore firmware** or in the companion device itself. Those
|
||||
belong upstream at the MeshCore project.
|
||||
- Vulnerabilities in the `meshcore` Python library, `bleak`, or any other dependency —
|
||||
report those to their maintainers. If a MeshTerm change can mitigate one, we want to
|
||||
know.
|
||||
- The fact that anyone within radio range can hear a public channel. That is how a mesh
|
||||
works, not a defect.
|
||||
- Anything requiring an attacker to already have write access to your filesystem or your
|
||||
companion device.
|
||||
- Vulnerabilities in the **MeshCore firmware** or in the companion device itself. These
|
||||
belong upstream, with the MeshCore project.
|
||||
- Vulnerabilities in the `meshcore` Python library, `bleak`, or any other dependency.
|
||||
Report these to their maintainers. If a change in MeshTerm can reduce the risk of one,
|
||||
we want to know.
|
||||
- The fact that everyone within radio range can hear a public channel. This is how a
|
||||
mesh works. It is not a defect.
|
||||
- Anything that needs an attacker who already has write access to your filesystem or to
|
||||
your companion device.
|
||||
|
||||
## A note on what MeshTerm handles
|
||||
|
||||
MeshTerm decrypts channel traffic using keys **you** give it, and it records everything it
|
||||
overhears to a local SQLite database. That database is not encrypted. Treat it as you would
|
||||
any file holding your mesh's history, and remember that a backup of it carries the same
|
||||
weight.
|
||||
MeshTerm decrypts channel traffic with keys that **you** give it. It stores everything that
|
||||
it overhears in a local SQLite database. This database is not encrypted. Treat it in the
|
||||
same way as each file that holds the history of your mesh. A backup of the database has the
|
||||
same importance.
|
||||
+43
-37
@@ -1,28 +1,32 @@
|
||||
# MeshTerm configuration: where things live, and which device to talk to.
|
||||
# Copy to ~/.meshterm/config.toml (or pass --db / --port on the CLI).
|
||||
# MeshTerm configuration: where things are, and which device to talk to.
|
||||
# Copy this file to ~/.meshterm/config.toml (or give --db / --port on the CLI).
|
||||
#
|
||||
# How MeshTerm itself behaves — transmit cooldown, message retries, how much history it
|
||||
# keeps — is *not* here. Those are preferences: they live in ~/.meshterm/preferences.toml,
|
||||
# and are edited on the Preferences page (or with `meshterm preferences set <key> <value>`).
|
||||
# Every one has a built-in default, so that file only ever lists what you have changed.
|
||||
# This file does not have the settings for how MeshTerm itself behaves. These are the
|
||||
# transmit cooldown, the message retries, and how much history MeshTerm keeps. They are
|
||||
# preferences. They are in ~/.meshterm/preferences.toml, and you edit them on the
|
||||
# Preferences page (or with `meshterm preferences set <key> <value>`).
|
||||
# Each preference has a built-in default, so that file lists only what you changed.
|
||||
#
|
||||
# Profiles are optional. With no profile, no --port/--ble/--tcp, MeshTerm discovers attached
|
||||
# serial devices and nearby Bluetooth companions, lets you pick one (interactively) or uses the
|
||||
# single one present, and remembers the last device that connected as the default for next time.
|
||||
# A network (TCP) companion isn't discoverable — name it with --tcp host:port, a TCP profile, or
|
||||
# the picker's "add a network device" prompt. Define profiles below only to give your hardware
|
||||
# stable aliases (e.g. --profile yagi, --profile handheld for Bluetooth, --profile wifi for TCP).
|
||||
# Profiles are optional. When you give no profile and none of --port, --ble, or --tcp,
|
||||
# MeshTerm discovers the serial devices that are connected and the Bluetooth companions that
|
||||
# are near. It lets you select one (interactively), or it uses the one device that is
|
||||
# there. It remembers the last device that connected, and uses it as the default the next
|
||||
# time. A network (TCP) companion is not discoverable. To name one, use --tcp host:port, a
|
||||
# TCP profile, or the "add a network device" prompt of the picker. Define profiles below
|
||||
# only to give your hardware names that do not change (for example --profile yagi,
|
||||
# --profile handheld for Bluetooth, or --profile wifi for TCP).
|
||||
|
||||
# Uncomment and name one of your own profiles below to make it the default. Left
|
||||
# commented, MeshTerm finds the device itself, as described above.
|
||||
# To make one of your own profiles the default, remove the comment mark below and write
|
||||
# the name of the profile. While the line is a comment, MeshTerm finds the device by
|
||||
# itself, as described above.
|
||||
# default_profile = "s3"
|
||||
|
||||
# Open the radio link as soon as the menu appears. False opens it lazily instead — only
|
||||
# once monitoring is turned on or a tool first needs the radio.
|
||||
# Open the radio link when the menu appears. When the value is false, MeshTerm opens the
|
||||
# link later: when you turn on monitoring, or when a tool first needs the radio.
|
||||
connect_on_start = true
|
||||
# db_path = "C:/Users/you/meshterm/meshterm.db"
|
||||
|
||||
# One [profiles.<name>] block per physical companion device.
|
||||
# Make one [profiles.<name>] block for each physical companion device.
|
||||
[profiles.s3]
|
||||
port = "COM5"
|
||||
baudrate = 115200
|
||||
@@ -40,44 +44,46 @@ description = "Local-range repeater"
|
||||
port = "COM8"
|
||||
description = "XIAO observer running a bot"
|
||||
|
||||
# A Bluetooth LE companion: give it an `address` (from `meshterm devices`) instead of a port.
|
||||
# The `transport = "ble"` line is optional — a bare `address` already implies Bluetooth.
|
||||
# A Bluetooth LE companion: give an `address` (from `meshterm devices`) and not a port.
|
||||
# The line `transport = "ble"` is optional. An `address` alone means Bluetooth.
|
||||
[profiles.handheld]
|
||||
address = "AA:BB:CC:DD:EE:FF"
|
||||
# transport = "ble"
|
||||
# ble_pin = "123456" # only if your device requires a pairing PIN
|
||||
# ble_pin = "123456" # only if your device needs a pairing PIN
|
||||
description = "Pocket handheld over Bluetooth"
|
||||
|
||||
# A network (TCP) companion — a WiFi board, or a meshcored-style proxy — reachable at a
|
||||
# host:port. Give it a `host` (and optional `tcp_port`, default 5000); a bare `host` already
|
||||
# implies TCP, so the `transport = "tcp"` line is optional. A TCP companion isn't discoverable,
|
||||
# so a profile (or --tcp host:port, or the picker's "add a network device" prompt) is how you
|
||||
# reach one.
|
||||
# A network (TCP) companion, such as a WiFi board or a meshcored-style proxy, at a
|
||||
# host:port. Give a `host` (and an optional `tcp_port`, which is 5000 by default). A `host`
|
||||
# alone means TCP, so the line `transport = "tcp"` is optional. A TCP companion is not
|
||||
# discoverable. To reach one, use a profile, or --tcp host:port, or the "add a network
|
||||
# device" prompt of the picker.
|
||||
[profiles.wifi]
|
||||
host = "192.168.1.50"
|
||||
# tcp_port = 5000
|
||||
# transport = "tcp"
|
||||
description = "Basestation over WiFi"
|
||||
|
||||
# A radio on this machine's own SPI bus: MeshTerm runs its node while it is connected.
|
||||
# The pins default to the uConsole AIO v1's, which needs nothing more than this (and is
|
||||
# listed on the device screen anyway, whenever /dev/spidev1.0 exists).
|
||||
# A radio on the own SPI bus of this machine: MeshTerm runs its node while the radio is
|
||||
# connected. The default pins are the pins of the uConsole AIO v1. That board needs only
|
||||
# this profile. The device screen also lists it, when /dev/spidev1.0 exists.
|
||||
[profiles.aio]
|
||||
transport = "spi"
|
||||
# [profiles.aio.spi]
|
||||
# en_pins = [27] # the AIO v2 powers its radio from pin 27
|
||||
#
|
||||
# Which /dev/gpiochip<n> carries the pins is not a fact about the board: a uConsole's CM4
|
||||
# has its 40-pin header on chip 0, a CM5 had it on chip 4 under its early kernels, chip 0
|
||||
# once Raspberry Pi aliased it there, and another number again on some later kernels. So
|
||||
# gpio_chip defaults to -1, which finds the chip by its label (pinctrl-rp1 on a CM5,
|
||||
# pinctrl-bcm2711 on a CM4; `gpiodetect` lists them) and falls back to chip 0 where none
|
||||
# carries either. Give it a number only to override that:
|
||||
# The number of the /dev/gpiochip<n> that has the pins does not depend on the board. A
|
||||
# uConsole CM4 has its 40-pin header on chip 0. A CM5 had it on chip 4 with its early
|
||||
# kernels, and on chip 0 after Raspberry Pi made an alias there. Some later kernels use
|
||||
# another number. For this reason, gpio_chip is -1 by default. This value finds the chip by
|
||||
# its label (pinctrl-rp1 on a CM5, pinctrl-bcm2711 on a CM4, and `gpiodetect` lists them).
|
||||
# If no chip has one of these labels, MeshTerm uses chip 0. Give a number only to
|
||||
# override this:
|
||||
# gpio_chip = 4
|
||||
|
||||
# Any other board states its whole wiring — every pin and both switches, because a key
|
||||
# left out keeps the AIO's value. Read them off the board's meshtasticd config; the
|
||||
# values here are an illustration only. docs/guide/configuration.md has the key-by-key mapping.
|
||||
# Any other board must give its complete wiring: each pin and both switches. A key that
|
||||
# you omit keeps the value of the AIO. Read the values from the meshtasticd config of the
|
||||
# board. The values here are only an example. docs/guide/configuration.md shows which
|
||||
# key matches which value.
|
||||
# [profiles.hat.spi]
|
||||
# bus_id = 0 # /dev/spidev0.0
|
||||
# cs_id = 0
|
||||
|
||||
+31
-29
@@ -1,64 +1,66 @@
|
||||
# MeshTerm documentation
|
||||
|
||||
Everything that didn't belong on the front page. The [README](../README.md) is the front
|
||||
door — what MeshTerm is, how to install it, and enough to get a first session running.
|
||||
This folder is the rest, in four parts:
|
||||
This folder has everything that does not belong on the front page. The
|
||||
[README](../README.md) is the front door. It says what MeshTerm is, how to install it, and
|
||||
how to start a first session. This folder has the other documents, in four parts:
|
||||
|
||||
| Folder | What's in it |
|
||||
| Folder | What is in it |
|
||||
| --- | --- |
|
||||
| [`guide/`](#using-meshterm) | Using MeshTerm: what each screen does, configuring it, connecting a companion, terminals. |
|
||||
| [`cli/`](#the-command-line) | The command line manual, and a cookbook of one-liners. |
|
||||
| [`devices/`](#running-it-on-a-handheld) | Running MeshTerm on a handheld's own screen, one manual per device. |
|
||||
| [`development/`](#reading-the-code) | How the code is laid out, and a historical survey. |
|
||||
| [`guide/`](#using-meshterm) | How to use MeshTerm: what each screen does, how to configure it, how to connect a companion, and terminals. |
|
||||
| [`cli/`](#the-command-line) | The manual of the command line, and a cookbook of one-line commands. |
|
||||
| [`devices/`](#running-it-on-a-handheld) | How to run MeshTerm on a handheld's own screen, with one manual for each device. |
|
||||
| [`development/`](#reading-the-code) | How the code is laid out, how we write, and a historical survey. |
|
||||
|
||||
## Using MeshTerm
|
||||
|
||||
| Read | For |
|
||||
| --- | --- |
|
||||
| **[What MeshTerm does](guide/features.md)** | Every screen the menu offers, grouped the way the menu groups them, with the command that does the same job without it. |
|
||||
| [Configuring MeshTerm](guide/configuration.md) | Preferences versus config, device profiles, and everything kept under `~/.meshterm`. |
|
||||
| [When a companion won't connect](guide/connecting.md) | Bluetooth pairing on each system, finding the PIN, and every connection error MeshTerm reports, with what to do about it. |
|
||||
| [Terminals, icons, and the Windows console](guide/terminals.md) | Why the icons and charts draw on some terminals and not others, and what MeshTerm does about it on Windows. |
|
||||
| **[What MeshTerm does](guide/features.md)** | Each screen that the menu has, in the same groups as the menu, with the command that does the same job without the menu. |
|
||||
| [Configuring MeshTerm](guide/configuration.md) | The difference between preferences and config, the device profiles, and all the files that MeshTerm keeps in `~/.meshterm`. |
|
||||
| [When a companion does not connect](guide/connecting.md) | Bluetooth pairing on each system, how to find the PIN, and each connection error that MeshTerm reports, with the action to take. |
|
||||
| [Terminals, icons, and the Windows console](guide/terminals.md) | Why the icons and charts draw correctly on some terminals and not on others, and what MeshTerm does about it on Windows. |
|
||||
|
||||
## The command line
|
||||
|
||||
| Read | For |
|
||||
| --- | --- |
|
||||
| **[The command line](cli/README.md)** | The manual. Every subcommand and option, what each prints on the plain and JSON faces, the exit statuses, and recipes built on `jq`. |
|
||||
| [The CLI cookbook](cli/cookbook.md) | Common one-liners by task, for when you want the shape of a command rather than its specification. |
|
||||
| **[The command line](cli/README.md)** | The manual. Each subcommand and option, what each prints on the plain face and on the JSON face, the exit statuses, and recipes that use `jq`. |
|
||||
| [The CLI cookbook](cli/cookbook.md) | Common one-line commands, in groups by task. Use it when you want the form of a command and not its specification. |
|
||||
|
||||
## Running it on a handheld
|
||||
|
||||
Only if you want MeshTerm running on a handheld's own screen. A MeshCore companion on
|
||||
USB, Bluetooth, or Wi-Fi needs none of this.
|
||||
Read these documents only if you want to run MeshTerm on a handheld's own screen. A
|
||||
MeshCore companion on USB, Bluetooth, or Wi-Fi does not need them.
|
||||
|
||||
| Read | For |
|
||||
| --- | --- |
|
||||
| **[MeshTerm on hardware](devices/README.md)** | Start here — which manual below is yours, and what the device-side scripts do. |
|
||||
| [MeshTerm on the PicoCalc](devices/picocalc-lyra.md) | A stock ClockworkPi PicoCalc to a Linux handheld with a LoRa radio soldered inside: the shopping list, the swap to a Luckfox Lyra core, Calculinux, and the radio. |
|
||||
| [MeshTerm on the uConsole](devices/uconsole.md) | A uConsole (CM4 or CM5) with the hackergadgets AIO LoRa board, whose SX1262 sits on the computer's own SPI bus — MeshTerm drives it directly with `--spi`, or a small bridge fronts it as a companion for an always-on node. |
|
||||
| **[MeshTerm on hardware](devices/README.md)** | Start here. It says which of the manuals below is the correct one for you, and what the scripts on the device do. |
|
||||
| [MeshTerm on the PicoCalc](devices/picocalc-lyra.md) | How to change a stock ClockworkPi PicoCalc into a Linux handheld that has a LoRa radio soldered inside: the shopping list, the change to a Luckfox Lyra core, Calculinux, and the radio. |
|
||||
| [MeshTerm on the uConsole](devices/uconsole.md) | A uConsole (CM4 or CM5) with the hackergadgets AIO LoRa board. Its SX1262 is on the computer's own SPI bus. MeshTerm can control it directly with `--spi`. Or a small bridge can make it a companion for a node that is always on. |
|
||||
| [MeshTerm on the Cardputer Zero](devices/cardputer-zero.md) | ⚠️ **Not supported yet.** What exists for M5Stack's Linux Cardputer, and how to try it in the desktop emulator. |
|
||||
|
||||
## Reading the code
|
||||
|
||||
| Read | For |
|
||||
| --- | --- |
|
||||
| [How the code is laid out](development/architecture.md) | The layering, and where a new feature goes. |
|
||||
| [CLAUDE.md](../CLAUDE.md) | The actual style guide — terminology, UX standards, and the reusable building blocks. Written for AI assistants, and the house rules for everyone. |
|
||||
| [CONTRIBUTING.md](../CONTRIBUTING.md) | How to propose a change, the rules on AI use, the dev install, the three checks a change must pass, and what happens to your contribution's licensing. |
|
||||
| [How the code is laid out](development/architecture.md) | The layers of the code, and where to put a new feature. |
|
||||
| [How we write](development/writing-style.md) | How we write: the ASD-STE100 rules and the glossary for docstrings, comments, and documents. |
|
||||
| [CLAUDE.md](../CLAUDE.md) | The real style guide: the terms, the UX standards, and the building blocks that you can use again. It is written for AI assistants, and it has the house rules for everyone. |
|
||||
| [CONTRIBUTING.md](../CONTRIBUTING.md) | How to propose a change, the rules for the use of AI, the dev install, the three checks that a change must pass, and what happens to the licensing of your contribution. |
|
||||
|
||||
### Historical record
|
||||
|
||||
A survey of the code, kept because a project that writes down its own problems is easier
|
||||
to trust than one that doesn't — but it is a **snapshot of a date, not a to-do list**.
|
||||
Some of what it describes has been fixed since, and it does not mark which.
|
||||
This is a survey of the code. We keep it, because a project that writes down its own
|
||||
problems is easier to trust than a project that does not. But it is a **snapshot of one
|
||||
date, not a list of tasks**. Some of the problems that it describes are corrected now, and
|
||||
the survey does not say which.
|
||||
|
||||
| Read | For |
|
||||
| --- | --- |
|
||||
| [UI consistency survey](development/survey-ui-consistency.md) | Everything else in the CLAUDE.md UX standard — six sweeps found nothing, which is the useful part. |
|
||||
| [UI consistency survey](development/survey-ui-consistency.md) | Each other point of the CLAUDE.md UX standard. Six sweeps found nothing, and this is the useful result. |
|
||||
|
||||
## Also here
|
||||
|
||||
`screenshots/` holds the captures the README shows and the ones
|
||||
[meshterm.net](https://meshterm.net) builds its landing page from, so a file in there is
|
||||
referenced from more than one place.
|
||||
`screenshots/` has the captures that the README shows. It also has the captures from which
|
||||
[meshterm.net](https://meshterm.net) builds its landing page. Thus more than one place
|
||||
refers to each file in this folder.
|
||||
@@ -1,11 +1,11 @@
|
||||
# How the code is laid out
|
||||
|
||||
A map of the package for anyone reading it for the first time. The house rules that
|
||||
govern what goes where are in [CLAUDE.md](../../CLAUDE.md), which is the real style guide;
|
||||
[CONTRIBUTING.md](../../CONTRIBUTING.md) has the dev install and the gates a change has to
|
||||
pass.
|
||||
This page is a map of the package for a person who reads it for the first time.
|
||||
[CLAUDE.md](../../CLAUDE.md) has the house rules that decide what goes where. It is the real
|
||||
style guide. [CONTRIBUTING.md](../../CONTRIBUTING.md) has the dev install and the checks
|
||||
that a change must pass.
|
||||
|
||||
MeshTerm is layered so a new feature is one file in `meshterm/tools/`:
|
||||
MeshTerm is divided into layers. Thus a new feature is one file in `meshterm/tools/`:
|
||||
|
||||
```
|
||||
cli.py Typer app; no subcommand -> interactive menu
|
||||
@@ -17,7 +17,6 @@ persistence/ SQLite schema, repository, structured logging
|
||||
ui/ Rich theme/widgets + the full-screen TUI (screens, dialogs, map, chat)
|
||||
```
|
||||
|
||||
Adding a feature means subclassing `Tool`, decorating it with `@register`, and
|
||||
implementing `run()`. The same registry builds both the CLI and the menu, and the base
|
||||
class wraps every execution in a logged `runs` row automatically.
|
||||
|
||||
To add a feature, make a subclass of `Tool`, add the `@register` decorator to it, and
|
||||
write the `run()` method. The same registry builds the CLI and the menu. The base class
|
||||
automatically writes a logged `runs` row for each execution.
|
||||
@@ -1,23 +1,25 @@
|
||||
# UI consistency survey — what is off, and what turned out to be fine
|
||||
# UI consistency survey: what is off, and what turned out to be fine
|
||||
|
||||
> **A historical record.** This was a snapshot of the code on the date below, kept
|
||||
> because a project that writes down its own problems is easier to trust than one
|
||||
> that doesn't. Some of what's here has since been fixed and some hasn't; nothing in
|
||||
> it is a to-do list. Read it as "what this looked like then", not "what is wrong now".
|
||||
> **A historical record.** This was a snapshot of the code on the date below. We keep it,
|
||||
> because a project that writes down its own problems is easier to trust than a project
|
||||
> that does not. We have corrected some of these problems since then, and we have not
|
||||
> corrected others. Nothing in it is a list of tasks. Read it as "what the code looked like
|
||||
> then", not as "what is wrong now".
|
||||
|
||||
Written 2026-08-22 against `main` @ `4b29248`. It covers the CLAUDE.md UX standard apart
|
||||
from navigation. A catalogue, not a change.
|
||||
The survey was written on 2026-08-22 against `main` @ `4b29248`. It covers the UX standard
|
||||
of CLAUDE.md, except navigation. It is a catalogue. It is not a change.
|
||||
|
||||
**The short version: the standard is very well kept.** Six sweeps found nothing at all;
|
||||
what remains is nine small, specific things. That is the useful result — it means the list
|
||||
below is close to complete rather than a sample.
|
||||
**The short version: the code follows the standard very well.** Six sweeps found nothing at
|
||||
all. Nine small, specific points remain. This is the useful result. It means that the list
|
||||
below is almost complete and is not a sample.
|
||||
|
||||
---
|
||||
|
||||
## Part 1 — the clean sweeps
|
||||
## Part 1: the clean sweeps
|
||||
|
||||
Each of these was checked mechanically across the whole package, not spot-checked. The scripts
|
||||
are one-offs and were not kept; each is a dozen lines of `ast.walk` and is trivial to redo.
|
||||
We checked each of these with a program across the whole package. We did not check them by
|
||||
hand on a sample. We did not keep the scripts. Each script is a dozen lines of `ast.walk`,
|
||||
and it is easy to write again.
|
||||
|
||||
| Rule (CLAUDE.md) | How it was checked | Result |
|
||||
|---|---|---|
|
||||
@@ -27,31 +29,34 @@ are one-offs and were not kept; each is a dozen lines of `ast.walk` and is trivi
|
||||
| Filter atom is always `type to filter` | regex over all hints | **0 deviations** |
|
||||
| No emoji in a screen or dialog title | 86 title literals vs an emoji range | **0** (one hit was a `Choice` row title, where icons belong) |
|
||||
| Sentence case in titles | capitalised non-leading word, minus proper nouns | **0** (5 hits were markup-wrapped panel headings) |
|
||||
| Lexicon: "heard" not "seen"; "contacts" not "nodes list" | regex over all non-docstring literals | **0** |
|
||||
| Lexicon: "heard" and not "seen", "contacts" and not "nodes list" | regex over all non-docstring literals | **0** |
|
||||
| Status marks: never `✔ ✖ ✅ ❌` | scan of every source line | **0** (the 3 hits are the fold table that *normalizes* them, and a docstring warning against them) |
|
||||
| `…` on rows that open a prompt, none on rows that act | 40 row labels via the builders | **1 arguable** — see §2.7 |
|
||||
| `…` on rows that open a prompt, none on rows that act | 40 row labels via the builders | **1 arguable** (refer to §2.7) |
|
||||
| Empty state never parenthesised | scan of 53 empty-state-shaped strings | **0** |
|
||||
| `render.query_line` is the one query line | import graph | **exactly** the four screens CLAUDE.md names: select list, path composer, map, mesh walk |
|
||||
|
||||
Two more worth calling out because they are the kind of rule that usually rots:
|
||||
Two more points are important, because rules of this kind usually decay:
|
||||
|
||||
- **`Del` on a row is gated correctly.** The hint atom only appears when the highlighted row
|
||||
is actually deletable ([`select.py:448`](../../meshterm/ui/tui/select.py#L448)) — a real
|
||||
instance of "never advertise a key that would do nothing", implemented rather than assumed.
|
||||
- **The PicoCalc lane's "a chord earns a chip" rule holds.** The one bare-letter shortcut in
|
||||
the app — Time Machine's `w` — has its F3 chip, with the reasoning written out in full at
|
||||
- **`Del` on a row is correctly gated.** The hint atom is visible only when the highlighted
|
||||
row can be deleted ([`select.py:448`](../../meshterm/ui/tui/select.py#L448)). This is a
|
||||
real example of "never advertise a key that would do nothing". The code implements the
|
||||
rule, and the survey did not only assume it.
|
||||
- **The rule "a chord earns a chip" on the lane of the PicoCalc holds.** The app has one
|
||||
bare-letter shortcut, which is `w` in the Time Machine. It has its F3 chip. The reason is
|
||||
written in full at
|
||||
[`timemachine_screen.py:227-242`](../../meshterm/ui/timemachine_screen.py#L227).
|
||||
|
||||
---
|
||||
|
||||
## Part 2 — the findings
|
||||
## Part 2: the findings
|
||||
|
||||
Ordered by how visible each is to someone using the app.
|
||||
The findings are in order of how visible each one is to a person who uses the app.
|
||||
|
||||
### 2.1 Empty-state voice: four sites shout (rule: lowercase, muted, `— explanation`, no full stop)
|
||||
|
||||
The house voice is `no contacts yet — receive an advert first`. These four break it, and they
|
||||
are the empty states of four whole screens, so each is the *only* thing on screen when it shows:
|
||||
The house voice is `no contacts yet — receive an advert first`. These four sites do not
|
||||
follow it. They are the empty states of four complete screens. Thus each one is the *only*
|
||||
thing on the screen when it shows:
|
||||
|
||||
| Site | Current | House voice |
|
||||
|---|---|---|
|
||||
@@ -60,158 +65,172 @@ are the empty states of four whole screens, so each is the *only* thing on scree
|
||||
| [`timemachine_screen.py:335-336`](../../meshterm/ui/timemachine_screen.py#L335) | `Nothing recorded in this window.` + `Press w to widen it.` | lowercase, no full stop |
|
||||
| [`timemachine_screen.py:453-454`](../../meshterm/ui/timemachine_screen.py#L453) | `Nothing sent in this window.` + `Press w to widen it.` | ditto |
|
||||
|
||||
The two `Press w to widen it.` lines are a second question: they are *hints*, sitting in the
|
||||
body, on a screen whose F-key lane and footer already advertise `w`. Either the empty state
|
||||
absorbs it (`nothing recorded in this window — press w to widen it`) or it goes.
|
||||
The two `Press w to widen it.` lines raise a second question. They are *hints*, and they are
|
||||
in the body of a screen where the F-key lane and the footer already advertise `w`. There are
|
||||
two options. The empty state can include the hint (`nothing recorded in this window — press w to widen it`).
|
||||
Or we can remove the hint.
|
||||
|
||||
Deliberately **not** on this list: `Never heard` ([`contacts_screen.py:258`](../../meshterm/ui/contacts_screen.py#L258))
|
||||
is a purge-ladder *row label*, and `No revisits` ([`records.py:237`](../../meshterm/services/records.py#L237))
|
||||
is a discipline *title*. Both correctly capitalised.
|
||||
These two sites are **not** on this list on purpose. `Never heard`
|
||||
([`contacts_screen.py:258`](../../meshterm/ui/contacts_screen.py#L258)) is the *row label* of
|
||||
a purge ladder. `No revisits` ([`records.py:237`](../../meshterm/services/records.py#L237))
|
||||
is the *title* of a discipline. Both are correctly capitalised.
|
||||
|
||||
### 2.2 One dialog inverts the button convention
|
||||
|
||||
The rule: *safe way out on the left, committing verb on the right and default*.
|
||||
The rule is: *the safe way out is on the left, and the committing verb is on the right and
|
||||
is the default*.
|
||||
|
||||
[`config_editor.py:538-541`](../../meshterm/ui/config_editor.py#L538) — the Location dialog:
|
||||
This is the Location dialog, at
|
||||
[`config_editor.py:538-541`](../../meshterm/ui/config_editor.py#L538):
|
||||
|
||||
```python
|
||||
[("Pick on map", "map"), ("Type coordinates", "type"), ("Clear", "clear")],
|
||||
title="Location",
|
||||
```
|
||||
|
||||
No `Cancel`, no `default=`. So the leftmost slot — reserved for the safe way out — holds an
|
||||
action, the rightmost — reserved for the default commit — holds the *destructive* option
|
||||
(`Clear`), and the default falls to index 0. Esc still cancels, so nothing is lost; but it is
|
||||
the only dialog in the app shaped this way, out of 49 call sites.
|
||||
It has no `Cancel` and no `default=`. Thus the leftmost slot, which is for the safe way out,
|
||||
holds an action. The rightmost slot, which is for the default commit, holds the *destructive*
|
||||
option (`Clear`). The default is index 0. Esc still cancels, so the user loses nothing. But
|
||||
this is the only dialog in the app with this shape, out of 49 call sites.
|
||||
|
||||
### 2.3 One dialog defaults to the middle button
|
||||
|
||||
[`config_editor.py:988-992`](../../meshterm/ui/config_editor.py#L988) — `[Cancel, Preview, Apply]`
|
||||
with `default=1`, so Enter lands on **Preview**, not the rightmost `Apply`.
|
||||
This is at [`config_editor.py:988-992`](../../meshterm/ui/config_editor.py#L988). The buttons
|
||||
are `[Cancel, Preview, Apply]` with `default=1`. Thus Enter selects **Preview**, and not the
|
||||
rightmost button `Apply`.
|
||||
|
||||
This is very likely deliberate — previewing before overwriting a whole device config is the
|
||||
cautious path — but the standard as written says the rightmost verb is the default, and this
|
||||
is the only three-button dialog with a real Cancel. Either the rule grows a clause ("with
|
||||
three buttons, the *cautious* commit defaults") or this changes.
|
||||
This is very probably intentional. Before it overwrites the complete config of a device, the
|
||||
dialog gives the user a preview, which is the careful path. But the standard, as it is
|
||||
written, says that the rightmost verb is the default. This is the only dialog with three
|
||||
buttons and a real Cancel. There are two options. The rule can get a clause ("with three
|
||||
buttons, the *careful* commit is the default"). Or this dialog changes.
|
||||
|
||||
### 2.4 Two dialog *shapes* the standard doesn't describe
|
||||
### 2.4 Two dialog *shapes* that the standard does not describe
|
||||
|
||||
Beyond confirms, the app has two other dialog shapes, both used consistently but neither
|
||||
written down:
|
||||
The app has two dialog shapes in addition to the confirm. The app uses both consistently,
|
||||
but the standard does not describe either:
|
||||
|
||||
- **The toggle picker** — `[Off, On]` with the default tracking the current value
|
||||
- **The toggle picker.** It has `[Off, On]`, and the default follows the current value
|
||||
([`config_editor.py:441`](../../meshterm/ui/config_editor.py#L441),
|
||||
[`repeater_admin.py:313`](../../meshterm/ui/repeater_admin.py#L313)). No Cancel; `Esc keep`
|
||||
carries it, which is exactly the footer verb CLAUDE.md reserves for a value picker. Correct
|
||||
in practice, undescribed in the standard.
|
||||
- **The notification with an onward door** — `[Trophy case, Close]`, `default=1`
|
||||
([`trace_screen.py:637-639`](../../meshterm/ui/trace_screen.py#L637)). Appears unbidden after a
|
||||
record-setting trace. `Close` is the default so a stray Enter dismisses rather than
|
||||
navigating — the inverse of the usual rule, and right for a dialog the user did not ask for.
|
||||
[`repeater_admin.py:313`](../../meshterm/ui/repeater_admin.py#L313)). It has no Cancel.
|
||||
`Esc keep` does that job. This is the footer verb that CLAUDE.md reserves for a value
|
||||
picker. It is correct in practice, but the standard does not describe it.
|
||||
- **The notification with a door to a next screen.** It has `[Trophy case, Close]` with
|
||||
`default=1`
|
||||
([`trace_screen.py:637-639`](../../meshterm/ui/trace_screen.py#L637)). It opens without a
|
||||
request from the user, after a trace that sets a record. `Close` is the default. Thus an
|
||||
Enter that the user pressed by mistake closes the dialog and does not open another screen.
|
||||
This is the opposite of the usual rule, and it is correct for a dialog that the user did
|
||||
not ask for.
|
||||
|
||||
Both are good designs. The gap is that CLAUDE.md describes only the confirm, so a future
|
||||
dialog of either shape has nothing to copy.
|
||||
Both designs are good. The gap is that CLAUDE.md describes only the confirm. Thus a future
|
||||
dialog of one of these two shapes has no example to copy.
|
||||
|
||||
### 2.5 Two full screens have no F-key lane of their own — one of them is Trace
|
||||
### 2.5 Two full screens have no F-key lane of their own, and one of them is Trace
|
||||
|
||||
Twelve classes declare `floating = False` (they are full screens, not dialogs). Ten override
|
||||
`fkey_lane`. Two do not, and so inherit `Screen.fkey_lane` → the raw `DEFAULT_LANE`
|
||||
([`fkeys.py:116`](../../meshterm/ui/tui/fkeys.py#L116)):
|
||||
Twelve classes declare `floating = False`. These are full screens and not dialogs. Ten of
|
||||
them override `fkey_lane`. Two do not override it. They use `Screen.fkey_lane`, which gives
|
||||
the raw `DEFAULT_LANE` ([`fkeys.py:116`](../../meshterm/ui/tui/fkeys.py#L116)):
|
||||
|
||||
| Screen | |
|
||||
|---|---|
|
||||
| [`TraceScreen`](../../meshterm/ui/trace_screen.py#L345) (`trace_screen.py:345`) | the app's busiest screen |
|
||||
| [`TraceScreen`](../../meshterm/ui/trace_screen.py#L345) (`trace_screen.py:345`) | the busiest screen of the app |
|
||||
| [`TxSweepScreen`](../../meshterm/ui/tx_screen.py#L93) (`tx_screen.py:93`) | |
|
||||
|
||||
`DEFAULT_LANE` is the *always-enabled* pager. The ten others build from
|
||||
`default_lane(nav=self.content_overflows)` ([`fkeys.py:131`](../../meshterm/ui/tui/fkeys.py#L131)),
|
||||
which dims the chips when the body already fits. So on PicoCalc both screens advertise live
|
||||
`Page ↑` / `Page ↓` chips whether or not there is anything to page — the one thing the lane's
|
||||
standing rule forbids.
|
||||
`DEFAULT_LANE` is the pager that is *always enabled*. The other ten screens build their lane
|
||||
from `default_lane(nav=self.content_overflows)`
|
||||
([`fkeys.py:131`](../../meshterm/ui/tui/fkeys.py#L131)). This dims the chips when the body
|
||||
fits on the screen. Thus on the PicoCalc, both screens show live `Page ↑` and `Page ↓`
|
||||
chips, also when there is nothing to page. The standing rule of the lane forbids exactly
|
||||
this.
|
||||
|
||||
Trace is the one worth a proper look rather than a one-liner. Its footer
|
||||
Trace needs a proper examination, and not only a one-line change. Its footer
|
||||
([`trace_screen.py:498-505`](../../meshterm/ui/trace_screen.py#L498)) reads
|
||||
`↑↓ actions · Enter run · PgUp/PgDn scroll · Esc back` — every key physical on the PicoCalc, so
|
||||
nothing is *undiscoverable*. But it is the screen where F1–F3 would earn their keep most
|
||||
(re-run, flip the path, open the composer are all one cursor move away today), and it is
|
||||
currently the only central screen that hasn't been through the lane pass. Worth deciding what
|
||||
its three free slots should say, not just gating the pager.
|
||||
`↑↓ actions · Enter run · PgUp/PgDn scroll · Esc back`. Each of these keys is a physical key
|
||||
on the PicoCalc, so no function is *hidden*. But Trace is the screen where F1 to F3 are most
|
||||
useful. To run again, to flip the path, and to open the composer, the user now needs only
|
||||
one move of the highlight. Trace is also the only central screen that has not had the lane
|
||||
pass. We must decide what its three free slots say, and not only gate the pager.
|
||||
|
||||
`TxSweepScreen` genuinely is one line: `return default_lane(nav=self.content_overflows)`.
|
||||
`TxSweepScreen` really is one line: `return default_lane(nav=self.content_overflows)`.
|
||||
|
||||
(The About page reads as an exception in a grep but isn't: it passes `floating=False` as a
|
||||
constructor argument rather than a class attribute, and it does define its own lane at
|
||||
[`about.py:115`](../../meshterm/ui/about.py#L115).)
|
||||
(The About page looks like an exception in a grep, but it is not. It gives `floating=False`
|
||||
as an argument of the constructor and not as a class attribute. It does define its own lane
|
||||
at [`about.py:115`](../../meshterm/ui/about.py#L115).)
|
||||
|
||||
### 2.6 Dialogs that scroll but declare no lane
|
||||
|
||||
CLAUDE.md says `EMPTY_LANE` is for "a screen with none at all (**every dialog**)". Five
|
||||
surfaces set it explicitly — the confirms, the text prompt, the busy spinner, the reorder
|
||||
list, one trace dialog. But two floating dialogs that genuinely *do* page inherit
|
||||
CLAUDE.md says that `EMPTY_LANE` is for "a screen with none at all (**every dialog**)". Five
|
||||
surfaces set it explicitly: the confirms, the text prompt, the busy spinner, the reorder
|
||||
list, and one trace dialog. But two floating dialogs that really *do* page use
|
||||
`DEFAULT_LANE` instead:
|
||||
|
||||
- `RecordDialog` ([`records_screen.py:146`](../../meshterm/ui/records_screen.py#L146)) — a record's
|
||||
story, which scrolls
|
||||
- `PathComposerScreen` ([`path_composer.py`](../../meshterm/ui/path_composer.py)) — a windowed
|
||||
list of hop suggestions
|
||||
- `RecordDialog` ([`records_screen.py:146`](../../meshterm/ui/records_screen.py#L146)). It
|
||||
shows the story of a record, and it scrolls.
|
||||
- `PathComposerScreen` ([`path_composer.py`](../../meshterm/ui/path_composer.py)). It is a
|
||||
list of hop suggestions, which shows a window of the list.
|
||||
|
||||
Both arguably *want* the pager, which would make the doc's "every dialog" too strong. The
|
||||
question is whether the rule is "dialogs get no lane" (then these two need `EMPTY_LANE` and
|
||||
lose paging chips) or "a surface gets the pager iff it pages" (then the doc's parenthetical
|
||||
should read *every dialog that doesn't scroll*).
|
||||
It is possible that both *need* the pager. If so, "every dialog" in the document is too
|
||||
strong. We must decide between two rules. The first rule is "dialogs get no lane". Then
|
||||
these two dialogs need `EMPTY_LANE`, and they lose their paging chips. The second rule is
|
||||
"a surface gets the pager if and only if it pages". Then the parenthetical text in the
|
||||
document must say *every dialog that does not scroll*.
|
||||
|
||||
### 2.7 One row label that may want an ellipsis
|
||||
### 2.7 One row label that possibly needs an ellipsis
|
||||
|
||||
[`config_editor.py:787`](../../meshterm/ui/config_editor.py#L787) — `Share QR / URI`. The rule is
|
||||
"a row that opens further prompts ends with `…`". This one opens a *view* (the QR popup), not
|
||||
a prompt — so it turns on whether "further prompts" means "any further surface". Every other
|
||||
row in the app is unambiguous; this is the only edge.
|
||||
This is at [`config_editor.py:787`](../../meshterm/ui/config_editor.py#L787), and the label
|
||||
is `Share QR / URI`. The rule is "a row that opens further prompts ends with `…`". This row
|
||||
opens a *view* (the QR dialog), and it does not open a prompt. Thus the answer depends on the
|
||||
meaning of "further prompts": is it "each further surface"? Each other row in the app is
|
||||
clear. This is the only boundary case.
|
||||
|
||||
Also worth a glance while there: `Share QR / URI` is the only row label in the app using ` / `
|
||||
as a separator.
|
||||
Also note: `Share QR / URI` is the only row label in the app that uses ` / ` as a separator.
|
||||
|
||||
### 2.8 `★` carries two meanings
|
||||
### 2.8 `★` has two meanings
|
||||
|
||||
CLAUDE.md itself lists it twice: `★ you (yellow)` under node types, and `★ best/winner` under
|
||||
concept icons. In practice both are live — the map and every path line draw `★` for our own
|
||||
node ([`pathline.SELF_GLYPH`](../../meshterm/ui/pathline.py)), while the trophy case and the
|
||||
new-record dialog draw `★` in accent for a record
|
||||
CLAUDE.md itself lists it two times: `★ you (yellow)` under node types, and `★ best/winner`
|
||||
under concept icons. In practice, both are in use. The map and each path line draw `★` for
|
||||
our node ([`pathline.SELF_GLYPH`](../../meshterm/ui/pathline.py)). The trophy case and
|
||||
the dialog of a new record draw `★` in the accent colour for a record
|
||||
([`trace_screen.py:635-636`](../../meshterm/ui/trace_screen.py#L635)).
|
||||
|
||||
They are never on the same surface, and the colours differ (yellow vs accent), so nothing is
|
||||
actually ambiguous today. Flagged only because it is the single glyph in the whole marks table
|
||||
with two entries, and the "one glyph per concept" heading promises otherwise. If a record ever
|
||||
needs to appear next to a path line, this is where it bites — `🏆` is already reserved for the
|
||||
trophy case and would carry it.
|
||||
The two uses are never on the same screen, and the colours are different (yellow and
|
||||
accent). Thus nothing is unclear today. We note it only because it is the single glyph in
|
||||
the whole table of marks that has two entries, and the heading "one glyph per concept" says
|
||||
that this does not occur. If a record must appear next to a path line in the future, this is
|
||||
where a problem will occur. `🏆` is already reserved for the trophy case, and it can carry
|
||||
the record.
|
||||
|
||||
### 2.9 `node.unknown` vs `muted`, unverified
|
||||
### 2.9 `node.unknown` and `muted`, not verified
|
||||
|
||||
CLAUDE.md is emphatic that an unidentified node takes `node.unknown` and that this is
|
||||
CLAUDE.md is clear that an unidentified node takes `node.unknown`, and that this is
|
||||
*deliberately not* `muted` ("muted is chrome and may sit a step darker, while an unidentified
|
||||
node is content you can still act on"). `node.unknown` appears at 11 sites. There are also 26
|
||||
`style="muted"` uses inside [`pathline.py`](../../meshterm/ui/pathline.py) and
|
||||
[`widgets.py`](../../meshterm/ui/widgets.py) — the two modules that draw nodes.
|
||||
node is content you can still act on"). `node.unknown` is used at 11 sites. There are also 26
|
||||
uses of `style="muted"` in [`pathline.py`](../../meshterm/ui/pathline.py) and
|
||||
[`widgets.py`](../../meshterm/ui/widgets.py). These are the two modules that draw nodes.
|
||||
|
||||
Most of those 26 are certainly chrome (separators, labels, the un-lit part of a key, which the
|
||||
standard explicitly says stays muted). But this is the one rule in the standard that a
|
||||
mechanical sweep cannot settle: it needs a human to look at each and say "that's chrome" or
|
||||
"that's an unnamed node". Listed here as the one unaudited corner, not as a finding.
|
||||
Most of these 26 uses are certainly chrome (separators, labels, and the part of a key that
|
||||
is not lit, which the standard says must stay muted). But a program cannot decide this rule
|
||||
in a sweep. A person must look at each use and decide: "this is chrome" or "this is a node
|
||||
without a name". We list it here as the one corner that we did not audit. It is not a
|
||||
finding.
|
||||
|
||||
---
|
||||
|
||||
## Part 3 — shortlist
|
||||
## Part 3: shortlist
|
||||
|
||||
| # | Finding | Sites | Effort | Weight |
|
||||
|---|---|---|---|---|
|
||||
| 2.1 | Empty states shout on four screens | 4 (6 strings) | trivial | **high** — it is the whole screen |
|
||||
| 2.2 | Location dialog has no Cancel and defaults to an action | 1 | small | medium |
|
||||
| 2.5 | Trace and TX advertise a dead pager on PicoCalc; Trace has never had a lane pass | 2 | one line + one design pass | **medium-high** |
|
||||
| 2.3 | Restore dialog defaults to the middle button | 1 | decision, not code | low |
|
||||
| 2.6 | Two scrolling dialogs inherit the pager the doc says they shouldn't have | 2 | decision | low |
|
||||
| 2.4 | Two dialog shapes used consistently but undocumented | — | doc only | low |
|
||||
| 2.7 | `Share QR / URI` — ellipsis? separator? | 1 | trivial | low |
|
||||
| 2.8 | `★` has two entries in a "one glyph per concept" table | — | doc only | low |
|
||||
| 2.9 | `node.unknown` vs `muted` in the two node-drawing modules | ~26 to eyeball | manual | unknown |
|
||||
| 2.1 | Empty states shout on four screens | 4 (6 strings) | trivial | **high**, because it is the whole screen |
|
||||
| 2.2 | The Location dialog has no Cancel and its default is an action | 1 | small | medium |
|
||||
| 2.5 | Trace and TX advertise a dead pager on the PicoCalc. Trace has never had a lane pass | 2 | one line and one design pass | **medium-high** |
|
||||
| 2.3 | The Restore dialog defaults to the middle button | 1 | a decision, not code | low |
|
||||
| 2.6 | Two scrolling dialogs inherit the pager that the document says they must not have | 2 | a decision | low |
|
||||
| 2.4 | Two dialog shapes that the app uses consistently but that no document describes | none | document only | low |
|
||||
| 2.7 | `Share QR / URI`: an ellipsis or a different separator? | 1 | trivial | low |
|
||||
| 2.8 | `★` has two entries in a table of "one glyph per concept" | none | document only | low |
|
||||
| 2.9 | `node.unknown` and `muted` in the two modules that draw nodes | approximately 26 to examine | manual | unknown |
|
||||
|
||||
Items 2.1, 2.2, 2.5 and 2.7 are code. The rest are decisions about the standard itself, and
|
||||
2.3, 2.4 and 2.8 may well resolve as "the standard should say what the code already does".
|
||||
Items 2.1, 2.2, 2.5, and 2.7 are code. The other items are decisions about the standard
|
||||
itself. For items 2.3, 2.4, and 2.8, the decision can be "the standard must say what the code
|
||||
already does".
|
||||
+36
-32
@@ -1,15 +1,15 @@
|
||||
[build-system]
|
||||
# 1.26 is where hatchling started writing the PEP 639 fields below (`License-Expression`,
|
||||
# `License-File`) into the metadata; an older one builds a wheel that says nothing about
|
||||
# the licence at all, silently.
|
||||
# Hatchling 1.26 is the first version that writes the PEP 639 fields below
|
||||
# (`License-Expression`, `License-File`) into the metadata. An older version builds a
|
||||
# wheel that says nothing about the licence, and it gives no warning.
|
||||
requires = ["hatchling>=1.26"]
|
||||
build-backend = "hatchling.build"
|
||||
|
||||
[project]
|
||||
# The distribution name only. The import package and the console script both stay
|
||||
# `meshterm` — PyPI prohibits that name (see pypi/support#12162), and nothing about the
|
||||
# app changes because of it. `[tool.hatch.build.targets.wheel]` below is what keeps the
|
||||
# wheel shipping the `meshterm/` package under this different name.
|
||||
# This is only the name of the distribution. The import package and the console script stay
|
||||
# `meshterm`. PyPI prohibits that name (refer to pypi/support#12162), but the app does not
|
||||
# change because of this. The `[tool.hatch.build.targets.wheel]` section below makes the
|
||||
# wheel contain the `meshterm/` package under this different name.
|
||||
name = "mesh-term"
|
||||
dynamic = ["version"]
|
||||
description = "MeshTerm is a full-featured TUI MeshCore client for your terminal. Connects to a companion over USB, Bluetooth, or TCP. For Windows, macOS, and Linux."
|
||||
@@ -19,7 +19,7 @@ authors = [{ name = "Jean-Pierre Martineau" }]
|
||||
license = "Apache-2.0"
|
||||
license-files = ["LICENSE", "NOTICE"]
|
||||
keywords = ["meshcore", "lora", "mesh", "radio", "cli"]
|
||||
# No `License ::` entry: PEP 639 forbids one alongside the SPDX expression above.
|
||||
# There is no `License ::` entry, because PEP 639 does not permit one with the SPDX expression above.
|
||||
classifiers = [
|
||||
"Development Status :: 4 - Beta",
|
||||
"Environment :: Console",
|
||||
@@ -42,7 +42,7 @@ dependencies = [
|
||||
"pycryptodome>=3.20", # AES/HMAC for decrypting overheard channel packets
|
||||
"pyserial>=3.5", # serial-port enumeration for device discovery
|
||||
"bleak>=0.22", # Bluetooth LE scanning + companion connection
|
||||
"typer>=0.12", # scripted CLI; subcommands mirror the menu
|
||||
"typer>=0.12", # scripted CLI. The subcommands mirror the menu
|
||||
"rich>=13.7", # modern console rendering (progress, tables, panels)
|
||||
"markdown-it-py>=3.0", # CommonMark parser behind the written About pages
|
||||
"prompt_toolkit>=3.0", # full-screen interactive TUI (terminal, input, resize)
|
||||
@@ -55,16 +55,17 @@ dependencies = [
|
||||
dev = [
|
||||
"pytest>=8.0",
|
||||
"pytest-asyncio>=0.23",
|
||||
# Pinned to a minor line, not floated. A formatter that drifts between machines is
|
||||
# not a gate: 0.16 formats Python inside Markdown and 0.15 does not, so an
|
||||
# unpinned `ruff>=0.4` had CI and a laptop disagreeing about a clean tree.
|
||||
# The version is pinned to one minor line. A formatter that is different on different
|
||||
# machines is not a gate. Ruff 0.16 formats Python inside Markdown and 0.15 does not.
|
||||
# With an unpinned `ruff>=0.4`, CI and a laptop disagreed about a tree with no errors.
|
||||
"ruff>=0.16.6,<0.17",
|
||||
# The SPI radio's node, run for real in tests/test_radionode_live.py on a fake radio.
|
||||
# The bare library is pure Python everywhere; only the `hardware` extra is Linux-bound.
|
||||
# The node of the SPI radio. tests/test_radionode_live.py runs it with a fake radio.
|
||||
# The library alone is pure Python on all systems. Only the `hardware` extra needs Linux.
|
||||
"openhop-core>=1.1.3",
|
||||
]
|
||||
# A LoRa chip on this machine's own SPI bus (the uConsole AIO), driven by a node MeshTerm
|
||||
# runs itself — see meshterm/core/spiradio.py. Linux only, and nowhere else worth carrying.
|
||||
# A LoRa chip on this machine's own SPI bus (the uConsole AIO). A node that MeshTerm runs
|
||||
# itself controls it (refer to meshterm/core/spiradio.py). It works only on Linux, so
|
||||
# the other systems do not need it.
|
||||
spi = [
|
||||
"openhop-core[hardware]>=1.1.3",
|
||||
]
|
||||
@@ -78,20 +79,21 @@ Changelog = "https://github.com/jpmartineau/MeshTerm/blob/main/CHANGELOG.md"
|
||||
[project.scripts]
|
||||
meshterm = "meshterm.cli:main"
|
||||
|
||||
# The one place a version is written; `[project] version` is dynamic so the package
|
||||
# attribute and the distribution metadata cannot drift apart.
|
||||
# This is the one place where we write the version. `[project] version` is dynamic, so the
|
||||
# package attribute and the distribution metadata cannot become different.
|
||||
[tool.hatch.version]
|
||||
path = "meshterm/__init__.py"
|
||||
|
||||
[tool.hatch.build.targets.wheel]
|
||||
packages = ["meshterm"]
|
||||
|
||||
# The sdist is the package's source, not the repository's: what someone needs to build
|
||||
# and test the wheel. Hatchling's default is everything git does not ignore, which swept
|
||||
# in the Claude skills, the GitHub configuration, the PyInstaller spec, the device-side
|
||||
# shell scripts (one of which carries data under another licence) and the developer
|
||||
# environment template — none of which builds, tests, or documents the package. Kept:
|
||||
# the package, the tests and their fixture, the manual, and the files a build reads.
|
||||
# The sdist is the source of the package. It is not the source of the repository. It has
|
||||
# what a person needs to build and test the wheel. By default, Hatchling includes each file
|
||||
# that git does not ignore. This included the Claude skills, the GitHub configuration, the
|
||||
# PyInstaller spec, the shell scripts for the device (one of which has data under another
|
||||
# licence), and the template of the developer environment. None of these builds, tests, or
|
||||
# documents the package. The sdist keeps the package, the tests and their fixture, the
|
||||
# manual, and the files that a build reads.
|
||||
[tool.hatch.build.targets.sdist]
|
||||
exclude = [
|
||||
"/.claude",
|
||||
@@ -108,23 +110,25 @@ exclude = [
|
||||
[tool.ruff]
|
||||
line-length = 100
|
||||
target-version = "py310"
|
||||
# Helper scripts for the Claude skills; they are not part of the package. And `docs/`,
|
||||
# which holds dated survey documents: ruff formats Python blocks inside Markdown, and the
|
||||
# code they quote is an excerpt of how a file looked on a past date. Reformatting a
|
||||
# quotation edits the record — and the excerpts are call fragments, not whole programs, so
|
||||
# the formatter's idea of "fixed" turns them into something that was never there.
|
||||
# The helper scripts for the Claude skills are not part of the package. Also exclude `docs/`.
|
||||
# It has survey documents with dates. Ruff formats Python blocks inside Markdown, and the
|
||||
# code in these documents is an excerpt that shows how a file looked on a past date. If
|
||||
# ruff formats a quotation, it changes the record. The excerpts are fragments of calls and
|
||||
# not complete programs. Thus the formatter would change them into something that never
|
||||
# existed.
|
||||
extend-exclude = [".claude", "docs"]
|
||||
|
||||
[tool.ruff.lint]
|
||||
select = ["E", "F", "I", "UP", "B", "D"]
|
||||
ignore = ["D203", "D213"] # pick one blank-line-before-class / multi-line-summary style
|
||||
ignore = ["D203", "D213"] # select one style for the blank line before a class and for the multi-line summary
|
||||
|
||||
[tool.ruff.lint.pydocstyle]
|
||||
convention = "google"
|
||||
|
||||
[tool.ruff.lint.flake8-bugbear]
|
||||
# Typer declares CLI parameters by calling these in the default; the objects they
|
||||
# return are inert sentinels the command never mutates, which is what B008 guards.
|
||||
# Typer declares CLI parameters when the default value calls these functions. The objects
|
||||
# that they return are inert markers, and the command never changes them. B008 guards
|
||||
# against this kind of change.
|
||||
extend-immutable-calls = ["typer.Argument", "typer.Option"]
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
|
||||
+28
-24
@@ -1,42 +1,46 @@
|
||||
# scripts/
|
||||
|
||||
Repo-side tools. None of these ship in the wheel.
|
||||
These are tools for the repository. None of them is in the wheel.
|
||||
|
||||
| Script | What it is for |
|
||||
|---|---|
|
||||
| `basemap-doctor.py` | Why a user's map has no basemap — certificates, DNS, proxy, or colour depth |
|
||||
| `cardputer-zero/` | `launcher-entry.sh` puts a source checkout on the Cardputer Zero's app launcher (and `remove` takes it off), until there is a store package; `meshterm.png` is its icon, the website's logo at the launcher's 100×100. `type-text.py` types a pasted string (an API key, a Wi-Fi password) on the Cardputer's keyboard from another computer, over SSH |
|
||||
| `picocalc-lyra/` | Calculinux device setup: console font, palette, Wi-Fi; `xiao-radio/` is the XIAO radio kit (firmware build, flashing, UART setup) |
|
||||
| `uconsole/` | The uConsole's always-on SPI bridge service (MeshTerm can also drive that radio directly, with no script at all — see `docs/devices/uconsole.md`) |
|
||||
| `basemap-doctor.py` | Finds why a user's map has no basemap: certificates, DNS, proxy, or colour depth |
|
||||
| `cardputer-zero/` | `launcher-entry.sh` puts a source checkout in the Cardputer Zero's app launcher. `remove` takes it out. This is for the time before there is a store package. `meshterm.png` is the icon: the website's logo, at the launcher's 100×100 size. `type-text.py` types a pasted string (an API key, a Wi-Fi password) on the Cardputer's keyboard from another computer, over SSH |
|
||||
| `picocalc-lyra/` | Setup of the Calculinux device: console font, palette, Wi-Fi. `xiao-radio/` is the kit for the XIAO radio (firmware build, flashing, UART setup) |
|
||||
| `uconsole/` | The uConsole's always-on SPI bridge service. MeshTerm can also control that radio directly, with no script (refer to `docs/devices/uconsole.md`) |
|
||||
|
||||
## basemap-doctor.py
|
||||
|
||||
The map is the only networked part of MeshTerm, and every fetch failure on that path is
|
||||
logged at DEBUG while `log_level` defaults to WARNING — so a reader whose tiles never
|
||||
arrive sees a blank ground and no explanation. This prints the explanation and the fix.
|
||||
The map is the only part of MeshTerm that uses the network. MeshTerm logs each fetch
|
||||
failure on this path at DEBUG, and `log_level` is WARNING by default. Thus a user whose
|
||||
tiles do not arrive sees a blank ground and no explanation. This script prints the
|
||||
explanation and the correction.
|
||||
|
||||
Send a user this, to run **on the machine with the problem**:
|
||||
To use it, tell the user to run this **on the machine that has the problem**:
|
||||
|
||||
```bash
|
||||
curl -fsSL https://raw.githubusercontent.com/jpmartineau/MeshTerm/main/scripts/basemap-doctor.py -o /tmp/basemap-doctor.py
|
||||
python3 /tmp/basemap-doctor.py
|
||||
```
|
||||
|
||||
Any `python3` will do: if that one has no MeshTerm, the script reads the interpreter out
|
||||
of the installed `meshterm` command's shebang and hands itself over, so the answer always
|
||||
comes from **the app's own Python** — a Mac routinely has three and only that one has the
|
||||
trust store that matters. Save it to a file as above rather than piping into `python3 -`:
|
||||
with no file on disk there is nothing to hand over.
|
||||
Any `python3` is correct. If that `python3` does not have MeshTerm, the script reads the
|
||||
interpreter from the installed `meshterm` command's shebang, and it runs again with
|
||||
that interpreter. Thus the answer always comes from **the app's own Python**. A Mac
|
||||
often has three Pythons, and only one of them has the trust store that is important. Save
|
||||
the script to a file, as in the example. Do not pipe it into `python3 -`. When there is no
|
||||
file on the disk, the script cannot run again with the other interpreter.
|
||||
|
||||
Have them paste the whole output back. It names the interpreter, the certificate store,
|
||||
the tile source, the cached-tile count, `COLORTERM`, and then one verdict:
|
||||
Ask the user to paste all the output back to you. The output names the interpreter, the
|
||||
certificate store, the tile source, the number of cached tiles, and `COLORTERM`. Then it
|
||||
gives one verdict:
|
||||
|
||||
- **curl reached it, Python did not** — the Python's certificate store is empty. A
|
||||
python.org install on macOS ships that way until `Install Certificates.command` is run.
|
||||
- **neither reached it** — the network path: a firewall, a proxy, a DNS filter, or an
|
||||
outage. Nothing to fix in MeshTerm.
|
||||
- **both reached it** — tiles are fine and the complaint is colour: an unset `COLORTERM`
|
||||
means 256 colours, where the basemap's greens and blues collide. Terminal.app cannot do
|
||||
24-bit colour at all.
|
||||
- **curl reached it, Python did not.** The Python's certificate store is empty. A
|
||||
python.org install on macOS has an empty store until the user runs
|
||||
`Install Certificates.command`.
|
||||
- **neither reached it.** The problem is in the network path: a firewall, a proxy, a DNS
|
||||
filter, or an outage. There is nothing to correct in MeshTerm.
|
||||
- **both reached it.** The tiles are correct. The problem is colour. When `COLORTERM` is not
|
||||
set, the terminal uses 256 colours, and the greens and blues of the basemap are too
|
||||
similar. Terminal.app cannot do 24-bit colour.
|
||||
|
||||
Exit status is 0 when tiles arrive and 1 when they do not, so it can be scripted.
|
||||
The exit status is 0 when the tiles arrive and 1 when they do not. Thus a script can use it.
|
||||
@@ -1,19 +1,20 @@
|
||||
# PicoCalc mesh radio — XIAO nRF52840 over UART
|
||||
# PicoCalc mesh radio: XIAO nRF52840 over UART
|
||||
|
||||
A Seeed XIAO nRF52840 + Wio-SX1262 wired to the Luckfox Lyra's UART1, running MeshCore
|
||||
companion firmware on `Serial1`, so MeshTerm on the PicoCalc opens `/dev/ttyS1` like any
|
||||
other serial companion. No BLE, no USB host, no SD-slot sacrifice.
|
||||
This is a Seeed XIAO nRF52840 with a Wio-SX1262. It is wired to UART1 of the Luckfox Lyra,
|
||||
and it runs the MeshCore companion firmware on `Serial1`. Thus MeshTerm on the PicoCalc
|
||||
opens `/dev/ttyS1` in the same way as for each other serial companion. This design does not
|
||||
use BLE, does not use a USB host, and does not use the SD slot.
|
||||
|
||||
**The step-by-step guide is [`docs/devices/picocalc-lyra.md`, Phase 3](../../../docs/devices/picocalc-lyra.md#phase-3--add-the-radio)** — parts,
|
||||
the firmware build and the patch explained, flashing, wiring, the Lyra setup, and what has
|
||||
only been proven on one bench. What the register poke does is in `uart1-mux.py`'s own
|
||||
header. This page is the bench card.
|
||||
**The step-by-step guide is [`docs/devices/picocalc-lyra.md`, Phase 3](../../../docs/devices/picocalc-lyra.md#phase-3-add-the-radio).**
|
||||
It has the parts, the firmware build and an explanation of the patch, the flashing, the
|
||||
wiring, the setup of the Lyra, and what we proved on only one bench. The header of
|
||||
`uart1-mux.py` explains what the register write does. This page is the bench card.
|
||||
|
||||
**Licensing.** The firmware is MeshCore, under the MIT License. The `meshcore-uart1.patch`
|
||||
modifies MeshCore source and `build-firmware.sh` produces a MeshCore binary; the licence is
|
||||
[`LICENSE.MeshCore`](LICENSE.MeshCore) in this directory.
|
||||
changes the source of MeshCore, and `build-firmware.sh` makes a MeshCore binary. The licence
|
||||
is [`LICENSE.MeshCore`](LICENSE.MeshCore) in this directory.
|
||||
|
||||
Three steps on two machines:
|
||||
There are three steps on two machines:
|
||||
|
||||
```bash
|
||||
# 1. dev machine: build (needs git + PlatformIO)
|
||||
@@ -29,7 +30,7 @@ sh scripts/picocalc-lyra/xiao-radio/lyra-setup.sh # MT_USER=meshterm by
|
||||
meshterm
|
||||
```
|
||||
|
||||
Four wires, TX and RX crossed:
|
||||
There are four wires. Cross TX and RX:
|
||||
|
||||
| XIAO pad | → | Lyra header | Physical pin | Carries |
|
||||
| --- | --- | --- | --- | --- |
|
||||
@@ -38,6 +39,7 @@ Four wires, TX and RX crossed:
|
||||
| **GND** | → | **GND** | pin **8** | common ground |
|
||||
| **3V3** | → | **3V3 OUT** | pin **36** | power, so it runs without USB |
|
||||
|
||||
> **The Lyra does not use Raspberry Pi Pico GPIO numbering** — the header is Pico-shaped, the
|
||||
> mapping is Luckfox's own. GP4 is `gpio0-0`, GP5 is `gpio0-1`. All signals are 3.3 V and
|
||||
> there is **no 5 V rail**: power the XIAO from 3V3 only.
|
||||
> **The Lyra does not use the GPIO numbers of the Raspberry Pi Pico.** The header has the
|
||||
> shape of a Pico header, but Luckfox made its own mapping. GP4 is `gpio0-0`, and GP5 is
|
||||
> `gpio0-1`. All signals are 3.3 V, and there is **no 5 V rail**. Connect the power of the
|
||||
> XIAO to 3V3 only.
|
||||
Reference in new issue
Block a user