Skip to content

About

Android App to work with Reticulum over LXMF and compatible with LaBuche-Stump

Topics

Resources

Stars

4 stars

Watchers

0 watching

Forks

Repository files navigation

FireFly for Android

A LoRa-first Reticulum messenger for Android, built for the Stump network. It speaks standard LXMF (so it talks to Sideband, NomadNet, MeshChat and FireFly handhelds) and understands Stump nodes: rooms, DMs, /auth, over the mesh and over the node's Wi-Fi, with one identity.

Android Demo

The radio is an RNode on Bluetooth LE or USB, found and attached at runtime. Both run through Reticulum's own RNode driver.

Status: 0.5.1: verified on every Stump, the billboard over LoRa (for Stump 1.0.0-beta.43 to beta.46: checked against beta.46). Built and field-tested: BLE and USB RNodes, LoRa messaging with the R36MAX handheld, Stump /auth over LoRa, Opus voice notes everywhere, and delivery with the screen locked. Betas are made in Sandbox_FireFly_Android, alongside the Stump beta (SandBox_Stump_On).

0.5.1: verified on every Stump, the billboard over LoRa

Everything from the nine betas below, in short:

  • Verified before you go in: opening a Stump waits behind "Verifying you with …" until it has FireFly's signature, over Wi-Fi (/hello) and now over LoRa too (/auth, sent by itself); a failure says why, with Try again, Go in anyway and Back. A Stump that restarts (its boot id, Stump 1.0.0-beta.43) is verified again by itself.
  • The billboard over LoRa: read the newest notices, open one, post one (/board, /post, Stump 1.0.0-beta.38); answers stay in the Board tab, in any order, even late.
  • A new Stump screen: the room on the left half, #room (topic) scrolling; private messages, billboard and files as icons, filled when something's new; what isn't available here is dimmed and says why ("N/A over LoRa", "N/A without Wi-Fi", " is offline").
  • Stumps follow the Wi-Fi: joining a Wi-Fi finds your Stumps on it by itself; the billboard and files come from their Wi-Fi side as soon as it answers. A filled stump icon for those that answer; pull down to refresh.
  • The radio channel is confirmed by the RNode: FireFly is online only when the RNode reports the frequency, bandwidth and SF asked, and says so in Settings ("The RNode confirmed 907.000 MHz") or says what it reports.
  • No double taps: room buttons, refresh, Use and Search now wait while they're working ("joining…", "refreshing…", "Searching…").

0.5.1-beta.9: a Stump that restarts (Stump 1.0.0-beta.43)

  • The beacon's caps carry b, an id for each boot. Caps keys FireFly doesn't know are still ignored; a beacon without b (older Stumps) changes nothing.
  • A new b means the Stump restarted and forgot who proved who they are: FireFly stops showing it as "verified" and, if you'd entered it this run, verifies again by itself, quietly (no popup, no lines in the room). A Stump you haven't entered is sent nothing.
  • Its chain (/chain.json) is asked again 5 s and 20 s after a restart, as the Stump refills it within seconds.

0.5.1-beta.8: verified before you go in

  • Entering a Stump now waits behind a "Verifying you with …" popup until the Stump has FireFly's signature, then the room opens by itself. Over Wi-Fi that's the /hello FireFly already sends when the Stump appears (a second or two); over LoRa FireFly now sends /auth itself on entering (about 10 s, up to a minute and a half) — before, LoRa waited for a tap on "Verify me". Quiet: no AUTH- lines in the room.
  • Already verified this run: no popup. A Stump without /auth: nothing to prove, straight in. A refusal, no answer, no radio or an unreachable Stump says why, with Try again, Go in anyway (reading works; writing may be refused) and Back. A failure never retries on its own while the popup is up. Stumps only heard over LoRa aren't sent anything (no airtime spent).
  • Tests: run_verify_gate (Wi-Fi /hello, LoRa challenge and AUTH-OK, refusal and Try again, timeout, no /auth, no radio).

0.5.1-beta.7: a frequency change is only "done" when the RNode says so

  • Reported with an RF sniffer: set to 907 MHz, the signal stayed on 915. Reticulum's RNode driver calls a radio set up without the RNode confirming its frequency (it skips that one check while no report has come back). FireFly now waits for the RNode's own report of its frequency, bandwidth and SF, and goes online only when they match what was asked; it asks once more when no report comes. An RNode that stays on another frequency is refused with what it reports ("the RNode reports 915.000 MHz, not the 907.000 MHz asked: it didn't change"); one that never reports is refused too.
  • Settings › Radio channel waits for the RNode: "Saved · retuning the RNode…", then "The RNode confirmed 907.000 MHz", or its own answer, or "no RNode connected". The on-air line and the Network tab's channel say "confirmed by the RNode" (✓) when the radio's report matches.
  • What FireFly can confirm is what the RNode reports; an RF sniffer remains the proof on air.

0.5.1-beta.6: LoRa billboard answers stay in the Board tab (field fix)

  • Reported: after a long post over LoRa, the answer to opening a notice (#568944 with its title and text) showed up in the room. It came back after FireFly had stopped waiting (2 min), or while it waited for another answer. Now each request (the list, each notice, a post) is waited for on its own, and answers are recognised by their shape, in any order, up to 30 minutes late. "No answer yet" still shows after 2 minutes.
  • The post form shows its size on air (one packet, or several through a link): a long notice takes a while over LoRa.
  • Room buttons and refresh wait, and show it. Tapping a room: it reads #room … and blinks until the Stump has moved you there (or refused), and the other rooms wait meanwhile. "refresh" reads "refreshing…" until the Stump's room list is in. Over LoRa they wait longer (up to 90 s). The billboard's and files' search doesn't start a second fetch while one runs.

0.5.1-beta.5: the billboard over LoRa (Stump 1.0.0-beta.38)

  • A LoRa Board tab. Over LoRa only, the billboard icon now opens: the newest notices' titles (/board, at most 5; search with words), a notice's text when you open it (/board <id>), and Post over LoRa (/post, title 80, details 600). Each answer comes back over LoRa, so the tab says when it's asking, and asks nothing twice while an answer is on its way; no answer in 2 minutes says so. Files stay "N/A over LoRa".
  • Radio down is not "not sent". A Stump whose radio is down keeps a line for LoRa and sends it when it's back, saying so with a ⧗; FireFly shows that note and no longer marks the line "not sent (too fast)".
  • Forged Stump beacons are ignored: a stump.node beacon must name its own identity's LXMF address.
  • Bots aren't DM contacts: lines marked "bot": true (BarKeep, the plugins) leave their author out of "Message someone".

0.5.1-beta.4: one answer for "can I reach this Stump?"

  • The engine decides, the screen follows. Whether a Stump's billboard and files can be had, and why not, is worked out in one place for all of its sides (Wi-Fi and LoRa) and the phone's Wi-Fi: reachable on the Wi-Fi; N/A over LoRa (only LoRa reaches it: heard in the last 10 min, and the radio or a path can carry); N/A without Wi-Fi (a Wi-Fi-only Stump, the phone off Wi-Fi); " is offline" (nothing reaches it). A Stump that's simply down no longer says "N/A over LoRa".
  • A tap checks first: the underline goes to the icon while the Stump is asked (1.5 s); then it opens, or the right note shows and it goes back to the room. Taps while it's checking or showing a note are ignored.
  • No blinking: a change only counts once it has held (lighting up after 0.3 s, dimming after 1.5 s), so one missed poll doesn't flip the icons.
  • The Stumps tab: each Stump has a stump icon, filled when it answers (on the Wi-Fi, or heard over LoRa lately) and outlined when not. Pull down to refresh: every Stump is asked (Wi-Fi probed, LoRa asked for a path, which it answers with an announce); pulling again meanwhile does nothing.

0.5.1-beta.3: the billboard and files follow the Wi-Fi

  • When the phone joins (or changes) a Wi-Fi, FireFly waits until it has settled (3 s) and looks for your Stumps on it, wherever you are in the app: no search needed.
  • A Stump's screen opened over LoRa takes the billboard and files from the same Stump's Wi-Fi side as soon as it answers there: their icons light up by themselves, and dim again ("N/A over LoRa") when it stops.
  • The other way too: a Stump opened over Wi-Fi whose Wi-Fi goes (Wi-Fi off, out of range) dims its billboard and files. A tap on them checks that the Stump still answers first (the underline goes there meanwhile) and, if not, says "N/A over LoRa" and goes back to the room; being on the billboard or files when the Wi-Fi goes does the same.

0.5.1-beta.2: a new navigation on a Stump's screen

  • The room on the left half; icons on the right half for private messages, the billboard and files, sharing it equally. The same layout over Wi-Fi and LoRa: what the Stump doesn't offer here (the billboard and files over LoRa) is dimmed and can't be opened.
  • The room tab reads #room (topic), from the left; when it's longer than its half, it scrolls by itself so it can be read through. (The topic's own line above the chat is gone: it's in the tab.)
  • A dimmed tab says why: tapped over LoRa, the underline slides to it, stays 300 ms, "N/A over LoRa" shows for 600 ms, and it slides back to the room (on Wi-Fi, a Stump without a billboard or files says so instead). One underline now moves between the tabs.
  • Outline: nothing new. Filled: something new. The envelope fills when a private message is unread (no count). The billboard and files icons have the same two forms, ready for "new notices" and "new files" later.
  • The icons are drawn for FireFly (res/drawable/ic_tab_*.xml, MIT like the rest of the app); a screen reader names each tab.

0.5.1-beta.1

  • A Stump's private messages tab is an envelope, ✉ (0), instead of "Private (0)" (a screen reader still says "Private"); it turns pink when something is unread. The tabs are spaced by need: more room for the room's name, less for the envelope.

0.5.0: one person on a Stump, over LoRa and Wi-Fi

Everything from the ten betas below, in short:

  • The greeting: on a Stump's Wi-Fi FireFly says who it is by itself (/hello, both sides signing; /auth + /nick on older Stumps), so you are one person there, under your name, with your DMs reaching you over Wi-Fi or LoRa as you choose (Settings › Stump messages).
  • Stumps found by who they are, not where they are: Find Stumps asks the Wi-Fi (STUMP?), a Stump the router moved is followed to its new address with its history, and the gateway of a home Wi-Fi is no longer taken for a Stump.
  • No radio, no dead ends: a Stump reached over LoRa and Wi-Fi opens on the side that can reach it; real reasons instead of "-1" or a false "too fast"; Verify that can't succeed says why at once.
  • Private messages: one conversation per person across LoRa and Wi-Fi, renames followed, Delete and Block.
  • Screens: LoRa and Wi-Fi lights (green, red, grey; orange-green while connecting), a steady Stumps list, Stump over Wi-Fi in the Network tab, the RNode in use named, Use to switch RNodes, the version in Settings.
  • Stump 1.0.0-beta.19 to beta.33: room renames, hybrid and closed access, busy Stumps (503), upload limits, rescue starts.
  • From 0.4.1: local-network discovery on Android, message states that no longer get stuck.

0.5.0-beta.10: a steadier Stumps tab, two connection lights

  • The Stumps list keeps still: it's sorted by name. It was sorted by "last heard", so Stumps close to each other kept swapping places.
  • "Stump over Wi-Fi" (open a Stump by its address) moved from the Stumps tab to the Network tab.
  • Two lights at the top instead of the radio label: LoRa and Wi-Fi, each green (working), red (needs attention) or grey (not connected). LoRa: green with an RNode online; red when one is refused or the paired ones don't answer; grey when there's none. Wi-Fi: grey off Wi-Fi; red when the Wi-Fi Stumps you have open all stopped answering; green otherwise. A tap still opens the Network tab.
  • The Network tab's traffic counter goes up to GB.
  • Connecting shows: after Search now or Use, the buttons wait ("Searching…", "Connecting…" on the RNode being connected) until the radio has done it, and the LoRa light flashes orange and green meanwhile (also while an RNode is being connected on its own).
  • Private messages on a Stump, whichever way they came (field fix): a Stump reached over LoRa and Wi-Fi is one row but two entries, and a DM that arrived over LoRa (to the phone's old nick) was counted on the row but not shown, because the row opens the Wi-Fi side. A Stump's private messages are now one conversation per person across both; a reply from the LoRa side with no radio goes over Wi-Fi.
  • Delete and block in a Stump's private messages: long-press a conversation (or use its ⋮ menu). Delete removes it from this phone; Block also drops that nick's private messages on this Stump from then on, without a notification (the Stump has no block of its own, so it still carries them). Blocked nicks are listed under the conversations, with Unblock.
  • One person on a Stump over Wi-Fi and LoRa (Stump 1.0.0-beta.25): FireFly now verifies itself (/auth) as soon as it connects over Wi-Fi, without a tap, so the Stump can join your Wi-Fi and LoRa sides into one person (one nick, private messages reaching you either way). Quietly: no line in the room, and a Stump that doesn't answer leaves no "failed".
  • The greeting (Stump 1.0.0-beta.31, "one person, one name"): on every Wi-Fi connection FireFly says who it is before anything else, /auth then /nick <Your name> (just /auth when the Stump already heard the name on LoRa), and again whenever the Stump asks ("hello": "required"). Until then a line you type waits and then goes out; the screen keeps what it showed. A new name in Settings reaches the Wi-Fi Stumps too (/nick). The phone is then one person on the Stump, listed once under your name, with your DMs reaching you over Wi-Fi.
  • /hello (Stump 1.0.0-beta.32/33): the greeting is now one exchange where both sides sign. FireFly checks the Stump's signature (and that its key is the one behind its LXMF address, and the Stump it knows at that address) before it introduces itself with its own signature, your name and your route. An impostor gets nothing; a real but different Stump at that address sends FireFly looking for yours. A 429 is waited out. Older Stumps still get /auth + /nick.
  • Route toggle, Settings › Stump messages: Over Wi-Fi (in the app while it's connected) or Over LoRa (always by radio). Changing it, or your name, says /hello again to the Stumps you're on.
  • Rescue start: a Stump that says so (/node.json "mode": "rescue") gets a line on its screen: no radio, chain or plugins for now, Wi-Fi chat works.
  • Renames followed: when someone changes nick (the poll's new "renamed"), their private conversation, and a block, follow the new name.
  • A busy Stump (503, Stump 1.0.0-beta.24's connection limits): FireFly waits 2 s and asks again, instead of calling it offline or losing a line.

Checked against Stump 1.0.0-beta.22 and beta.23

Nothing a client reads changed (beta.22 and beta.23 are about a computer Stump's storage and stopping). Computer Stumps now accept uploads, so FireFly's Files tab can upload to a Stump on a Mac or PC: engine-tests/run_real_upload.py <SandBox_Stump_On> sends FireFly's own upload request to a real one (a name with accents included), finds the file in FireFly's list, and checks that a too-big one gets the 507 answer FireFly shows. The Network tab's traffic counter now goes up to GB.

0.5.0-beta.9: a Stump is kept by who it is, not where it is (Stump 1.0.0-beta.21)

A home router moves a laptop Stump from address to address; FireFly kept the old one. Now a Stump is kept by its identity (its LXMF address) and found on the Wi-Fi by asking:

  • Find Stumps asks the network (STUMP? on UDP 4244, as soon as it opens): every Stump that answers is listed with its name, wherever the router put it. The ones those Stumps heard (/stumps.json) are listed too, and the old address sweep still finds older Stumps.
  • A Stump that stops answering is found again by its identity and followed to its new address: same entry, same history, a line saying "found macOS at its new address", and the line that failed is sent there. An open chat screen follows it.
  • Wi-Fi when it answers, LoRa when there's a radio: a Stump known only over LoRa that answers on the Wi-Fi gets a Wi-Fi entry (the Stumps tab asks when it opens), and with no radio its LoRa screen looks for it on the Wi-Fi by itself.
  • The stump.node beacon's new fifth item (its web addresses) is read.
  • Checked against a real computer Stump (1.0.0-beta.21, its own stump_pc.py): engine-tests/run_real_discovery.py <SandBox_Stump_On>. It found two things, both fixed: when a Stump's answer names no address on the phone's network, FireFly now tries the address it answered from (with its port) before the others, and keeps the first that answers as that Stump; and a saved Stump keeps its name ("macOS") when it is reopened or followed, instead of showing "Stump Wi-Fi (address)" until its first poll.
  • Hybrid, as Stump 1.0.0-beta.21 now means it (everyone reads every room and writes in #main; writing elsewhere takes Verify): a line written elsewhere before Verify is kept, marked "not sent: only verified people write in this room", and a tap sends it again once verified; a private message fails with the same reason. In a closed (mandatory) Stump the room stays empty with the Stump's topic saying why, and fills in after Verify.
  • Version on screen: Settings › About and the start screen show the version and build number ("0.5.0-beta.9 (build 25)").
  • Which RNode: the Network tab's radio section names the RNode in use ("RNode EA69", its Bluetooth name; a USB device's name; a Wi-Fi host).
  • Use, under Paired RNodes: switch to another paired RNode at once (it is then tried first from then on); the one in use says "in use".

0.5.0-beta.8: home Wi-Fi is not a Stump's hotspot (from the Stump team's notes)

On home Wi-Fi with no radio, the "Stump over Wi-Fi" box offered the gateway (the router) as "the Stump you're next to"; sends went there and came back as a bare "send failed (-1)".

  • An address is a Stump only once it says so. The gateway is asked /node.json first: a Stump shows its name, anything else is called your router. Connect checks too (port 80, then 8080) and, when no Stump answers, says what's there instead ("connection refused", "a web page, but not a Stump") with a shortcut to Find Stumps.
  • The same Stump typed at a new address replaces its old entry at once, without waiting for the first poll.
  • Real reasons instead of -1: a line that never reached the node is drawn "not sent (the Stump didn't answer)" with the reason beside it; "too fast" now comes only from the node's own ⧗. An offline Wi-Fi Stump says why in its header.
  • Verify ends at once when /auth can't be sent, with the reason.
  • No radio and no Wi-Fi entry yet: the LoRa side offers Find over Wi-Fi.
  • FireFly still keeps no cookies, so a Stump knows it by its address and the one-button /auth keeps working; the browsers' visitor ID (stump_v, Stump 1.0.0-beta.19) is left for later.

0.5.0-beta.7: a Stump that moved, a phone with no radio (field fix)

Reported with a Stump on a Mac and a phone without its RNode: the Stump showed "LoRa + Wi-Fi", but /join did nothing, Verify never finished, and every line came back "not sent (too fast)". The router had given the Mac a new address, so the saved Wi-Fi entry was offline, and the entry fell back to its LoRa side, which had no radio to send with.

  • A Stump found at a new address replaces its old entry (matched by the LXMF address its poll gives).
  • The merged entry opens what can reach the Stump: Wi-Fi while it answers, LoRa only when the radio (or another path) can carry it, else Wi-Fi.
  • A LoRa side with nothing to carry it says so ("No radio: nothing can be sent over LoRa"), with Open over Wi-Fi when the Stump has a Wi-Fi entry.
  • Verify fails at once over LoRa with no way to the Stump, and gives up after its window if nobody answers, instead of "verifying" forever.
  • Failed lines say why: "not sent (too fast)" only when the node refused it (⧗); "not delivered" when LoRa couldn't deliver it.
  • /rooms: [LoRa] read where rrc.py puts it, after the topic.

0.5.0-beta.6: following Stump 1.0.0-beta.19

Checked against the beta.19 CLIENT_QUICKSTART and barkeep.py.

  • Radio settings checked against the Stump's. A Stump's /node.json now gives its radio (frequency, bandwidth, SF, CR). When FireFly's differ, the node screen says so in red, with the Stump's settings and Use these (TX power stays yours). A mismatched radio doesn't error: it just never hears anything, so this was the hardest thing to notice.
  • The node's radio state (from the poll's hw) shows under its name: on, connecting, problem, or no radio.
  • [LoRa] rooms: a Stump on a computer with its own Heltec may mark #lora-mesh [LoRa] in /rooms; it's read as the room's tier now, not glued to its topic.
  • Already right, checked: #lora-mesh shared between Stumps (lines as nick@thatstump); FireFly never titles its messages #lora-mesh or #wifi-mesh (that would mark it a Stump); its HTTP client keeps no cookies, so a Stump still knows it by its address and /auth works as before; in hybrid, FireFly over LoRa now counts as verified by its signature alone, because it announces itself.

0.5.0-beta.5: following Stump 1.0.0-beta.4

Checked against the beta.4 CLIENT_QUICKSTART and rrc.py. Works with older nodes too: the Stump tests run against both.

  • #lxmf is #lora-mesh. FireFly keeps the name the node uses and treats lxmf, lora and lora-mesh as one room (and chained/mesh as wifi-mesh). It never renames a room itself: on an older node #lxmf is still the real room. The landing room is learned from the node's own → #room, and history kept under #lxmf moves to #lora-mesh once the node uses the new name.
  • ⧗ for lines too fast (3 at once, then one every 2 s): the line or DM it refused shows not sent, and a tap sends it again. Over Wi-Fi the reply names the exact line; over LoRa it's the last thing sent within a minute. Voice notes and commands as before.
  • Billboard: 429 rate_limited (3 posts a minute) says to wait a moment. A search box on the Board, and on Files a search box plus video / music / documents / pictures / other chips (?q=, ?cat=; applied by FireFly too on older nodes, accents ignored).
  • Chains: people on other Stumps (rod@LaBuche) are listed and reachable like anyone; the Stumps themselves are grouped last with 🪵.
  • Find Stumps also tries port 8080, where a Stump on a computer may serve, at the gateway and in the network search.

After 0.5.0-beta.4 (tests and docs)

  • Stump's Tools page now has a folder per tool, and /tools.json gained sections; the FireFly apps live in tools/firefly/ and their url names that folder. The update offer already downloads from the url as given, so nothing changed in the app. The test node serves the new shape (and the old flat one), and the docs point to tools/firefly/.

0.5.0-beta.4: /node.json as the Stump team shipped it

  • Find Stumps reads /node.json exactly as it landed on Stump main (next release): name, LXMF address, release (firmware), hotspot address, chain (null unless chained), and features. The card now shows the release and what the node offers, and says so in red when a node has chat switched off.
  • A /node.json whose name or LXMF address is null (no name set, LoRa off) is still read as one. A beta.2 node answers /node.json with its home page (HTML); that falls back to /chain.json, as before.

0.5.0-beta.3: Find Stumps

A Find Stumps button on the Stumps tab: every way to reach a Stump from where you stand, best link first. Nothing in it changes anything on a node: it never polls the chat, so no one sees a ✓ guest join while you look.

  • This Wi-Fi: what answers at the gateway of the Wi-Fi you're on (a lone Stump's 192.168.4.1, or 192.168.n.1 along a chain), with its name and chain role, and how steady the link is (five small round trips: median time and how many were lost). A plain router is said to be one.
  • Stump hotspots nearby: a Wi-Fi scan, strongest first, with bars from the signal. Hotspots you've used as Stumps, the firmware's default name, or names with "stump" in them are marked; others are "maybe". Join connects that hotspot for FireFly only (Android asks once), checks that a Stump answers, remembers the name, and opens it; Leave gives the phone's Wi-Fi back. In a chain, that's how you pick the closest Stump. Android 8–9: join in the system settings instead. Scanning asks for the Nearby devices permission (location before Android 13), only when you tap Scan.
  • On this network: a Stump that joined the same router as you. FireFly asks each address of your /24, 32 at a time (a few seconds), and lists the Stumps that answer, steadiest first.
  • Heard on LoRa: hops, and RSSI/SNR of the last message from each Stump, with bars from the SNR.
  • One entry per Stump. A Stump you reach over LoRa and Wi-Fi is one row on the Stumps tab ("LoRa + Wi-Fi"), matched by the LXMF address its Wi-Fi poll gives; it opens over Wi-Fi while that answers. Hide and Remove act on both.
  • Identification asks /node.json first (name, version, LXMF address, chain), requested from the Stump team for exactly this, then /chain.json (beta.2), then /files.json or /billboard.json (older).

0.5.0-beta.2: following Stump 1.0.0-beta.2 (chained Stumps)

  • The Stump's address is your Wi-Fi's gateway. Chained Stumps each serve their own range (192.168.5.1, 192.168.6.1…), so "Stump over Wi-Fi" now fills in the gateway of the Wi-Fi you joined, and says which it is. 192.168.4.1 (or your saved default) is only used off Wi-Fi.
  • Chain status: a Stump that's part of a chain says so under its name ("first Stump of a chain" or "in a chain of Stumps"), from GET /chain.json.
  • Beacons with more fields (later firmware may append them) are read by their first four, as before; now tested.
  • Big transfers relayed onto LoRa stop at 48 KB per link every 10 minutes. Nothing to change: a voice note (≤16 KB) and a propagation sync fit.

0.5.0-beta.1: following Stump 1.0.0-beta.1

Checked against the Stump beta's CLIENT_QUICKSTART and barkeep.py. Every change also works with an older node, and the tests run both.

  • Billboard posts answered as data. FireFly asks for JSON (Accept: application/json): 201 with the post's id, 400 title_required, or 507 storage ("the node couldn't save the post"). An older node still answers 303 whatever happened, and FireFly still proves the post by reading the board back.
  • Uploads answered as data: the stored (sanitized) name, your credit balance, slot deliveries, and an error token (no_card, full, interrupted…) shown in the app's language instead of the node's sentence.
  • Filenames: on the hotspot the name still goes as raw UTF-8, which every node reads. Over HTTPS it now goes percent-encoded with X-Filename-Encoding: url (accents were dropped before).
  • Plugins: a node's plugin commands (/plugins.json) show as !roll, !8ball… chips above the room box; a tap puts the word in the box. stumpid and fservbot are left out, since their commands are slash only.
  • ⧗ now means "slow down" for plugin commands too. It names neither, so it now fails a pending voice note only if the note was the last thing sent, within the last minute. After a burst of !roll it shows in the room instead of failing a note you sent earlier.
  • Update from a Stump: when a node's /tools.json carries a newer FireFly for Android, the node screen offers it. Get it saves the APK where you choose, and Install hands it to Android's installer (Android asks once to let FireFly install apps). It installs over yours only if it's signed with the same key. Versions compare as the node compares them, so 0.5.0 beats 0.5.0-beta.1.
  • Tags with a suffix (v0.5.0-beta.1) publish as GitHub pre-releases.

0.4.1: local-network discovery and stuck message states fixed

  • Wi-Fi peers are found again. "Local network" (Reticulum's AutoInterface) failed on any network with IPv6, which is nearly every Wi-Fi: Android's Python has no socket.if_nametoindex, and the fallback asked Reticulum's netinfo, which on Android needs that same function and returned nothing ("required argument is not an integer"). FireFly now asks the C library directly, then /sys/class/net. Found by the new CI; engine-tests/run_autointerface.py now checks the lookup on every interface.
  • Messages no longer get stuck at "sent" or "sending". A message left at a propagation node could show "sent" forever although the node had it (and the friend received it), and a direct message could stay at "sending" the same way: the outbound monitor could write over the final state if it had listed the message just before delivery. It now only writes while the message is still in flight. Seen about once in 16 runs of run_propagation.py; run_outbound_race.py forces the timing.
  • Engine tests and a debug build run on every push and pull request.

0.4.0: Board and Files, over a Stump's Wi-Fi

  • Board tab on a Stump joined over Wi-Fi: the node's billboard from GET /billboard.json, newest first. Tap a post to open its details. Each shows its signature, when it was posted, and how long until it expires (72 h). Write one with a title (80 characters, required) and details (600, optional). POST /post answers 303 whatever happened, so FireFly reads the board back and only says "Posted" once the post is really there. A post the node quietly drops is reported as such.
  • Files tab: the shelf from GET /files.json with size, kind and credit cost. Tap a file to save it where you choose (streamed straight to the file, any size, with progress). Bring a file uploads one with POST /upload, with the cost shown first and an optional slot code for a regulated upload. The node's own answer ("Uploaded. Your balance: 4") is shown as it gives it; 400/404/500/503/507 each get a plain reason. One transfer at a time, and it carries on if you leave the screen.
  • Accented filenames upload intact. The node reads X-Filename as UTF-8 and doesn't URL-decode it, but Android's HTTP stack refuses non-ASCII header values. So on the node's hotspot FireFly writes the upload request itself, on a socket of the Wi-Fi network, with the name as real UTF-8: Fête de l'été.pdf is stored as the node would store it from any correct client. (Over HTTPS, accents are dropped from the name instead.)
  • Both tabs appear only when the node offers the feature (a 404 "not offered on this node" hides the tab) and only for Wi-Fi: neither rides the mesh.
  • Fix: removing or hiding a Wi-Fi Stump while a poll was in flight could write the node straight back. A closed session now drops late answers. The test suite caught this intermittently.
  • Release build: your own signing key from keystore.properties or environment variables, arm64 only (about half the size of the debug APK). See docs/RELEASE.md, including how to move a phone from the debug build without losing its identity.

0.2.8: deleting a conversation deletes all of it

  • Deleting a conversation now removes the contact too (name, saved, last heard), not only the messages and voice notes; it no longer lingers under "Heard on the network". Anything still being sent is cancelled. They can still write (a new conversation), and reappear under "heard" if they announce again.
  • Long-press someone under "Heard on the network" to remove them the same way.

0.2.11: aligned with the Stump node (checked against its CLIENT_QUICKSTART)

  • Tier refusals: ⊘ #vip minted — … is understood; FireFly stays put. Before, the room name in that line could be taken for a successful move.
  • Tokens: = #room (already there) and ? /cmd (no such command) are read as tokens; the old sentence-matching stays only for older nodes.
  • Room titles: pushed batches name their room in the LXMF title; room lines are filed under that room, so a line sent just as you /join elsewhere stays in the room it was said in. A re-landing takes its room from the title too.
  • ♪ labels round tenths half up, as the node does (2.25 s is ♪ 2.3 s); the length under each voice note uses the same rule.
  • Voice limits exactly as the node's: Codec 2 up to 15,000 ms, Opus up to 15,100 ms, at most 16 KB through a Stump.
  • Refusals: only a voice-note refusal ("Opus"/"Codec"/"0.6 to 15") fails a pending voice DM; other sentences (help, notices) don't.
  • Node name: a Wi-Fi session takes the node's name from the poll's node field. (Merging the LoRa and Wi-Fi entries into one is next.)
  • The fake Stump in engine-tests now follows the node's current protocol.

0.2.10: Opus in Stump voice DMs too

  • Voice DMs through a Stump node follow the same voice setting as every chat: Opus at 8 kbit/s by default (Automatic judges the link to the node: LoRa gives Codec 2, Wi-Fi Opus). Over LXMF: /msg <nick> with the Ogg Opus file in the audio field; over HTTP: POST /rrc/voice?mode=16.
  • The ♪ 5.0 s label of an Opus note comes from its Ogg pages (last granule minus pre-skip), as a node relaying Opus would compute it.
  • The node must relay Opus: accept mode 16, label it from the Ogg pages, allow an HTTP body of at least 14 KB, pass the field on unchanged. A node not yet updated answers with a sentence ("unreadable voice note (Codec 2 expected)"); FireFly fails the note with those words instead of leaving it pending (engine-tests/run_stump_legacy.py).
  • Tested against a fake Stump relaying Opus: sending and receiving Opus voice DMs over LoRa and Wi-Fi, labels, playback, byte for byte.

0.2.9: Opus by default on every link, at 8 kbit/s

  • Voice notes default to Opus on every link, LoRa included: voice quality first. Automatic (Codec 2 over LoRa) and the Codec 2 modes stay available to save airtime. Installs on an earlier default move to Opus; a chosen mode is kept.
  • Opus at 8 kbit/s, constrained VBR: a 15 s note is about 13.4 KB (two test voices: 13.4 and 12.9 KB), still inside a Stump's 15 KB propagation limit; 9 kbit/s would not be. On LoRa at SF8 a full 15 s note is about 47 packets, roughly a minute of airtime; a 5 s note about 20 s.
  • Stump voice DMs stayed Codec 2 here (the node refused Opus); see 0.2.10.

0.2.8: blocking

  • Block (long-press in Chats or "Heard on the network", or ⋯ in a conversation, after a confirmation): deletes everything with that person, then ignores their announces and messages on this phone, through LXMF's own ignore list too (so also messages collected from a propagation node). Sending to them is refused; their address can't be added as a contact.
  • Needs only the address: works for someone you'll never hear again, and Settings > Blocked people can block a pasted address. Unblock there; unblocked people come back only if they announce or write.
  • Local only: they can still transmit, this phone ignores it. Stump room nicknames aren't covered (the node relays them).
  • Delete stays as it was: everything, including the contact; they come back as new if they announce or write.
  • Tested with the second phone announcing throughout: stays gone while blocked, returns with no history after unblocking.

0.2.7: natural voices with Opus

  • Voice note quality now defaults to Automatic: Opus (natural voice) when the phone's first hop toward the person isn't LoRa (Wi-Fi, TCP, the local network), Codec 2 1200 when it is, or when the note would reach a propagation node over LoRa. Opus or any Codec 2 mode can be forced. Installs on the old 1200 default move to Automatic; other choices stay.
  • Opus: 16 kHz, 6 kbit/s, 60 ms frames, as a standard Ogg Opus file in LXMF's AM_OPUS_OGG field (Sideband's format). About 680 B/s, so a 15 s note is ~10 KB: inside a Stump's 15 KB propagation limit. Opus notes from Sideband and others play, mono or stereo.
  • Recording is now 16 kHz first; Codec 2 still gets 8 kHz from it.
  • Stump voice DMs stay Codec 2 (the node refuses Opus): the chosen Codec 2 mode, or 1200 under Automatic or Opus.
  • libopus 1.5.2 (BSD) is vendored in app/src/main/cpp/opus, trimmed of its unused deep-learning model data, and built by the same CMake file.
  • Tested: FireFly's files pass opusinfo without warnings and play in the reference opusdec; reference opusenc files (mono, stereo) play in FireFly; exact length round trips at 8 and 16 kHz; an Opus note across two radios byte for byte; Automatic picks Codec 2 over LoRa and Opus over TCP; Stump DMs stay Codec 2 even with Opus forced.

0.2.6: voice DMs through Stump

  • Hold to talk in a Stump DM, over LoRa or Wi-Fi, as the Stump quickstart describes: over LXMF, /msg <nick> with the audio field; over HTTP, POST /rrc/voice, with incoming notes fetched from /rrc/voice?id=. Same quality setting as plain chats.
  • A note you send shows pending until the node's [DM] <you>: ♪ 5.0 s; it never says who it went to, so it's matched to the pending note of that length. ⊖ nick and the new ⧗ (too many notes too fast: one per 5 s, 30 an hour) fail it with the reason. Notes under 0.6 s or over 15 s are refused before sending. Your own notes stay playable on your side.
  • Voice DMs play in DM purple; rooms stay text, as on the node.
  • Tested against a fake Stump implementing the quickstart's voice section on both transports. On LoRa a short one-packet note can overtake a long one; FireFly matches each confirmation to the right note regardless.

0.2.5: deleting

  • Conversations: long-press in Chats, or ⋯ > Delete conversation. Every message and voice note with that person is deleted from the phone and anything still being sent is cancelled (including a message still waiting for a path, or one that would have fallen back to a propagation node). They stay known, so they can still write.
  • Stumps, hide: long-press > Hide, or ⋯ > Hide. Off the list, history kept; stays hidden while it beacons; comes back if it messages you. "N hidden · show again" at the bottom of the list.
  • Stumps, remove: long-press > Remove, or ⋯ > Remove, after a confirmation. Deletes its rooms, DMs, raw traffic and peer entry. A node still on the air appears again later as a new, empty entry; one that changed identity never comes back under its old address.

0.2.4: settings, one rule

  • A section with a Save button changes nothing until Save (Discard puts back what's saved): Your name, Radio channel, Announcing, Other links. A section without one applies each tap at once: LoRa radio, Offline messages, Voice note quality.
  • Every change is confirmed by the same short banner, shown only once the engine has taken it ("Saved · the radio retunes in a moment"), or saying it failed. Identity backup and restore use it too.
  • Save is disabled until something changed; invalid values (frequency out of 137–1020 MHz, a TCP peer without a port…) say what's wrong.
  • Fixed: typed but unsaved values were wiped whenever a message arrived.

0.2.3: voice note quality

  • Settings > Voice note quality: Codec 2 700C, 1200 (default), 1600, 2400 or 3200, with the speech per LoRa packet and a 15 s note's size shown for the choice (3200: 0.7 s per packet, 6 KB per 15 s note, well inside a Stump's 15 KB propagation limit). Received notes play in any Codec 2 mode, whatever is chosen.
  • Tested: every mode on recorded speech through the app's own Codec2.kt; a 3200 note across two radios byte for byte.

0.2.2: fast stamps

  • Proof-of-work stamps (for messages left at a propagation node, and for recipients who ask for one) are computed ~200x faster with the same result: a stamp is valid when SHA-256(work block + stamp) is under the target, and SHA-256 reads its input in order, so the 250-750 KB work block is hashed once and each attempt only hashes its own 32 bytes on a copy of that state. LXMF re-hashes the whole block on every attempt. A cost-16 stamp now takes a few hundredths of a second on a desktop (several seconds before), so a fraction of a second on a phone, and correspondingly less battery. Stamps are checked by LXMF's own validator and accepted by an upstream propagation node in the tests.
  • What's left of the wait before "✓ node" is LXMF's job loop (a few seconds) and the LoRa transfer itself.
  • Replaces 0.2.1's multiprocessing probe and single-core fallback: this is faster than LXMF's multi-process path would be anyway.

0.2.1: propagation nodes (offline messages)

  • Finds a Stump's propagation node without waiting for its announce. A propagation node announces rarely (once when switched on). Its address is on the same identity as the Stump's LXMF address, so FireFly derives it and asks the network for it as soon as it recognises a Stump, and every 5 minutes until found.
  • Chooses well: automatic mode prefers a Stump's node over a nearer one (Stump nodes don't peer yet, so everyone around a Stump must use the same node), and can be pinned to a heard node or to an address pasted from a node's /admin, or switched off. Network > Offline messages > Choose…; a Stump's screen offers "use it".
  • Leaves messages at the node straight away when the recipient has no path (they're offline), instead of burning airtime on direct attempts first; direct-then-node fallback stays for everyone else.
  • Respects the node's per-message limit (15 KB on a Stump, read from its announce): an over-limit message is refused with the reason.
  • Stamps work on Android: LXMF makes stamps on Android with Python multiprocessing, which Chaquopy's Python lacks (superseded in 0.2.2).
  • Collects until empty: a Stump answers each sync with at most ~14 KB; FireFly syncs again while rounds bring messages (up to 5). One sync runs at a time: requests made meanwhile (radio up, app opened, the button) join it rather than starting a second one that could end the first early. A failed round (no path yet, link failed) is retried after 10 s, then 30 s.
  • Syncs when it matters: when the radio comes online, when the app is opened (at most every 2 min), when a node is chosen, and every 30 min (installs that kept the old 60 min default are moved to 30).
  • Stump chat (/auth, room lines, commands) never goes through a propagation node. Received messages that came from a node say "via node".

0.2.0: voice notes

  • Hold to talk in any LXMF chat, release to send, slide left to cancel. Up to 15 s. Tap a voice note to play it.
  • Codec 2 at 1200 bps: 150 bytes per second, so about 1.9 s of speech fits one LoRa packet (sent opportunistically) and a full 15 s note is 2,250 bytes (about 8 packets, over a link, automatically).
  • Sent in the standard LXMF audio field (FIELD_AUDIO, mode AM_CODEC2_1200), the format Sideband uses, so notes should play across the two. Received notes in any Codec 2 mode play (700C to 3200); Opus notes show as "format not supported yet".
  • Not in Stump rooms or Stump DMs: RRC is text. That needs a relay on the node.
  • Codec 2 1.2.0 (LGPL-2.1) is vendored in app/src/main/cpp/codec2 and built as its own shared library; see its README there. Building now needs the NDK and CMake (Android Studio > SDK Manager > SDK Tools).

0.1.2

  • The room shown is the room the node actually has you in, after you go quiet on the mesh. The node drops a silent mesh peer from its room after MESH_PEER_TIMEOUT (5 min), and the next message lands them in #lxmf with only ✓ ~you, no → #room. FireFly now reads that: a ✓ of your own that isn't the one following a /join means you were re-landed. Your message sent at that moment is filed in the room it really went to.
  • A node reply naming the room you just asked to join ("tu es déjà dans #lxmf") also corrects the room, in any language.

0.1.1

  • Notifications open the right chat or Stump DM (and clear when you open it).
  • Settings shows what the RNode reports it is using ("on air now"), and "applying…" while it retunes. The Stump defaults line no longer looks like the current state.
  • FireFly announces just before the first message to someone in a session, so the recipient can verify its signature ("sender signature not verified" on the handheld came from the message beating the announce).
  • Local-network discovery works on Chaquopy's Python (no socket.if_nametoindex): AutoInterface's own fallback is used through a subclass. An interface that fails after registering is removed, so it's no longer listed twice.

How it's put together

Jetpack Compose UI (Amber theme)              Kotlin
        │  JSON over Chaquopy (engine/Engine.kt)
firefly/api.py ─ core.py ─ stump_session.py   Python: the FireFly handheld engine
        │           │            └ MeshPipe (LXMF) / WifiPipe (HTTP)
   official rns + lxmf, unmodified
        │
RNodeInterface (Reticulum's desktop driver, byte for byte)
        │  pyserial-shaped port: firefly/androidlink.py
RadioLink (Kotlin): BleRadioLink (Nordic UART, bonded)  |  UsbRadioLink (usb-serial-for-android)

Why this shape. Reticulum's Android RNode driver needs Kivy libraries that don't exist under Chaquopy. The prototype worked around that by configuring the RNode from Kotlin and feeding Reticulum raw KISS over a loopback socket, with about 1,800 lines of patches to Python's socket, os, subprocess, ctypes and signal modules. FireFly instead hands the reference driver a Kotlin RadioLink as its serial port. Detection, the firmware check, radio configuration and its verification, and RSSI/SNR/battery statistics all stay Reticulum's own code.

The one shim. (Plus one subclass for local-network discovery; see 0.1.1.) Reticulum's desktop driver refuses to construct on Android. allow_desktop_rnode_driver() (10 lines) makes that single check answer "not Android" only when it is asked from inside that constructor. The rest of Reticulum keeps its Android behaviour; a test asserts this. That is the only patch FireFly applies to Reticulum.

No startup crashes from optional parts. Reticulum exits the process if an interface in its config file fails. So the config has no interfaces; AutoInterface, TCP peers and the radio are attached at runtime, each in its own try/except.

Signals without patches. Python is started on a dedicated thread, and the engine starts on that same thread, which makes it Python's main thread, where Reticulum and LXMF are allowed to install their signal handlers.

BLE done properly (radio/BleRadioLink.kt): one GATT operation at a time, with each write waiting for its callback; chunks sized from the negotiated MTU (up to 517); notifications enabled before the link reports ready; bonded devices only, failing fast with a readable reason; high connection priority while attached. The prototype fired writes on a 10 ms timer.

Stump, per CLIENT_QUICKSTART.md

  • One protocol, two pipes. The same StumpSession runs over LXMF and over /rrc/poll + /rrc/send. /auth, rooms and DMs are one code path.
  • /auth identity. Standard Reticulum identity (128-hex key). The signature is over the nonce's ASCII text. On the mesh the answer fits in one packet (263/295 bytes, empty title, opportunistic, never propagated), and an expired challenge restarts on its own.
  • Rooms. Mesh users land in #lxmf and Wi-Fi users in #main. The room is tracked from the node's replies (→ #room or the room field), never assumed. Leaving the DM view is local only, with no /join.
  • DMs. A DM shows as pending until the node's [DM] <you>: text echo arrives, or fails on ⊖ nick. Incoming DMs are threaded by sender. Your exact nick is learned from the first confirmation.
  • Wi-Fi details. Polling runs at 2 s, doubling on failure up to 30 s, with 0–30 % jitter. A 404 means "not offered on this node". HTTP is bound to the Wi-Fi network, so a hotspot without internet still works while mobile data stays on.
  • Beacons. Stumps are recognised from stump.node beacons, or asked on demand when an LXMF announce is heard.
  • Announces. FireFly announces whenever a new interface comes online, so a node learns your nick from a real announce and not only a path response. This was a real bug that the tests caught.
  • Billboard and files (Wi-Fi only): /billboard.json and /post, checked by reading the board back; /files.json, /download?f= (UTF-8 percent-encoded) and /upload (raw body, UTF-8 X-Filename, optional X-Hash), streamed by the app, never through the engine.
  • Look. The palette and typography are the quickstart's Amber theme, verbatim, and the app ships in English, French and Spanish.

Verified (desktop, engine-tests/) and on hardware

Run from the repository root with pip install rns==1.5.5 lxmf==1.2.0:

Test What it proves
python3 engine-tests/run_radio.py Reticulum in Android mode drives simulated RNodes through the RadioLink contract. It covers USB online with Stump defaults, live retune, cable pull, BLE online over a chunked and laggy link, BLE out-of-range then automatic reconnect, unpairing detaches the radio, and old firmware reported without crashing.
engine-tests/voice/ (Kotlin + JNI) The real Codec2.kt and native library on recorded speech: 150 B/s, 1.88 s per packet, 15 s = 2,250 B, speech contour preserved; the resampler suppresses 48/16 kHz aliasing by 60–68 dB. See its README.
python3 engine-tests/run_air.py Two engines in two processes, phone A on USB and phone B on BLE, over a simulated LoRa channel: announces heard, delivery proofs, replies, RSSI/SNR, and a 700-character message over a Reticulum link through both radios.
python3 engine-tests/run_autointerface.py Local-network discovery on a Python without socket.if_nametoindex (as on the phone), listed exactly once.
python3 engine-tests/run_stamps.py FireFly's stamp search through LXMF's own Android entry point: valid propagation and delivery stamps at costs 8-16 by LXMF's validator, timing, and cancellation the way LXMF cancels.
python3 engine-tests/run_propagation.py A real upstream LXMF propagation node on a Stump's identity (stamp cost 16, 2 KB limit). Found without its announce; stamps with multiprocessing unavailable; an offline friend and a never-online contact collect by sync; no-path messages go to the node at once; over-limit refused; multi-round sync with no duplicates; Stump chat and "off" never use the node; pin and automatic choice.
(also in run_stump / run_air) Hide keeps history and stays hidden while beaconing; remove deletes everything and a node on the air returns empty; deleting a conversation removes messages and voice notes and a new one starts normally.
python3 engine-tests/run_reach.py The Oct 18 field report: no radio, a Stump's saved Wi-Fi address gone stale. The merged entry doesn't open LoRa, the LoRa side offers Wi-Fi, Verify fails at once, and the Stump found at its new address replaces the old entry.
python3 engine-tests/run_discovery.py Five fake Stumps and a plain web server on loopback addresses (port 80, run as root): a sweep of the /24 finds exactly the Stumps in about a second, reads /node.json in the shape barkeep.py builds (nulls included), falls back to /chain.json when it's an HTML home page and to /files.json on older nodes, ranks the slow one last, and never polls the chat. Plus subnet sizes, bars and hotspot names.
python3 engine-tests/run_web.py Billboard, file shelf, plugins, apps and chain against the fake node, as a current node (JSON answers: 201/400/507 posts; upload name, balance and error tokens; percent-encoded and Latin-1 names; /plugins.json, /tools.json and version order; /chain.json; beacons with extra fields) and as an older one (FAKE_STUMP_OLD=1: posting proven by refetch, a dropped post detected, 80/600 limits, + $ and line breaks intact, UTF-8 download names, a UTF-8 X-Filename written on a raw socket stored exactly (with the node's filename rules), the node's 400/503/507 answers, and features switched off reading as "off").
FAKE_STUMP_OLD=1 python3 engine-tests/run_stump.py The same against a node from before Stump 1.0.0-beta.4: #lxmf, no line limits.
python3 engine-tests/run_stump.py A fake Stump over the mesh (LXMF) and over Wi-Fi (HTTP), covering everything in the Stump section above.

Kotlin was checked with kotlinc 2.2.10 for syntax only. An APK has not been built yet: the build environment had no Android SDK.

Build

Android Studio (Ladybug or newer), JDK 17, and Python 3.13 on the build machine (Chaquopy needs it; set firefly.buildPython in gradle.properties if it isn't on PATH).

./gradlew assembleDebug        # app/build/outputs/apk/debug/app-debug.apk
./gradlew assembleRelease      # signed with your key: see docs/RELEASE.md

The toolchain versions (AGP 9.1.1, Gradle 9.3.1, Kotlin 2.2.10, Chaquopy 17, Python 3.13) are the ones the prototype already built with.

First run on hardware: what to check

Only a real phone and a real RNode can confirm these. They are listed in the order most likely to need attention.

  1. Engine start. Chaquopy starts Python on the firefly-python thread, and Reticulum's signal handlers install there. If start-up fails, the app shows the error text on screen.
  2. BLE pairing (Heltec V3). Enable Bluetooth on the RNode (rnodeconf <port> -b), put it in pairing mode, pair from Network > Bluetooth RNode, and enter the PIN from its display.
  3. USB line control. CP210x boards get DTR on, then RTS on (like pyserial). ESP32-S3 native USB gets DTR on and RTS off, as the prototype found. If a board resets or enters its bootloader on connect, change this in UsbRadioLink.kt.
  4. AutoInterface on Android. It is attached at runtime and its failure is harmless; the Network tab shows why if it didn't start.
  5. Battery. A partial wake lock is held only while a radio is attached; it can be switched off in Settings.

Layout

app/src/main/python/firefly/   engine: api, core, stump_session, stump, radio, androidlink, store, settings
app/src/main/java/.../radio/   RadioLink, UsbRadioLink, BleRadioLink, RadioLinks, BlePairing
app/src/main/java/.../engine/  Engine (Chaquopy bridge), EngineService (foreground service)
app/src/main/java/.../net/     WifiHttp (Stump Wi-Fi, bound to the Wi-Fi network)
app/src/main/java/.../ui/      Compose screens, Amber theme
engine-tests/                  desktop tests with simulated RNodes and a fake Stump

Phase 2

Billboard and files over Wi-Fi (waiting on JSON endpoints from Stump), attachments, QR identity sharing, the Phosphor/OLED/Paper themes, and a propagation node picker.

Credits and licence

Reticulum and LXMF by Mark Qvist (Reticulum License). Codec 2 by David Rowe and contributors (LGPL-2.1). libopus by Xiph.Org and contributors (BSD). The engine comes from FireFly for RK3326 handhelds (MIT). usb-serial-for-android by mik3y (MIT). The palette is LaBuche-Stump's Amber theme. FireFly for Android is under the MIT licence.

About

Android App to work with Reticulum over LXMF and compatible with LaBuche-Stump

Topics

Resources

Stars

4 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages