mirror of
https://github.com/ALLFATHER-BV/wadamesh.git
synced 2026-09-26 09:28:01 +00:00
Merge branch 'ALLFATHER-BV:main' into main
This commit is contained in:
@@ -0,0 +1,156 @@
|
||||
# Testing Lua audio playback
|
||||
|
||||
This guide tests issue #315 with a real WAV or MP3. The examples use the
|
||||
T-Deck environment and the provided `scripts/audio_test.lua` app.
|
||||
|
||||
## Build and flash
|
||||
|
||||
Build and upload the branch as usual:
|
||||
|
||||
```sh
|
||||
pio run -e LilyGo_TDeck_companion_radio_touch
|
||||
pio run -e LilyGo_TDeck_companion_radio_touch -t upload
|
||||
```
|
||||
|
||||
Keep the serial monitor available for `[LUAAPP]` or storage errors:
|
||||
|
||||
```sh
|
||||
pio device monitor -b 115200
|
||||
```
|
||||
|
||||
## Quick MP3 test from SD
|
||||
|
||||
1. Use a FAT32 SD card and create these paths on it:
|
||||
|
||||
```text
|
||||
/Music/test.mp3
|
||||
/meshcomod/apps/audio_test.lua
|
||||
```
|
||||
|
||||
2. Copy `scripts/audio_test.lua` from this repository to the second path. Rename
|
||||
your MP3 to `test.mp3`, or edit `SD_PATH` near the top of the Lua file.
|
||||
3. Insert the card before boot, then reboot the T-Deck.
|
||||
4. Enable **Settings > Sound** and set volume above zero.
|
||||
5. Open **Apps > audio_test**. The screen should report
|
||||
`audio=true`, `wav=true`, and `mp3=true`.
|
||||
6. Press **Play**. Expected status:
|
||||
|
||||
```text
|
||||
State: playing
|
||||
Source: sd Format: mp3
|
||||
```
|
||||
|
||||
7. Exercise **Pause**, **Resume**, and **Stop**. Pressing **Play** while already
|
||||
playing must restart/replace the current track without overlapping it.
|
||||
8. Start playback and leave the app. Audio must stop immediately when the app
|
||||
closes.
|
||||
|
||||
The direct SD API used by the test is:
|
||||
|
||||
```lua
|
||||
local ok, err = wada.audio.play("sd:/Music/test.mp3")
|
||||
```
|
||||
|
||||
`sd:` paths are absolute from the physical card root. Empty segments,
|
||||
backslashes, trailing slashes, `.` and `..` are rejected.
|
||||
|
||||
## Test app-local/internal storage
|
||||
|
||||
A plain filename is resolved inside the current app's private directory. For
|
||||
`audio_test.lua`, the logical files are:
|
||||
|
||||
```text
|
||||
/apps/audio_test.lua
|
||||
/apps/audio_test.d/test.mp3
|
||||
```
|
||||
|
||||
On T-Deck and Pager, internal app storage is SPIFFS. The least destructive way
|
||||
to populate it is the repository's serial uploader:
|
||||
|
||||
1. Power off, remove the SD card, and reboot. This forces T-Deck app storage to
|
||||
the internal SPIFFS backend.
|
||||
2. Make a short internal-storage fixture. T-Deck has a 3.375 MB SPIFFS partition
|
||||
shared with settings and history, so use the full song for SD testing and a
|
||||
small clip here:
|
||||
|
||||
```sh
|
||||
ffmpeg -y -i "/path/to/your/file.mp3" \
|
||||
-t 10 -ac 1 -ar 22050 -b:a 48k \
|
||||
/tmp/wadamesh-audio-test.mp3
|
||||
```
|
||||
|
||||
3. Find the serial port with `pio device list`.
|
||||
4. Install `pyserial` once if needed:
|
||||
|
||||
```sh
|
||||
python3 -m pip install pyserial
|
||||
```
|
||||
|
||||
5. Upload the app and short MP3. Replace the port as needed:
|
||||
|
||||
```sh
|
||||
python3 scripts/sideload_app.py \
|
||||
--port /dev/cu.usbmodem101 \
|
||||
--remote /apps/audio_test.lua \
|
||||
scripts/audio_test.lua
|
||||
|
||||
python3 scripts/sideload_app.py \
|
||||
--port /dev/cu.usbmodem101 \
|
||||
--remote /apps/audio_test.d/test.mp3 \
|
||||
--reboot \
|
||||
/tmp/wadamesh-audio-test.mp3
|
||||
```
|
||||
|
||||
6. Open **Apps > audio_test** after reboot. With no card inserted, the app
|
||||
selects `test.mp3`; press **Play**.
|
||||
7. Expected status is `Source: app Format: mp3`.
|
||||
|
||||
The app-local API is simply:
|
||||
|
||||
```lua
|
||||
local ok, err = wada.audio.play("test.mp3")
|
||||
```
|
||||
|
||||
Tanmatsu uses internal FFat and T-Display P4 uses internal LittleFS. The same
|
||||
serial destination `/apps/audio_test.d/test.mp3` follows the active internal
|
||||
backend, so no Lua code changes are needed. Direct `sd:` playback is shown only
|
||||
when `wada.sys.caps().audio_sd` is true.
|
||||
|
||||
Do not use `uploadfs` on a device with data you need to keep: a filesystem-image
|
||||
upload replaces the internal storage partition, including settings and history.
|
||||
|
||||
## Error and lifecycle checks
|
||||
|
||||
- Disable Sound, then press **Play**: expect `Command: muted`.
|
||||
- Rename the file or remove the card: expect `not found` or `no sd`.
|
||||
- Try a non-WAV/MP3 extension: expect `unsupported format`.
|
||||
- Remove the card during SD playback only after the baseline test. Playback must
|
||||
enter `error` or stop without rebooting; reinserting the card should allow a
|
||||
later play after the normal SD recovery delay.
|
||||
- Receive a mesh notification during playback: it must not start a second audio
|
||||
stream over the media track.
|
||||
- Repeatedly open/play/close the app: each close must release the worker, codec,
|
||||
amplifier, file handle, and storage lease.
|
||||
|
||||
## Host decoder regression test
|
||||
|
||||
The standalone test validates the pinned decoder and the same rolling 16 KB
|
||||
input algorithm used by firmware:
|
||||
|
||||
```sh
|
||||
c++ -std=c++17 -O2 test/test_minimp3_decoder.cpp -o /tmp/test_minimp3_decoder
|
||||
/tmp/test_minimp3_decoder /path/to/your/file.mp3
|
||||
```
|
||||
|
||||
A successful run prints decoded frames, samples per channel, sample rate,
|
||||
channels, and bitrate changes. The implementation has also been checked with
|
||||
CBR, VBR, leading ID3v2, trailing ID3v1, and trailing APEv2 metadata.
|
||||
|
||||
## Supported formats
|
||||
|
||||
- WAV: PCM, 16-bit, mono or stereo, 8-48 kHz.
|
||||
- MP3: MPEG Layer III, mono or stereo, 8-48 kHz, CBR or VBR, with common ID3 and
|
||||
APEv2 metadata.
|
||||
|
||||
Stereo is downmixed to the device's mono speaker path. `status()` reports
|
||||
`stopped`, `playing`, `paused`, `ended`, or `error`.
|
||||
@@ -23,6 +23,10 @@ USB) and the [GitHub releases](https://github.com/ALLFATHER-BV/wadamesh/releases
|
||||
- **T-Deck**: the everything device: touch, physical keyboard, trackball cursor
|
||||
or d-pad navigation, microSD (deep 5000-message chat history, map tile packs,
|
||||
data storage), GPS on the Plus, notification sounds through the I2S speaker.
|
||||
Tap Sym or Alt for one symbol, or double-tap either to lock the symbol layer;
|
||||
this needs [LilyGO keyboard-controller firmware with raw matrix mode](https://github.com/Xinyuan-LilyGO/T-Deck/tree/master/examples/Keyboard_ESP32C3)
|
||||
(June 2025 or newer). Older controller firmware keeps normal typing and
|
||||
reports the unavailable latch mode on Serial.
|
||||
- **Heltec V4 + TFT**: touch UI with the on-screen keyboard; the optional
|
||||
Expansion Kit adds environment sensors (home-screen chart) and a piezo
|
||||
buzzer. V4.3 boards get the switchable high-gain receive LNA toggle.
|
||||
@@ -33,6 +37,8 @@ USB) and the [GitHub releases](https://github.com/ALLFATHER-BV/wadamesh/releases
|
||||
- **T-Lora Pager**: no touchscreen at all — the QWERTY keyboard and the rotary
|
||||
encoder drive everything (Alt+turn free-scrolls a page; see
|
||||
[TLORA_PAGER_SHORTCUTS.md](TLORA_PAGER_SHORTCUTS.md) for the full key map).
|
||||
Tap Fn/Alt for one symbol or double-tap it to lock the symbol layer; physical
|
||||
hold combinations keep their existing behavior.
|
||||
GPS, microSD, keyboard backlight, lock screen and notification sound through
|
||||
the onboard codec and amp all work. Two builds, one per radio: LR1121 and
|
||||
SX1262 — flashing the wrong one leaves you with no radio, so check the label
|
||||
|
||||
@@ -54,6 +54,13 @@ ed25519 (bundled in lib/ed25519)
|
||||
Orson Peters / orlp — public domain (Creative Commons Zero / zlib)
|
||||
https://github.com/orlp/ed25519
|
||||
|
||||
minimp3 (bundled in lib/minimp3)
|
||||
lieff/minimp3 contributors — CC0 1.0 Universal
|
||||
https://github.com/lieff/minimp3
|
||||
Pinned at ea99364f61c14656440e8d77e9c233ccf3124633. The bundled header has
|
||||
a small opt-in scratch-storage hook so the decoder workspace can live in
|
||||
PSRAM; default upstream behavior is unchanged when that hook is not used.
|
||||
|
||||
AsyncElegantOTA (vendored in arch/esp32/AsyncElegantOTA)
|
||||
Copyright (c) Ayush Sharma — MIT License
|
||||
|
||||
|
||||
@@ -42,6 +42,10 @@ the transports, …) are dropped from the lib via `-DMC_VENDORED_TOUCH_APP` so t
|
||||
aren't compiled twice. The build is byte-identical to the original in-tree
|
||||
meshcomod firmware.
|
||||
|
||||
Lua apps use the on-device SDK described in [LUA_APPS.md](LUA_APPS.md). For
|
||||
WAV/MP3 playback, including a ready-to-sideload transport test app and exact SD
|
||||
and internal-storage paths, see [AUDIO_PLAYBACK_TESTING.md](AUDIO_PLAYBACK_TESTING.md).
|
||||
|
||||
## Build
|
||||
|
||||
[PlatformIO](https://platformio.org/) pulls the core fork and all libraries
|
||||
|
||||
+6
-6
@@ -225,8 +225,8 @@ WASD-panning).
|
||||
`updatePagerAltShiftChord()` toggles Caps Lock ONLY while a text field is
|
||||
actually being edited (same "ta" derivation `handleHwKey()`'s TLORA_PAGER
|
||||
branch already uses to tell a bound-but-unfocused field apart from one
|
||||
actually being edited), and jumps straight to Home (`navGoToMainTab`)
|
||||
everywhere else.
|
||||
actually being edited), and is a no-op everywhere else. Alt+Backspace is
|
||||
the context-independent Home shortcut.
|
||||
3. **@-mention contact picker made encoder-navigable** (commit `57802eb`) —
|
||||
the same unreachable-without-touch problem the accent box had, fixed the
|
||||
same way (`mentionNavRestyle()`/`mentionNavConfirm()` mirror
|
||||
@@ -622,9 +622,9 @@ existing boards. Notes:
|
||||
`pagerKeyboardConsumeAltShiftChord()`; `UITask.cpp`'s
|
||||
`updatePagerAltShiftChord()` toggles persistent Caps Lock ONLY while a text
|
||||
field is actually being edited (same "ta" derivation as `handleHwKey()`'s
|
||||
TLORA_PAGER branch), and jumps straight Home (`navGoToMainTab`) everywhere
|
||||
else — toggling Caps Lock with no field to see it change in was reported as
|
||||
surprising/purposeless outside of typing.
|
||||
TLORA_PAGER branch), and is a no-op everywhere else — toggling Caps Lock
|
||||
with no field to see it change in was reported as surprising/purposeless
|
||||
outside of typing. Alt+Backspace owns the Home shortcut.
|
||||
See `PagerKeyboard.cpp`'s matrix-legend comment for the one documented
|
||||
edge case (Alt+Shift+O or Alt+Shift+L all three held phantom-ghosts a
|
||||
stray 'q'/'a' — a diode-less-matrix limitation, not a bug, and not the
|
||||
@@ -751,7 +751,7 @@ existing boards. Notes:
|
||||
| Accent-variant popup keyboard nav | Fn+Space arms it; encoder walks variants; encoder click/Enter confirms; Backspace cancels (`s_accentnav_active`/`accentNavRestyle()`/`accentNavConfirm()`) | medium | done |
|
||||
| @-mention picker keyboard nav | Auto-arms the instant the list appears (no Fn+Space needed); same encoder walk/confirm/cancel as the accent popup (`s_mentionnav_active`/`mentionNavRestyle()`/`mentionNavConfirm()`) | medium | done |
|
||||
| Encoder short click on a focused bubble | Mirrors keyboard Enter — opens the same action menu instead of a plain ENTER a bubble doesn't react to | small | done |
|
||||
| Fn+Shift chord effect | Toggles Caps Lock while editing a text field; jumps to Home (`navGoToMainTab`) everywhere else — `PagerKeyboard.cpp` only reports the chord, `UITask.cpp` decides the effect | small | done |
|
||||
| Fn+Shift chord effect | Toggles Caps Lock while editing a text field; no-op everywhere else. Fn+Backspace jumps Home — `PagerKeyboard.cpp` only reports the chords, `UITask.cpp` decides the effects | small | done |
|
||||
| Map pan (WASD) + slider nudge (Q/E) | WASD pans the Map tab (`mapNudge()`, shared with Tanmatsu's Ctrl+Arrow); Q/E adjust a focused slider (moved off D/F to avoid colliding with WASD) | small | done |
|
||||
| App-drawer tile focus highlight | `NAV_ACCENTFOCUS_FLAG` makes `navFocusCb` use the tile's own accent tint instead of the generic white reverse-fill, on every board | small | done |
|
||||
| SX1262-variant env | `tlora_pager_sx1262_companion_radio_touch` — same board/pins, `CustomSX1262`/`CustomSX1262Wrapper` instead of the LR1121 classes | medium | done (worklist ⑩) |
|
||||
|
||||
+15
-22
@@ -40,7 +40,7 @@ while editing a text field, where the keys type normally.
|
||||
|
||||
| Gesture | Action | Keyboard equivalent |
|
||||
|---|---|---|
|
||||
| Turn | Move focus to the next/previous item on screen | **Fn (Alt) tapped alone** moves forward one step (NEXT only — no keyboard way to go backward) |
|
||||
| Turn | Move focus to the next/previous item on screen | none — encoder only |
|
||||
| Short click | Select / confirm the focused item | **Enter** |
|
||||
| Hold ~1 s, then release | **Back**: closes a popup → closes an open chat → goes Home → Esc (whichever applies first) | **Backspace held ~1 s** |
|
||||
| **Fn (Alt) + turn**, on a main tab | Move between the 5 main tabs (Mail / Contacts / Home / Map / Settings) | **M / C / H / A / S** jumps directly |
|
||||
@@ -93,29 +93,19 @@ text box → Send**, in that order, in both directions — nothing is skipped.
|
||||
Turning past the △ chip (toward the messages) always jumps straight to the
|
||||
newest message, regardless of channel or DM.
|
||||
|
||||
### Backspace and Fn (Alt) — leaving vs. catching up
|
||||
### Backspace — catching up
|
||||
|
||||
These two keys are your shortcuts in and out of "reading mode" on this
|
||||
screen, and they do different things depending on whether you've got unread
|
||||
messages:
|
||||
**Backspace (tap)** jumps to whichever message needs your attention:
|
||||
|
||||
- **Fn (Alt) tapped alone** — jumps straight to the **newest** message and
|
||||
moves focus into the text box, ready to type a reply. This is the
|
||||
deliberate "I'm done reading, let me respond" gesture — it works regardless
|
||||
of where your focus currently is in the chat.
|
||||
- **Backspace (tap)** — jumps to whichever message needs your attention:
|
||||
- If there are unread messages, it jumps to the first one — right below the
|
||||
**"NEW ----"** divider — and selects it, so you can then turn forward
|
||||
through your unread messages in order, oldest-of-the-unread first.
|
||||
- If nothing is unread, it jumps to the newest message instead (same
|
||||
destination as Fn+Alt, but leaves focus on the message itself rather than
|
||||
the text box).
|
||||
- If there are unread messages, it jumps to the first one — right below the
|
||||
**"NEW ----"** divider — and selects it, so you can then turn forward
|
||||
through your unread messages in order, oldest-of-the-unread first.
|
||||
- If nothing is unread, it jumps to the newest message instead (same
|
||||
destination, with focus left on the message).
|
||||
|
||||
Backspace's override above only applies while you're inside an open chat and
|
||||
*not* actively editing the text box — the usual rule from the top of this
|
||||
guide; with a field focused, Backspace deletes a character as normal.
|
||||
Fn (Alt) tap's override fires the same way whether or not the text box
|
||||
already has focus, since landing there is the whole point of the gesture.
|
||||
|
||||
## Keyboard layout
|
||||
|
||||
@@ -128,7 +118,9 @@ a s d f g h j k l [Enter]
|
||||
[Space]
|
||||
```
|
||||
|
||||
Hold **Fn (Alt)** for numbers/symbols instead:
|
||||
Hold **Fn (Alt)** for numbers/symbols, or tap it once to apply this layer to
|
||||
the next key only. Double-tap Fn to lock the symbol layer; tap it again to
|
||||
return to letters.
|
||||
|
||||
```
|
||||
1 2 3 4 5 6 7 8 9 0
|
||||
@@ -162,7 +154,7 @@ Hold **Fn (Alt)** for numbers/symbols instead:
|
||||
| **Space** (tap) | Types a space | — |
|
||||
| **Space** (double-tap, within 250 ms) | Switches between English and your configured secondary keyboard layout | — |
|
||||
| **Space** (hold ~1 s) | — | Locks the screen (shows a "Locking…" progress bar; tapping any key cancels) |
|
||||
| **Fn (Alt)** tapped alone (press+release, nothing else) | — | Moves focus to the next field/item — a keyboard-only substitute for a turn of the encoder. In an open chat: jumps to the newest message and focuses the text box instead — see [Chat screen](#chat-screen) |
|
||||
| **Fn (Alt)** (tap / double-tap) | Next key uses symbols / lock symbols until tapped again | Same |
|
||||
|
||||
The **BOOT** button (top of the device) instantly wakes the screen from
|
||||
idle-dim. It does *not* unlock a screen you've manually locked with the
|
||||
@@ -249,13 +241,14 @@ that changes keyboard language.
|
||||
| Input | Action |
|
||||
|---|---|
|
||||
| M / C / H / A / S (top-level main screens only) | Open Mail / Contacts / Home / Map / Settings |
|
||||
| Turn encoder (or Fn tap = NEXT only) | Move focus |
|
||||
| Turn encoder | Move focus |
|
||||
| Turn, at the loaded edge of a chat | Load more history, keep moving — only exits the list at the true oldest/newest message |
|
||||
| Click encoder (or Enter) | Select / confirm |
|
||||
| Hold encoder ~1s (or hold Backspace ~1s) | Back |
|
||||
| Fn + turn (main tab) | Switch tabs |
|
||||
| Fn + turn (page/chat) | Scroll |
|
||||
| Fn tap alone | Next field (in a chat: jump to newest message + focus the text box) |
|
||||
| Fn tap alone | Use the symbol layer for the next key |
|
||||
| Fn double-tap | Lock the symbol layer; tap Fn again to unlock |
|
||||
| Hold Shift + letter | Momentary uppercase |
|
||||
| Fn + Shift (editing a field) | Toggle Caps Lock |
|
||||
| Fn + Shift (not editing a field) | Nothing |
|
||||
|
||||
@@ -16,8 +16,10 @@ One Lua file plus a small manifest. Apps talk to the firmware through the `wada.
|
||||
API — `wada.ui` (widgets, colours, a text prompt), `wada.sys`, `wada.store`
|
||||
(persistence), `wada.timer`, and on the larger boards `wada.fs`, `wada.net`,
|
||||
`wada.crypto` and the writable half of `wada.mesh`. Supported boards also expose
|
||||
read-only SD directory metadata through `wada.sd`; file contents and writes stay
|
||||
inaccessible. There is no general-purpose filesystem or network access; the API
|
||||
asynchronous WAV/MP3 playback through `wada.audio`; plain filenames come from the
|
||||
app sandbox on internal flash, SD, or SD_MMC. Read-only physical-SD directory
|
||||
metadata is available through `wada.sd`, and direct card playback uses an explicit
|
||||
`sd:/...` path. There is no general-purpose filesystem or network access; the API
|
||||
is the whole surface, which is what makes reviewing tractable. It is documented at
|
||||
[wadamesh.com/sdk.html](https://wadamesh.com/sdk.html).
|
||||
|
||||
|
||||
@@ -21,8 +21,8 @@
|
||||
{
|
||||
"id": "sdktest",
|
||||
"name": "SDK Test",
|
||||
"ver": "1.6",
|
||||
"desc": "Developer tool. Checks the extended Lua SDK on this board: capabilities, clock, battery, GPS, private file access, read-only SD listing, crypto against published RFC vectors, channel discovery, and buttons that TRANSMIT test messages."
|
||||
"ver": "1.7",
|
||||
"desc": "Developer tool. Checks the extended Lua SDK on this board: capabilities, clock, battery, GPS, private file access, audio API, read-only SD listing, crypto against published RFC vectors, channel discovery, and buttons that TRANSMIT test messages."
|
||||
},
|
||||
{
|
||||
"id": "2048",
|
||||
@@ -33,14 +33,16 @@
|
||||
{
|
||||
"id": "wardrive",
|
||||
"name": "Wardrive",
|
||||
"ver": "1.0",
|
||||
"desc": "LoRa coverage survey. Probes every 20s and logs each reply with GPS position, altitude and BOTH link directions (how well you heard them, how well they heard you) to a CSV. Reference app for wada.mesh.discover."
|
||||
"ver": "1.1",
|
||||
"desc": "LoRa coverage survey. Probes every 20s and logs each reply with GPS position, altitude and BOTH link directions (how well you heard them, how well they heard you) to a CSV. Reference app for wada.mesh.discover.",
|
||||
"requires": "sdk_ext"
|
||||
},
|
||||
{
|
||||
"id": "nearby",
|
||||
"name": "Nearby",
|
||||
"ver": "1.0",
|
||||
"desc": "Your contacts on a real map, sorted by distance, with bearing and a live frame counter. Reference app for wada.map, wada.ui.list, wada.geo and app.on_packet."
|
||||
"desc": "Your contacts on a real map, sorted by distance, with bearing and a live frame counter. Reference app for wada.map, wada.ui.list, wada.geo and app.on_packet.",
|
||||
"requires": "sdk_ext"
|
||||
},
|
||||
{
|
||||
"id": "gpscompass",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# wadamesh language file
|
||||
# canonical source - translators: edit this file and PR it
|
||||
# code: bg
|
||||
# ver: 18
|
||||
# ver: 19
|
||||
# name: Български
|
||||
# base: bg
|
||||
(device behind) (устройството изостава)
|
||||
@@ -826,7 +826,7 @@ TCP on TCP вкл
|
||||
TOUCH BETA TOUCH BETA
|
||||
TX TX
|
||||
Tab hotkeys — tap a row, then press a key Клавиши за раздела — докосни ред, после натисни клавиш
|
||||
Tap Use to switch - the device reboots to apply. Натиснете „Избери“, за да превключите — устройството ще се рестартира.
|
||||
Tap Use to switch - the device reboots to apply. Натиснете "Избери", за да превключите — устройството ще се рестартира.
|
||||
Tap Use to switch - the device reboots to apply. Language files live in /lang on the storage; edit them or add your own. Докосни «Избери» за смяна - устройството се рестартира. Езиковите файлове са в /lang в паметта; редактирай ги или добави свои.
|
||||
Tap a language; the device reboots to apply. Изберете език; устройството ще се рестартира.
|
||||
Taste the rainbow! Вкуси дъгата!
|
||||
@@ -977,8 +977,6 @@ tap to exit full screen докоснете за изход от цял екра
|
||||
zoom мащаб
|
||||
zoom %d мащаб %d
|
||||
~17 dB amp for quiet/remote areas; turn off in noisy spots. усилвател ~17 dB за тихи/отдалечени места; изключи в шумни.
|
||||
© OpenStreetMap © OpenStreetMap
|
||||
© OpenTopoMap © OpenTopoMap
|
||||
Scope
|
||||
another region
|
||||
my region
|
||||
@@ -1000,7 +998,6 @@ Overwrite oldest non-favorite
|
||||
1-character messages will be shown 1-character messages will be shown
|
||||
All All
|
||||
Allow Allow
|
||||
Apps that may send messages on the mesh. Anything they send goes out under your node name and cannot be told apart from a message you typed. Turn one off to take the permission back. Apps that may send messages on the mesh. Anything they send goes out under your node name and cannot be told apart from a message you typed. Turn one off to take the permission back.
|
||||
Ignore 1-character messages Ignore 1-character messages
|
||||
Join w/ password Join w/ password
|
||||
Location no longer shared Location no longer shared
|
||||
@@ -1014,9 +1011,7 @@ Never\nChosen contacts only\nAnyone who asks Never\nChosen contacts only\nAnyone
|
||||
No apps installed. No apps installed.
|
||||
No reply. Path hash is %u bytes;\nolder repeaters drop those.\nTry 1 byte (Settings > Radio & Mesh) No reply. Path hash is %u bytes;\nolder repeaters drop those.\nTry 1 byte (Settings > Radio & Mesh)
|
||||
Not supported on this board Not supported on this board
|
||||
Read incoming messages Read incoming messages
|
||||
Reconnected to %.40s Reconnected to %.40s
|
||||
Send messages as me Send messages as me
|
||||
Sent only when that contact asks, encrypted to them. Nothing is broadcast. Needs "Answer telemetry requests" on. Pick contacts in a contact's menu. Sent only when that contact asks, encrypted to them. Nothing is broadcast. Needs "Answer telemetry requests" on. Pick contacts in a contact's menu.
|
||||
Server has forgotten you.\nJoin with the room password. Server has forgotten you.\nJoin with the room password.
|
||||
Share my loc Share my loc
|
||||
@@ -1042,7 +1037,6 @@ App permissions App permissions
|
||||
About / credits About / credits
|
||||
Auth failed Auth failed
|
||||
Auto-add on for %s — new %s land in Contacts automatically. Auto-add on for %s — new %s land in Contacts automatically.
|
||||
Batteryactivityon Batteryactivityon
|
||||
Console mode Console mode
|
||||
Console mode (experimental) Console mode (experimental)
|
||||
Direct (0-hop) only Direct (0-hop) only
|
||||
@@ -1051,8 +1045,6 @@ EXPERIMENTAL. Boots into a text console with no graphical interface: type comman
|
||||
Enter text Enter text
|
||||
Entering console mode - rebooting... Entering console mode - rebooting...
|
||||
Favorite Favorite
|
||||
Geblokkeerde gebruikers Geblokkeerde gebruikers
|
||||
Hide system filesShow system files Hide system filesShow system files
|
||||
How tiles work:\nThe map is built from 256×256 "slippy" tiles. Only the tiles for the area you're viewing are fetched — there is no bulk pre-download.\n\nBecause this device can't do HTTPS (not enough heap after Wi-Fi starts) and decodes JPEG far more cheaply than PNG, tiles come from the wadamesh proxy: it fetches the upstream PNG over HTTPS with an identifying User-Agent, re-encodes it as JPEG, and caches it. Your device then caches each tile to its own flash, so a tile is only downloaded once.\n\nUse Options → Reload tiles to re-download the tiles currently in view if one looks corrupted. How tiles work:\nThe map is built from 256×256 "slippy" tiles. Only the tiles for the area you're viewing are fetched — there is no bulk pre-download.\n\nBecause this device can't do HTTPS (not enough heap after Wi-Fi starts) and decodes JPEG far more cheaply than PNG, tiles come from the wadamesh proxy: it fetches the upstream PNG over HTTPS with an identifying User-Agent, re-encodes it as JPEG, and caches it. Your device then caches each tile to its own flash, so a tile is only downloaded once.\n\nUse Options → Reload tiles to re-download the tiles currently in view if one looks corrupted.
|
||||
Init… Init…
|
||||
Leaving console mode - rebooting... Leaving console mode - rebooting...
|
||||
@@ -1062,8 +1054,6 @@ Map data © OpenStreetMap contributors.\nopenstreetmap.org/copyright\nLicensed u
|
||||
Map style © OpenTopoMap (CC-BY-SA) — opentopomap.org\nMap data © OpenStreetMap contributors (ODbL) + SRTM.\n\n Map style © OpenTopoMap (CC-BY-SA) — opentopomap.org\nMap data © OpenStreetMap contributors (ODbL) + SRTM.\n\n
|
||||
Name (A-Z) Name (A-Z)
|
||||
Nearest first Nearest first
|
||||
Other networksNetworks Other networksNetworks
|
||||
Paste (move)Paste (copy) Paste (move)Paste (copy)
|
||||
Recent message Recent message
|
||||
Reload tiles in view Reload tiles in view
|
||||
Repeaters Repeaters
|
||||
@@ -1076,13 +1066,8 @@ Show contacts Show contacts
|
||||
Show coordinates Show coordinates
|
||||
Show tile z/x/y Show tile z/x/y
|
||||
Starting… Starting…
|
||||
Stop sharing loc Share my loc Stop sharing loc Share my loc
|
||||
Supprimer Supprimer
|
||||
Switch to the external antenna?\n\nMake sure an antenna is actually connected to the external antenna socket first. Transmitting with nothing attached can damage the radio.\n\nResets to the on-board antenna on every reboot.Turn on legacy per-transmit switching?\n\nDiagnostic mode, for comparison only. It transmits on the on-board antenna and listens on the external one, so your outbound signal will be much weaker than your inbound.\n\nResets to the on-board antenna on every reboot. Switch to the external antenna?\n\nMake sure an antenna is actually connected to the external antenna socket first. Transmitting with nothing attached can damage the radio.\n\nResets to the on-board antenna on every reboot.Turn on legacy per-transmit switching?\n\nDiagnostic mode, for comparison only. It transmits on the on-board antenna and listens on the external one, so your outbound signal will be much weaker than your inbound.\n\nResets to the on-board antenna on every reboot.
|
||||
Text console (experimental) Text console (experimental)
|
||||
Tile debug overlay Tile debug overlay
|
||||
Unblock Block Unblock Block
|
||||
Unfav Favorite Unfav Favorite
|
||||
chats chats
|
||||
repeaters repeaters
|
||||
rooms rooms
|
||||
@@ -1098,3 +1083,23 @@ channel busy %.1f%% channel busy %.1f%%
|
||||
sampling... sampling...
|
||||
teal = now blue = average teal = now blue = average
|
||||
window restarted window restarted
|
||||
Filter Filter
|
||||
Hide system files Hide system files
|
||||
Networks Networks
|
||||
Other networks Other networks
|
||||
Show system files Show system files
|
||||
Unfav Unfav
|
||||
Firmware beta_%d\nChecking for updates over Wi-Fi… Firmware beta_%d\nChecking for updates over Wi-Fi…
|
||||
Install beta_%d?\nThis downgrades the firmware and reboots. Install beta_%d?\nThis downgrades the firmware and reboots.
|
||||
Other (hidden) network… Other (hidden) network…
|
||||
Paused Paused
|
||||
Scan again Scan again
|
||||
At a glance At a glance
|
||||
At a glance disabled At a glance disabled
|
||||
At a glance enabled At a glance enabled
|
||||
At a glance while locked At a glance while locked
|
||||
Glance only shows when unlocked Glance only shows when unlocked
|
||||
Glance shows message previews\nwhile the screen is locked Glance shows message previews\nwhile the screen is locked
|
||||
Snake — swipe, roll, or press a direction Snake — swipe, roll, or press a direction
|
||||
N/A N/A
|
||||
This board cannot run this app. This board cannot run this app.
|
||||
@@ -1,7 +1,7 @@
|
||||
# wadamesh language file
|
||||
# canonical source - translators: edit this file and PR it
|
||||
# code: de
|
||||
# ver: 18
|
||||
# ver: 19
|
||||
# name: Deutsch
|
||||
# base: de
|
||||
(device behind) (Gerät geht nach)
|
||||
@@ -452,7 +452,6 @@ Login OK (perms %u) Anmeldung OK (Rechte %u)
|
||||
Login failed Anmeldung fehlgeschlagen
|
||||
Logs Protokolle
|
||||
Longitude Längengrad
|
||||
Lua Store Lua Store
|
||||
MHz MHz
|
||||
MQTT bridge MQTT-Brücke
|
||||
Map Karte
|
||||
@@ -694,7 +693,7 @@ SD card remounted SD-Karte neu eingebunden
|
||||
SD card removed SD-Karte entfernt
|
||||
SD data is unavailable - contacts and channels cannot be saved until the card is reinserted. SD-Daten nicht verfügbar – Kontakte und Kanäle können erst nach Einsetzen der Karte gespeichert werden.
|
||||
SD data is unavailable - identity, settings, contacts and channels cannot be saved until the card is reinserted. SD-Daten nicht verfügbar – Identität, Einstellungen, Kontakte und Kanäle können erst nach Einsetzen der Karte gespeichert werden.
|
||||
SD data migration is incomplete. Identity and settings remain internal; use Copy internal data to SD to retry. SD-Datenübertragung unvollständig. Identität und Einstellungen bleiben intern; „Interne Daten auf SD kopieren“ zum erneuten Versuch verwenden.
|
||||
SD data migration is incomplete. Identity and settings remain internal; use Copy internal data to SD to retry. SD-Datenübertragung unvollständig. Identität und Einstellungen bleiben intern; "Interne Daten auf SD kopieren" zum erneuten Versuch verwenden.
|
||||
SD download failed: %s SD-Download fehlgeschlagen: %s
|
||||
SD formatted - %s (MESHCOMOD) SD formatiert - %s (MESHCOMOD)
|
||||
SD is busy - close tools and retry SD ist belegt – Tools schließen und erneut versuchen
|
||||
@@ -797,7 +796,6 @@ Sleep ready Bereit zum Schlafen
|
||||
Slot %d cleared Slot %d gelöscht
|
||||
Slot empty Slot leer
|
||||
Small\nMedium\nLarge Klein\nMittel\nGroß
|
||||
Snake Snake
|
||||
Snake — swipe or roll the trackball Snake — wischen oder Trackball rollen
|
||||
Sort Sortieren
|
||||
Sound Ton
|
||||
@@ -805,7 +803,6 @@ Sound off Ton aus
|
||||
Sound on Ton ein
|
||||
Sound: %s Ton: %s
|
||||
Start time Startzeit
|
||||
Status Status
|
||||
Step 1 of 3 Schritt 1 von 3
|
||||
Step 2 of 3 Schritt 2 von 3
|
||||
Step 3 of 3 Schritt 3 von 3
|
||||
@@ -818,7 +815,6 @@ Streams the device screen as a live picture you tap to control. Heavier; the pan
|
||||
Switch Wechseln
|
||||
Switch to the external antenna?\n\nMake sure an antenna is actually connected to the external antenna socket first. Transmitting with nothing attached can damage the radio.\n\nResets to the on-board antenna on every reboot. Auf die externe Antenne wechseln?\n\nErst sicherstellen, dass wirklich eine Antenne an der Buchse steckt. Senden ohne Antenne kann das Funkmodul beschädigen.\n\nFällt bei jedem Neustart auf die interne Antenne zurück.
|
||||
Sync clock from system Uhr synchronisieren
|
||||
System System
|
||||
System info Systeminfo
|
||||
TCP %s BLE %s\nWS clients %d GPS %s Buzzer %s TCP %s BLE %s\nWS-Clients %d GPS %s Summer %s
|
||||
TCP off TCP aus
|
||||
@@ -977,8 +973,6 @@ tap to exit full screen tippen zum Verlassen des Vollbilds
|
||||
zoom Zoom
|
||||
zoom %d Zoom %d
|
||||
~17 dB amp for quiet/remote areas; turn off in noisy spots. ~17 dB Verstärker für ruhige/abgelegene Gebiete; in lauten Zonen aus.
|
||||
© OpenStreetMap © OpenStreetMap
|
||||
© OpenTopoMap © OpenTopoMap
|
||||
Scope
|
||||
another region
|
||||
my region
|
||||
@@ -1000,7 +994,6 @@ Overwrite oldest non-favorite
|
||||
1-character messages will be shown 1-character messages will be shown
|
||||
All All
|
||||
Allow Allow
|
||||
Apps that may send messages on the mesh. Anything they send goes out under your node name and cannot be told apart from a message you typed. Turn one off to take the permission back. Apps that may send messages on the mesh. Anything they send goes out under your node name and cannot be told apart from a message you typed. Turn one off to take the permission back.
|
||||
Ignore 1-character messages Ignore 1-character messages
|
||||
Join w/ password Join w/ password
|
||||
Location no longer shared Location no longer shared
|
||||
@@ -1014,9 +1007,7 @@ Never\nChosen contacts only\nAnyone who asks Never\nChosen contacts only\nAnyone
|
||||
No apps installed. No apps installed.
|
||||
No reply. Path hash is %u bytes;\nolder repeaters drop those.\nTry 1 byte (Settings > Radio & Mesh) No reply. Path hash is %u bytes;\nolder repeaters drop those.\nTry 1 byte (Settings > Radio & Mesh)
|
||||
Not supported on this board Not supported on this board
|
||||
Read incoming messages Read incoming messages
|
||||
Reconnected to %.40s Reconnected to %.40s
|
||||
Send messages as me Send messages as me
|
||||
Sent only when that contact asks, encrypted to them. Nothing is broadcast. Needs "Answer telemetry requests" on. Pick contacts in a contact's menu. Sent only when that contact asks, encrypted to them. Nothing is broadcast. Needs "Answer telemetry requests" on. Pick contacts in a contact's menu.
|
||||
Server has forgotten you.\nJoin with the room password. Server has forgotten you.\nJoin with the room password.
|
||||
Share my loc Share my loc
|
||||
@@ -1042,7 +1033,6 @@ App permissions App permissions
|
||||
About / credits About / credits
|
||||
Auth failed Auth failed
|
||||
Auto-add on for %s — new %s land in Contacts automatically. Auto-add on for %s — new %s land in Contacts automatically.
|
||||
Batteryactivityon Batteryactivityon
|
||||
Console mode Console mode
|
||||
Console mode (experimental) Console mode (experimental)
|
||||
Direct (0-hop) only Direct (0-hop) only
|
||||
@@ -1051,8 +1041,6 @@ EXPERIMENTAL. Boots into a text console with no graphical interface: type comman
|
||||
Enter text Enter text
|
||||
Entering console mode - rebooting... Entering console mode - rebooting...
|
||||
Favorite Favorite
|
||||
Geblokkeerde gebruikers Geblokkeerde gebruikers
|
||||
Hide system filesShow system files Hide system filesShow system files
|
||||
How tiles work:\nThe map is built from 256×256 "slippy" tiles. Only the tiles for the area you're viewing are fetched — there is no bulk pre-download.\n\nBecause this device can't do HTTPS (not enough heap after Wi-Fi starts) and decodes JPEG far more cheaply than PNG, tiles come from the wadamesh proxy: it fetches the upstream PNG over HTTPS with an identifying User-Agent, re-encodes it as JPEG, and caches it. Your device then caches each tile to its own flash, so a tile is only downloaded once.\n\nUse Options → Reload tiles to re-download the tiles currently in view if one looks corrupted. How tiles work:\nThe map is built from 256×256 "slippy" tiles. Only the tiles for the area you're viewing are fetched — there is no bulk pre-download.\n\nBecause this device can't do HTTPS (not enough heap after Wi-Fi starts) and decodes JPEG far more cheaply than PNG, tiles come from the wadamesh proxy: it fetches the upstream PNG over HTTPS with an identifying User-Agent, re-encodes it as JPEG, and caches it. Your device then caches each tile to its own flash, so a tile is only downloaded once.\n\nUse Options → Reload tiles to re-download the tiles currently in view if one looks corrupted.
|
||||
Init… Init…
|
||||
Leaving console mode - rebooting... Leaving console mode - rebooting...
|
||||
@@ -1062,8 +1050,6 @@ Map data © OpenStreetMap contributors.\nopenstreetmap.org/copyright\nLicensed u
|
||||
Map style © OpenTopoMap (CC-BY-SA) — opentopomap.org\nMap data © OpenStreetMap contributors (ODbL) + SRTM.\n\n Map style © OpenTopoMap (CC-BY-SA) — opentopomap.org\nMap data © OpenStreetMap contributors (ODbL) + SRTM.\n\n
|
||||
Name (A-Z) Name (A-Z)
|
||||
Nearest first Nearest first
|
||||
Other networksNetworks Other networksNetworks
|
||||
Paste (move)Paste (copy) Paste (move)Paste (copy)
|
||||
Recent message Recent message
|
||||
Reload tiles in view Reload tiles in view
|
||||
Repeaters Repeaters
|
||||
@@ -1076,13 +1062,8 @@ Show contacts Show contacts
|
||||
Show coordinates Show coordinates
|
||||
Show tile z/x/y Show tile z/x/y
|
||||
Starting… Starting…
|
||||
Stop sharing loc Share my loc Stop sharing loc Share my loc
|
||||
Supprimer Supprimer
|
||||
Switch to the external antenna?\n\nMake sure an antenna is actually connected to the external antenna socket first. Transmitting with nothing attached can damage the radio.\n\nResets to the on-board antenna on every reboot.Turn on legacy per-transmit switching?\n\nDiagnostic mode, for comparison only. It transmits on the on-board antenna and listens on the external one, so your outbound signal will be much weaker than your inbound.\n\nResets to the on-board antenna on every reboot. Switch to the external antenna?\n\nMake sure an antenna is actually connected to the external antenna socket first. Transmitting with nothing attached can damage the radio.\n\nResets to the on-board antenna on every reboot.Turn on legacy per-transmit switching?\n\nDiagnostic mode, for comparison only. It transmits on the on-board antenna and listens on the external one, so your outbound signal will be much weaker than your inbound.\n\nResets to the on-board antenna on every reboot.
|
||||
Text console (experimental) Text console (experimental)
|
||||
Tile debug overlay Tile debug overlay
|
||||
Unblock Block Unblock Block
|
||||
Unfav Favorite Unfav Favorite
|
||||
chats chats
|
||||
repeaters repeaters
|
||||
rooms rooms
|
||||
@@ -1098,3 +1079,23 @@ channel busy %.1f%% channel busy %.1f%%
|
||||
sampling... sampling...
|
||||
teal = now blue = average teal = now blue = average
|
||||
window restarted window restarted
|
||||
Filter Filter
|
||||
Hide system files Hide system files
|
||||
Networks Networks
|
||||
Other networks Other networks
|
||||
Show system files Show system files
|
||||
Unfav Unfav
|
||||
Firmware beta_%d\nChecking for updates over Wi-Fi… Firmware beta_%d\nChecking for updates over Wi-Fi…
|
||||
Install beta_%d?\nThis downgrades the firmware and reboots. Install beta_%d?\nThis downgrades the firmware and reboots.
|
||||
Other (hidden) network… Other (hidden) network…
|
||||
Paused Paused
|
||||
Scan again Scan again
|
||||
At a glance At a glance
|
||||
At a glance disabled At a glance disabled
|
||||
At a glance enabled At a glance enabled
|
||||
At a glance while locked At a glance while locked
|
||||
Glance only shows when unlocked Glance only shows when unlocked
|
||||
Glance shows message previews\nwhile the screen is locked Glance shows message previews\nwhile the screen is locked
|
||||
Snake — swipe, roll, or press a direction Snake — swipe, roll, or press a direction
|
||||
N/A N/A
|
||||
This board cannot run this app. This board cannot run this app.
|
||||
@@ -1,7 +1,7 @@
|
||||
# wadamesh language file
|
||||
# canonical source - translators: edit this file and PR it
|
||||
# code: el
|
||||
# ver: 18
|
||||
# ver: 19
|
||||
# name: Ελληνικά
|
||||
# base: el
|
||||
(device behind) (συσκευή πίσω)
|
||||
@@ -977,8 +977,6 @@ tap to exit full screen πατήστε για έξοδο από πλήρη οθ
|
||||
zoom ζουμ
|
||||
zoom %d ζουμ %d
|
||||
~17 dB amp for quiet/remote areas; turn off in noisy spots. ενισχυτής ~17 dB για ήσυχες/απομακρυσμένες περιοχές· κλείστε σε θορυβώδεις.
|
||||
© OpenStreetMap © OpenStreetMap
|
||||
© OpenTopoMap © OpenTopoMap
|
||||
Scope
|
||||
another region
|
||||
my region
|
||||
@@ -1000,7 +998,6 @@ Overwrite oldest non-favorite
|
||||
1-character messages will be shown 1-character messages will be shown
|
||||
All All
|
||||
Allow Allow
|
||||
Apps that may send messages on the mesh. Anything they send goes out under your node name and cannot be told apart from a message you typed. Turn one off to take the permission back. Apps that may send messages on the mesh. Anything they send goes out under your node name and cannot be told apart from a message you typed. Turn one off to take the permission back.
|
||||
Ignore 1-character messages Ignore 1-character messages
|
||||
Join w/ password Join w/ password
|
||||
Location no longer shared Location no longer shared
|
||||
@@ -1014,9 +1011,7 @@ Never\nChosen contacts only\nAnyone who asks Never\nChosen contacts only\nAnyone
|
||||
No apps installed. No apps installed.
|
||||
No reply. Path hash is %u bytes;\nolder repeaters drop those.\nTry 1 byte (Settings > Radio & Mesh) No reply. Path hash is %u bytes;\nolder repeaters drop those.\nTry 1 byte (Settings > Radio & Mesh)
|
||||
Not supported on this board Not supported on this board
|
||||
Read incoming messages Read incoming messages
|
||||
Reconnected to %.40s Reconnected to %.40s
|
||||
Send messages as me Send messages as me
|
||||
Sent only when that contact asks, encrypted to them. Nothing is broadcast. Needs "Answer telemetry requests" on. Pick contacts in a contact's menu. Sent only when that contact asks, encrypted to them. Nothing is broadcast. Needs "Answer telemetry requests" on. Pick contacts in a contact's menu.
|
||||
Server has forgotten you.\nJoin with the room password. Server has forgotten you.\nJoin with the room password.
|
||||
Share my loc Share my loc
|
||||
@@ -1042,7 +1037,6 @@ App permissions App permissions
|
||||
About / credits About / credits
|
||||
Auth failed Auth failed
|
||||
Auto-add on for %s — new %s land in Contacts automatically. Auto-add on for %s — new %s land in Contacts automatically.
|
||||
Batteryactivityon Batteryactivityon
|
||||
Console mode Console mode
|
||||
Console mode (experimental) Console mode (experimental)
|
||||
Direct (0-hop) only Direct (0-hop) only
|
||||
@@ -1051,8 +1045,6 @@ EXPERIMENTAL. Boots into a text console with no graphical interface: type comman
|
||||
Enter text Enter text
|
||||
Entering console mode - rebooting... Entering console mode - rebooting...
|
||||
Favorite Favorite
|
||||
Geblokkeerde gebruikers Geblokkeerde gebruikers
|
||||
Hide system filesShow system files Hide system filesShow system files
|
||||
How tiles work:\nThe map is built from 256×256 "slippy" tiles. Only the tiles for the area you're viewing are fetched — there is no bulk pre-download.\n\nBecause this device can't do HTTPS (not enough heap after Wi-Fi starts) and decodes JPEG far more cheaply than PNG, tiles come from the wadamesh proxy: it fetches the upstream PNG over HTTPS with an identifying User-Agent, re-encodes it as JPEG, and caches it. Your device then caches each tile to its own flash, so a tile is only downloaded once.\n\nUse Options → Reload tiles to re-download the tiles currently in view if one looks corrupted. How tiles work:\nThe map is built from 256×256 "slippy" tiles. Only the tiles for the area you're viewing are fetched — there is no bulk pre-download.\n\nBecause this device can't do HTTPS (not enough heap after Wi-Fi starts) and decodes JPEG far more cheaply than PNG, tiles come from the wadamesh proxy: it fetches the upstream PNG over HTTPS with an identifying User-Agent, re-encodes it as JPEG, and caches it. Your device then caches each tile to its own flash, so a tile is only downloaded once.\n\nUse Options → Reload tiles to re-download the tiles currently in view if one looks corrupted.
|
||||
Init… Init…
|
||||
Leaving console mode - rebooting... Leaving console mode - rebooting...
|
||||
@@ -1062,8 +1054,6 @@ Map data © OpenStreetMap contributors.\nopenstreetmap.org/copyright\nLicensed u
|
||||
Map style © OpenTopoMap (CC-BY-SA) — opentopomap.org\nMap data © OpenStreetMap contributors (ODbL) + SRTM.\n\n Map style © OpenTopoMap (CC-BY-SA) — opentopomap.org\nMap data © OpenStreetMap contributors (ODbL) + SRTM.\n\n
|
||||
Name (A-Z) Name (A-Z)
|
||||
Nearest first Nearest first
|
||||
Other networksNetworks Other networksNetworks
|
||||
Paste (move)Paste (copy) Paste (move)Paste (copy)
|
||||
Recent message Recent message
|
||||
Reload tiles in view Reload tiles in view
|
||||
Repeaters Repeaters
|
||||
@@ -1076,13 +1066,8 @@ Show contacts Show contacts
|
||||
Show coordinates Show coordinates
|
||||
Show tile z/x/y Show tile z/x/y
|
||||
Starting… Starting…
|
||||
Stop sharing loc Share my loc Stop sharing loc Share my loc
|
||||
Supprimer Supprimer
|
||||
Switch to the external antenna?\n\nMake sure an antenna is actually connected to the external antenna socket first. Transmitting with nothing attached can damage the radio.\n\nResets to the on-board antenna on every reboot.Turn on legacy per-transmit switching?\n\nDiagnostic mode, for comparison only. It transmits on the on-board antenna and listens on the external one, so your outbound signal will be much weaker than your inbound.\n\nResets to the on-board antenna on every reboot. Switch to the external antenna?\n\nMake sure an antenna is actually connected to the external antenna socket first. Transmitting with nothing attached can damage the radio.\n\nResets to the on-board antenna on every reboot.Turn on legacy per-transmit switching?\n\nDiagnostic mode, for comparison only. It transmits on the on-board antenna and listens on the external one, so your outbound signal will be much weaker than your inbound.\n\nResets to the on-board antenna on every reboot.
|
||||
Text console (experimental) Text console (experimental)
|
||||
Tile debug overlay Tile debug overlay
|
||||
Unblock Block Unblock Block
|
||||
Unfav Favorite Unfav Favorite
|
||||
chats chats
|
||||
repeaters repeaters
|
||||
rooms rooms
|
||||
@@ -1098,3 +1083,23 @@ channel busy %.1f%% channel busy %.1f%%
|
||||
sampling... sampling...
|
||||
teal = now blue = average teal = now blue = average
|
||||
window restarted window restarted
|
||||
Filter Filter
|
||||
Hide system files Hide system files
|
||||
Networks Networks
|
||||
Other networks Other networks
|
||||
Show system files Show system files
|
||||
Unfav Unfav
|
||||
Firmware beta_%d\nChecking for updates over Wi-Fi… Firmware beta_%d\nChecking for updates over Wi-Fi…
|
||||
Install beta_%d?\nThis downgrades the firmware and reboots. Install beta_%d?\nThis downgrades the firmware and reboots.
|
||||
Other (hidden) network… Other (hidden) network…
|
||||
Paused Paused
|
||||
Scan again Scan again
|
||||
At a glance At a glance
|
||||
At a glance disabled At a glance disabled
|
||||
At a glance enabled At a glance enabled
|
||||
At a glance while locked At a glance while locked
|
||||
Glance only shows when unlocked Glance only shows when unlocked
|
||||
Glance shows message previews\nwhile the screen is locked Glance shows message previews\nwhile the screen is locked
|
||||
Snake — swipe, roll, or press a direction Snake — swipe, roll, or press a direction
|
||||
N/A N/A
|
||||
This board cannot run this app. This board cannot run this app.
|
||||
@@ -1,7 +1,7 @@
|
||||
# wadamesh language file
|
||||
# canonical source - translators: edit this file and PR it
|
||||
# code: es
|
||||
# ver: 18
|
||||
# ver: 19
|
||||
# name: Español
|
||||
# base: es
|
||||
(device behind) (dispositivo atrasado)
|
||||
@@ -977,8 +977,6 @@ tap to exit full screen toca para salir de pantalla completa
|
||||
zoom zoom
|
||||
zoom %d zoom %d
|
||||
~17 dB amp for quiet/remote areas; turn off in noisy spots. amplificador ~17 dB para zonas tranquilas/remotas; apaga en zonas ruidosas.
|
||||
© OpenStreetMap © OpenStreetMap
|
||||
© OpenTopoMap © OpenTopoMap
|
||||
Scope
|
||||
another region
|
||||
my region
|
||||
@@ -1000,7 +998,6 @@ Overwrite oldest non-favorite
|
||||
1-character messages will be shown 1-character messages will be shown
|
||||
All All
|
||||
Allow Allow
|
||||
Apps that may send messages on the mesh. Anything they send goes out under your node name and cannot be told apart from a message you typed. Turn one off to take the permission back. Apps that may send messages on the mesh. Anything they send goes out under your node name and cannot be told apart from a message you typed. Turn one off to take the permission back.
|
||||
Ignore 1-character messages Ignore 1-character messages
|
||||
Join w/ password Join w/ password
|
||||
Location no longer shared Location no longer shared
|
||||
@@ -1014,9 +1011,7 @@ Never\nChosen contacts only\nAnyone who asks Never\nChosen contacts only\nAnyone
|
||||
No apps installed. No apps installed.
|
||||
No reply. Path hash is %u bytes;\nolder repeaters drop those.\nTry 1 byte (Settings > Radio & Mesh) No reply. Path hash is %u bytes;\nolder repeaters drop those.\nTry 1 byte (Settings > Radio & Mesh)
|
||||
Not supported on this board Not supported on this board
|
||||
Read incoming messages Read incoming messages
|
||||
Reconnected to %.40s Reconnected to %.40s
|
||||
Send messages as me Send messages as me
|
||||
Sent only when that contact asks, encrypted to them. Nothing is broadcast. Needs "Answer telemetry requests" on. Pick contacts in a contact's menu. Sent only when that contact asks, encrypted to them. Nothing is broadcast. Needs "Answer telemetry requests" on. Pick contacts in a contact's menu.
|
||||
Server has forgotten you.\nJoin with the room password. Server has forgotten you.\nJoin with the room password.
|
||||
Share my loc Share my loc
|
||||
@@ -1042,7 +1037,6 @@ App permissions App permissions
|
||||
About / credits About / credits
|
||||
Auth failed Auth failed
|
||||
Auto-add on for %s — new %s land in Contacts automatically. Auto-add on for %s — new %s land in Contacts automatically.
|
||||
Batteryactivityon Batteryactivityon
|
||||
Console mode Console mode
|
||||
Console mode (experimental) Console mode (experimental)
|
||||
Direct (0-hop) only Direct (0-hop) only
|
||||
@@ -1051,8 +1045,6 @@ EXPERIMENTAL. Boots into a text console with no graphical interface: type comman
|
||||
Enter text Enter text
|
||||
Entering console mode - rebooting... Entering console mode - rebooting...
|
||||
Favorite Favorite
|
||||
Geblokkeerde gebruikers Geblokkeerde gebruikers
|
||||
Hide system filesShow system files Hide system filesShow system files
|
||||
How tiles work:\nThe map is built from 256×256 "slippy" tiles. Only the tiles for the area you're viewing are fetched — there is no bulk pre-download.\n\nBecause this device can't do HTTPS (not enough heap after Wi-Fi starts) and decodes JPEG far more cheaply than PNG, tiles come from the wadamesh proxy: it fetches the upstream PNG over HTTPS with an identifying User-Agent, re-encodes it as JPEG, and caches it. Your device then caches each tile to its own flash, so a tile is only downloaded once.\n\nUse Options → Reload tiles to re-download the tiles currently in view if one looks corrupted. How tiles work:\nThe map is built from 256×256 "slippy" tiles. Only the tiles for the area you're viewing are fetched — there is no bulk pre-download.\n\nBecause this device can't do HTTPS (not enough heap after Wi-Fi starts) and decodes JPEG far more cheaply than PNG, tiles come from the wadamesh proxy: it fetches the upstream PNG over HTTPS with an identifying User-Agent, re-encodes it as JPEG, and caches it. Your device then caches each tile to its own flash, so a tile is only downloaded once.\n\nUse Options → Reload tiles to re-download the tiles currently in view if one looks corrupted.
|
||||
Init… Init…
|
||||
Leaving console mode - rebooting... Leaving console mode - rebooting...
|
||||
@@ -1062,8 +1054,6 @@ Map data © OpenStreetMap contributors.\nopenstreetmap.org/copyright\nLicensed u
|
||||
Map style © OpenTopoMap (CC-BY-SA) — opentopomap.org\nMap data © OpenStreetMap contributors (ODbL) + SRTM.\n\n Map style © OpenTopoMap (CC-BY-SA) — opentopomap.org\nMap data © OpenStreetMap contributors (ODbL) + SRTM.\n\n
|
||||
Name (A-Z) Name (A-Z)
|
||||
Nearest first Nearest first
|
||||
Other networksNetworks Other networksNetworks
|
||||
Paste (move)Paste (copy) Paste (move)Paste (copy)
|
||||
Recent message Recent message
|
||||
Reload tiles in view Reload tiles in view
|
||||
Repeaters Repeaters
|
||||
@@ -1076,13 +1066,8 @@ Show contacts Show contacts
|
||||
Show coordinates Show coordinates
|
||||
Show tile z/x/y Show tile z/x/y
|
||||
Starting… Starting…
|
||||
Stop sharing loc Share my loc Stop sharing loc Share my loc
|
||||
Supprimer Supprimer
|
||||
Switch to the external antenna?\n\nMake sure an antenna is actually connected to the external antenna socket first. Transmitting with nothing attached can damage the radio.\n\nResets to the on-board antenna on every reboot.Turn on legacy per-transmit switching?\n\nDiagnostic mode, for comparison only. It transmits on the on-board antenna and listens on the external one, so your outbound signal will be much weaker than your inbound.\n\nResets to the on-board antenna on every reboot. Switch to the external antenna?\n\nMake sure an antenna is actually connected to the external antenna socket first. Transmitting with nothing attached can damage the radio.\n\nResets to the on-board antenna on every reboot.Turn on legacy per-transmit switching?\n\nDiagnostic mode, for comparison only. It transmits on the on-board antenna and listens on the external one, so your outbound signal will be much weaker than your inbound.\n\nResets to the on-board antenna on every reboot.
|
||||
Text console (experimental) Text console (experimental)
|
||||
Tile debug overlay Tile debug overlay
|
||||
Unblock Block Unblock Block
|
||||
Unfav Favorite Unfav Favorite
|
||||
chats chats
|
||||
repeaters repeaters
|
||||
rooms rooms
|
||||
@@ -1098,3 +1083,23 @@ channel busy %.1f%% channel busy %.1f%%
|
||||
sampling... sampling...
|
||||
teal = now blue = average teal = now blue = average
|
||||
window restarted window restarted
|
||||
Filter Filter
|
||||
Hide system files Hide system files
|
||||
Networks Networks
|
||||
Other networks Other networks
|
||||
Show system files Show system files
|
||||
Unfav Unfav
|
||||
Firmware beta_%d\nChecking for updates over Wi-Fi… Firmware beta_%d\nChecking for updates over Wi-Fi…
|
||||
Install beta_%d?\nThis downgrades the firmware and reboots. Install beta_%d?\nThis downgrades the firmware and reboots.
|
||||
Other (hidden) network… Other (hidden) network…
|
||||
Paused Paused
|
||||
Scan again Scan again
|
||||
At a glance At a glance
|
||||
At a glance disabled At a glance disabled
|
||||
At a glance enabled At a glance enabled
|
||||
At a glance while locked At a glance while locked
|
||||
Glance only shows when unlocked Glance only shows when unlocked
|
||||
Glance shows message previews\nwhile the screen is locked Glance shows message previews\nwhile the screen is locked
|
||||
Snake — swipe, roll, or press a direction Snake — swipe, roll, or press a direction
|
||||
N/A N/A
|
||||
This board cannot run this app. This board cannot run this app.
|
||||
@@ -1,7 +1,7 @@
|
||||
# wadamesh language file
|
||||
# canonical source - translators: edit this file and PR it
|
||||
# code: fr
|
||||
# ver: 18
|
||||
# ver: 19
|
||||
# name: Français
|
||||
# base: fr
|
||||
(device behind) (appareil en retard)
|
||||
@@ -452,7 +452,6 @@ Login OK (perms %u) Connexion OK (perms %u)
|
||||
Login failed Échec de connexion
|
||||
Logs Journaux
|
||||
Longitude Longitude
|
||||
Lua Store Lua Store
|
||||
MHz MHz
|
||||
MQTT bridge Pont MQTT
|
||||
Map Carte
|
||||
@@ -977,8 +976,6 @@ tap to exit full screen toucher pour quitter le plein écran
|
||||
zoom zoom
|
||||
zoom %d zoom %d
|
||||
~17 dB amp for quiet/remote areas; turn off in noisy spots. ampli ~17 dB pour zones calmes/isolées ; désactiver en zone bruyante.
|
||||
© OpenStreetMap © OpenStreetMap
|
||||
© OpenTopoMap © OpenTopoMap
|
||||
Scope
|
||||
another region
|
||||
my region
|
||||
@@ -1000,7 +997,6 @@ Overwrite oldest non-favorite
|
||||
1-character messages will be shown 1-character messages will be shown
|
||||
All All
|
||||
Allow Allow
|
||||
Apps that may send messages on the mesh. Anything they send goes out under your node name and cannot be told apart from a message you typed. Turn one off to take the permission back. Apps that may send messages on the mesh. Anything they send goes out under your node name and cannot be told apart from a message you typed. Turn one off to take the permission back.
|
||||
Ignore 1-character messages Ignore 1-character messages
|
||||
Join w/ password Join w/ password
|
||||
Location no longer shared Location no longer shared
|
||||
@@ -1014,9 +1010,7 @@ Never\nChosen contacts only\nAnyone who asks Never\nChosen contacts only\nAnyone
|
||||
No apps installed. No apps installed.
|
||||
No reply. Path hash is %u bytes;\nolder repeaters drop those.\nTry 1 byte (Settings > Radio & Mesh) No reply. Path hash is %u bytes;\nolder repeaters drop those.\nTry 1 byte (Settings > Radio & Mesh)
|
||||
Not supported on this board Not supported on this board
|
||||
Read incoming messages Read incoming messages
|
||||
Reconnected to %.40s Reconnected to %.40s
|
||||
Send messages as me Send messages as me
|
||||
Sent only when that contact asks, encrypted to them. Nothing is broadcast. Needs "Answer telemetry requests" on. Pick contacts in a contact's menu. Sent only when that contact asks, encrypted to them. Nothing is broadcast. Needs "Answer telemetry requests" on. Pick contacts in a contact's menu.
|
||||
Server has forgotten you.\nJoin with the room password. Server has forgotten you.\nJoin with the room password.
|
||||
Share my loc Share my loc
|
||||
@@ -1042,7 +1036,6 @@ App permissions App permissions
|
||||
About / credits About / credits
|
||||
Auth failed Auth failed
|
||||
Auto-add on for %s — new %s land in Contacts automatically. Auto-add on for %s — new %s land in Contacts automatically.
|
||||
Batteryactivityon Batteryactivityon
|
||||
Console mode Console mode
|
||||
Console mode (experimental) Console mode (experimental)
|
||||
Direct (0-hop) only Direct (0-hop) only
|
||||
@@ -1051,8 +1044,6 @@ EXPERIMENTAL. Boots into a text console with no graphical interface: type comman
|
||||
Enter text Enter text
|
||||
Entering console mode - rebooting... Entering console mode - rebooting...
|
||||
Favorite Favorite
|
||||
Geblokkeerde gebruikers Geblokkeerde gebruikers
|
||||
Hide system filesShow system files Hide system filesShow system files
|
||||
How tiles work:\nThe map is built from 256×256 "slippy" tiles. Only the tiles for the area you're viewing are fetched — there is no bulk pre-download.\n\nBecause this device can't do HTTPS (not enough heap after Wi-Fi starts) and decodes JPEG far more cheaply than PNG, tiles come from the wadamesh proxy: it fetches the upstream PNG over HTTPS with an identifying User-Agent, re-encodes it as JPEG, and caches it. Your device then caches each tile to its own flash, so a tile is only downloaded once.\n\nUse Options → Reload tiles to re-download the tiles currently in view if one looks corrupted. How tiles work:\nThe map is built from 256×256 "slippy" tiles. Only the tiles for the area you're viewing are fetched — there is no bulk pre-download.\n\nBecause this device can't do HTTPS (not enough heap after Wi-Fi starts) and decodes JPEG far more cheaply than PNG, tiles come from the wadamesh proxy: it fetches the upstream PNG over HTTPS with an identifying User-Agent, re-encodes it as JPEG, and caches it. Your device then caches each tile to its own flash, so a tile is only downloaded once.\n\nUse Options → Reload tiles to re-download the tiles currently in view if one looks corrupted.
|
||||
Init… Init…
|
||||
Leaving console mode - rebooting... Leaving console mode - rebooting...
|
||||
@@ -1062,8 +1053,6 @@ Map data © OpenStreetMap contributors.\nopenstreetmap.org/copyright\nLicensed u
|
||||
Map style © OpenTopoMap (CC-BY-SA) — opentopomap.org\nMap data © OpenStreetMap contributors (ODbL) + SRTM.\n\n Map style © OpenTopoMap (CC-BY-SA) — opentopomap.org\nMap data © OpenStreetMap contributors (ODbL) + SRTM.\n\n
|
||||
Name (A-Z) Name (A-Z)
|
||||
Nearest first Nearest first
|
||||
Other networksNetworks Other networksNetworks
|
||||
Paste (move)Paste (copy) Paste (move)Paste (copy)
|
||||
Recent message Recent message
|
||||
Reload tiles in view Reload tiles in view
|
||||
Repeaters Repeaters
|
||||
@@ -1076,13 +1065,8 @@ Show contacts Show contacts
|
||||
Show coordinates Show coordinates
|
||||
Show tile z/x/y Show tile z/x/y
|
||||
Starting… Starting…
|
||||
Stop sharing loc Share my loc Stop sharing loc Share my loc
|
||||
Supprimer Supprimer
|
||||
Switch to the external antenna?\n\nMake sure an antenna is actually connected to the external antenna socket first. Transmitting with nothing attached can damage the radio.\n\nResets to the on-board antenna on every reboot.Turn on legacy per-transmit switching?\n\nDiagnostic mode, for comparison only. It transmits on the on-board antenna and listens on the external one, so your outbound signal will be much weaker than your inbound.\n\nResets to the on-board antenna on every reboot. Switch to the external antenna?\n\nMake sure an antenna is actually connected to the external antenna socket first. Transmitting with nothing attached can damage the radio.\n\nResets to the on-board antenna on every reboot.Turn on legacy per-transmit switching?\n\nDiagnostic mode, for comparison only. It transmits on the on-board antenna and listens on the external one, so your outbound signal will be much weaker than your inbound.\n\nResets to the on-board antenna on every reboot.
|
||||
Text console (experimental) Text console (experimental)
|
||||
Tile debug overlay Tile debug overlay
|
||||
Unblock Block Unblock Block
|
||||
Unfav Favorite Unfav Favorite
|
||||
chats chats
|
||||
repeaters repeaters
|
||||
rooms rooms
|
||||
@@ -1098,3 +1082,23 @@ channel busy %.1f%% channel busy %.1f%%
|
||||
sampling... sampling...
|
||||
teal = now blue = average teal = now blue = average
|
||||
window restarted window restarted
|
||||
Filter Filter
|
||||
Hide system files Hide system files
|
||||
Networks Networks
|
||||
Other networks Other networks
|
||||
Show system files Show system files
|
||||
Unfav Unfav
|
||||
Firmware beta_%d\nChecking for updates over Wi-Fi… Firmware beta_%d\nChecking for updates over Wi-Fi…
|
||||
Install beta_%d?\nThis downgrades the firmware and reboots. Install beta_%d?\nThis downgrades the firmware and reboots.
|
||||
Other (hidden) network… Other (hidden) network…
|
||||
Paused Paused
|
||||
Scan again Scan again
|
||||
At a glance At a glance
|
||||
At a glance disabled At a glance disabled
|
||||
At a glance enabled At a glance enabled
|
||||
At a glance while locked At a glance while locked
|
||||
Glance only shows when unlocked Glance only shows when unlocked
|
||||
Glance shows message previews\nwhile the screen is locked Glance shows message previews\nwhile the screen is locked
|
||||
Snake — swipe, roll, or press a direction Snake — swipe, roll, or press a direction
|
||||
N/A N/A
|
||||
This board cannot run this app. This board cannot run this app.
|
||||
@@ -1,7 +1,7 @@
|
||||
# wadamesh language file
|
||||
# canonical source - translators: edit this file and PR it
|
||||
# code: hu
|
||||
# ver: 18
|
||||
# ver: 19
|
||||
# name: Magyar
|
||||
# base: hu
|
||||
(device behind) (készülék hátulján)
|
||||
@@ -703,7 +703,7 @@ SD card remounted SD kártya újracsatolva
|
||||
SD card removed SD-kártya eltávolítva
|
||||
SD data is unavailable - contacts and channels cannot be saved until the card is reinserted. Az SD-kártya adatai nem érhetők el – a névjegyek és a csatornák nem menthetők a kártya újbóli behelyezéséig.
|
||||
SD data is unavailable - identity, settings, contacts and channels cannot be saved until the card is reinserted. Az SD-kártya adatai nem érhetők el – az azonosító, a beállítások, a névjegyek és a csatornák nem menthetők a kártya újbóli behelyezéséig.
|
||||
SD data migration is incomplete. Identity and settings remain internal; use Copy internal data to SD to retry. Az SD-kártya adatainak áthelyezése nem teljes. Az azonosító és a beállítások továbbra is a belső tárhelyen vannak; a folytatáshoz használd a „Belső adatok másolása SD-kártyára” lehetőséget.
|
||||
SD data migration is incomplete. Identity and settings remain internal; use Copy internal data to SD to retry. Az SD-kártya adatainak áthelyezése nem teljes. Az azonosító és a beállítások továbbra is a belső tárhelyen vannak; a folytatáshoz használd a "Belső adatok másolása SD-kártyára" lehetőséget.
|
||||
SD download failed: %s SD-letöltés sikertelen: %s
|
||||
SD formatted - %s (MESHCOMOD) SD formázás - %s (MESHCOMOD)
|
||||
SD is busy - close tools and retry Az SD-kártya foglalt – zárd be az eszközöket, majd próbáld újra.
|
||||
@@ -812,7 +812,6 @@ Sleep ready Készen áll az alvásra
|
||||
Slot %d cleared %d hely kiürítve
|
||||
Slot empty Üres hely
|
||||
Small\nMedium\nLarge Kicsi\nKözepes\nNagy
|
||||
Snake Snake
|
||||
Snake — swipe or roll the trackball Kígyó – pöccintsd vagy görgesd a görgetőgolyót
|
||||
Sort Sort
|
||||
Sound Hang
|
||||
@@ -997,8 +996,6 @@ tap to exit full screen koppintson a teljes képernyős kilépéshez
|
||||
zoom zoom
|
||||
zoom %d zoom %d
|
||||
~17 dB amp for quiet/remote areas; turn off in noisy spots. ~17 dB erősítővel csendes/távoli helyeken; zajos helyeken kapcsold ki.
|
||||
© OpenStreetMap © OpenStreetMap
|
||||
© OpenTopoMap © OpenTopoMap
|
||||
Scope Hatókör
|
||||
another region másik régió
|
||||
my region saját régió
|
||||
@@ -1065,11 +1062,10 @@ sensors érzékelők
|
||||
Auto-add on for %s — new %s land in Contacts automatically. Automatikus hozzáadás bekapcsolva: %s — az új %s automatikusan a Névjegyek közé kerülnek.
|
||||
Map data © OpenStreetMap contributors.\nopenstreetmap.org/copyright\nLicensed under the Open Database License (ODbL).\n\n Térkép adatok © OpenStreetMap közreműködői.\nopenstreetmap.org/copyright\nAz Open Database License (ODbL) licenc alatt.\n\n
|
||||
Map style © OpenTopoMap (CC-BY-SA) — opentopomap.org\nMap data © OpenStreetMap contributors (ODbL) + SRTM.\n\n Térkép stílus © OpenTopoMap (CC-BY-SA) — opentopomap.org\nTérkép adatok © OpenStreetMap közreműködői (ODbL) + SRTM.\n\n
|
||||
How tiles work:\nThe map is built from 256×256 "slippy" tiles. Only the tiles for the area you're viewing are fetched — there is no bulk pre-download.\n\nBecause this device can't do HTTPS (not enough heap after Wi-Fi starts) and decodes JPEG far more cheaply than PNG, tiles come from the wadamesh proxy: it fetches the upstream PNG over HTTPS with an identifying User-Agent, re-encodes it as JPEG, and caches it. Your device then caches each tile to its own flash, so a tile is only downloaded once.\n\nUse Options → Reload tiles to re-download the tiles currently in view if one looks corrupted. A csempék működése:\nA térkép 256×256 képpontos „slippy” csempékből épül fel. Csak a megtekintett területhez szükséges csempék töltődnek le — nincs tömeges előzetes letöltés.\n\nMivel az eszköz nem támogatja a HTTPS-t (a Wi-Fi elindítása után nincs elegendő heap memória), és a JPEG képek dekódolása sokkal kevesebb erőforrást igényel, mint a PNG-é, a csempék a wadamesh proxyn keresztül érkeznek: a proxy HTTPS-en keresztül lekéri a forrásból a PNG-t egy azonosító User-Agent használatával, JPEG formátumba kódolja át, majd eltárolja a gyorsítótárban. Az eszköz ezután minden csempét a saját flash memóriájában is eltárol, így egy csempét csak egyszer kell letölteni.\n\nAz Options → Reload tiles lehetőséggel újra letöltheted a jelenleg megtekintett terület csempéit, ha valamelyik hibásan jelenik meg.
|
||||
How tiles work:\nThe map is built from 256×256 "slippy" tiles. Only the tiles for the area you're viewing are fetched — there is no bulk pre-download.\n\nBecause this device can't do HTTPS (not enough heap after Wi-Fi starts) and decodes JPEG far more cheaply than PNG, tiles come from the wadamesh proxy: it fetches the upstream PNG over HTTPS with an identifying User-Agent, re-encodes it as JPEG, and caches it. Your device then caches each tile to its own flash, so a tile is only downloaded once.\n\nUse Options → Reload tiles to re-download the tiles currently in view if one looks corrupted. A csempék működése:\nA térkép 256×256 képpontos "slippy" csempékből épül fel. Csak a megtekintett területhez szükséges csempék töltődnek le — nincs tömeges előzetes letöltés.\n\nMivel az eszköz nem támogatja a HTTPS-t (a Wi-Fi elindítása után nincs elegendő heap memória), és a JPEG képek dekódolása sokkal kevesebb erőforrást igényel, mint a PNG-é, a csempék a wadamesh proxyn keresztül érkeznek: a proxy HTTPS-en keresztül lekéri a forrásból a PNG-t egy azonosító User-Agent használatával, JPEG formátumba kódolja át, majd eltárolja a gyorsítótárban. Az eszköz ezután minden csempét a saját flash memóriájában is eltárol, így egy csempét csak egyszer kell letölteni.\n\nAz Options → Reload tiles lehetőséggel újra letöltheted a jelenleg megtekintett terület csempéit, ha valamelyik hibásan jelenik meg.
|
||||
"%s" wants to send discovery probes.\n\nEach probe asks every node in range to reply, so it uses airtime on the whole local mesh, not just yours.\n\nNothing is sent under your name and no message is transmitted. "%s" felfedezési probe-ok küldését kéri.\n\nMinden probe válaszra kéri a hatótávolságon belüli node-okat, ezért a teljes helyi mesh hálózat airtime-ját használja, nem csak a tiédet.\n\nSemmi sem kerül elküldésre a nevedben, és üzenet sem kerül továbbításra.
|
||||
? ?
|
||||
Auth failed Auth failed
|
||||
Batteryactivityon Batteryactivityon
|
||||
Console mode Console mode
|
||||
Console mode (experimental) Console mode (experimental)
|
||||
Direct (0-hop) only Csak közvetlen (0-hop)
|
||||
@@ -1078,16 +1074,12 @@ EXPERIMENTAL. Boots into a text console with no graphical interface: type comman
|
||||
Enter text Szöveg megadása
|
||||
Entering console mode - rebooting... Entering console mode - rebooting...
|
||||
Favorite Favorite
|
||||
Geblokkeerde gebruikers Geblokkeerde gebruikers
|
||||
Hide system filesShow system files Hide system filesShow system files
|
||||
Init… Init…
|
||||
Leaving console mode - rebooting... Leaving console mode - rebooting...
|
||||
Link lost Link lost
|
||||
MESH MESH
|
||||
Name (A-Z) Név (A–Z)
|
||||
Nearest first Legközelebbi elöl
|
||||
Other networksNetworks Other networksNetworks
|
||||
Paste (move)Paste (copy) Paste (move)Paste (copy)
|
||||
Recent message Legutóbbi üzenet
|
||||
Reload tiles in view Csempék újratöltése
|
||||
Repeaters Repeaters
|
||||
@@ -1098,13 +1090,8 @@ Show contacts Névjegyek megjelenítése
|
||||
Show coordinates Koordináták
|
||||
Show tile z/x/y Csempe z/x/y
|
||||
Starting… Starting…
|
||||
Stop sharing loc Share my loc Stop sharing loc Share my loc
|
||||
Supprimer Supprimer
|
||||
Switch to the external antenna?\n\nMake sure an antenna is actually connected to the external antenna socket first. Transmitting with nothing attached can damage the radio.\n\nResets to the on-board antenna on every reboot.Turn on legacy per-transmit switching?\n\nDiagnostic mode, for comparison only. It transmits on the on-board antenna and listens on the external one, so your outbound signal will be much weaker than your inbound.\n\nResets to the on-board antenna on every reboot. Switch to the external antenna?\n\nMake sure an antenna is actually connected to the external antenna socket first. Transmitting with nothing attached can damage the radio.\n\nResets to the on-board antenna on every reboot.Turn on legacy per-transmit switching?\n\nDiagnostic mode, for comparison only. It transmits on the on-board antenna and listens on the external one, so your outbound signal will be much weaker than your inbound.\n\nResets to the on-board antenna on every reboot.
|
||||
Text console (experimental) Text console (experimental)
|
||||
Tile debug overlay Csempe hiba keret
|
||||
Unblock Block Unblock Block
|
||||
Unfav Favorite Unfav Favorite
|
||||
tap to cycle off / on / auto tap to cycle off / on / auto
|
||||
#7A7F87 Scanning… nothing has answered yet# #7A7F87 Scanning… nothing has answered yet#
|
||||
Export crash report (%uK) Hibajelentés exportálása (%uK)
|
||||
@@ -1116,3 +1103,21 @@ channel busy %.1f%% channel busy %.1f%%
|
||||
sampling... sampling...
|
||||
teal = now blue = average teal = now blue = average
|
||||
window restarted window restarted
|
||||
Filter Filter
|
||||
Hide system files Hide system files
|
||||
Networks Networks
|
||||
Show system files Show system files
|
||||
Unfav Unfav
|
||||
Firmware beta_%d\nChecking for updates over Wi-Fi… Firmware beta_%d\nChecking for updates over Wi-Fi…
|
||||
Install beta_%d?\nThis downgrades the firmware and reboots. A beta_%d telepítése?\nEz leminősíti a firmware-t és újraindul.
|
||||
Other (hidden) network… Other (hidden) network…
|
||||
Paused Paused
|
||||
At a glance At a glance
|
||||
At a glance disabled At a glance disabled
|
||||
At a glance enabled At a glance enabled
|
||||
At a glance while locked At a glance while locked
|
||||
Glance only shows when unlocked Glance only shows when unlocked
|
||||
Glance shows message previews\nwhile the screen is locked Glance shows message previews\nwhile the screen is locked
|
||||
Snake — swipe, roll, or press a direction Snake — swipe, roll, or press a direction
|
||||
N/A N/A
|
||||
This board cannot run this app. This board cannot run this app.
|
||||
@@ -1,7 +1,7 @@
|
||||
# wadamesh language file
|
||||
# canonical source - translators: edit this file and PR it
|
||||
# code: it
|
||||
# ver: 18
|
||||
# ver: 19
|
||||
# name: Italiano
|
||||
# base: it
|
||||
(device behind) (dispositivo indietro)
|
||||
@@ -452,7 +452,6 @@ Login OK (perms %u) Accesso OK (permessi %u)
|
||||
Login failed Accesso non riuscito
|
||||
Logs Log
|
||||
Longitude Longitudine
|
||||
Lua Store Lua Store
|
||||
MHz MHz
|
||||
MQTT bridge Ponte MQTT
|
||||
Map Mappa
|
||||
@@ -977,8 +976,6 @@ tap to exit full screen tocca per uscire da schermo intero
|
||||
zoom zoom
|
||||
zoom %d zoom %d
|
||||
~17 dB amp for quiet/remote areas; turn off in noisy spots. ampli ~17 dB per zone tranquille/remote; spegni in zone rumorose.
|
||||
© OpenStreetMap © OpenStreetMap
|
||||
© OpenTopoMap © OpenTopoMap
|
||||
Scope
|
||||
another region
|
||||
my region
|
||||
@@ -1000,7 +997,6 @@ Overwrite oldest non-favorite
|
||||
1-character messages will be shown 1-character messages will be shown
|
||||
All All
|
||||
Allow Allow
|
||||
Apps that may send messages on the mesh. Anything they send goes out under your node name and cannot be told apart from a message you typed. Turn one off to take the permission back. Apps that may send messages on the mesh. Anything they send goes out under your node name and cannot be told apart from a message you typed. Turn one off to take the permission back.
|
||||
Ignore 1-character messages Ignore 1-character messages
|
||||
Join w/ password Join w/ password
|
||||
Location no longer shared Location no longer shared
|
||||
@@ -1014,9 +1010,7 @@ Never\nChosen contacts only\nAnyone who asks Never\nChosen contacts only\nAnyone
|
||||
No apps installed. No apps installed.
|
||||
No reply. Path hash is %u bytes;\nolder repeaters drop those.\nTry 1 byte (Settings > Radio & Mesh) No reply. Path hash is %u bytes;\nolder repeaters drop those.\nTry 1 byte (Settings > Radio & Mesh)
|
||||
Not supported on this board Not supported on this board
|
||||
Read incoming messages Read incoming messages
|
||||
Reconnected to %.40s Reconnected to %.40s
|
||||
Send messages as me Send messages as me
|
||||
Sent only when that contact asks, encrypted to them. Nothing is broadcast. Needs "Answer telemetry requests" on. Pick contacts in a contact's menu. Sent only when that contact asks, encrypted to them. Nothing is broadcast. Needs "Answer telemetry requests" on. Pick contacts in a contact's menu.
|
||||
Server has forgotten you.\nJoin with the room password. Server has forgotten you.\nJoin with the room password.
|
||||
Share my loc Share my loc
|
||||
@@ -1042,7 +1036,6 @@ App permissions App permissions
|
||||
About / credits About / credits
|
||||
Auth failed Auth failed
|
||||
Auto-add on for %s — new %s land in Contacts automatically. Auto-add on for %s — new %s land in Contacts automatically.
|
||||
Batteryactivityon Batteryactivityon
|
||||
Console mode Console mode
|
||||
Console mode (experimental) Console mode (experimental)
|
||||
Direct (0-hop) only Direct (0-hop) only
|
||||
@@ -1051,8 +1044,6 @@ EXPERIMENTAL. Boots into a text console with no graphical interface: type comman
|
||||
Enter text Enter text
|
||||
Entering console mode - rebooting... Entering console mode - rebooting...
|
||||
Favorite Favorite
|
||||
Geblokkeerde gebruikers Geblokkeerde gebruikers
|
||||
Hide system filesShow system files Hide system filesShow system files
|
||||
How tiles work:\nThe map is built from 256×256 "slippy" tiles. Only the tiles for the area you're viewing are fetched — there is no bulk pre-download.\n\nBecause this device can't do HTTPS (not enough heap after Wi-Fi starts) and decodes JPEG far more cheaply than PNG, tiles come from the wadamesh proxy: it fetches the upstream PNG over HTTPS with an identifying User-Agent, re-encodes it as JPEG, and caches it. Your device then caches each tile to its own flash, so a tile is only downloaded once.\n\nUse Options → Reload tiles to re-download the tiles currently in view if one looks corrupted. How tiles work:\nThe map is built from 256×256 "slippy" tiles. Only the tiles for the area you're viewing are fetched — there is no bulk pre-download.\n\nBecause this device can't do HTTPS (not enough heap after Wi-Fi starts) and decodes JPEG far more cheaply than PNG, tiles come from the wadamesh proxy: it fetches the upstream PNG over HTTPS with an identifying User-Agent, re-encodes it as JPEG, and caches it. Your device then caches each tile to its own flash, so a tile is only downloaded once.\n\nUse Options → Reload tiles to re-download the tiles currently in view if one looks corrupted.
|
||||
Init… Init…
|
||||
Leaving console mode - rebooting... Leaving console mode - rebooting...
|
||||
@@ -1062,8 +1053,6 @@ Map data © OpenStreetMap contributors.\nopenstreetmap.org/copyright\nLicensed u
|
||||
Map style © OpenTopoMap (CC-BY-SA) — opentopomap.org\nMap data © OpenStreetMap contributors (ODbL) + SRTM.\n\n Map style © OpenTopoMap (CC-BY-SA) — opentopomap.org\nMap data © OpenStreetMap contributors (ODbL) + SRTM.\n\n
|
||||
Name (A-Z) Name (A-Z)
|
||||
Nearest first Nearest first
|
||||
Other networksNetworks Other networksNetworks
|
||||
Paste (move)Paste (copy) Paste (move)Paste (copy)
|
||||
Recent message Recent message
|
||||
Reload tiles in view Reload tiles in view
|
||||
Repeaters Repeaters
|
||||
@@ -1076,13 +1065,8 @@ Show contacts Show contacts
|
||||
Show coordinates Show coordinates
|
||||
Show tile z/x/y Show tile z/x/y
|
||||
Starting… Starting…
|
||||
Stop sharing loc Share my loc Stop sharing loc Share my loc
|
||||
Supprimer Supprimer
|
||||
Switch to the external antenna?\n\nMake sure an antenna is actually connected to the external antenna socket first. Transmitting with nothing attached can damage the radio.\n\nResets to the on-board antenna on every reboot.Turn on legacy per-transmit switching?\n\nDiagnostic mode, for comparison only. It transmits on the on-board antenna and listens on the external one, so your outbound signal will be much weaker than your inbound.\n\nResets to the on-board antenna on every reboot. Switch to the external antenna?\n\nMake sure an antenna is actually connected to the external antenna socket first. Transmitting with nothing attached can damage the radio.\n\nResets to the on-board antenna on every reboot.Turn on legacy per-transmit switching?\n\nDiagnostic mode, for comparison only. It transmits on the on-board antenna and listens on the external one, so your outbound signal will be much weaker than your inbound.\n\nResets to the on-board antenna on every reboot.
|
||||
Text console (experimental) Text console (experimental)
|
||||
Tile debug overlay Tile debug overlay
|
||||
Unblock Block Unblock Block
|
||||
Unfav Favorite Unfav Favorite
|
||||
chats chats
|
||||
repeaters repeaters
|
||||
rooms rooms
|
||||
@@ -1098,3 +1082,23 @@ channel busy %.1f%% channel busy %.1f%%
|
||||
sampling... sampling...
|
||||
teal = now blue = average teal = now blue = average
|
||||
window restarted window restarted
|
||||
Filter Filter
|
||||
Hide system files Hide system files
|
||||
Networks Networks
|
||||
Other networks Other networks
|
||||
Show system files Show system files
|
||||
Unfav Unfav
|
||||
Firmware beta_%d\nChecking for updates over Wi-Fi… Firmware beta_%d\nChecking for updates over Wi-Fi…
|
||||
Install beta_%d?\nThis downgrades the firmware and reboots. Install beta_%d?\nThis downgrades the firmware and reboots.
|
||||
Other (hidden) network… Other (hidden) network…
|
||||
Paused Paused
|
||||
Scan again Scan again
|
||||
At a glance At a glance
|
||||
At a glance disabled At a glance disabled
|
||||
At a glance enabled At a glance enabled
|
||||
At a glance while locked At a glance while locked
|
||||
Glance only shows when unlocked Glance only shows when unlocked
|
||||
Glance shows message previews\nwhile the screen is locked Glance shows message previews\nwhile the screen is locked
|
||||
Snake — swipe, roll, or press a direction Snake — swipe, roll, or press a direction
|
||||
N/A N/A
|
||||
This board cannot run this app. This board cannot run this app.
|
||||
@@ -1,7 +1,7 @@
|
||||
# wadamesh language file
|
||||
# canonical source - translators: edit this file and PR it
|
||||
# code: nl
|
||||
# ver: 18
|
||||
# ver: 19
|
||||
# name: Nederlands
|
||||
# base: nl
|
||||
(device behind) (apparaat loopt achter)
|
||||
@@ -452,7 +452,6 @@ Login OK (perms %u) Inloggen OK (rechten %u)
|
||||
Login failed Inloggen mislukt
|
||||
Logs Logboeken
|
||||
Longitude Lengtegraad
|
||||
Lua Store Lua Store
|
||||
MHz MHz
|
||||
MQTT bridge MQTT-brug
|
||||
Map Kaart
|
||||
@@ -797,7 +796,6 @@ Sleep ready Klaar voor slaap
|
||||
Slot %d cleared Slot %d gewist
|
||||
Slot empty Slot leeg
|
||||
Small\nMedium\nLarge Klein\nMiddel\nGroot
|
||||
Snake Snake
|
||||
Snake — swipe or roll the trackball Snake — veeg of rol de trackball
|
||||
Sort Sorteer
|
||||
Sound Geluid
|
||||
@@ -805,7 +803,6 @@ Sound off Geluid uit
|
||||
Sound on Geluid aan
|
||||
Sound: %s Geluid: %s
|
||||
Start time Starttijd
|
||||
Status Status
|
||||
Step 1 of 3 Stap 1 van 3
|
||||
Step 2 of 3 Stap 2 van 3
|
||||
Step 3 of 3 Stap 3 van 3
|
||||
@@ -977,8 +974,6 @@ tap to exit full screen tik om volledig scherm te verlaten
|
||||
zoom zoom
|
||||
zoom %d zoom %d
|
||||
~17 dB amp for quiet/remote areas; turn off in noisy spots. ~17 dB versterker voor stille/afgelegen gebieden; uit in drukke plekken.
|
||||
© OpenStreetMap © OpenStreetMap
|
||||
© OpenTopoMap © OpenTopoMap
|
||||
Scope
|
||||
another region
|
||||
my region
|
||||
@@ -1000,7 +995,6 @@ Overwrite oldest non-favorite
|
||||
1-character messages will be shown 1-character messages will be shown
|
||||
All All
|
||||
Allow Allow
|
||||
Apps that may send messages on the mesh. Anything they send goes out under your node name and cannot be told apart from a message you typed. Turn one off to take the permission back. Apps that may send messages on the mesh. Anything they send goes out under your node name and cannot be told apart from a message you typed. Turn one off to take the permission back.
|
||||
Ignore 1-character messages Ignore 1-character messages
|
||||
Join w/ password Join w/ password
|
||||
Location no longer shared Location no longer shared
|
||||
@@ -1014,9 +1008,7 @@ Never\nChosen contacts only\nAnyone who asks Never\nChosen contacts only\nAnyone
|
||||
No apps installed. No apps installed.
|
||||
No reply. Path hash is %u bytes;\nolder repeaters drop those.\nTry 1 byte (Settings > Radio & Mesh) No reply. Path hash is %u bytes;\nolder repeaters drop those.\nTry 1 byte (Settings > Radio & Mesh)
|
||||
Not supported on this board Not supported on this board
|
||||
Read incoming messages Read incoming messages
|
||||
Reconnected to %.40s Reconnected to %.40s
|
||||
Send messages as me Send messages as me
|
||||
Sent only when that contact asks, encrypted to them. Nothing is broadcast. Needs "Answer telemetry requests" on. Pick contacts in a contact's menu. Sent only when that contact asks, encrypted to them. Nothing is broadcast. Needs "Answer telemetry requests" on. Pick contacts in a contact's menu.
|
||||
Server has forgotten you.\nJoin with the room password. Server has forgotten you.\nJoin with the room password.
|
||||
Share my loc Share my loc
|
||||
@@ -1042,7 +1034,6 @@ App permissions App permissions
|
||||
About / credits About / credits
|
||||
Auth failed Auth failed
|
||||
Auto-add on for %s — new %s land in Contacts automatically. Auto-add on for %s — new %s land in Contacts automatically.
|
||||
Batteryactivityon Batteryactivityon
|
||||
Console mode Console mode
|
||||
Console mode (experimental) Console mode (experimental)
|
||||
Direct (0-hop) only Direct (0-hop) only
|
||||
@@ -1051,8 +1042,6 @@ EXPERIMENTAL. Boots into a text console with no graphical interface: type comman
|
||||
Enter text Enter text
|
||||
Entering console mode - rebooting... Entering console mode - rebooting...
|
||||
Favorite Favorite
|
||||
Geblokkeerde gebruikers Geblokkeerde gebruikers
|
||||
Hide system filesShow system files Hide system filesShow system files
|
||||
How tiles work:\nThe map is built from 256×256 "slippy" tiles. Only the tiles for the area you're viewing are fetched — there is no bulk pre-download.\n\nBecause this device can't do HTTPS (not enough heap after Wi-Fi starts) and decodes JPEG far more cheaply than PNG, tiles come from the wadamesh proxy: it fetches the upstream PNG over HTTPS with an identifying User-Agent, re-encodes it as JPEG, and caches it. Your device then caches each tile to its own flash, so a tile is only downloaded once.\n\nUse Options → Reload tiles to re-download the tiles currently in view if one looks corrupted. How tiles work:\nThe map is built from 256×256 "slippy" tiles. Only the tiles for the area you're viewing are fetched — there is no bulk pre-download.\n\nBecause this device can't do HTTPS (not enough heap after Wi-Fi starts) and decodes JPEG far more cheaply than PNG, tiles come from the wadamesh proxy: it fetches the upstream PNG over HTTPS with an identifying User-Agent, re-encodes it as JPEG, and caches it. Your device then caches each tile to its own flash, so a tile is only downloaded once.\n\nUse Options → Reload tiles to re-download the tiles currently in view if one looks corrupted.
|
||||
Init… Init…
|
||||
Leaving console mode - rebooting... Leaving console mode - rebooting...
|
||||
@@ -1062,8 +1051,6 @@ Map data © OpenStreetMap contributors.\nopenstreetmap.org/copyright\nLicensed u
|
||||
Map style © OpenTopoMap (CC-BY-SA) — opentopomap.org\nMap data © OpenStreetMap contributors (ODbL) + SRTM.\n\n Map style © OpenTopoMap (CC-BY-SA) — opentopomap.org\nMap data © OpenStreetMap contributors (ODbL) + SRTM.\n\n
|
||||
Name (A-Z) Name (A-Z)
|
||||
Nearest first Nearest first
|
||||
Other networksNetworks Other networksNetworks
|
||||
Paste (move)Paste (copy) Paste (move)Paste (copy)
|
||||
Recent message Recent message
|
||||
Reload tiles in view Reload tiles in view
|
||||
Repeaters Repeaters
|
||||
@@ -1076,13 +1063,8 @@ Show contacts Show contacts
|
||||
Show coordinates Show coordinates
|
||||
Show tile z/x/y Show tile z/x/y
|
||||
Starting… Starting…
|
||||
Stop sharing loc Share my loc Stop sharing loc Share my loc
|
||||
Supprimer Supprimer
|
||||
Switch to the external antenna?\n\nMake sure an antenna is actually connected to the external antenna socket first. Transmitting with nothing attached can damage the radio.\n\nResets to the on-board antenna on every reboot.Turn on legacy per-transmit switching?\n\nDiagnostic mode, for comparison only. It transmits on the on-board antenna and listens on the external one, so your outbound signal will be much weaker than your inbound.\n\nResets to the on-board antenna on every reboot. Switch to the external antenna?\n\nMake sure an antenna is actually connected to the external antenna socket first. Transmitting with nothing attached can damage the radio.\n\nResets to the on-board antenna on every reboot.Turn on legacy per-transmit switching?\n\nDiagnostic mode, for comparison only. It transmits on the on-board antenna and listens on the external one, so your outbound signal will be much weaker than your inbound.\n\nResets to the on-board antenna on every reboot.
|
||||
Text console (experimental) Text console (experimental)
|
||||
Tile debug overlay Tile debug overlay
|
||||
Unblock Block Unblock Block
|
||||
Unfav Favorite Unfav Favorite
|
||||
chats chats
|
||||
repeaters repeaters
|
||||
rooms rooms
|
||||
@@ -1098,3 +1080,23 @@ channel busy %.1f%% channel busy %.1f%%
|
||||
sampling... sampling...
|
||||
teal = now blue = average teal = now blue = average
|
||||
window restarted window restarted
|
||||
Filter Filter
|
||||
Hide system files Hide system files
|
||||
Networks Networks
|
||||
Other networks Other networks
|
||||
Show system files Show system files
|
||||
Unfav Unfav
|
||||
Firmware beta_%d\nChecking for updates over Wi-Fi… Firmware beta_%d\nChecking for updates over Wi-Fi…
|
||||
Install beta_%d?\nThis downgrades the firmware and reboots. Install beta_%d?\nThis downgrades the firmware and reboots.
|
||||
Other (hidden) network… Other (hidden) network…
|
||||
Paused Paused
|
||||
Scan again Scan again
|
||||
At a glance At a glance
|
||||
At a glance disabled At a glance disabled
|
||||
At a glance enabled At a glance enabled
|
||||
At a glance while locked At a glance while locked
|
||||
Glance only shows when unlocked Glance only shows when unlocked
|
||||
Glance shows message previews\nwhile the screen is locked Glance shows message previews\nwhile the screen is locked
|
||||
Snake — swipe, roll, or press a direction Snake — swipe, roll, or press a direction
|
||||
N/A N/A
|
||||
This board cannot run this app. This board cannot run this app.
|
||||
@@ -1,7 +1,7 @@
|
||||
# wadamesh language file
|
||||
# canonical source - translators: edit this file and PR it
|
||||
# code: pt-br
|
||||
# ver: 18
|
||||
# ver: 19
|
||||
# name: Português (BR)
|
||||
# base: pt-br
|
||||
(device behind) (dispositivo atrasado)
|
||||
@@ -805,7 +805,6 @@ Sound off Som desligado
|
||||
Sound on Som ligado
|
||||
Sound: %s Som: %s
|
||||
Start time Hora de início
|
||||
Status Status
|
||||
Step 1 of 3 Passo 1 de 3
|
||||
Step 2 of 3 Passo 2 de 3
|
||||
Step 3 of 3 Passo 3 de 3
|
||||
@@ -977,8 +976,6 @@ tap to exit full screen toque para sair da tela cheia
|
||||
zoom zoom
|
||||
zoom %d zoom %d
|
||||
~17 dB amp for quiet/remote areas; turn off in noisy spots. amplificador ~17 dB para áreas silenciosas/remotas; desligue em locais ruidosos.
|
||||
© OpenStreetMap © OpenStreetMap
|
||||
© OpenTopoMap © OpenTopoMap
|
||||
Scope
|
||||
another region
|
||||
my region
|
||||
@@ -1000,7 +997,6 @@ Overwrite oldest non-favorite
|
||||
1-character messages will be shown 1-character messages will be shown
|
||||
All All
|
||||
Allow Allow
|
||||
Apps that may send messages on the mesh. Anything they send goes out under your node name and cannot be told apart from a message you typed. Turn one off to take the permission back. Apps that may send messages on the mesh. Anything they send goes out under your node name and cannot be told apart from a message you typed. Turn one off to take the permission back.
|
||||
Ignore 1-character messages Ignore 1-character messages
|
||||
Join w/ password Join w/ password
|
||||
Location no longer shared Location no longer shared
|
||||
@@ -1014,9 +1010,7 @@ Never\nChosen contacts only\nAnyone who asks Never\nChosen contacts only\nAnyone
|
||||
No apps installed. No apps installed.
|
||||
No reply. Path hash is %u bytes;\nolder repeaters drop those.\nTry 1 byte (Settings > Radio & Mesh) No reply. Path hash is %u bytes;\nolder repeaters drop those.\nTry 1 byte (Settings > Radio & Mesh)
|
||||
Not supported on this board Not supported on this board
|
||||
Read incoming messages Read incoming messages
|
||||
Reconnected to %.40s Reconnected to %.40s
|
||||
Send messages as me Send messages as me
|
||||
Sent only when that contact asks, encrypted to them. Nothing is broadcast. Needs "Answer telemetry requests" on. Pick contacts in a contact's menu. Sent only when that contact asks, encrypted to them. Nothing is broadcast. Needs "Answer telemetry requests" on. Pick contacts in a contact's menu.
|
||||
Server has forgotten you.\nJoin with the room password. Server has forgotten you.\nJoin with the room password.
|
||||
Share my loc Share my loc
|
||||
@@ -1042,7 +1036,6 @@ App permissions App permissions
|
||||
About / credits About / credits
|
||||
Auth failed Auth failed
|
||||
Auto-add on for %s — new %s land in Contacts automatically. Auto-add on for %s — new %s land in Contacts automatically.
|
||||
Batteryactivityon Batteryactivityon
|
||||
Console mode Console mode
|
||||
Console mode (experimental) Console mode (experimental)
|
||||
Direct (0-hop) only Direct (0-hop) only
|
||||
@@ -1051,8 +1044,6 @@ EXPERIMENTAL. Boots into a text console with no graphical interface: type comman
|
||||
Enter text Enter text
|
||||
Entering console mode - rebooting... Entering console mode - rebooting...
|
||||
Favorite Favorite
|
||||
Geblokkeerde gebruikers Geblokkeerde gebruikers
|
||||
Hide system filesShow system files Hide system filesShow system files
|
||||
How tiles work:\nThe map is built from 256×256 "slippy" tiles. Only the tiles for the area you're viewing are fetched — there is no bulk pre-download.\n\nBecause this device can't do HTTPS (not enough heap after Wi-Fi starts) and decodes JPEG far more cheaply than PNG, tiles come from the wadamesh proxy: it fetches the upstream PNG over HTTPS with an identifying User-Agent, re-encodes it as JPEG, and caches it. Your device then caches each tile to its own flash, so a tile is only downloaded once.\n\nUse Options → Reload tiles to re-download the tiles currently in view if one looks corrupted. How tiles work:\nThe map is built from 256×256 "slippy" tiles. Only the tiles for the area you're viewing are fetched — there is no bulk pre-download.\n\nBecause this device can't do HTTPS (not enough heap after Wi-Fi starts) and decodes JPEG far more cheaply than PNG, tiles come from the wadamesh proxy: it fetches the upstream PNG over HTTPS with an identifying User-Agent, re-encodes it as JPEG, and caches it. Your device then caches each tile to its own flash, so a tile is only downloaded once.\n\nUse Options → Reload tiles to re-download the tiles currently in view if one looks corrupted.
|
||||
Init… Init…
|
||||
Leaving console mode - rebooting... Leaving console mode - rebooting...
|
||||
@@ -1062,8 +1053,6 @@ Map data © OpenStreetMap contributors.\nopenstreetmap.org/copyright\nLicensed u
|
||||
Map style © OpenTopoMap (CC-BY-SA) — opentopomap.org\nMap data © OpenStreetMap contributors (ODbL) + SRTM.\n\n Map style © OpenTopoMap (CC-BY-SA) — opentopomap.org\nMap data © OpenStreetMap contributors (ODbL) + SRTM.\n\n
|
||||
Name (A-Z) Name (A-Z)
|
||||
Nearest first Nearest first
|
||||
Other networksNetworks Other networksNetworks
|
||||
Paste (move)Paste (copy) Paste (move)Paste (copy)
|
||||
Recent message Recent message
|
||||
Reload tiles in view Reload tiles in view
|
||||
Repeaters Repeaters
|
||||
@@ -1076,13 +1065,8 @@ Show contacts Show contacts
|
||||
Show coordinates Show coordinates
|
||||
Show tile z/x/y Show tile z/x/y
|
||||
Starting… Starting…
|
||||
Stop sharing loc Share my loc Stop sharing loc Share my loc
|
||||
Supprimer Supprimer
|
||||
Switch to the external antenna?\n\nMake sure an antenna is actually connected to the external antenna socket first. Transmitting with nothing attached can damage the radio.\n\nResets to the on-board antenna on every reboot.Turn on legacy per-transmit switching?\n\nDiagnostic mode, for comparison only. It transmits on the on-board antenna and listens on the external one, so your outbound signal will be much weaker than your inbound.\n\nResets to the on-board antenna on every reboot. Switch to the external antenna?\n\nMake sure an antenna is actually connected to the external antenna socket first. Transmitting with nothing attached can damage the radio.\n\nResets to the on-board antenna on every reboot.Turn on legacy per-transmit switching?\n\nDiagnostic mode, for comparison only. It transmits on the on-board antenna and listens on the external one, so your outbound signal will be much weaker than your inbound.\n\nResets to the on-board antenna on every reboot.
|
||||
Text console (experimental) Text console (experimental)
|
||||
Tile debug overlay Tile debug overlay
|
||||
Unblock Block Unblock Block
|
||||
Unfav Favorite Unfav Favorite
|
||||
chats chats
|
||||
repeaters repeaters
|
||||
rooms rooms
|
||||
@@ -1098,3 +1082,23 @@ channel busy %.1f%% channel busy %.1f%%
|
||||
sampling... sampling...
|
||||
teal = now blue = average teal = now blue = average
|
||||
window restarted window restarted
|
||||
Filter Filter
|
||||
Hide system files Hide system files
|
||||
Networks Networks
|
||||
Other networks Other networks
|
||||
Show system files Show system files
|
||||
Unfav Unfav
|
||||
Firmware beta_%d\nChecking for updates over Wi-Fi… Firmware beta_%d\nChecking for updates over Wi-Fi…
|
||||
Install beta_%d?\nThis downgrades the firmware and reboots. Install beta_%d?\nThis downgrades the firmware and reboots.
|
||||
Other (hidden) network… Other (hidden) network…
|
||||
Paused Paused
|
||||
Scan again Scan again
|
||||
At a glance At a glance
|
||||
At a glance disabled At a glance disabled
|
||||
At a glance enabled At a glance enabled
|
||||
At a glance while locked At a glance while locked
|
||||
Glance only shows when unlocked Glance only shows when unlocked
|
||||
Glance shows message previews\nwhile the screen is locked Glance shows message previews\nwhile the screen is locked
|
||||
Snake — swipe, roll, or press a direction Snake — swipe, roll, or press a direction
|
||||
N/A N/A
|
||||
This board cannot run this app. This board cannot run this app.
|
||||
@@ -1,7 +1,7 @@
|
||||
# wadamesh language file
|
||||
# canonical source - translators: edit this file and PR it
|
||||
# code: ro
|
||||
# ver: 18
|
||||
# ver: 19
|
||||
# name: Română
|
||||
# base: ro
|
||||
(device behind) (dispozitiv în urmă)
|
||||
@@ -978,7 +978,6 @@ zoom zoom
|
||||
zoom %d zoom %d
|
||||
~17 dB amp for quiet/remote areas; turn off in noisy spots. amplificator ~17 dB pentru zone liniştite/izolate; opreşte-l în locuri zgomotoase.
|
||||
© OpenStreetMap
|
||||
© OpenTopoMap © OpenTopoMap
|
||||
Scope
|
||||
another region
|
||||
my region
|
||||
@@ -1000,7 +999,6 @@ Overwrite oldest non-favorite
|
||||
1-character messages will be shown 1-character messages will be shown
|
||||
All All
|
||||
Allow Allow
|
||||
Apps that may send messages on the mesh. Anything they send goes out under your node name and cannot be told apart from a message you typed. Turn one off to take the permission back. Apps that may send messages on the mesh. Anything they send goes out under your node name and cannot be told apart from a message you typed. Turn one off to take the permission back.
|
||||
Ignore 1-character messages Ignore 1-character messages
|
||||
Join w/ password Join w/ password
|
||||
Location no longer shared Location no longer shared
|
||||
@@ -1014,9 +1012,7 @@ Never\nChosen contacts only\nAnyone who asks Never\nChosen contacts only\nAnyone
|
||||
No apps installed. No apps installed.
|
||||
No reply. Path hash is %u bytes;\nolder repeaters drop those.\nTry 1 byte (Settings > Radio & Mesh) No reply. Path hash is %u bytes;\nolder repeaters drop those.\nTry 1 byte (Settings > Radio & Mesh)
|
||||
Not supported on this board Not supported on this board
|
||||
Read incoming messages Read incoming messages
|
||||
Reconnected to %.40s Reconnected to %.40s
|
||||
Send messages as me Send messages as me
|
||||
Sent only when that contact asks, encrypted to them. Nothing is broadcast. Needs "Answer telemetry requests" on. Pick contacts in a contact's menu. Sent only when that contact asks, encrypted to them. Nothing is broadcast. Needs "Answer telemetry requests" on. Pick contacts in a contact's menu.
|
||||
Server has forgotten you.\nJoin with the room password. Server has forgotten you.\nJoin with the room password.
|
||||
Share my loc Share my loc
|
||||
@@ -1042,7 +1038,6 @@ App permissions App permissions
|
||||
About / credits About / credits
|
||||
Auth failed Auth failed
|
||||
Auto-add on for %s — new %s land in Contacts automatically. Auto-add on for %s — new %s land in Contacts automatically.
|
||||
Batteryactivityon Batteryactivityon
|
||||
Console mode Console mode
|
||||
Console mode (experimental) Console mode (experimental)
|
||||
Direct (0-hop) only Direct (0-hop) only
|
||||
@@ -1051,8 +1046,6 @@ EXPERIMENTAL. Boots into a text console with no graphical interface: type comman
|
||||
Enter text Enter text
|
||||
Entering console mode - rebooting... Entering console mode - rebooting...
|
||||
Favorite Favorite
|
||||
Geblokkeerde gebruikers Geblokkeerde gebruikers
|
||||
Hide system filesShow system files Hide system filesShow system files
|
||||
How tiles work:\nThe map is built from 256×256 "slippy" tiles. Only the tiles for the area you're viewing are fetched — there is no bulk pre-download.\n\nBecause this device can't do HTTPS (not enough heap after Wi-Fi starts) and decodes JPEG far more cheaply than PNG, tiles come from the wadamesh proxy: it fetches the upstream PNG over HTTPS with an identifying User-Agent, re-encodes it as JPEG, and caches it. Your device then caches each tile to its own flash, so a tile is only downloaded once.\n\nUse Options → Reload tiles to re-download the tiles currently in view if one looks corrupted. How tiles work:\nThe map is built from 256×256 "slippy" tiles. Only the tiles for the area you're viewing are fetched — there is no bulk pre-download.\n\nBecause this device can't do HTTPS (not enough heap after Wi-Fi starts) and decodes JPEG far more cheaply than PNG, tiles come from the wadamesh proxy: it fetches the upstream PNG over HTTPS with an identifying User-Agent, re-encodes it as JPEG, and caches it. Your device then caches each tile to its own flash, so a tile is only downloaded once.\n\nUse Options → Reload tiles to re-download the tiles currently in view if one looks corrupted.
|
||||
Init… Init…
|
||||
Leaving console mode - rebooting... Leaving console mode - rebooting...
|
||||
@@ -1062,8 +1055,6 @@ Map data © OpenStreetMap contributors.\nopenstreetmap.org/copyright\nLicensed u
|
||||
Map style © OpenTopoMap (CC-BY-SA) — opentopomap.org\nMap data © OpenStreetMap contributors (ODbL) + SRTM.\n\n Map style © OpenTopoMap (CC-BY-SA) — opentopomap.org\nMap data © OpenStreetMap contributors (ODbL) + SRTM.\n\n
|
||||
Name (A-Z) Name (A-Z)
|
||||
Nearest first Nearest first
|
||||
Other networksNetworks Other networksNetworks
|
||||
Paste (move)Paste (copy) Paste (move)Paste (copy)
|
||||
Recent message Recent message
|
||||
Reload tiles in view Reload tiles in view
|
||||
Repeaters Repeaters
|
||||
@@ -1076,13 +1067,8 @@ Show contacts Show contacts
|
||||
Show coordinates Show coordinates
|
||||
Show tile z/x/y Show tile z/x/y
|
||||
Starting… Starting…
|
||||
Stop sharing loc Share my loc Stop sharing loc Share my loc
|
||||
Supprimer Supprimer
|
||||
Switch to the external antenna?\n\nMake sure an antenna is actually connected to the external antenna socket first. Transmitting with nothing attached can damage the radio.\n\nResets to the on-board antenna on every reboot.Turn on legacy per-transmit switching?\n\nDiagnostic mode, for comparison only. It transmits on the on-board antenna and listens on the external one, so your outbound signal will be much weaker than your inbound.\n\nResets to the on-board antenna on every reboot. Switch to the external antenna?\n\nMake sure an antenna is actually connected to the external antenna socket first. Transmitting with nothing attached can damage the radio.\n\nResets to the on-board antenna on every reboot.Turn on legacy per-transmit switching?\n\nDiagnostic mode, for comparison only. It transmits on the on-board antenna and listens on the external one, so your outbound signal will be much weaker than your inbound.\n\nResets to the on-board antenna on every reboot.
|
||||
Text console (experimental) Text console (experimental)
|
||||
Tile debug overlay Tile debug overlay
|
||||
Unblock Block Unblock Block
|
||||
Unfav Favorite Unfav Favorite
|
||||
chats chats
|
||||
repeaters repeaters
|
||||
rooms rooms
|
||||
@@ -1098,3 +1084,23 @@ channel busy %.1f%% channel busy %.1f%%
|
||||
sampling... sampling...
|
||||
teal = now blue = average teal = now blue = average
|
||||
window restarted window restarted
|
||||
Filter Filter
|
||||
Hide system files Hide system files
|
||||
Networks Networks
|
||||
Other networks Other networks
|
||||
Show system files Show system files
|
||||
Unfav Unfav
|
||||
Firmware beta_%d\nChecking for updates over Wi-Fi… Firmware beta_%d\nChecking for updates over Wi-Fi…
|
||||
Install beta_%d?\nThis downgrades the firmware and reboots. Install beta_%d?\nThis downgrades the firmware and reboots.
|
||||
Other (hidden) network… Other (hidden) network…
|
||||
Paused Paused
|
||||
Scan again Scan again
|
||||
At a glance At a glance
|
||||
At a glance disabled At a glance disabled
|
||||
At a glance enabled At a glance enabled
|
||||
At a glance while locked At a glance while locked
|
||||
Glance only shows when unlocked Glance only shows when unlocked
|
||||
Glance shows message previews\nwhile the screen is locked Glance shows message previews\nwhile the screen is locked
|
||||
Snake — swipe, roll, or press a direction Snake — swipe, roll, or press a direction
|
||||
N/A N/A
|
||||
This board cannot run this app. This board cannot run this app.
|
||||
@@ -1,7 +1,7 @@
|
||||
# wadamesh language file
|
||||
# canonical source - translators: edit this file and PR it
|
||||
# code: ru
|
||||
# ver: 18
|
||||
# ver: 19
|
||||
# name: Русский
|
||||
# base: ru
|
||||
(device behind) (устройство отстаёт)
|
||||
@@ -977,8 +977,6 @@ tap to exit full screen нажмите для выхода из полноэкр
|
||||
zoom масштаб
|
||||
zoom %d масштаб %d
|
||||
~17 dB amp for quiet/remote areas; turn off in noisy spots. усилитель ~17 дБ для тихих/удалённых мест; выключайте в шумных.
|
||||
© OpenStreetMap © OpenStreetMap
|
||||
© OpenTopoMap © OpenTopoMap
|
||||
Scope
|
||||
another region
|
||||
my region
|
||||
@@ -1000,7 +998,6 @@ Overwrite oldest non-favorite
|
||||
1-character messages will be shown 1-character messages will be shown
|
||||
All All
|
||||
Allow Allow
|
||||
Apps that may send messages on the mesh. Anything they send goes out under your node name and cannot be told apart from a message you typed. Turn one off to take the permission back. Apps that may send messages on the mesh. Anything they send goes out under your node name and cannot be told apart from a message you typed. Turn one off to take the permission back.
|
||||
Ignore 1-character messages Ignore 1-character messages
|
||||
Join w/ password Join w/ password
|
||||
Location no longer shared Location no longer shared
|
||||
@@ -1014,9 +1011,7 @@ Never\nChosen contacts only\nAnyone who asks Never\nChosen contacts only\nAnyone
|
||||
No apps installed. No apps installed.
|
||||
No reply. Path hash is %u bytes;\nolder repeaters drop those.\nTry 1 byte (Settings > Radio & Mesh) No reply. Path hash is %u bytes;\nolder repeaters drop those.\nTry 1 byte (Settings > Radio & Mesh)
|
||||
Not supported on this board Not supported on this board
|
||||
Read incoming messages Read incoming messages
|
||||
Reconnected to %.40s Reconnected to %.40s
|
||||
Send messages as me Send messages as me
|
||||
Sent only when that contact asks, encrypted to them. Nothing is broadcast. Needs "Answer telemetry requests" on. Pick contacts in a contact's menu. Sent only when that contact asks, encrypted to them. Nothing is broadcast. Needs "Answer telemetry requests" on. Pick contacts in a contact's menu.
|
||||
Server has forgotten you.\nJoin with the room password. Server has forgotten you.\nJoin with the room password.
|
||||
Share my loc Share my loc
|
||||
@@ -1042,7 +1037,6 @@ App permissions App permissions
|
||||
About / credits About / credits
|
||||
Auth failed Auth failed
|
||||
Auto-add on for %s — new %s land in Contacts automatically. Auto-add on for %s — new %s land in Contacts automatically.
|
||||
Batteryactivityon Batteryactivityon
|
||||
Console mode Console mode
|
||||
Console mode (experimental) Console mode (experimental)
|
||||
Direct (0-hop) only Direct (0-hop) only
|
||||
@@ -1051,8 +1045,6 @@ EXPERIMENTAL. Boots into a text console with no graphical interface: type comman
|
||||
Enter text Enter text
|
||||
Entering console mode - rebooting... Entering console mode - rebooting...
|
||||
Favorite Favorite
|
||||
Geblokkeerde gebruikers Geblokkeerde gebruikers
|
||||
Hide system filesShow system files Hide system filesShow system files
|
||||
How tiles work:\nThe map is built from 256×256 "slippy" tiles. Only the tiles for the area you're viewing are fetched — there is no bulk pre-download.\n\nBecause this device can't do HTTPS (not enough heap after Wi-Fi starts) and decodes JPEG far more cheaply than PNG, tiles come from the wadamesh proxy: it fetches the upstream PNG over HTTPS with an identifying User-Agent, re-encodes it as JPEG, and caches it. Your device then caches each tile to its own flash, so a tile is only downloaded once.\n\nUse Options → Reload tiles to re-download the tiles currently in view if one looks corrupted. How tiles work:\nThe map is built from 256×256 "slippy" tiles. Only the tiles for the area you're viewing are fetched — there is no bulk pre-download.\n\nBecause this device can't do HTTPS (not enough heap after Wi-Fi starts) and decodes JPEG far more cheaply than PNG, tiles come from the wadamesh proxy: it fetches the upstream PNG over HTTPS with an identifying User-Agent, re-encodes it as JPEG, and caches it. Your device then caches each tile to its own flash, so a tile is only downloaded once.\n\nUse Options → Reload tiles to re-download the tiles currently in view if one looks corrupted.
|
||||
Init… Init…
|
||||
Leaving console mode - rebooting... Leaving console mode - rebooting...
|
||||
@@ -1062,8 +1054,6 @@ Map data © OpenStreetMap contributors.\nopenstreetmap.org/copyright\nLicensed u
|
||||
Map style © OpenTopoMap (CC-BY-SA) — opentopomap.org\nMap data © OpenStreetMap contributors (ODbL) + SRTM.\n\n Map style © OpenTopoMap (CC-BY-SA) — opentopomap.org\nMap data © OpenStreetMap contributors (ODbL) + SRTM.\n\n
|
||||
Name (A-Z) Name (A-Z)
|
||||
Nearest first Nearest first
|
||||
Other networksNetworks Other networksNetworks
|
||||
Paste (move)Paste (copy) Paste (move)Paste (copy)
|
||||
Recent message Recent message
|
||||
Reload tiles in view Reload tiles in view
|
||||
Repeaters Repeaters
|
||||
@@ -1076,13 +1066,8 @@ Show contacts Show contacts
|
||||
Show coordinates Show coordinates
|
||||
Show tile z/x/y Show tile z/x/y
|
||||
Starting… Starting…
|
||||
Stop sharing loc Share my loc Stop sharing loc Share my loc
|
||||
Supprimer Supprimer
|
||||
Switch to the external antenna?\n\nMake sure an antenna is actually connected to the external antenna socket first. Transmitting with nothing attached can damage the radio.\n\nResets to the on-board antenna on every reboot.Turn on legacy per-transmit switching?\n\nDiagnostic mode, for comparison only. It transmits on the on-board antenna and listens on the external one, so your outbound signal will be much weaker than your inbound.\n\nResets to the on-board antenna on every reboot. Switch to the external antenna?\n\nMake sure an antenna is actually connected to the external antenna socket first. Transmitting with nothing attached can damage the radio.\n\nResets to the on-board antenna on every reboot.Turn on legacy per-transmit switching?\n\nDiagnostic mode, for comparison only. It transmits on the on-board antenna and listens on the external one, so your outbound signal will be much weaker than your inbound.\n\nResets to the on-board antenna on every reboot.
|
||||
Text console (experimental) Text console (experimental)
|
||||
Tile debug overlay Tile debug overlay
|
||||
Unblock Block Unblock Block
|
||||
Unfav Favorite Unfav Favorite
|
||||
chats chats
|
||||
repeaters repeaters
|
||||
rooms rooms
|
||||
@@ -1098,3 +1083,23 @@ channel busy %.1f%% channel busy %.1f%%
|
||||
sampling... sampling...
|
||||
teal = now blue = average teal = now blue = average
|
||||
window restarted window restarted
|
||||
Filter Filter
|
||||
Hide system files Hide system files
|
||||
Networks Networks
|
||||
Other networks Other networks
|
||||
Show system files Show system files
|
||||
Unfav Unfav
|
||||
Firmware beta_%d\nChecking for updates over Wi-Fi… Firmware beta_%d\nChecking for updates over Wi-Fi…
|
||||
Install beta_%d?\nThis downgrades the firmware and reboots. Install beta_%d?\nThis downgrades the firmware and reboots.
|
||||
Other (hidden) network… Other (hidden) network…
|
||||
Paused Paused
|
||||
Scan again Scan again
|
||||
At a glance At a glance
|
||||
At a glance disabled At a glance disabled
|
||||
At a glance enabled At a glance enabled
|
||||
At a glance while locked At a glance while locked
|
||||
Glance only shows when unlocked Glance only shows when unlocked
|
||||
Glance shows message previews\nwhile the screen is locked Glance shows message previews\nwhile the screen is locked
|
||||
Snake — swipe, roll, or press a direction Snake — swipe, roll, or press a direction
|
||||
N/A N/A
|
||||
This board cannot run this app. This board cannot run this app.
|
||||
@@ -1,7 +1,7 @@
|
||||
# wadamesh language file
|
||||
# canonical source - translators: edit this file and PR it
|
||||
# code: sr
|
||||
# ver: 18
|
||||
# ver: 19
|
||||
# name: Српски
|
||||
# base: sr
|
||||
(device behind) (уређај касни)
|
||||
@@ -826,7 +826,7 @@ TCP on TCP укљ
|
||||
TOUCH BETA TOUCH BETA
|
||||
TX TX
|
||||
Tab hotkeys — tap a row, then press a key Пречице картице — додирни ред, па притисни тастер
|
||||
Tap Use to switch - the device reboots to apply. Додирните „Користи“ да промените — уређај ће се поново покренути.
|
||||
Tap Use to switch - the device reboots to apply. Додирните "Користи" да промените — уређај ће се поново покренути.
|
||||
Tap Use to switch - the device reboots to apply. Language files live in /lang on the storage; edit them or add your own. Додирни «Користи» за промену - уређај се поново покреће. Датотеке језика су у /lang на меморији; уреди их или додај своје.
|
||||
Tap a language; the device reboots to apply. Изаберите језик; уређај ће се рестартовати.
|
||||
Taste the rainbow! Окуси дугу!
|
||||
@@ -977,8 +977,6 @@ tap to exit full screen додирните за излаз из целог ек
|
||||
zoom зум
|
||||
zoom %d зум %d
|
||||
~17 dB amp for quiet/remote areas; turn off in noisy spots. појачало ~17 dB за тиха/удаљена места; искључи у бучним.
|
||||
© OpenStreetMap © OpenStreetMap
|
||||
© OpenTopoMap © OpenTopoMap
|
||||
Scope
|
||||
another region
|
||||
my region
|
||||
@@ -1000,7 +998,6 @@ Overwrite oldest non-favorite
|
||||
1-character messages will be shown 1-character messages will be shown
|
||||
All All
|
||||
Allow Allow
|
||||
Apps that may send messages on the mesh. Anything they send goes out under your node name and cannot be told apart from a message you typed. Turn one off to take the permission back. Apps that may send messages on the mesh. Anything they send goes out under your node name and cannot be told apart from a message you typed. Turn one off to take the permission back.
|
||||
Ignore 1-character messages Ignore 1-character messages
|
||||
Join w/ password Join w/ password
|
||||
Location no longer shared Location no longer shared
|
||||
@@ -1014,9 +1011,7 @@ Never\nChosen contacts only\nAnyone who asks Never\nChosen contacts only\nAnyone
|
||||
No apps installed. No apps installed.
|
||||
No reply. Path hash is %u bytes;\nolder repeaters drop those.\nTry 1 byte (Settings > Radio & Mesh) No reply. Path hash is %u bytes;\nolder repeaters drop those.\nTry 1 byte (Settings > Radio & Mesh)
|
||||
Not supported on this board Not supported on this board
|
||||
Read incoming messages Read incoming messages
|
||||
Reconnected to %.40s Reconnected to %.40s
|
||||
Send messages as me Send messages as me
|
||||
Sent only when that contact asks, encrypted to them. Nothing is broadcast. Needs "Answer telemetry requests" on. Pick contacts in a contact's menu. Sent only when that contact asks, encrypted to them. Nothing is broadcast. Needs "Answer telemetry requests" on. Pick contacts in a contact's menu.
|
||||
Server has forgotten you.\nJoin with the room password. Server has forgotten you.\nJoin with the room password.
|
||||
Share my loc Share my loc
|
||||
@@ -1042,7 +1037,6 @@ App permissions App permissions
|
||||
About / credits About / credits
|
||||
Auth failed Auth failed
|
||||
Auto-add on for %s — new %s land in Contacts automatically. Auto-add on for %s — new %s land in Contacts automatically.
|
||||
Batteryactivityon Batteryactivityon
|
||||
Console mode Console mode
|
||||
Console mode (experimental) Console mode (experimental)
|
||||
Direct (0-hop) only Direct (0-hop) only
|
||||
@@ -1051,8 +1045,6 @@ EXPERIMENTAL. Boots into a text console with no graphical interface: type comman
|
||||
Enter text Enter text
|
||||
Entering console mode - rebooting... Entering console mode - rebooting...
|
||||
Favorite Favorite
|
||||
Geblokkeerde gebruikers Geblokkeerde gebruikers
|
||||
Hide system filesShow system files Hide system filesShow system files
|
||||
How tiles work:\nThe map is built from 256×256 "slippy" tiles. Only the tiles for the area you're viewing are fetched — there is no bulk pre-download.\n\nBecause this device can't do HTTPS (not enough heap after Wi-Fi starts) and decodes JPEG far more cheaply than PNG, tiles come from the wadamesh proxy: it fetches the upstream PNG over HTTPS with an identifying User-Agent, re-encodes it as JPEG, and caches it. Your device then caches each tile to its own flash, so a tile is only downloaded once.\n\nUse Options → Reload tiles to re-download the tiles currently in view if one looks corrupted. How tiles work:\nThe map is built from 256×256 "slippy" tiles. Only the tiles for the area you're viewing are fetched — there is no bulk pre-download.\n\nBecause this device can't do HTTPS (not enough heap after Wi-Fi starts) and decodes JPEG far more cheaply than PNG, tiles come from the wadamesh proxy: it fetches the upstream PNG over HTTPS with an identifying User-Agent, re-encodes it as JPEG, and caches it. Your device then caches each tile to its own flash, so a tile is only downloaded once.\n\nUse Options → Reload tiles to re-download the tiles currently in view if one looks corrupted.
|
||||
Init… Init…
|
||||
Leaving console mode - rebooting... Leaving console mode - rebooting...
|
||||
@@ -1062,8 +1054,6 @@ Map data © OpenStreetMap contributors.\nopenstreetmap.org/copyright\nLicensed u
|
||||
Map style © OpenTopoMap (CC-BY-SA) — opentopomap.org\nMap data © OpenStreetMap contributors (ODbL) + SRTM.\n\n Map style © OpenTopoMap (CC-BY-SA) — opentopomap.org\nMap data © OpenStreetMap contributors (ODbL) + SRTM.\n\n
|
||||
Name (A-Z) Name (A-Z)
|
||||
Nearest first Nearest first
|
||||
Other networksNetworks Other networksNetworks
|
||||
Paste (move)Paste (copy) Paste (move)Paste (copy)
|
||||
Recent message Recent message
|
||||
Reload tiles in view Reload tiles in view
|
||||
Repeaters Repeaters
|
||||
@@ -1076,13 +1066,8 @@ Show contacts Show contacts
|
||||
Show coordinates Show coordinates
|
||||
Show tile z/x/y Show tile z/x/y
|
||||
Starting… Starting…
|
||||
Stop sharing loc Share my loc Stop sharing loc Share my loc
|
||||
Supprimer Supprimer
|
||||
Switch to the external antenna?\n\nMake sure an antenna is actually connected to the external antenna socket first. Transmitting with nothing attached can damage the radio.\n\nResets to the on-board antenna on every reboot.Turn on legacy per-transmit switching?\n\nDiagnostic mode, for comparison only. It transmits on the on-board antenna and listens on the external one, so your outbound signal will be much weaker than your inbound.\n\nResets to the on-board antenna on every reboot. Switch to the external antenna?\n\nMake sure an antenna is actually connected to the external antenna socket first. Transmitting with nothing attached can damage the radio.\n\nResets to the on-board antenna on every reboot.Turn on legacy per-transmit switching?\n\nDiagnostic mode, for comparison only. It transmits on the on-board antenna and listens on the external one, so your outbound signal will be much weaker than your inbound.\n\nResets to the on-board antenna on every reboot.
|
||||
Text console (experimental) Text console (experimental)
|
||||
Tile debug overlay Tile debug overlay
|
||||
Unblock Block Unblock Block
|
||||
Unfav Favorite Unfav Favorite
|
||||
chats chats
|
||||
repeaters repeaters
|
||||
rooms rooms
|
||||
@@ -1098,3 +1083,23 @@ channel busy %.1f%% channel busy %.1f%%
|
||||
sampling... sampling...
|
||||
teal = now blue = average teal = now blue = average
|
||||
window restarted window restarted
|
||||
Filter Filter
|
||||
Hide system files Hide system files
|
||||
Networks Networks
|
||||
Other networks Other networks
|
||||
Show system files Show system files
|
||||
Unfav Unfav
|
||||
Firmware beta_%d\nChecking for updates over Wi-Fi… Firmware beta_%d\nChecking for updates over Wi-Fi…
|
||||
Install beta_%d?\nThis downgrades the firmware and reboots. Install beta_%d?\nThis downgrades the firmware and reboots.
|
||||
Other (hidden) network… Other (hidden) network…
|
||||
Paused Paused
|
||||
Scan again Scan again
|
||||
At a glance At a glance
|
||||
At a glance disabled At a glance disabled
|
||||
At a glance enabled At a glance enabled
|
||||
At a glance while locked At a glance while locked
|
||||
Glance only shows when unlocked Glance only shows when unlocked
|
||||
Glance shows message previews\nwhile the screen is locked Glance shows message previews\nwhile the screen is locked
|
||||
Snake — swipe, roll, or press a direction Snake — swipe, roll, or press a direction
|
||||
N/A N/A
|
||||
This board cannot run this app. This board cannot run this app.
|
||||
@@ -1,7 +1,7 @@
|
||||
# wadamesh language file
|
||||
# canonical source - translators: edit this file and PR it
|
||||
# code: uk
|
||||
# ver: 18
|
||||
# ver: 19
|
||||
# name: Українська
|
||||
# base: uk
|
||||
(device behind) (пристрій відстає)
|
||||
@@ -977,8 +977,6 @@ tap to exit full screen торкніться, щоб вийти з повное
|
||||
zoom масштаб
|
||||
zoom %d масштаб %d
|
||||
~17 dB amp for quiet/remote areas; turn off in noisy spots. підсилювач ~17 дБ для тихих/віддалених місць; вимикайте в галасливих.
|
||||
© OpenStreetMap © OpenStreetMap
|
||||
© OpenTopoMap © OpenTopoMap
|
||||
Scope
|
||||
another region
|
||||
my region
|
||||
@@ -1000,7 +998,6 @@ Overwrite oldest non-favorite
|
||||
1-character messages will be shown 1-character messages will be shown
|
||||
All All
|
||||
Allow Allow
|
||||
Apps that may send messages on the mesh. Anything they send goes out under your node name and cannot be told apart from a message you typed. Turn one off to take the permission back. Apps that may send messages on the mesh. Anything they send goes out under your node name and cannot be told apart from a message you typed. Turn one off to take the permission back.
|
||||
Ignore 1-character messages Ignore 1-character messages
|
||||
Join w/ password Join w/ password
|
||||
Location no longer shared Location no longer shared
|
||||
@@ -1014,9 +1011,7 @@ Never\nChosen contacts only\nAnyone who asks Never\nChosen contacts only\nAnyone
|
||||
No apps installed. No apps installed.
|
||||
No reply. Path hash is %u bytes;\nolder repeaters drop those.\nTry 1 byte (Settings > Radio & Mesh) No reply. Path hash is %u bytes;\nolder repeaters drop those.\nTry 1 byte (Settings > Radio & Mesh)
|
||||
Not supported on this board Not supported on this board
|
||||
Read incoming messages Read incoming messages
|
||||
Reconnected to %.40s Reconnected to %.40s
|
||||
Send messages as me Send messages as me
|
||||
Sent only when that contact asks, encrypted to them. Nothing is broadcast. Needs "Answer telemetry requests" on. Pick contacts in a contact's menu. Sent only when that contact asks, encrypted to them. Nothing is broadcast. Needs "Answer telemetry requests" on. Pick contacts in a contact's menu.
|
||||
Server has forgotten you.\nJoin with the room password. Server has forgotten you.\nJoin with the room password.
|
||||
Share my loc Share my loc
|
||||
@@ -1042,7 +1037,6 @@ App permissions App permissions
|
||||
About / credits About / credits
|
||||
Auth failed Auth failed
|
||||
Auto-add on for %s — new %s land in Contacts automatically. Auto-add on for %s — new %s land in Contacts automatically.
|
||||
Batteryactivityon Batteryactivityon
|
||||
Console mode Console mode
|
||||
Console mode (experimental) Console mode (experimental)
|
||||
Direct (0-hop) only Direct (0-hop) only
|
||||
@@ -1051,8 +1045,6 @@ EXPERIMENTAL. Boots into a text console with no graphical interface: type comman
|
||||
Enter text Enter text
|
||||
Entering console mode - rebooting... Entering console mode - rebooting...
|
||||
Favorite Favorite
|
||||
Geblokkeerde gebruikers Geblokkeerde gebruikers
|
||||
Hide system filesShow system files Hide system filesShow system files
|
||||
How tiles work:\nThe map is built from 256×256 "slippy" tiles. Only the tiles for the area you're viewing are fetched — there is no bulk pre-download.\n\nBecause this device can't do HTTPS (not enough heap after Wi-Fi starts) and decodes JPEG far more cheaply than PNG, tiles come from the wadamesh proxy: it fetches the upstream PNG over HTTPS with an identifying User-Agent, re-encodes it as JPEG, and caches it. Your device then caches each tile to its own flash, so a tile is only downloaded once.\n\nUse Options → Reload tiles to re-download the tiles currently in view if one looks corrupted. How tiles work:\nThe map is built from 256×256 "slippy" tiles. Only the tiles for the area you're viewing are fetched — there is no bulk pre-download.\n\nBecause this device can't do HTTPS (not enough heap after Wi-Fi starts) and decodes JPEG far more cheaply than PNG, tiles come from the wadamesh proxy: it fetches the upstream PNG over HTTPS with an identifying User-Agent, re-encodes it as JPEG, and caches it. Your device then caches each tile to its own flash, so a tile is only downloaded once.\n\nUse Options → Reload tiles to re-download the tiles currently in view if one looks corrupted.
|
||||
Init… Init…
|
||||
Leaving console mode - rebooting... Leaving console mode - rebooting...
|
||||
@@ -1062,8 +1054,6 @@ Map data © OpenStreetMap contributors.\nopenstreetmap.org/copyright\nLicensed u
|
||||
Map style © OpenTopoMap (CC-BY-SA) — opentopomap.org\nMap data © OpenStreetMap contributors (ODbL) + SRTM.\n\n Map style © OpenTopoMap (CC-BY-SA) — opentopomap.org\nMap data © OpenStreetMap contributors (ODbL) + SRTM.\n\n
|
||||
Name (A-Z) Name (A-Z)
|
||||
Nearest first Nearest first
|
||||
Other networksNetworks Other networksNetworks
|
||||
Paste (move)Paste (copy) Paste (move)Paste (copy)
|
||||
Recent message Recent message
|
||||
Reload tiles in view Reload tiles in view
|
||||
Repeaters Repeaters
|
||||
@@ -1076,13 +1066,8 @@ Show contacts Show contacts
|
||||
Show coordinates Show coordinates
|
||||
Show tile z/x/y Show tile z/x/y
|
||||
Starting… Starting…
|
||||
Stop sharing loc Share my loc Stop sharing loc Share my loc
|
||||
Supprimer Supprimer
|
||||
Switch to the external antenna?\n\nMake sure an antenna is actually connected to the external antenna socket first. Transmitting with nothing attached can damage the radio.\n\nResets to the on-board antenna on every reboot.Turn on legacy per-transmit switching?\n\nDiagnostic mode, for comparison only. It transmits on the on-board antenna and listens on the external one, so your outbound signal will be much weaker than your inbound.\n\nResets to the on-board antenna on every reboot. Switch to the external antenna?\n\nMake sure an antenna is actually connected to the external antenna socket first. Transmitting with nothing attached can damage the radio.\n\nResets to the on-board antenna on every reboot.Turn on legacy per-transmit switching?\n\nDiagnostic mode, for comparison only. It transmits on the on-board antenna and listens on the external one, so your outbound signal will be much weaker than your inbound.\n\nResets to the on-board antenna on every reboot.
|
||||
Text console (experimental) Text console (experimental)
|
||||
Tile debug overlay Tile debug overlay
|
||||
Unblock Block Unblock Block
|
||||
Unfav Favorite Unfav Favorite
|
||||
chats chats
|
||||
repeaters repeaters
|
||||
rooms rooms
|
||||
@@ -1098,3 +1083,23 @@ channel busy %.1f%% channel busy %.1f%%
|
||||
sampling... sampling...
|
||||
teal = now blue = average teal = now blue = average
|
||||
window restarted window restarted
|
||||
Filter Filter
|
||||
Hide system files Hide system files
|
||||
Networks Networks
|
||||
Other networks Other networks
|
||||
Show system files Show system files
|
||||
Unfav Unfav
|
||||
Firmware beta_%d\nChecking for updates over Wi-Fi… Firmware beta_%d\nChecking for updates over Wi-Fi…
|
||||
Install beta_%d?\nThis downgrades the firmware and reboots. Install beta_%d?\nThis downgrades the firmware and reboots.
|
||||
Other (hidden) network… Other (hidden) network…
|
||||
Paused Paused
|
||||
Scan again Scan again
|
||||
At a glance At a glance
|
||||
At a glance disabled At a glance disabled
|
||||
At a glance enabled At a glance enabled
|
||||
At a glance while locked At a glance while locked
|
||||
Glance only shows when unlocked Glance only shows when unlocked
|
||||
Glance shows message previews\nwhile the screen is locked Glance shows message previews\nwhile the screen is locked
|
||||
Snake — swipe, roll, or press a direction Snake — swipe, roll, or press a direction
|
||||
N/A N/A
|
||||
This board cannot run this app. This board cannot run this app.
|
||||
+25
-17
@@ -1,7 +1,7 @@
|
||||
# wadamesh language file
|
||||
# canonical source - translators: edit this file and PR it
|
||||
# code: bg
|
||||
# ver: 18
|
||||
# ver: 19
|
||||
# name: Български
|
||||
# base: bg
|
||||
(device behind) (устройството изостава)
|
||||
@@ -826,7 +826,7 @@ TCP on TCP вкл
|
||||
TOUCH BETA TOUCH BETA
|
||||
TX TX
|
||||
Tab hotkeys — tap a row, then press a key Клавиши за раздела — докосни ред, после натисни клавиш
|
||||
Tap Use to switch - the device reboots to apply. Натиснете „Избери“, за да превключите — устройството ще се рестартира.
|
||||
Tap Use to switch - the device reboots to apply. Натиснете "Избери", за да превключите — устройството ще се рестартира.
|
||||
Tap Use to switch - the device reboots to apply. Language files live in /lang on the storage; edit them or add your own. Докосни «Избери» за смяна - устройството се рестартира. Езиковите файлове са в /lang в паметта; редактирай ги или добави свои.
|
||||
Tap a language; the device reboots to apply. Изберете език; устройството ще се рестартира.
|
||||
Taste the rainbow! Вкуси дъгата!
|
||||
@@ -977,8 +977,6 @@ tap to exit full screen докоснете за изход от цял екра
|
||||
zoom мащаб
|
||||
zoom %d мащаб %d
|
||||
~17 dB amp for quiet/remote areas; turn off in noisy spots. усилвател ~17 dB за тихи/отдалечени места; изключи в шумни.
|
||||
© OpenStreetMap © OpenStreetMap
|
||||
© OpenTopoMap © OpenTopoMap
|
||||
Scope
|
||||
another region
|
||||
my region
|
||||
@@ -1000,7 +998,6 @@ Overwrite oldest non-favorite
|
||||
1-character messages will be shown 1-character messages will be shown
|
||||
All All
|
||||
Allow Allow
|
||||
Apps that may send messages on the mesh. Anything they send goes out under your node name and cannot be told apart from a message you typed. Turn one off to take the permission back. Apps that may send messages on the mesh. Anything they send goes out under your node name and cannot be told apart from a message you typed. Turn one off to take the permission back.
|
||||
Ignore 1-character messages Ignore 1-character messages
|
||||
Join w/ password Join w/ password
|
||||
Location no longer shared Location no longer shared
|
||||
@@ -1014,9 +1011,7 @@ Never\nChosen contacts only\nAnyone who asks Never\nChosen contacts only\nAnyone
|
||||
No apps installed. No apps installed.
|
||||
No reply. Path hash is %u bytes;\nolder repeaters drop those.\nTry 1 byte (Settings > Radio & Mesh) No reply. Path hash is %u bytes;\nolder repeaters drop those.\nTry 1 byte (Settings > Radio & Mesh)
|
||||
Not supported on this board Not supported on this board
|
||||
Read incoming messages Read incoming messages
|
||||
Reconnected to %.40s Reconnected to %.40s
|
||||
Send messages as me Send messages as me
|
||||
Sent only when that contact asks, encrypted to them. Nothing is broadcast. Needs "Answer telemetry requests" on. Pick contacts in a contact's menu. Sent only when that contact asks, encrypted to them. Nothing is broadcast. Needs "Answer telemetry requests" on. Pick contacts in a contact's menu.
|
||||
Server has forgotten you.\nJoin with the room password. Server has forgotten you.\nJoin with the room password.
|
||||
Share my loc Share my loc
|
||||
@@ -1042,7 +1037,6 @@ App permissions App permissions
|
||||
About / credits About / credits
|
||||
Auth failed Auth failed
|
||||
Auto-add on for %s — new %s land in Contacts automatically. Auto-add on for %s — new %s land in Contacts automatically.
|
||||
Batteryactivityon Batteryactivityon
|
||||
Console mode Console mode
|
||||
Console mode (experimental) Console mode (experimental)
|
||||
Direct (0-hop) only Direct (0-hop) only
|
||||
@@ -1051,8 +1045,6 @@ EXPERIMENTAL. Boots into a text console with no graphical interface: type comman
|
||||
Enter text Enter text
|
||||
Entering console mode - rebooting... Entering console mode - rebooting...
|
||||
Favorite Favorite
|
||||
Geblokkeerde gebruikers Geblokkeerde gebruikers
|
||||
Hide system filesShow system files Hide system filesShow system files
|
||||
How tiles work:\nThe map is built from 256×256 "slippy" tiles. Only the tiles for the area you're viewing are fetched — there is no bulk pre-download.\n\nBecause this device can't do HTTPS (not enough heap after Wi-Fi starts) and decodes JPEG far more cheaply than PNG, tiles come from the wadamesh proxy: it fetches the upstream PNG over HTTPS with an identifying User-Agent, re-encodes it as JPEG, and caches it. Your device then caches each tile to its own flash, so a tile is only downloaded once.\n\nUse Options → Reload tiles to re-download the tiles currently in view if one looks corrupted. How tiles work:\nThe map is built from 256×256 "slippy" tiles. Only the tiles for the area you're viewing are fetched — there is no bulk pre-download.\n\nBecause this device can't do HTTPS (not enough heap after Wi-Fi starts) and decodes JPEG far more cheaply than PNG, tiles come from the wadamesh proxy: it fetches the upstream PNG over HTTPS with an identifying User-Agent, re-encodes it as JPEG, and caches it. Your device then caches each tile to its own flash, so a tile is only downloaded once.\n\nUse Options → Reload tiles to re-download the tiles currently in view if one looks corrupted.
|
||||
Init… Init…
|
||||
Leaving console mode - rebooting... Leaving console mode - rebooting...
|
||||
@@ -1062,8 +1054,6 @@ Map data © OpenStreetMap contributors.\nopenstreetmap.org/copyright\nLicensed u
|
||||
Map style © OpenTopoMap (CC-BY-SA) — opentopomap.org\nMap data © OpenStreetMap contributors (ODbL) + SRTM.\n\n Map style © OpenTopoMap (CC-BY-SA) — opentopomap.org\nMap data © OpenStreetMap contributors (ODbL) + SRTM.\n\n
|
||||
Name (A-Z) Name (A-Z)
|
||||
Nearest first Nearest first
|
||||
Other networksNetworks Other networksNetworks
|
||||
Paste (move)Paste (copy) Paste (move)Paste (copy)
|
||||
Recent message Recent message
|
||||
Reload tiles in view Reload tiles in view
|
||||
Repeaters Repeaters
|
||||
@@ -1076,13 +1066,8 @@ Show contacts Show contacts
|
||||
Show coordinates Show coordinates
|
||||
Show tile z/x/y Show tile z/x/y
|
||||
Starting… Starting…
|
||||
Stop sharing loc Share my loc Stop sharing loc Share my loc
|
||||
Supprimer Supprimer
|
||||
Switch to the external antenna?\n\nMake sure an antenna is actually connected to the external antenna socket first. Transmitting with nothing attached can damage the radio.\n\nResets to the on-board antenna on every reboot.Turn on legacy per-transmit switching?\n\nDiagnostic mode, for comparison only. It transmits on the on-board antenna and listens on the external one, so your outbound signal will be much weaker than your inbound.\n\nResets to the on-board antenna on every reboot. Switch to the external antenna?\n\nMake sure an antenna is actually connected to the external antenna socket first. Transmitting with nothing attached can damage the radio.\n\nResets to the on-board antenna on every reboot.Turn on legacy per-transmit switching?\n\nDiagnostic mode, for comparison only. It transmits on the on-board antenna and listens on the external one, so your outbound signal will be much weaker than your inbound.\n\nResets to the on-board antenna on every reboot.
|
||||
Text console (experimental) Text console (experimental)
|
||||
Tile debug overlay Tile debug overlay
|
||||
Unblock Block Unblock Block
|
||||
Unfav Favorite Unfav Favorite
|
||||
chats chats
|
||||
repeaters repeaters
|
||||
rooms rooms
|
||||
@@ -1098,3 +1083,26 @@ channel busy %.1f%% channel busy %.1f%%
|
||||
sampling... sampling...
|
||||
teal = now blue = average teal = now blue = average
|
||||
window restarted window restarted
|
||||
Filter Filter
|
||||
Hide system files Hide system files
|
||||
Networks Networks
|
||||
Other networks Other networks
|
||||
Show system files Show system files
|
||||
Unfav Unfav
|
||||
Firmware beta_%d\nChecking for updates over Wi-Fi… Firmware beta_%d\nChecking for updates over Wi-Fi…
|
||||
Install beta_%d?\nThis downgrades the firmware and reboots. Install beta_%d?\nThis downgrades the firmware and reboots.
|
||||
Other (hidden) network… Other (hidden) network…
|
||||
Paused Paused
|
||||
Scan again Scan again
|
||||
At a glance At a glance
|
||||
At a glance disabled At a glance disabled
|
||||
At a glance enabled At a glance enabled
|
||||
At a glance while locked At a glance while locked
|
||||
Glance only shows when unlocked Glance only shows when unlocked
|
||||
Glance shows message previews\nwhile the screen is locked Glance shows message previews\nwhile the screen is locked
|
||||
Snake — swipe, roll, or press a direction Snake — swipe, roll, or press a direction
|
||||
N/A N/A
|
||||
This board cannot run this app. This board cannot run this app.
|
||||
Signal Signal
|
||||
Signal (stale) Signal (stale)
|
||||
nothing heard yet nothing heard yet
|
||||
|
||||
+25
-21
@@ -1,7 +1,7 @@
|
||||
# wadamesh language file
|
||||
# canonical source - translators: edit this file and PR it
|
||||
# code: de
|
||||
# ver: 18
|
||||
# ver: 19
|
||||
# name: Deutsch
|
||||
# base: de
|
||||
(device behind) (Gerät geht nach)
|
||||
@@ -452,7 +452,6 @@ Login OK (perms %u) Anmeldung OK (Rechte %u)
|
||||
Login failed Anmeldung fehlgeschlagen
|
||||
Logs Protokolle
|
||||
Longitude Längengrad
|
||||
Lua Store Lua Store
|
||||
MHz MHz
|
||||
MQTT bridge MQTT-Brücke
|
||||
Map Karte
|
||||
@@ -694,7 +693,7 @@ SD card remounted SD-Karte neu eingebunden
|
||||
SD card removed SD-Karte entfernt
|
||||
SD data is unavailable - contacts and channels cannot be saved until the card is reinserted. SD-Daten nicht verfügbar – Kontakte und Kanäle können erst nach Einsetzen der Karte gespeichert werden.
|
||||
SD data is unavailable - identity, settings, contacts and channels cannot be saved until the card is reinserted. SD-Daten nicht verfügbar – Identität, Einstellungen, Kontakte und Kanäle können erst nach Einsetzen der Karte gespeichert werden.
|
||||
SD data migration is incomplete. Identity and settings remain internal; use Copy internal data to SD to retry. SD-Datenübertragung unvollständig. Identität und Einstellungen bleiben intern; „Interne Daten auf SD kopieren“ zum erneuten Versuch verwenden.
|
||||
SD data migration is incomplete. Identity and settings remain internal; use Copy internal data to SD to retry. SD-Datenübertragung unvollständig. Identität und Einstellungen bleiben intern; "Interne Daten auf SD kopieren" zum erneuten Versuch verwenden.
|
||||
SD download failed: %s SD-Download fehlgeschlagen: %s
|
||||
SD formatted - %s (MESHCOMOD) SD formatiert - %s (MESHCOMOD)
|
||||
SD is busy - close tools and retry SD ist belegt – Tools schließen und erneut versuchen
|
||||
@@ -797,7 +796,6 @@ Sleep ready Bereit zum Schlafen
|
||||
Slot %d cleared Slot %d gelöscht
|
||||
Slot empty Slot leer
|
||||
Small\nMedium\nLarge Klein\nMittel\nGroß
|
||||
Snake Snake
|
||||
Snake — swipe or roll the trackball Snake — wischen oder Trackball rollen
|
||||
Sort Sortieren
|
||||
Sound Ton
|
||||
@@ -805,7 +803,6 @@ Sound off Ton aus
|
||||
Sound on Ton ein
|
||||
Sound: %s Ton: %s
|
||||
Start time Startzeit
|
||||
Status Status
|
||||
Step 1 of 3 Schritt 1 von 3
|
||||
Step 2 of 3 Schritt 2 von 3
|
||||
Step 3 of 3 Schritt 3 von 3
|
||||
@@ -818,7 +815,6 @@ Streams the device screen as a live picture you tap to control. Heavier; the pan
|
||||
Switch Wechseln
|
||||
Switch to the external antenna?\n\nMake sure an antenna is actually connected to the external antenna socket first. Transmitting with nothing attached can damage the radio.\n\nResets to the on-board antenna on every reboot. Auf die externe Antenne wechseln?\n\nErst sicherstellen, dass wirklich eine Antenne an der Buchse steckt. Senden ohne Antenne kann das Funkmodul beschädigen.\n\nFällt bei jedem Neustart auf die interne Antenne zurück.
|
||||
Sync clock from system Uhr synchronisieren
|
||||
System System
|
||||
System info Systeminfo
|
||||
TCP %s BLE %s\nWS clients %d GPS %s Buzzer %s TCP %s BLE %s\nWS-Clients %d GPS %s Summer %s
|
||||
TCP off TCP aus
|
||||
@@ -977,8 +973,6 @@ tap to exit full screen tippen zum Verlassen des Vollbilds
|
||||
zoom Zoom
|
||||
zoom %d Zoom %d
|
||||
~17 dB amp for quiet/remote areas; turn off in noisy spots. ~17 dB Verstärker für ruhige/abgelegene Gebiete; in lauten Zonen aus.
|
||||
© OpenStreetMap © OpenStreetMap
|
||||
© OpenTopoMap © OpenTopoMap
|
||||
Scope
|
||||
another region
|
||||
my region
|
||||
@@ -1000,7 +994,6 @@ Overwrite oldest non-favorite
|
||||
1-character messages will be shown 1-character messages will be shown
|
||||
All All
|
||||
Allow Allow
|
||||
Apps that may send messages on the mesh. Anything they send goes out under your node name and cannot be told apart from a message you typed. Turn one off to take the permission back. Apps that may send messages on the mesh. Anything they send goes out under your node name and cannot be told apart from a message you typed. Turn one off to take the permission back.
|
||||
Ignore 1-character messages Ignore 1-character messages
|
||||
Join w/ password Join w/ password
|
||||
Location no longer shared Location no longer shared
|
||||
@@ -1014,9 +1007,7 @@ Never\nChosen contacts only\nAnyone who asks Never\nChosen contacts only\nAnyone
|
||||
No apps installed. No apps installed.
|
||||
No reply. Path hash is %u bytes;\nolder repeaters drop those.\nTry 1 byte (Settings > Radio & Mesh) No reply. Path hash is %u bytes;\nolder repeaters drop those.\nTry 1 byte (Settings > Radio & Mesh)
|
||||
Not supported on this board Not supported on this board
|
||||
Read incoming messages Read incoming messages
|
||||
Reconnected to %.40s Reconnected to %.40s
|
||||
Send messages as me Send messages as me
|
||||
Sent only when that contact asks, encrypted to them. Nothing is broadcast. Needs "Answer telemetry requests" on. Pick contacts in a contact's menu. Sent only when that contact asks, encrypted to them. Nothing is broadcast. Needs "Answer telemetry requests" on. Pick contacts in a contact's menu.
|
||||
Server has forgotten you.\nJoin with the room password. Server has forgotten you.\nJoin with the room password.
|
||||
Share my loc Share my loc
|
||||
@@ -1042,7 +1033,6 @@ App permissions App permissions
|
||||
About / credits About / credits
|
||||
Auth failed Auth failed
|
||||
Auto-add on for %s — new %s land in Contacts automatically. Auto-add on for %s — new %s land in Contacts automatically.
|
||||
Batteryactivityon Batteryactivityon
|
||||
Console mode Console mode
|
||||
Console mode (experimental) Console mode (experimental)
|
||||
Direct (0-hop) only Direct (0-hop) only
|
||||
@@ -1051,8 +1041,6 @@ EXPERIMENTAL. Boots into a text console with no graphical interface: type comman
|
||||
Enter text Enter text
|
||||
Entering console mode - rebooting... Entering console mode - rebooting...
|
||||
Favorite Favorite
|
||||
Geblokkeerde gebruikers Geblokkeerde gebruikers
|
||||
Hide system filesShow system files Hide system filesShow system files
|
||||
How tiles work:\nThe map is built from 256×256 "slippy" tiles. Only the tiles for the area you're viewing are fetched — there is no bulk pre-download.\n\nBecause this device can't do HTTPS (not enough heap after Wi-Fi starts) and decodes JPEG far more cheaply than PNG, tiles come from the wadamesh proxy: it fetches the upstream PNG over HTTPS with an identifying User-Agent, re-encodes it as JPEG, and caches it. Your device then caches each tile to its own flash, so a tile is only downloaded once.\n\nUse Options → Reload tiles to re-download the tiles currently in view if one looks corrupted. How tiles work:\nThe map is built from 256×256 "slippy" tiles. Only the tiles for the area you're viewing are fetched — there is no bulk pre-download.\n\nBecause this device can't do HTTPS (not enough heap after Wi-Fi starts) and decodes JPEG far more cheaply than PNG, tiles come from the wadamesh proxy: it fetches the upstream PNG over HTTPS with an identifying User-Agent, re-encodes it as JPEG, and caches it. Your device then caches each tile to its own flash, so a tile is only downloaded once.\n\nUse Options → Reload tiles to re-download the tiles currently in view if one looks corrupted.
|
||||
Init… Init…
|
||||
Leaving console mode - rebooting... Leaving console mode - rebooting...
|
||||
@@ -1062,8 +1050,6 @@ Map data © OpenStreetMap contributors.\nopenstreetmap.org/copyright\nLicensed u
|
||||
Map style © OpenTopoMap (CC-BY-SA) — opentopomap.org\nMap data © OpenStreetMap contributors (ODbL) + SRTM.\n\n Map style © OpenTopoMap (CC-BY-SA) — opentopomap.org\nMap data © OpenStreetMap contributors (ODbL) + SRTM.\n\n
|
||||
Name (A-Z) Name (A-Z)
|
||||
Nearest first Nearest first
|
||||
Other networksNetworks Other networksNetworks
|
||||
Paste (move)Paste (copy) Paste (move)Paste (copy)
|
||||
Recent message Recent message
|
||||
Reload tiles in view Reload tiles in view
|
||||
Repeaters Repeaters
|
||||
@@ -1076,13 +1062,8 @@ Show contacts Show contacts
|
||||
Show coordinates Show coordinates
|
||||
Show tile z/x/y Show tile z/x/y
|
||||
Starting… Starting…
|
||||
Stop sharing loc Share my loc Stop sharing loc Share my loc
|
||||
Supprimer Supprimer
|
||||
Switch to the external antenna?\n\nMake sure an antenna is actually connected to the external antenna socket first. Transmitting with nothing attached can damage the radio.\n\nResets to the on-board antenna on every reboot.Turn on legacy per-transmit switching?\n\nDiagnostic mode, for comparison only. It transmits on the on-board antenna and listens on the external one, so your outbound signal will be much weaker than your inbound.\n\nResets to the on-board antenna on every reboot. Switch to the external antenna?\n\nMake sure an antenna is actually connected to the external antenna socket first. Transmitting with nothing attached can damage the radio.\n\nResets to the on-board antenna on every reboot.Turn on legacy per-transmit switching?\n\nDiagnostic mode, for comparison only. It transmits on the on-board antenna and listens on the external one, so your outbound signal will be much weaker than your inbound.\n\nResets to the on-board antenna on every reboot.
|
||||
Text console (experimental) Text console (experimental)
|
||||
Tile debug overlay Tile debug overlay
|
||||
Unblock Block Unblock Block
|
||||
Unfav Favorite Unfav Favorite
|
||||
chats chats
|
||||
repeaters repeaters
|
||||
rooms rooms
|
||||
@@ -1098,3 +1079,26 @@ channel busy %.1f%% channel busy %.1f%%
|
||||
sampling... sampling...
|
||||
teal = now blue = average teal = now blue = average
|
||||
window restarted window restarted
|
||||
Filter Filter
|
||||
Hide system files Hide system files
|
||||
Networks Networks
|
||||
Other networks Other networks
|
||||
Show system files Show system files
|
||||
Unfav Unfav
|
||||
Firmware beta_%d\nChecking for updates over Wi-Fi… Firmware beta_%d\nChecking for updates over Wi-Fi…
|
||||
Install beta_%d?\nThis downgrades the firmware and reboots. Install beta_%d?\nThis downgrades the firmware and reboots.
|
||||
Other (hidden) network… Other (hidden) network…
|
||||
Paused Paused
|
||||
Scan again Scan again
|
||||
At a glance At a glance
|
||||
At a glance disabled At a glance disabled
|
||||
At a glance enabled At a glance enabled
|
||||
At a glance while locked At a glance while locked
|
||||
Glance only shows when unlocked Glance only shows when unlocked
|
||||
Glance shows message previews\nwhile the screen is locked Glance shows message previews\nwhile the screen is locked
|
||||
Snake — swipe, roll, or press a direction Snake — swipe, roll, or press a direction
|
||||
N/A N/A
|
||||
This board cannot run this app. This board cannot run this app.
|
||||
Signal Signal
|
||||
Signal (stale) Signal (stale)
|
||||
nothing heard yet nothing heard yet
|
||||
|
||||
+24
-16
@@ -1,7 +1,7 @@
|
||||
# wadamesh language file
|
||||
# canonical source - translators: edit this file and PR it
|
||||
# code: el
|
||||
# ver: 18
|
||||
# ver: 19
|
||||
# name: Ελληνικά
|
||||
# base: el
|
||||
(device behind) (συσκευή πίσω)
|
||||
@@ -977,8 +977,6 @@ tap to exit full screen πατήστε για έξοδο από πλήρη οθ
|
||||
zoom ζουμ
|
||||
zoom %d ζουμ %d
|
||||
~17 dB amp for quiet/remote areas; turn off in noisy spots. ενισχυτής ~17 dB για ήσυχες/απομακρυσμένες περιοχές· κλείστε σε θορυβώδεις.
|
||||
© OpenStreetMap © OpenStreetMap
|
||||
© OpenTopoMap © OpenTopoMap
|
||||
Scope
|
||||
another region
|
||||
my region
|
||||
@@ -1000,7 +998,6 @@ Overwrite oldest non-favorite
|
||||
1-character messages will be shown 1-character messages will be shown
|
||||
All All
|
||||
Allow Allow
|
||||
Apps that may send messages on the mesh. Anything they send goes out under your node name and cannot be told apart from a message you typed. Turn one off to take the permission back. Apps that may send messages on the mesh. Anything they send goes out under your node name and cannot be told apart from a message you typed. Turn one off to take the permission back.
|
||||
Ignore 1-character messages Ignore 1-character messages
|
||||
Join w/ password Join w/ password
|
||||
Location no longer shared Location no longer shared
|
||||
@@ -1014,9 +1011,7 @@ Never\nChosen contacts only\nAnyone who asks Never\nChosen contacts only\nAnyone
|
||||
No apps installed. No apps installed.
|
||||
No reply. Path hash is %u bytes;\nolder repeaters drop those.\nTry 1 byte (Settings > Radio & Mesh) No reply. Path hash is %u bytes;\nolder repeaters drop those.\nTry 1 byte (Settings > Radio & Mesh)
|
||||
Not supported on this board Not supported on this board
|
||||
Read incoming messages Read incoming messages
|
||||
Reconnected to %.40s Reconnected to %.40s
|
||||
Send messages as me Send messages as me
|
||||
Sent only when that contact asks, encrypted to them. Nothing is broadcast. Needs "Answer telemetry requests" on. Pick contacts in a contact's menu. Sent only when that contact asks, encrypted to them. Nothing is broadcast. Needs "Answer telemetry requests" on. Pick contacts in a contact's menu.
|
||||
Server has forgotten you.\nJoin with the room password. Server has forgotten you.\nJoin with the room password.
|
||||
Share my loc Share my loc
|
||||
@@ -1042,7 +1037,6 @@ App permissions App permissions
|
||||
About / credits About / credits
|
||||
Auth failed Auth failed
|
||||
Auto-add on for %s — new %s land in Contacts automatically. Auto-add on for %s — new %s land in Contacts automatically.
|
||||
Batteryactivityon Batteryactivityon
|
||||
Console mode Console mode
|
||||
Console mode (experimental) Console mode (experimental)
|
||||
Direct (0-hop) only Direct (0-hop) only
|
||||
@@ -1051,8 +1045,6 @@ EXPERIMENTAL. Boots into a text console with no graphical interface: type comman
|
||||
Enter text Enter text
|
||||
Entering console mode - rebooting... Entering console mode - rebooting...
|
||||
Favorite Favorite
|
||||
Geblokkeerde gebruikers Geblokkeerde gebruikers
|
||||
Hide system filesShow system files Hide system filesShow system files
|
||||
How tiles work:\nThe map is built from 256×256 "slippy" tiles. Only the tiles for the area you're viewing are fetched — there is no bulk pre-download.\n\nBecause this device can't do HTTPS (not enough heap after Wi-Fi starts) and decodes JPEG far more cheaply than PNG, tiles come from the wadamesh proxy: it fetches the upstream PNG over HTTPS with an identifying User-Agent, re-encodes it as JPEG, and caches it. Your device then caches each tile to its own flash, so a tile is only downloaded once.\n\nUse Options → Reload tiles to re-download the tiles currently in view if one looks corrupted. How tiles work:\nThe map is built from 256×256 "slippy" tiles. Only the tiles for the area you're viewing are fetched — there is no bulk pre-download.\n\nBecause this device can't do HTTPS (not enough heap after Wi-Fi starts) and decodes JPEG far more cheaply than PNG, tiles come from the wadamesh proxy: it fetches the upstream PNG over HTTPS with an identifying User-Agent, re-encodes it as JPEG, and caches it. Your device then caches each tile to its own flash, so a tile is only downloaded once.\n\nUse Options → Reload tiles to re-download the tiles currently in view if one looks corrupted.
|
||||
Init… Init…
|
||||
Leaving console mode - rebooting... Leaving console mode - rebooting...
|
||||
@@ -1062,8 +1054,6 @@ Map data © OpenStreetMap contributors.\nopenstreetmap.org/copyright\nLicensed u
|
||||
Map style © OpenTopoMap (CC-BY-SA) — opentopomap.org\nMap data © OpenStreetMap contributors (ODbL) + SRTM.\n\n Map style © OpenTopoMap (CC-BY-SA) — opentopomap.org\nMap data © OpenStreetMap contributors (ODbL) + SRTM.\n\n
|
||||
Name (A-Z) Name (A-Z)
|
||||
Nearest first Nearest first
|
||||
Other networksNetworks Other networksNetworks
|
||||
Paste (move)Paste (copy) Paste (move)Paste (copy)
|
||||
Recent message Recent message
|
||||
Reload tiles in view Reload tiles in view
|
||||
Repeaters Repeaters
|
||||
@@ -1076,13 +1066,8 @@ Show contacts Show contacts
|
||||
Show coordinates Show coordinates
|
||||
Show tile z/x/y Show tile z/x/y
|
||||
Starting… Starting…
|
||||
Stop sharing loc Share my loc Stop sharing loc Share my loc
|
||||
Supprimer Supprimer
|
||||
Switch to the external antenna?\n\nMake sure an antenna is actually connected to the external antenna socket first. Transmitting with nothing attached can damage the radio.\n\nResets to the on-board antenna on every reboot.Turn on legacy per-transmit switching?\n\nDiagnostic mode, for comparison only. It transmits on the on-board antenna and listens on the external one, so your outbound signal will be much weaker than your inbound.\n\nResets to the on-board antenna on every reboot. Switch to the external antenna?\n\nMake sure an antenna is actually connected to the external antenna socket first. Transmitting with nothing attached can damage the radio.\n\nResets to the on-board antenna on every reboot.Turn on legacy per-transmit switching?\n\nDiagnostic mode, for comparison only. It transmits on the on-board antenna and listens on the external one, so your outbound signal will be much weaker than your inbound.\n\nResets to the on-board antenna on every reboot.
|
||||
Text console (experimental) Text console (experimental)
|
||||
Tile debug overlay Tile debug overlay
|
||||
Unblock Block Unblock Block
|
||||
Unfav Favorite Unfav Favorite
|
||||
chats chats
|
||||
repeaters repeaters
|
||||
rooms rooms
|
||||
@@ -1098,3 +1083,26 @@ channel busy %.1f%% channel busy %.1f%%
|
||||
sampling... sampling...
|
||||
teal = now blue = average teal = now blue = average
|
||||
window restarted window restarted
|
||||
Filter Filter
|
||||
Hide system files Hide system files
|
||||
Networks Networks
|
||||
Other networks Other networks
|
||||
Show system files Show system files
|
||||
Unfav Unfav
|
||||
Firmware beta_%d\nChecking for updates over Wi-Fi… Firmware beta_%d\nChecking for updates over Wi-Fi…
|
||||
Install beta_%d?\nThis downgrades the firmware and reboots. Install beta_%d?\nThis downgrades the firmware and reboots.
|
||||
Other (hidden) network… Other (hidden) network…
|
||||
Paused Paused
|
||||
Scan again Scan again
|
||||
At a glance At a glance
|
||||
At a glance disabled At a glance disabled
|
||||
At a glance enabled At a glance enabled
|
||||
At a glance while locked At a glance while locked
|
||||
Glance only shows when unlocked Glance only shows when unlocked
|
||||
Glance shows message previews\nwhile the screen is locked Glance shows message previews\nwhile the screen is locked
|
||||
Snake — swipe, roll, or press a direction Snake — swipe, roll, or press a direction
|
||||
N/A N/A
|
||||
This board cannot run this app. This board cannot run this app.
|
||||
Signal Signal
|
||||
Signal (stale) Signal (stale)
|
||||
nothing heard yet nothing heard yet
|
||||
|
||||
+24
-16
@@ -1,7 +1,7 @@
|
||||
# wadamesh language file
|
||||
# canonical source - translators: edit this file and PR it
|
||||
# code: es
|
||||
# ver: 18
|
||||
# ver: 19
|
||||
# name: Español
|
||||
# base: es
|
||||
(device behind) (dispositivo atrasado)
|
||||
@@ -977,8 +977,6 @@ tap to exit full screen toca para salir de pantalla completa
|
||||
zoom zoom
|
||||
zoom %d zoom %d
|
||||
~17 dB amp for quiet/remote areas; turn off in noisy spots. amplificador ~17 dB para zonas tranquilas/remotas; apaga en zonas ruidosas.
|
||||
© OpenStreetMap © OpenStreetMap
|
||||
© OpenTopoMap © OpenTopoMap
|
||||
Scope
|
||||
another region
|
||||
my region
|
||||
@@ -1000,7 +998,6 @@ Overwrite oldest non-favorite
|
||||
1-character messages will be shown 1-character messages will be shown
|
||||
All All
|
||||
Allow Allow
|
||||
Apps that may send messages on the mesh. Anything they send goes out under your node name and cannot be told apart from a message you typed. Turn one off to take the permission back. Apps that may send messages on the mesh. Anything they send goes out under your node name and cannot be told apart from a message you typed. Turn one off to take the permission back.
|
||||
Ignore 1-character messages Ignore 1-character messages
|
||||
Join w/ password Join w/ password
|
||||
Location no longer shared Location no longer shared
|
||||
@@ -1014,9 +1011,7 @@ Never\nChosen contacts only\nAnyone who asks Never\nChosen contacts only\nAnyone
|
||||
No apps installed. No apps installed.
|
||||
No reply. Path hash is %u bytes;\nolder repeaters drop those.\nTry 1 byte (Settings > Radio & Mesh) No reply. Path hash is %u bytes;\nolder repeaters drop those.\nTry 1 byte (Settings > Radio & Mesh)
|
||||
Not supported on this board Not supported on this board
|
||||
Read incoming messages Read incoming messages
|
||||
Reconnected to %.40s Reconnected to %.40s
|
||||
Send messages as me Send messages as me
|
||||
Sent only when that contact asks, encrypted to them. Nothing is broadcast. Needs "Answer telemetry requests" on. Pick contacts in a contact's menu. Sent only when that contact asks, encrypted to them. Nothing is broadcast. Needs "Answer telemetry requests" on. Pick contacts in a contact's menu.
|
||||
Server has forgotten you.\nJoin with the room password. Server has forgotten you.\nJoin with the room password.
|
||||
Share my loc Share my loc
|
||||
@@ -1042,7 +1037,6 @@ App permissions App permissions
|
||||
About / credits About / credits
|
||||
Auth failed Auth failed
|
||||
Auto-add on for %s — new %s land in Contacts automatically. Auto-add on for %s — new %s land in Contacts automatically.
|
||||
Batteryactivityon Batteryactivityon
|
||||
Console mode Console mode
|
||||
Console mode (experimental) Console mode (experimental)
|
||||
Direct (0-hop) only Direct (0-hop) only
|
||||
@@ -1051,8 +1045,6 @@ EXPERIMENTAL. Boots into a text console with no graphical interface: type comman
|
||||
Enter text Enter text
|
||||
Entering console mode - rebooting... Entering console mode - rebooting...
|
||||
Favorite Favorite
|
||||
Geblokkeerde gebruikers Geblokkeerde gebruikers
|
||||
Hide system filesShow system files Hide system filesShow system files
|
||||
How tiles work:\nThe map is built from 256×256 "slippy" tiles. Only the tiles for the area you're viewing are fetched — there is no bulk pre-download.\n\nBecause this device can't do HTTPS (not enough heap after Wi-Fi starts) and decodes JPEG far more cheaply than PNG, tiles come from the wadamesh proxy: it fetches the upstream PNG over HTTPS with an identifying User-Agent, re-encodes it as JPEG, and caches it. Your device then caches each tile to its own flash, so a tile is only downloaded once.\n\nUse Options → Reload tiles to re-download the tiles currently in view if one looks corrupted. How tiles work:\nThe map is built from 256×256 "slippy" tiles. Only the tiles for the area you're viewing are fetched — there is no bulk pre-download.\n\nBecause this device can't do HTTPS (not enough heap after Wi-Fi starts) and decodes JPEG far more cheaply than PNG, tiles come from the wadamesh proxy: it fetches the upstream PNG over HTTPS with an identifying User-Agent, re-encodes it as JPEG, and caches it. Your device then caches each tile to its own flash, so a tile is only downloaded once.\n\nUse Options → Reload tiles to re-download the tiles currently in view if one looks corrupted.
|
||||
Init… Init…
|
||||
Leaving console mode - rebooting... Leaving console mode - rebooting...
|
||||
@@ -1062,8 +1054,6 @@ Map data © OpenStreetMap contributors.\nopenstreetmap.org/copyright\nLicensed u
|
||||
Map style © OpenTopoMap (CC-BY-SA) — opentopomap.org\nMap data © OpenStreetMap contributors (ODbL) + SRTM.\n\n Map style © OpenTopoMap (CC-BY-SA) — opentopomap.org\nMap data © OpenStreetMap contributors (ODbL) + SRTM.\n\n
|
||||
Name (A-Z) Name (A-Z)
|
||||
Nearest first Nearest first
|
||||
Other networksNetworks Other networksNetworks
|
||||
Paste (move)Paste (copy) Paste (move)Paste (copy)
|
||||
Recent message Recent message
|
||||
Reload tiles in view Reload tiles in view
|
||||
Repeaters Repeaters
|
||||
@@ -1076,13 +1066,8 @@ Show contacts Show contacts
|
||||
Show coordinates Show coordinates
|
||||
Show tile z/x/y Show tile z/x/y
|
||||
Starting… Starting…
|
||||
Stop sharing loc Share my loc Stop sharing loc Share my loc
|
||||
Supprimer Supprimer
|
||||
Switch to the external antenna?\n\nMake sure an antenna is actually connected to the external antenna socket first. Transmitting with nothing attached can damage the radio.\n\nResets to the on-board antenna on every reboot.Turn on legacy per-transmit switching?\n\nDiagnostic mode, for comparison only. It transmits on the on-board antenna and listens on the external one, so your outbound signal will be much weaker than your inbound.\n\nResets to the on-board antenna on every reboot. Switch to the external antenna?\n\nMake sure an antenna is actually connected to the external antenna socket first. Transmitting with nothing attached can damage the radio.\n\nResets to the on-board antenna on every reboot.Turn on legacy per-transmit switching?\n\nDiagnostic mode, for comparison only. It transmits on the on-board antenna and listens on the external one, so your outbound signal will be much weaker than your inbound.\n\nResets to the on-board antenna on every reboot.
|
||||
Text console (experimental) Text console (experimental)
|
||||
Tile debug overlay Tile debug overlay
|
||||
Unblock Block Unblock Block
|
||||
Unfav Favorite Unfav Favorite
|
||||
chats chats
|
||||
repeaters repeaters
|
||||
rooms rooms
|
||||
@@ -1098,3 +1083,26 @@ channel busy %.1f%% channel busy %.1f%%
|
||||
sampling... sampling...
|
||||
teal = now blue = average teal = now blue = average
|
||||
window restarted window restarted
|
||||
Filter Filter
|
||||
Hide system files Hide system files
|
||||
Networks Networks
|
||||
Other networks Other networks
|
||||
Show system files Show system files
|
||||
Unfav Unfav
|
||||
Firmware beta_%d\nChecking for updates over Wi-Fi… Firmware beta_%d\nChecking for updates over Wi-Fi…
|
||||
Install beta_%d?\nThis downgrades the firmware and reboots. Install beta_%d?\nThis downgrades the firmware and reboots.
|
||||
Other (hidden) network… Other (hidden) network…
|
||||
Paused Paused
|
||||
Scan again Scan again
|
||||
At a glance At a glance
|
||||
At a glance disabled At a glance disabled
|
||||
At a glance enabled At a glance enabled
|
||||
At a glance while locked At a glance while locked
|
||||
Glance only shows when unlocked Glance only shows when unlocked
|
||||
Glance shows message previews\nwhile the screen is locked Glance shows message previews\nwhile the screen is locked
|
||||
Snake — swipe, roll, or press a direction Snake — swipe, roll, or press a direction
|
||||
N/A N/A
|
||||
This board cannot run this app. This board cannot run this app.
|
||||
Signal Signal
|
||||
Signal (stale) Signal (stale)
|
||||
nothing heard yet nothing heard yet
|
||||
|
||||
+24
-17
@@ -1,7 +1,7 @@
|
||||
# wadamesh language file
|
||||
# canonical source - translators: edit this file and PR it
|
||||
# code: fr
|
||||
# ver: 18
|
||||
# ver: 19
|
||||
# name: Français
|
||||
# base: fr
|
||||
(device behind) (appareil en retard)
|
||||
@@ -452,7 +452,6 @@ Login OK (perms %u) Connexion OK (perms %u)
|
||||
Login failed Échec de connexion
|
||||
Logs Journaux
|
||||
Longitude Longitude
|
||||
Lua Store Lua Store
|
||||
MHz MHz
|
||||
MQTT bridge Pont MQTT
|
||||
Map Carte
|
||||
@@ -977,8 +976,6 @@ tap to exit full screen toucher pour quitter le plein écran
|
||||
zoom zoom
|
||||
zoom %d zoom %d
|
||||
~17 dB amp for quiet/remote areas; turn off in noisy spots. ampli ~17 dB pour zones calmes/isolées ; désactiver en zone bruyante.
|
||||
© OpenStreetMap © OpenStreetMap
|
||||
© OpenTopoMap © OpenTopoMap
|
||||
Scope
|
||||
another region
|
||||
my region
|
||||
@@ -1000,7 +997,6 @@ Overwrite oldest non-favorite
|
||||
1-character messages will be shown 1-character messages will be shown
|
||||
All All
|
||||
Allow Allow
|
||||
Apps that may send messages on the mesh. Anything they send goes out under your node name and cannot be told apart from a message you typed. Turn one off to take the permission back. Apps that may send messages on the mesh. Anything they send goes out under your node name and cannot be told apart from a message you typed. Turn one off to take the permission back.
|
||||
Ignore 1-character messages Ignore 1-character messages
|
||||
Join w/ password Join w/ password
|
||||
Location no longer shared Location no longer shared
|
||||
@@ -1014,9 +1010,7 @@ Never\nChosen contacts only\nAnyone who asks Never\nChosen contacts only\nAnyone
|
||||
No apps installed. No apps installed.
|
||||
No reply. Path hash is %u bytes;\nolder repeaters drop those.\nTry 1 byte (Settings > Radio & Mesh) No reply. Path hash is %u bytes;\nolder repeaters drop those.\nTry 1 byte (Settings > Radio & Mesh)
|
||||
Not supported on this board Not supported on this board
|
||||
Read incoming messages Read incoming messages
|
||||
Reconnected to %.40s Reconnected to %.40s
|
||||
Send messages as me Send messages as me
|
||||
Sent only when that contact asks, encrypted to them. Nothing is broadcast. Needs "Answer telemetry requests" on. Pick contacts in a contact's menu. Sent only when that contact asks, encrypted to them. Nothing is broadcast. Needs "Answer telemetry requests" on. Pick contacts in a contact's menu.
|
||||
Server has forgotten you.\nJoin with the room password. Server has forgotten you.\nJoin with the room password.
|
||||
Share my loc Share my loc
|
||||
@@ -1042,7 +1036,6 @@ App permissions App permissions
|
||||
About / credits About / credits
|
||||
Auth failed Auth failed
|
||||
Auto-add on for %s — new %s land in Contacts automatically. Auto-add on for %s — new %s land in Contacts automatically.
|
||||
Batteryactivityon Batteryactivityon
|
||||
Console mode Console mode
|
||||
Console mode (experimental) Console mode (experimental)
|
||||
Direct (0-hop) only Direct (0-hop) only
|
||||
@@ -1051,8 +1044,6 @@ EXPERIMENTAL. Boots into a text console with no graphical interface: type comman
|
||||
Enter text Enter text
|
||||
Entering console mode - rebooting... Entering console mode - rebooting...
|
||||
Favorite Favorite
|
||||
Geblokkeerde gebruikers Geblokkeerde gebruikers
|
||||
Hide system filesShow system files Hide system filesShow system files
|
||||
How tiles work:\nThe map is built from 256×256 "slippy" tiles. Only the tiles for the area you're viewing are fetched — there is no bulk pre-download.\n\nBecause this device can't do HTTPS (not enough heap after Wi-Fi starts) and decodes JPEG far more cheaply than PNG, tiles come from the wadamesh proxy: it fetches the upstream PNG over HTTPS with an identifying User-Agent, re-encodes it as JPEG, and caches it. Your device then caches each tile to its own flash, so a tile is only downloaded once.\n\nUse Options → Reload tiles to re-download the tiles currently in view if one looks corrupted. How tiles work:\nThe map is built from 256×256 "slippy" tiles. Only the tiles for the area you're viewing are fetched — there is no bulk pre-download.\n\nBecause this device can't do HTTPS (not enough heap after Wi-Fi starts) and decodes JPEG far more cheaply than PNG, tiles come from the wadamesh proxy: it fetches the upstream PNG over HTTPS with an identifying User-Agent, re-encodes it as JPEG, and caches it. Your device then caches each tile to its own flash, so a tile is only downloaded once.\n\nUse Options → Reload tiles to re-download the tiles currently in view if one looks corrupted.
|
||||
Init… Init…
|
||||
Leaving console mode - rebooting... Leaving console mode - rebooting...
|
||||
@@ -1062,8 +1053,6 @@ Map data © OpenStreetMap contributors.\nopenstreetmap.org/copyright\nLicensed u
|
||||
Map style © OpenTopoMap (CC-BY-SA) — opentopomap.org\nMap data © OpenStreetMap contributors (ODbL) + SRTM.\n\n Map style © OpenTopoMap (CC-BY-SA) — opentopomap.org\nMap data © OpenStreetMap contributors (ODbL) + SRTM.\n\n
|
||||
Name (A-Z) Name (A-Z)
|
||||
Nearest first Nearest first
|
||||
Other networksNetworks Other networksNetworks
|
||||
Paste (move)Paste (copy) Paste (move)Paste (copy)
|
||||
Recent message Recent message
|
||||
Reload tiles in view Reload tiles in view
|
||||
Repeaters Repeaters
|
||||
@@ -1076,13 +1065,8 @@ Show contacts Show contacts
|
||||
Show coordinates Show coordinates
|
||||
Show tile z/x/y Show tile z/x/y
|
||||
Starting… Starting…
|
||||
Stop sharing loc Share my loc Stop sharing loc Share my loc
|
||||
Supprimer Supprimer
|
||||
Switch to the external antenna?\n\nMake sure an antenna is actually connected to the external antenna socket first. Transmitting with nothing attached can damage the radio.\n\nResets to the on-board antenna on every reboot.Turn on legacy per-transmit switching?\n\nDiagnostic mode, for comparison only. It transmits on the on-board antenna and listens on the external one, so your outbound signal will be much weaker than your inbound.\n\nResets to the on-board antenna on every reboot. Switch to the external antenna?\n\nMake sure an antenna is actually connected to the external antenna socket first. Transmitting with nothing attached can damage the radio.\n\nResets to the on-board antenna on every reboot.Turn on legacy per-transmit switching?\n\nDiagnostic mode, for comparison only. It transmits on the on-board antenna and listens on the external one, so your outbound signal will be much weaker than your inbound.\n\nResets to the on-board antenna on every reboot.
|
||||
Text console (experimental) Text console (experimental)
|
||||
Tile debug overlay Tile debug overlay
|
||||
Unblock Block Unblock Block
|
||||
Unfav Favorite Unfav Favorite
|
||||
chats chats
|
||||
repeaters repeaters
|
||||
rooms rooms
|
||||
@@ -1098,3 +1082,26 @@ channel busy %.1f%% channel busy %.1f%%
|
||||
sampling... sampling...
|
||||
teal = now blue = average teal = now blue = average
|
||||
window restarted window restarted
|
||||
Filter Filter
|
||||
Hide system files Hide system files
|
||||
Networks Networks
|
||||
Other networks Other networks
|
||||
Show system files Show system files
|
||||
Unfav Unfav
|
||||
Firmware beta_%d\nChecking for updates over Wi-Fi… Firmware beta_%d\nChecking for updates over Wi-Fi…
|
||||
Install beta_%d?\nThis downgrades the firmware and reboots. Install beta_%d?\nThis downgrades the firmware and reboots.
|
||||
Other (hidden) network… Other (hidden) network…
|
||||
Paused Paused
|
||||
Scan again Scan again
|
||||
At a glance At a glance
|
||||
At a glance disabled At a glance disabled
|
||||
At a glance enabled At a glance enabled
|
||||
At a glance while locked At a glance while locked
|
||||
Glance only shows when unlocked Glance only shows when unlocked
|
||||
Glance shows message previews\nwhile the screen is locked Glance shows message previews\nwhile the screen is locked
|
||||
Snake — swipe, roll, or press a direction Snake — swipe, roll, or press a direction
|
||||
N/A N/A
|
||||
This board cannot run this app. This board cannot run this app.
|
||||
Signal Signal
|
||||
Signal (stale) Signal (stale)
|
||||
nothing heard yet nothing heard yet
|
||||
|
||||
+23
-16
@@ -1,7 +1,7 @@
|
||||
# wadamesh language file
|
||||
# canonical source - translators: edit this file and PR it
|
||||
# code: hu
|
||||
# ver: 18
|
||||
# ver: 19
|
||||
# name: Magyar
|
||||
# base: hu
|
||||
(device behind) (készülék hátulján)
|
||||
@@ -703,7 +703,7 @@ SD card remounted SD kártya újracsatolva
|
||||
SD card removed SD-kártya eltávolítva
|
||||
SD data is unavailable - contacts and channels cannot be saved until the card is reinserted. Az SD-kártya adatai nem érhetők el – a névjegyek és a csatornák nem menthetők a kártya újbóli behelyezéséig.
|
||||
SD data is unavailable - identity, settings, contacts and channels cannot be saved until the card is reinserted. Az SD-kártya adatai nem érhetők el – az azonosító, a beállítások, a névjegyek és a csatornák nem menthetők a kártya újbóli behelyezéséig.
|
||||
SD data migration is incomplete. Identity and settings remain internal; use Copy internal data to SD to retry. Az SD-kártya adatainak áthelyezése nem teljes. Az azonosító és a beállítások továbbra is a belső tárhelyen vannak; a folytatáshoz használd a „Belső adatok másolása SD-kártyára” lehetőséget.
|
||||
SD data migration is incomplete. Identity and settings remain internal; use Copy internal data to SD to retry. Az SD-kártya adatainak áthelyezése nem teljes. Az azonosító és a beállítások továbbra is a belső tárhelyen vannak; a folytatáshoz használd a "Belső adatok másolása SD-kártyára" lehetőséget.
|
||||
SD download failed: %s SD-letöltés sikertelen: %s
|
||||
SD formatted - %s (MESHCOMOD) SD formázás - %s (MESHCOMOD)
|
||||
SD is busy - close tools and retry Az SD-kártya foglalt – zárd be az eszközöket, majd próbáld újra.
|
||||
@@ -812,7 +812,6 @@ Sleep ready Készen áll az alvásra
|
||||
Slot %d cleared %d hely kiürítve
|
||||
Slot empty Üres hely
|
||||
Small\nMedium\nLarge Kicsi\nKözepes\nNagy
|
||||
Snake Snake
|
||||
Snake — swipe or roll the trackball Kígyó – pöccintsd vagy görgesd a görgetőgolyót
|
||||
Sort Sort
|
||||
Sound Hang
|
||||
@@ -997,8 +996,6 @@ tap to exit full screen koppintson a teljes képernyős kilépéshez
|
||||
zoom zoom
|
||||
zoom %d zoom %d
|
||||
~17 dB amp for quiet/remote areas; turn off in noisy spots. ~17 dB erősítővel csendes/távoli helyeken; zajos helyeken kapcsold ki.
|
||||
© OpenStreetMap © OpenStreetMap
|
||||
© OpenTopoMap © OpenTopoMap
|
||||
Scope Hatókör
|
||||
another region másik régió
|
||||
my region saját régió
|
||||
@@ -1065,11 +1062,10 @@ sensors érzékelők
|
||||
Auto-add on for %s — new %s land in Contacts automatically. Automatikus hozzáadás bekapcsolva: %s — az új %s automatikusan a Névjegyek közé kerülnek.
|
||||
Map data © OpenStreetMap contributors.\nopenstreetmap.org/copyright\nLicensed under the Open Database License (ODbL).\n\n Térkép adatok © OpenStreetMap közreműködői.\nopenstreetmap.org/copyright\nAz Open Database License (ODbL) licenc alatt.\n\n
|
||||
Map style © OpenTopoMap (CC-BY-SA) — opentopomap.org\nMap data © OpenStreetMap contributors (ODbL) + SRTM.\n\n Térkép stílus © OpenTopoMap (CC-BY-SA) — opentopomap.org\nTérkép adatok © OpenStreetMap közreműködői (ODbL) + SRTM.\n\n
|
||||
How tiles work:\nThe map is built from 256×256 "slippy" tiles. Only the tiles for the area you're viewing are fetched — there is no bulk pre-download.\n\nBecause this device can't do HTTPS (not enough heap after Wi-Fi starts) and decodes JPEG far more cheaply than PNG, tiles come from the wadamesh proxy: it fetches the upstream PNG over HTTPS with an identifying User-Agent, re-encodes it as JPEG, and caches it. Your device then caches each tile to its own flash, so a tile is only downloaded once.\n\nUse Options → Reload tiles to re-download the tiles currently in view if one looks corrupted. A csempék működése:\nA térkép 256×256 képpontos „slippy” csempékből épül fel. Csak a megtekintett területhez szükséges csempék töltődnek le — nincs tömeges előzetes letöltés.\n\nMivel az eszköz nem támogatja a HTTPS-t (a Wi-Fi elindítása után nincs elegendő heap memória), és a JPEG képek dekódolása sokkal kevesebb erőforrást igényel, mint a PNG-é, a csempék a wadamesh proxyn keresztül érkeznek: a proxy HTTPS-en keresztül lekéri a forrásból a PNG-t egy azonosító User-Agent használatával, JPEG formátumba kódolja át, majd eltárolja a gyorsítótárban. Az eszköz ezután minden csempét a saját flash memóriájában is eltárol, így egy csempét csak egyszer kell letölteni.\n\nAz Options → Reload tiles lehetőséggel újra letöltheted a jelenleg megtekintett terület csempéit, ha valamelyik hibásan jelenik meg.
|
||||
How tiles work:\nThe map is built from 256×256 "slippy" tiles. Only the tiles for the area you're viewing are fetched — there is no bulk pre-download.\n\nBecause this device can't do HTTPS (not enough heap after Wi-Fi starts) and decodes JPEG far more cheaply than PNG, tiles come from the wadamesh proxy: it fetches the upstream PNG over HTTPS with an identifying User-Agent, re-encodes it as JPEG, and caches it. Your device then caches each tile to its own flash, so a tile is only downloaded once.\n\nUse Options → Reload tiles to re-download the tiles currently in view if one looks corrupted. A csempék működése:\nA térkép 256×256 képpontos "slippy" csempékből épül fel. Csak a megtekintett területhez szükséges csempék töltődnek le — nincs tömeges előzetes letöltés.\n\nMivel az eszköz nem támogatja a HTTPS-t (a Wi-Fi elindítása után nincs elegendő heap memória), és a JPEG képek dekódolása sokkal kevesebb erőforrást igényel, mint a PNG-é, a csempék a wadamesh proxyn keresztül érkeznek: a proxy HTTPS-en keresztül lekéri a forrásból a PNG-t egy azonosító User-Agent használatával, JPEG formátumba kódolja át, majd eltárolja a gyorsítótárban. Az eszköz ezután minden csempét a saját flash memóriájában is eltárol, így egy csempét csak egyszer kell letölteni.\n\nAz Options → Reload tiles lehetőséggel újra letöltheted a jelenleg megtekintett terület csempéit, ha valamelyik hibásan jelenik meg.
|
||||
"%s" wants to send discovery probes.\n\nEach probe asks every node in range to reply, so it uses airtime on the whole local mesh, not just yours.\n\nNothing is sent under your name and no message is transmitted. "%s" felfedezési probe-ok küldését kéri.\n\nMinden probe válaszra kéri a hatótávolságon belüli node-okat, ezért a teljes helyi mesh hálózat airtime-ját használja, nem csak a tiédet.\n\nSemmi sem kerül elküldésre a nevedben, és üzenet sem kerül továbbításra.
|
||||
? ?
|
||||
Auth failed Auth failed
|
||||
Batteryactivityon Batteryactivityon
|
||||
Console mode Console mode
|
||||
Console mode (experimental) Console mode (experimental)
|
||||
Direct (0-hop) only Csak közvetlen (0-hop)
|
||||
@@ -1078,16 +1074,12 @@ EXPERIMENTAL. Boots into a text console with no graphical interface: type comman
|
||||
Enter text Szöveg megadása
|
||||
Entering console mode - rebooting... Entering console mode - rebooting...
|
||||
Favorite Favorite
|
||||
Geblokkeerde gebruikers Geblokkeerde gebruikers
|
||||
Hide system filesShow system files Hide system filesShow system files
|
||||
Init… Init…
|
||||
Leaving console mode - rebooting... Leaving console mode - rebooting...
|
||||
Link lost Link lost
|
||||
MESH MESH
|
||||
Name (A-Z) Név (A–Z)
|
||||
Nearest first Legközelebbi elöl
|
||||
Other networksNetworks Other networksNetworks
|
||||
Paste (move)Paste (copy) Paste (move)Paste (copy)
|
||||
Recent message Legutóbbi üzenet
|
||||
Reload tiles in view Csempék újratöltése
|
||||
Repeaters Repeaters
|
||||
@@ -1098,13 +1090,8 @@ Show contacts Névjegyek megjelenítése
|
||||
Show coordinates Koordináták
|
||||
Show tile z/x/y Csempe z/x/y
|
||||
Starting… Starting…
|
||||
Stop sharing loc Share my loc Stop sharing loc Share my loc
|
||||
Supprimer Supprimer
|
||||
Switch to the external antenna?\n\nMake sure an antenna is actually connected to the external antenna socket first. Transmitting with nothing attached can damage the radio.\n\nResets to the on-board antenna on every reboot.Turn on legacy per-transmit switching?\n\nDiagnostic mode, for comparison only. It transmits on the on-board antenna and listens on the external one, so your outbound signal will be much weaker than your inbound.\n\nResets to the on-board antenna on every reboot. Switch to the external antenna?\n\nMake sure an antenna is actually connected to the external antenna socket first. Transmitting with nothing attached can damage the radio.\n\nResets to the on-board antenna on every reboot.Turn on legacy per-transmit switching?\n\nDiagnostic mode, for comparison only. It transmits on the on-board antenna and listens on the external one, so your outbound signal will be much weaker than your inbound.\n\nResets to the on-board antenna on every reboot.
|
||||
Text console (experimental) Text console (experimental)
|
||||
Tile debug overlay Csempe hiba keret
|
||||
Unblock Block Unblock Block
|
||||
Unfav Favorite Unfav Favorite
|
||||
tap to cycle off / on / auto tap to cycle off / on / auto
|
||||
#7A7F87 Scanning… nothing has answered yet# #7A7F87 Scanning… nothing has answered yet#
|
||||
Export crash report (%uK) Hibajelentés exportálása (%uK)
|
||||
@@ -1116,3 +1103,23 @@ channel busy %.1f%% channel busy %.1f%%
|
||||
sampling... sampling...
|
||||
teal = now blue = average teal = now blue = average
|
||||
window restarted window restarted
|
||||
Filter Filter
|
||||
Hide system files Hide system files
|
||||
Networks Networks
|
||||
Show system files Show system files
|
||||
Unfav Unfav
|
||||
Firmware beta_%d\nChecking for updates over Wi-Fi… Firmware beta_%d\nChecking for updates over Wi-Fi…
|
||||
Install beta_%d?\nThis downgrades the firmware and reboots. A beta_%d telepítése?\nEz leminősíti a firmware-t és újraindul.
|
||||
Other (hidden) network… Other (hidden) network…
|
||||
Paused Paused
|
||||
At a glance At a glance
|
||||
At a glance disabled At a glance disabled
|
||||
At a glance enabled At a glance enabled
|
||||
At a glance while locked At a glance while locked
|
||||
Glance only shows when unlocked Glance only shows when unlocked
|
||||
Glance shows message previews\nwhile the screen is locked Glance shows message previews\nwhile the screen is locked
|
||||
Snake — swipe, roll, or press a direction Snake — swipe, roll, or press a direction
|
||||
N/A N/A
|
||||
This board cannot run this app. This board cannot run this app.
|
||||
Signal (stale) Jel (elavult)
|
||||
nothing heard yet Még semmi hír.
|
||||
|
||||
+24
-17
@@ -1,7 +1,7 @@
|
||||
# wadamesh language file
|
||||
# canonical source - translators: edit this file and PR it
|
||||
# code: it
|
||||
# ver: 18
|
||||
# ver: 19
|
||||
# name: Italiano
|
||||
# base: it
|
||||
(device behind) (dispositivo indietro)
|
||||
@@ -452,7 +452,6 @@ Login OK (perms %u) Accesso OK (permessi %u)
|
||||
Login failed Accesso non riuscito
|
||||
Logs Log
|
||||
Longitude Longitudine
|
||||
Lua Store Lua Store
|
||||
MHz MHz
|
||||
MQTT bridge Ponte MQTT
|
||||
Map Mappa
|
||||
@@ -977,8 +976,6 @@ tap to exit full screen tocca per uscire da schermo intero
|
||||
zoom zoom
|
||||
zoom %d zoom %d
|
||||
~17 dB amp for quiet/remote areas; turn off in noisy spots. ampli ~17 dB per zone tranquille/remote; spegni in zone rumorose.
|
||||
© OpenStreetMap © OpenStreetMap
|
||||
© OpenTopoMap © OpenTopoMap
|
||||
Scope
|
||||
another region
|
||||
my region
|
||||
@@ -1000,7 +997,6 @@ Overwrite oldest non-favorite
|
||||
1-character messages will be shown 1-character messages will be shown
|
||||
All All
|
||||
Allow Allow
|
||||
Apps that may send messages on the mesh. Anything they send goes out under your node name and cannot be told apart from a message you typed. Turn one off to take the permission back. Apps that may send messages on the mesh. Anything they send goes out under your node name and cannot be told apart from a message you typed. Turn one off to take the permission back.
|
||||
Ignore 1-character messages Ignore 1-character messages
|
||||
Join w/ password Join w/ password
|
||||
Location no longer shared Location no longer shared
|
||||
@@ -1014,9 +1010,7 @@ Never\nChosen contacts only\nAnyone who asks Never\nChosen contacts only\nAnyone
|
||||
No apps installed. No apps installed.
|
||||
No reply. Path hash is %u bytes;\nolder repeaters drop those.\nTry 1 byte (Settings > Radio & Mesh) No reply. Path hash is %u bytes;\nolder repeaters drop those.\nTry 1 byte (Settings > Radio & Mesh)
|
||||
Not supported on this board Not supported on this board
|
||||
Read incoming messages Read incoming messages
|
||||
Reconnected to %.40s Reconnected to %.40s
|
||||
Send messages as me Send messages as me
|
||||
Sent only when that contact asks, encrypted to them. Nothing is broadcast. Needs "Answer telemetry requests" on. Pick contacts in a contact's menu. Sent only when that contact asks, encrypted to them. Nothing is broadcast. Needs "Answer telemetry requests" on. Pick contacts in a contact's menu.
|
||||
Server has forgotten you.\nJoin with the room password. Server has forgotten you.\nJoin with the room password.
|
||||
Share my loc Share my loc
|
||||
@@ -1042,7 +1036,6 @@ App permissions App permissions
|
||||
About / credits About / credits
|
||||
Auth failed Auth failed
|
||||
Auto-add on for %s — new %s land in Contacts automatically. Auto-add on for %s — new %s land in Contacts automatically.
|
||||
Batteryactivityon Batteryactivityon
|
||||
Console mode Console mode
|
||||
Console mode (experimental) Console mode (experimental)
|
||||
Direct (0-hop) only Direct (0-hop) only
|
||||
@@ -1051,8 +1044,6 @@ EXPERIMENTAL. Boots into a text console with no graphical interface: type comman
|
||||
Enter text Enter text
|
||||
Entering console mode - rebooting... Entering console mode - rebooting...
|
||||
Favorite Favorite
|
||||
Geblokkeerde gebruikers Geblokkeerde gebruikers
|
||||
Hide system filesShow system files Hide system filesShow system files
|
||||
How tiles work:\nThe map is built from 256×256 "slippy" tiles. Only the tiles for the area you're viewing are fetched — there is no bulk pre-download.\n\nBecause this device can't do HTTPS (not enough heap after Wi-Fi starts) and decodes JPEG far more cheaply than PNG, tiles come from the wadamesh proxy: it fetches the upstream PNG over HTTPS with an identifying User-Agent, re-encodes it as JPEG, and caches it. Your device then caches each tile to its own flash, so a tile is only downloaded once.\n\nUse Options → Reload tiles to re-download the tiles currently in view if one looks corrupted. How tiles work:\nThe map is built from 256×256 "slippy" tiles. Only the tiles for the area you're viewing are fetched — there is no bulk pre-download.\n\nBecause this device can't do HTTPS (not enough heap after Wi-Fi starts) and decodes JPEG far more cheaply than PNG, tiles come from the wadamesh proxy: it fetches the upstream PNG over HTTPS with an identifying User-Agent, re-encodes it as JPEG, and caches it. Your device then caches each tile to its own flash, so a tile is only downloaded once.\n\nUse Options → Reload tiles to re-download the tiles currently in view if one looks corrupted.
|
||||
Init… Init…
|
||||
Leaving console mode - rebooting... Leaving console mode - rebooting...
|
||||
@@ -1062,8 +1053,6 @@ Map data © OpenStreetMap contributors.\nopenstreetmap.org/copyright\nLicensed u
|
||||
Map style © OpenTopoMap (CC-BY-SA) — opentopomap.org\nMap data © OpenStreetMap contributors (ODbL) + SRTM.\n\n Map style © OpenTopoMap (CC-BY-SA) — opentopomap.org\nMap data © OpenStreetMap contributors (ODbL) + SRTM.\n\n
|
||||
Name (A-Z) Name (A-Z)
|
||||
Nearest first Nearest first
|
||||
Other networksNetworks Other networksNetworks
|
||||
Paste (move)Paste (copy) Paste (move)Paste (copy)
|
||||
Recent message Recent message
|
||||
Reload tiles in view Reload tiles in view
|
||||
Repeaters Repeaters
|
||||
@@ -1076,13 +1065,8 @@ Show contacts Show contacts
|
||||
Show coordinates Show coordinates
|
||||
Show tile z/x/y Show tile z/x/y
|
||||
Starting… Starting…
|
||||
Stop sharing loc Share my loc Stop sharing loc Share my loc
|
||||
Supprimer Supprimer
|
||||
Switch to the external antenna?\n\nMake sure an antenna is actually connected to the external antenna socket first. Transmitting with nothing attached can damage the radio.\n\nResets to the on-board antenna on every reboot.Turn on legacy per-transmit switching?\n\nDiagnostic mode, for comparison only. It transmits on the on-board antenna and listens on the external one, so your outbound signal will be much weaker than your inbound.\n\nResets to the on-board antenna on every reboot. Switch to the external antenna?\n\nMake sure an antenna is actually connected to the external antenna socket first. Transmitting with nothing attached can damage the radio.\n\nResets to the on-board antenna on every reboot.Turn on legacy per-transmit switching?\n\nDiagnostic mode, for comparison only. It transmits on the on-board antenna and listens on the external one, so your outbound signal will be much weaker than your inbound.\n\nResets to the on-board antenna on every reboot.
|
||||
Text console (experimental) Text console (experimental)
|
||||
Tile debug overlay Tile debug overlay
|
||||
Unblock Block Unblock Block
|
||||
Unfav Favorite Unfav Favorite
|
||||
chats chats
|
||||
repeaters repeaters
|
||||
rooms rooms
|
||||
@@ -1098,3 +1082,26 @@ channel busy %.1f%% channel busy %.1f%%
|
||||
sampling... sampling...
|
||||
teal = now blue = average teal = now blue = average
|
||||
window restarted window restarted
|
||||
Filter Filter
|
||||
Hide system files Hide system files
|
||||
Networks Networks
|
||||
Other networks Other networks
|
||||
Show system files Show system files
|
||||
Unfav Unfav
|
||||
Firmware beta_%d\nChecking for updates over Wi-Fi… Firmware beta_%d\nChecking for updates over Wi-Fi…
|
||||
Install beta_%d?\nThis downgrades the firmware and reboots. Install beta_%d?\nThis downgrades the firmware and reboots.
|
||||
Other (hidden) network… Other (hidden) network…
|
||||
Paused Paused
|
||||
Scan again Scan again
|
||||
At a glance At a glance
|
||||
At a glance disabled At a glance disabled
|
||||
At a glance enabled At a glance enabled
|
||||
At a glance while locked At a glance while locked
|
||||
Glance only shows when unlocked Glance only shows when unlocked
|
||||
Glance shows message previews\nwhile the screen is locked Glance shows message previews\nwhile the screen is locked
|
||||
Snake — swipe, roll, or press a direction Snake — swipe, roll, or press a direction
|
||||
N/A N/A
|
||||
This board cannot run this app. This board cannot run this app.
|
||||
Signal Signal
|
||||
Signal (stale) Signal (stale)
|
||||
nothing heard yet nothing heard yet
|
||||
|
||||
+24
-19
@@ -1,7 +1,7 @@
|
||||
# wadamesh language file
|
||||
# canonical source - translators: edit this file and PR it
|
||||
# code: nl
|
||||
# ver: 18
|
||||
# ver: 19
|
||||
# name: Nederlands
|
||||
# base: nl
|
||||
(device behind) (apparaat loopt achter)
|
||||
@@ -452,7 +452,6 @@ Login OK (perms %u) Inloggen OK (rechten %u)
|
||||
Login failed Inloggen mislukt
|
||||
Logs Logboeken
|
||||
Longitude Lengtegraad
|
||||
Lua Store Lua Store
|
||||
MHz MHz
|
||||
MQTT bridge MQTT-brug
|
||||
Map Kaart
|
||||
@@ -797,7 +796,6 @@ Sleep ready Klaar voor slaap
|
||||
Slot %d cleared Slot %d gewist
|
||||
Slot empty Slot leeg
|
||||
Small\nMedium\nLarge Klein\nMiddel\nGroot
|
||||
Snake Snake
|
||||
Snake — swipe or roll the trackball Snake — veeg of rol de trackball
|
||||
Sort Sorteer
|
||||
Sound Geluid
|
||||
@@ -805,7 +803,6 @@ Sound off Geluid uit
|
||||
Sound on Geluid aan
|
||||
Sound: %s Geluid: %s
|
||||
Start time Starttijd
|
||||
Status Status
|
||||
Step 1 of 3 Stap 1 van 3
|
||||
Step 2 of 3 Stap 2 van 3
|
||||
Step 3 of 3 Stap 3 van 3
|
||||
@@ -977,8 +974,6 @@ tap to exit full screen tik om volledig scherm te verlaten
|
||||
zoom zoom
|
||||
zoom %d zoom %d
|
||||
~17 dB amp for quiet/remote areas; turn off in noisy spots. ~17 dB versterker voor stille/afgelegen gebieden; uit in drukke plekken.
|
||||
© OpenStreetMap © OpenStreetMap
|
||||
© OpenTopoMap © OpenTopoMap
|
||||
Scope
|
||||
another region
|
||||
my region
|
||||
@@ -1000,7 +995,6 @@ Overwrite oldest non-favorite
|
||||
1-character messages will be shown 1-character messages will be shown
|
||||
All All
|
||||
Allow Allow
|
||||
Apps that may send messages on the mesh. Anything they send goes out under your node name and cannot be told apart from a message you typed. Turn one off to take the permission back. Apps that may send messages on the mesh. Anything they send goes out under your node name and cannot be told apart from a message you typed. Turn one off to take the permission back.
|
||||
Ignore 1-character messages Ignore 1-character messages
|
||||
Join w/ password Join w/ password
|
||||
Location no longer shared Location no longer shared
|
||||
@@ -1014,9 +1008,7 @@ Never\nChosen contacts only\nAnyone who asks Never\nChosen contacts only\nAnyone
|
||||
No apps installed. No apps installed.
|
||||
No reply. Path hash is %u bytes;\nolder repeaters drop those.\nTry 1 byte (Settings > Radio & Mesh) No reply. Path hash is %u bytes;\nolder repeaters drop those.\nTry 1 byte (Settings > Radio & Mesh)
|
||||
Not supported on this board Not supported on this board
|
||||
Read incoming messages Read incoming messages
|
||||
Reconnected to %.40s Reconnected to %.40s
|
||||
Send messages as me Send messages as me
|
||||
Sent only when that contact asks, encrypted to them. Nothing is broadcast. Needs "Answer telemetry requests" on. Pick contacts in a contact's menu. Sent only when that contact asks, encrypted to them. Nothing is broadcast. Needs "Answer telemetry requests" on. Pick contacts in a contact's menu.
|
||||
Server has forgotten you.\nJoin with the room password. Server has forgotten you.\nJoin with the room password.
|
||||
Share my loc Share my loc
|
||||
@@ -1042,7 +1034,6 @@ App permissions App permissions
|
||||
About / credits About / credits
|
||||
Auth failed Auth failed
|
||||
Auto-add on for %s — new %s land in Contacts automatically. Auto-add on for %s — new %s land in Contacts automatically.
|
||||
Batteryactivityon Batteryactivityon
|
||||
Console mode Console mode
|
||||
Console mode (experimental) Console mode (experimental)
|
||||
Direct (0-hop) only Direct (0-hop) only
|
||||
@@ -1051,8 +1042,6 @@ EXPERIMENTAL. Boots into a text console with no graphical interface: type comman
|
||||
Enter text Enter text
|
||||
Entering console mode - rebooting... Entering console mode - rebooting...
|
||||
Favorite Favorite
|
||||
Geblokkeerde gebruikers Geblokkeerde gebruikers
|
||||
Hide system filesShow system files Hide system filesShow system files
|
||||
How tiles work:\nThe map is built from 256×256 "slippy" tiles. Only the tiles for the area you're viewing are fetched — there is no bulk pre-download.\n\nBecause this device can't do HTTPS (not enough heap after Wi-Fi starts) and decodes JPEG far more cheaply than PNG, tiles come from the wadamesh proxy: it fetches the upstream PNG over HTTPS with an identifying User-Agent, re-encodes it as JPEG, and caches it. Your device then caches each tile to its own flash, so a tile is only downloaded once.\n\nUse Options → Reload tiles to re-download the tiles currently in view if one looks corrupted. How tiles work:\nThe map is built from 256×256 "slippy" tiles. Only the tiles for the area you're viewing are fetched — there is no bulk pre-download.\n\nBecause this device can't do HTTPS (not enough heap after Wi-Fi starts) and decodes JPEG far more cheaply than PNG, tiles come from the wadamesh proxy: it fetches the upstream PNG over HTTPS with an identifying User-Agent, re-encodes it as JPEG, and caches it. Your device then caches each tile to its own flash, so a tile is only downloaded once.\n\nUse Options → Reload tiles to re-download the tiles currently in view if one looks corrupted.
|
||||
Init… Init…
|
||||
Leaving console mode - rebooting... Leaving console mode - rebooting...
|
||||
@@ -1062,8 +1051,6 @@ Map data © OpenStreetMap contributors.\nopenstreetmap.org/copyright\nLicensed u
|
||||
Map style © OpenTopoMap (CC-BY-SA) — opentopomap.org\nMap data © OpenStreetMap contributors (ODbL) + SRTM.\n\n Map style © OpenTopoMap (CC-BY-SA) — opentopomap.org\nMap data © OpenStreetMap contributors (ODbL) + SRTM.\n\n
|
||||
Name (A-Z) Name (A-Z)
|
||||
Nearest first Nearest first
|
||||
Other networksNetworks Other networksNetworks
|
||||
Paste (move)Paste (copy) Paste (move)Paste (copy)
|
||||
Recent message Recent message
|
||||
Reload tiles in view Reload tiles in view
|
||||
Repeaters Repeaters
|
||||
@@ -1076,13 +1063,8 @@ Show contacts Show contacts
|
||||
Show coordinates Show coordinates
|
||||
Show tile z/x/y Show tile z/x/y
|
||||
Starting… Starting…
|
||||
Stop sharing loc Share my loc Stop sharing loc Share my loc
|
||||
Supprimer Supprimer
|
||||
Switch to the external antenna?\n\nMake sure an antenna is actually connected to the external antenna socket first. Transmitting with nothing attached can damage the radio.\n\nResets to the on-board antenna on every reboot.Turn on legacy per-transmit switching?\n\nDiagnostic mode, for comparison only. It transmits on the on-board antenna and listens on the external one, so your outbound signal will be much weaker than your inbound.\n\nResets to the on-board antenna on every reboot. Switch to the external antenna?\n\nMake sure an antenna is actually connected to the external antenna socket first. Transmitting with nothing attached can damage the radio.\n\nResets to the on-board antenna on every reboot.Turn on legacy per-transmit switching?\n\nDiagnostic mode, for comparison only. It transmits on the on-board antenna and listens on the external one, so your outbound signal will be much weaker than your inbound.\n\nResets to the on-board antenna on every reboot.
|
||||
Text console (experimental) Text console (experimental)
|
||||
Tile debug overlay Tile debug overlay
|
||||
Unblock Block Unblock Block
|
||||
Unfav Favorite Unfav Favorite
|
||||
chats chats
|
||||
repeaters repeaters
|
||||
rooms rooms
|
||||
@@ -1098,3 +1080,26 @@ channel busy %.1f%% channel busy %.1f%%
|
||||
sampling... sampling...
|
||||
teal = now blue = average teal = now blue = average
|
||||
window restarted window restarted
|
||||
Filter Filter
|
||||
Hide system files Hide system files
|
||||
Networks Networks
|
||||
Other networks Other networks
|
||||
Show system files Show system files
|
||||
Unfav Unfav
|
||||
Firmware beta_%d\nChecking for updates over Wi-Fi… Firmware beta_%d\nChecking for updates over Wi-Fi…
|
||||
Install beta_%d?\nThis downgrades the firmware and reboots. Install beta_%d?\nThis downgrades the firmware and reboots.
|
||||
Other (hidden) network… Other (hidden) network…
|
||||
Paused Paused
|
||||
Scan again Scan again
|
||||
At a glance At a glance
|
||||
At a glance disabled At a glance disabled
|
||||
At a glance enabled At a glance enabled
|
||||
At a glance while locked At a glance while locked
|
||||
Glance only shows when unlocked Glance only shows when unlocked
|
||||
Glance shows message previews\nwhile the screen is locked Glance shows message previews\nwhile the screen is locked
|
||||
Snake — swipe, roll, or press a direction Snake — swipe, roll, or press a direction
|
||||
N/A N/A
|
||||
This board cannot run this app. This board cannot run this app.
|
||||
Signal Signal
|
||||
Signal (stale) Signal (stale)
|
||||
nothing heard yet nothing heard yet
|
||||
|
||||
+24
-17
@@ -1,7 +1,7 @@
|
||||
# wadamesh language file
|
||||
# canonical source - translators: edit this file and PR it
|
||||
# code: pt-br
|
||||
# ver: 18
|
||||
# ver: 19
|
||||
# name: Português (BR)
|
||||
# base: pt-br
|
||||
(device behind) (dispositivo atrasado)
|
||||
@@ -805,7 +805,6 @@ Sound off Som desligado
|
||||
Sound on Som ligado
|
||||
Sound: %s Som: %s
|
||||
Start time Hora de início
|
||||
Status Status
|
||||
Step 1 of 3 Passo 1 de 3
|
||||
Step 2 of 3 Passo 2 de 3
|
||||
Step 3 of 3 Passo 3 de 3
|
||||
@@ -977,8 +976,6 @@ tap to exit full screen toque para sair da tela cheia
|
||||
zoom zoom
|
||||
zoom %d zoom %d
|
||||
~17 dB amp for quiet/remote areas; turn off in noisy spots. amplificador ~17 dB para áreas silenciosas/remotas; desligue em locais ruidosos.
|
||||
© OpenStreetMap © OpenStreetMap
|
||||
© OpenTopoMap © OpenTopoMap
|
||||
Scope
|
||||
another region
|
||||
my region
|
||||
@@ -1000,7 +997,6 @@ Overwrite oldest non-favorite
|
||||
1-character messages will be shown 1-character messages will be shown
|
||||
All All
|
||||
Allow Allow
|
||||
Apps that may send messages on the mesh. Anything they send goes out under your node name and cannot be told apart from a message you typed. Turn one off to take the permission back. Apps that may send messages on the mesh. Anything they send goes out under your node name and cannot be told apart from a message you typed. Turn one off to take the permission back.
|
||||
Ignore 1-character messages Ignore 1-character messages
|
||||
Join w/ password Join w/ password
|
||||
Location no longer shared Location no longer shared
|
||||
@@ -1014,9 +1010,7 @@ Never\nChosen contacts only\nAnyone who asks Never\nChosen contacts only\nAnyone
|
||||
No apps installed. No apps installed.
|
||||
No reply. Path hash is %u bytes;\nolder repeaters drop those.\nTry 1 byte (Settings > Radio & Mesh) No reply. Path hash is %u bytes;\nolder repeaters drop those.\nTry 1 byte (Settings > Radio & Mesh)
|
||||
Not supported on this board Not supported on this board
|
||||
Read incoming messages Read incoming messages
|
||||
Reconnected to %.40s Reconnected to %.40s
|
||||
Send messages as me Send messages as me
|
||||
Sent only when that contact asks, encrypted to them. Nothing is broadcast. Needs "Answer telemetry requests" on. Pick contacts in a contact's menu. Sent only when that contact asks, encrypted to them. Nothing is broadcast. Needs "Answer telemetry requests" on. Pick contacts in a contact's menu.
|
||||
Server has forgotten you.\nJoin with the room password. Server has forgotten you.\nJoin with the room password.
|
||||
Share my loc Share my loc
|
||||
@@ -1042,7 +1036,6 @@ App permissions App permissions
|
||||
About / credits About / credits
|
||||
Auth failed Auth failed
|
||||
Auto-add on for %s — new %s land in Contacts automatically. Auto-add on for %s — new %s land in Contacts automatically.
|
||||
Batteryactivityon Batteryactivityon
|
||||
Console mode Console mode
|
||||
Console mode (experimental) Console mode (experimental)
|
||||
Direct (0-hop) only Direct (0-hop) only
|
||||
@@ -1051,8 +1044,6 @@ EXPERIMENTAL. Boots into a text console with no graphical interface: type comman
|
||||
Enter text Enter text
|
||||
Entering console mode - rebooting... Entering console mode - rebooting...
|
||||
Favorite Favorite
|
||||
Geblokkeerde gebruikers Geblokkeerde gebruikers
|
||||
Hide system filesShow system files Hide system filesShow system files
|
||||
How tiles work:\nThe map is built from 256×256 "slippy" tiles. Only the tiles for the area you're viewing are fetched — there is no bulk pre-download.\n\nBecause this device can't do HTTPS (not enough heap after Wi-Fi starts) and decodes JPEG far more cheaply than PNG, tiles come from the wadamesh proxy: it fetches the upstream PNG over HTTPS with an identifying User-Agent, re-encodes it as JPEG, and caches it. Your device then caches each tile to its own flash, so a tile is only downloaded once.\n\nUse Options → Reload tiles to re-download the tiles currently in view if one looks corrupted. How tiles work:\nThe map is built from 256×256 "slippy" tiles. Only the tiles for the area you're viewing are fetched — there is no bulk pre-download.\n\nBecause this device can't do HTTPS (not enough heap after Wi-Fi starts) and decodes JPEG far more cheaply than PNG, tiles come from the wadamesh proxy: it fetches the upstream PNG over HTTPS with an identifying User-Agent, re-encodes it as JPEG, and caches it. Your device then caches each tile to its own flash, so a tile is only downloaded once.\n\nUse Options → Reload tiles to re-download the tiles currently in view if one looks corrupted.
|
||||
Init… Init…
|
||||
Leaving console mode - rebooting... Leaving console mode - rebooting...
|
||||
@@ -1062,8 +1053,6 @@ Map data © OpenStreetMap contributors.\nopenstreetmap.org/copyright\nLicensed u
|
||||
Map style © OpenTopoMap (CC-BY-SA) — opentopomap.org\nMap data © OpenStreetMap contributors (ODbL) + SRTM.\n\n Map style © OpenTopoMap (CC-BY-SA) — opentopomap.org\nMap data © OpenStreetMap contributors (ODbL) + SRTM.\n\n
|
||||
Name (A-Z) Name (A-Z)
|
||||
Nearest first Nearest first
|
||||
Other networksNetworks Other networksNetworks
|
||||
Paste (move)Paste (copy) Paste (move)Paste (copy)
|
||||
Recent message Recent message
|
||||
Reload tiles in view Reload tiles in view
|
||||
Repeaters Repeaters
|
||||
@@ -1076,13 +1065,8 @@ Show contacts Show contacts
|
||||
Show coordinates Show coordinates
|
||||
Show tile z/x/y Show tile z/x/y
|
||||
Starting… Starting…
|
||||
Stop sharing loc Share my loc Stop sharing loc Share my loc
|
||||
Supprimer Supprimer
|
||||
Switch to the external antenna?\n\nMake sure an antenna is actually connected to the external antenna socket first. Transmitting with nothing attached can damage the radio.\n\nResets to the on-board antenna on every reboot.Turn on legacy per-transmit switching?\n\nDiagnostic mode, for comparison only. It transmits on the on-board antenna and listens on the external one, so your outbound signal will be much weaker than your inbound.\n\nResets to the on-board antenna on every reboot. Switch to the external antenna?\n\nMake sure an antenna is actually connected to the external antenna socket first. Transmitting with nothing attached can damage the radio.\n\nResets to the on-board antenna on every reboot.Turn on legacy per-transmit switching?\n\nDiagnostic mode, for comparison only. It transmits on the on-board antenna and listens on the external one, so your outbound signal will be much weaker than your inbound.\n\nResets to the on-board antenna on every reboot.
|
||||
Text console (experimental) Text console (experimental)
|
||||
Tile debug overlay Tile debug overlay
|
||||
Unblock Block Unblock Block
|
||||
Unfav Favorite Unfav Favorite
|
||||
chats chats
|
||||
repeaters repeaters
|
||||
rooms rooms
|
||||
@@ -1098,3 +1082,26 @@ channel busy %.1f%% channel busy %.1f%%
|
||||
sampling... sampling...
|
||||
teal = now blue = average teal = now blue = average
|
||||
window restarted window restarted
|
||||
Filter Filter
|
||||
Hide system files Hide system files
|
||||
Networks Networks
|
||||
Other networks Other networks
|
||||
Show system files Show system files
|
||||
Unfav Unfav
|
||||
Firmware beta_%d\nChecking for updates over Wi-Fi… Firmware beta_%d\nChecking for updates over Wi-Fi…
|
||||
Install beta_%d?\nThis downgrades the firmware and reboots. Install beta_%d?\nThis downgrades the firmware and reboots.
|
||||
Other (hidden) network… Other (hidden) network…
|
||||
Paused Paused
|
||||
Scan again Scan again
|
||||
At a glance At a glance
|
||||
At a glance disabled At a glance disabled
|
||||
At a glance enabled At a glance enabled
|
||||
At a glance while locked At a glance while locked
|
||||
Glance only shows when unlocked Glance only shows when unlocked
|
||||
Glance shows message previews\nwhile the screen is locked Glance shows message previews\nwhile the screen is locked
|
||||
Snake — swipe, roll, or press a direction Snake — swipe, roll, or press a direction
|
||||
N/A N/A
|
||||
This board cannot run this app. This board cannot run this app.
|
||||
Signal Signal
|
||||
Signal (stale) Signal (stale)
|
||||
nothing heard yet nothing heard yet
|
||||
|
||||
+24
-15
@@ -1,7 +1,7 @@
|
||||
# wadamesh language file
|
||||
# canonical source - translators: edit this file and PR it
|
||||
# code: ro
|
||||
# ver: 18
|
||||
# ver: 19
|
||||
# name: Română
|
||||
# base: ro
|
||||
(device behind) (dispozitiv în urmă)
|
||||
@@ -978,7 +978,6 @@ zoom zoom
|
||||
zoom %d zoom %d
|
||||
~17 dB amp for quiet/remote areas; turn off in noisy spots. amplificator ~17 dB pentru zone liniştite/izolate; opreşte-l în locuri zgomotoase.
|
||||
© OpenStreetMap
|
||||
© OpenTopoMap © OpenTopoMap
|
||||
Scope
|
||||
another region
|
||||
my region
|
||||
@@ -1000,7 +999,6 @@ Overwrite oldest non-favorite
|
||||
1-character messages will be shown 1-character messages will be shown
|
||||
All All
|
||||
Allow Allow
|
||||
Apps that may send messages on the mesh. Anything they send goes out under your node name and cannot be told apart from a message you typed. Turn one off to take the permission back. Apps that may send messages on the mesh. Anything they send goes out under your node name and cannot be told apart from a message you typed. Turn one off to take the permission back.
|
||||
Ignore 1-character messages Ignore 1-character messages
|
||||
Join w/ password Join w/ password
|
||||
Location no longer shared Location no longer shared
|
||||
@@ -1014,9 +1012,7 @@ Never\nChosen contacts only\nAnyone who asks Never\nChosen contacts only\nAnyone
|
||||
No apps installed. No apps installed.
|
||||
No reply. Path hash is %u bytes;\nolder repeaters drop those.\nTry 1 byte (Settings > Radio & Mesh) No reply. Path hash is %u bytes;\nolder repeaters drop those.\nTry 1 byte (Settings > Radio & Mesh)
|
||||
Not supported on this board Not supported on this board
|
||||
Read incoming messages Read incoming messages
|
||||
Reconnected to %.40s Reconnected to %.40s
|
||||
Send messages as me Send messages as me
|
||||
Sent only when that contact asks, encrypted to them. Nothing is broadcast. Needs "Answer telemetry requests" on. Pick contacts in a contact's menu. Sent only when that contact asks, encrypted to them. Nothing is broadcast. Needs "Answer telemetry requests" on. Pick contacts in a contact's menu.
|
||||
Server has forgotten you.\nJoin with the room password. Server has forgotten you.\nJoin with the room password.
|
||||
Share my loc Share my loc
|
||||
@@ -1042,7 +1038,6 @@ App permissions App permissions
|
||||
About / credits About / credits
|
||||
Auth failed Auth failed
|
||||
Auto-add on for %s — new %s land in Contacts automatically. Auto-add on for %s — new %s land in Contacts automatically.
|
||||
Batteryactivityon Batteryactivityon
|
||||
Console mode Console mode
|
||||
Console mode (experimental) Console mode (experimental)
|
||||
Direct (0-hop) only Direct (0-hop) only
|
||||
@@ -1051,8 +1046,6 @@ EXPERIMENTAL. Boots into a text console with no graphical interface: type comman
|
||||
Enter text Enter text
|
||||
Entering console mode - rebooting... Entering console mode - rebooting...
|
||||
Favorite Favorite
|
||||
Geblokkeerde gebruikers Geblokkeerde gebruikers
|
||||
Hide system filesShow system files Hide system filesShow system files
|
||||
How tiles work:\nThe map is built from 256×256 "slippy" tiles. Only the tiles for the area you're viewing are fetched — there is no bulk pre-download.\n\nBecause this device can't do HTTPS (not enough heap after Wi-Fi starts) and decodes JPEG far more cheaply than PNG, tiles come from the wadamesh proxy: it fetches the upstream PNG over HTTPS with an identifying User-Agent, re-encodes it as JPEG, and caches it. Your device then caches each tile to its own flash, so a tile is only downloaded once.\n\nUse Options → Reload tiles to re-download the tiles currently in view if one looks corrupted. How tiles work:\nThe map is built from 256×256 "slippy" tiles. Only the tiles for the area you're viewing are fetched — there is no bulk pre-download.\n\nBecause this device can't do HTTPS (not enough heap after Wi-Fi starts) and decodes JPEG far more cheaply than PNG, tiles come from the wadamesh proxy: it fetches the upstream PNG over HTTPS with an identifying User-Agent, re-encodes it as JPEG, and caches it. Your device then caches each tile to its own flash, so a tile is only downloaded once.\n\nUse Options → Reload tiles to re-download the tiles currently in view if one looks corrupted.
|
||||
Init… Init…
|
||||
Leaving console mode - rebooting... Leaving console mode - rebooting...
|
||||
@@ -1062,8 +1055,6 @@ Map data © OpenStreetMap contributors.\nopenstreetmap.org/copyright\nLicensed u
|
||||
Map style © OpenTopoMap (CC-BY-SA) — opentopomap.org\nMap data © OpenStreetMap contributors (ODbL) + SRTM.\n\n Map style © OpenTopoMap (CC-BY-SA) — opentopomap.org\nMap data © OpenStreetMap contributors (ODbL) + SRTM.\n\n
|
||||
Name (A-Z) Name (A-Z)
|
||||
Nearest first Nearest first
|
||||
Other networksNetworks Other networksNetworks
|
||||
Paste (move)Paste (copy) Paste (move)Paste (copy)
|
||||
Recent message Recent message
|
||||
Reload tiles in view Reload tiles in view
|
||||
Repeaters Repeaters
|
||||
@@ -1076,13 +1067,8 @@ Show contacts Show contacts
|
||||
Show coordinates Show coordinates
|
||||
Show tile z/x/y Show tile z/x/y
|
||||
Starting… Starting…
|
||||
Stop sharing loc Share my loc Stop sharing loc Share my loc
|
||||
Supprimer Supprimer
|
||||
Switch to the external antenna?\n\nMake sure an antenna is actually connected to the external antenna socket first. Transmitting with nothing attached can damage the radio.\n\nResets to the on-board antenna on every reboot.Turn on legacy per-transmit switching?\n\nDiagnostic mode, for comparison only. It transmits on the on-board antenna and listens on the external one, so your outbound signal will be much weaker than your inbound.\n\nResets to the on-board antenna on every reboot. Switch to the external antenna?\n\nMake sure an antenna is actually connected to the external antenna socket first. Transmitting with nothing attached can damage the radio.\n\nResets to the on-board antenna on every reboot.Turn on legacy per-transmit switching?\n\nDiagnostic mode, for comparison only. It transmits on the on-board antenna and listens on the external one, so your outbound signal will be much weaker than your inbound.\n\nResets to the on-board antenna on every reboot.
|
||||
Text console (experimental) Text console (experimental)
|
||||
Tile debug overlay Tile debug overlay
|
||||
Unblock Block Unblock Block
|
||||
Unfav Favorite Unfav Favorite
|
||||
chats chats
|
||||
repeaters repeaters
|
||||
rooms rooms
|
||||
@@ -1098,3 +1084,26 @@ channel busy %.1f%% channel busy %.1f%%
|
||||
sampling... sampling...
|
||||
teal = now blue = average teal = now blue = average
|
||||
window restarted window restarted
|
||||
Filter Filter
|
||||
Hide system files Hide system files
|
||||
Networks Networks
|
||||
Other networks Other networks
|
||||
Show system files Show system files
|
||||
Unfav Unfav
|
||||
Firmware beta_%d\nChecking for updates over Wi-Fi… Firmware beta_%d\nChecking for updates over Wi-Fi…
|
||||
Install beta_%d?\nThis downgrades the firmware and reboots. Install beta_%d?\nThis downgrades the firmware and reboots.
|
||||
Other (hidden) network… Other (hidden) network…
|
||||
Paused Paused
|
||||
Scan again Scan again
|
||||
At a glance At a glance
|
||||
At a glance disabled At a glance disabled
|
||||
At a glance enabled At a glance enabled
|
||||
At a glance while locked At a glance while locked
|
||||
Glance only shows when unlocked Glance only shows when unlocked
|
||||
Glance shows message previews\nwhile the screen is locked Glance shows message previews\nwhile the screen is locked
|
||||
Snake — swipe, roll, or press a direction Snake — swipe, roll, or press a direction
|
||||
N/A N/A
|
||||
This board cannot run this app. This board cannot run this app.
|
||||
Signal Signal
|
||||
Signal (stale) Signal (stale)
|
||||
nothing heard yet nothing heard yet
|
||||
|
||||
+24
-16
@@ -1,7 +1,7 @@
|
||||
# wadamesh language file
|
||||
# canonical source - translators: edit this file and PR it
|
||||
# code: ru
|
||||
# ver: 18
|
||||
# ver: 19
|
||||
# name: Русский
|
||||
# base: ru
|
||||
(device behind) (устройство отстаёт)
|
||||
@@ -977,8 +977,6 @@ tap to exit full screen нажмите для выхода из полноэкр
|
||||
zoom масштаб
|
||||
zoom %d масштаб %d
|
||||
~17 dB amp for quiet/remote areas; turn off in noisy spots. усилитель ~17 дБ для тихих/удалённых мест; выключайте в шумных.
|
||||
© OpenStreetMap © OpenStreetMap
|
||||
© OpenTopoMap © OpenTopoMap
|
||||
Scope
|
||||
another region
|
||||
my region
|
||||
@@ -1000,7 +998,6 @@ Overwrite oldest non-favorite
|
||||
1-character messages will be shown 1-character messages will be shown
|
||||
All All
|
||||
Allow Allow
|
||||
Apps that may send messages on the mesh. Anything they send goes out under your node name and cannot be told apart from a message you typed. Turn one off to take the permission back. Apps that may send messages on the mesh. Anything they send goes out under your node name and cannot be told apart from a message you typed. Turn one off to take the permission back.
|
||||
Ignore 1-character messages Ignore 1-character messages
|
||||
Join w/ password Join w/ password
|
||||
Location no longer shared Location no longer shared
|
||||
@@ -1014,9 +1011,7 @@ Never\nChosen contacts only\nAnyone who asks Never\nChosen contacts only\nAnyone
|
||||
No apps installed. No apps installed.
|
||||
No reply. Path hash is %u bytes;\nolder repeaters drop those.\nTry 1 byte (Settings > Radio & Mesh) No reply. Path hash is %u bytes;\nolder repeaters drop those.\nTry 1 byte (Settings > Radio & Mesh)
|
||||
Not supported on this board Not supported on this board
|
||||
Read incoming messages Read incoming messages
|
||||
Reconnected to %.40s Reconnected to %.40s
|
||||
Send messages as me Send messages as me
|
||||
Sent only when that contact asks, encrypted to them. Nothing is broadcast. Needs "Answer telemetry requests" on. Pick contacts in a contact's menu. Sent only when that contact asks, encrypted to them. Nothing is broadcast. Needs "Answer telemetry requests" on. Pick contacts in a contact's menu.
|
||||
Server has forgotten you.\nJoin with the room password. Server has forgotten you.\nJoin with the room password.
|
||||
Share my loc Share my loc
|
||||
@@ -1042,7 +1037,6 @@ App permissions App permissions
|
||||
About / credits About / credits
|
||||
Auth failed Auth failed
|
||||
Auto-add on for %s — new %s land in Contacts automatically. Auto-add on for %s — new %s land in Contacts automatically.
|
||||
Batteryactivityon Batteryactivityon
|
||||
Console mode Console mode
|
||||
Console mode (experimental) Console mode (experimental)
|
||||
Direct (0-hop) only Direct (0-hop) only
|
||||
@@ -1051,8 +1045,6 @@ EXPERIMENTAL. Boots into a text console with no graphical interface: type comman
|
||||
Enter text Enter text
|
||||
Entering console mode - rebooting... Entering console mode - rebooting...
|
||||
Favorite Favorite
|
||||
Geblokkeerde gebruikers Geblokkeerde gebruikers
|
||||
Hide system filesShow system files Hide system filesShow system files
|
||||
How tiles work:\nThe map is built from 256×256 "slippy" tiles. Only the tiles for the area you're viewing are fetched — there is no bulk pre-download.\n\nBecause this device can't do HTTPS (not enough heap after Wi-Fi starts) and decodes JPEG far more cheaply than PNG, tiles come from the wadamesh proxy: it fetches the upstream PNG over HTTPS with an identifying User-Agent, re-encodes it as JPEG, and caches it. Your device then caches each tile to its own flash, so a tile is only downloaded once.\n\nUse Options → Reload tiles to re-download the tiles currently in view if one looks corrupted. How tiles work:\nThe map is built from 256×256 "slippy" tiles. Only the tiles for the area you're viewing are fetched — there is no bulk pre-download.\n\nBecause this device can't do HTTPS (not enough heap after Wi-Fi starts) and decodes JPEG far more cheaply than PNG, tiles come from the wadamesh proxy: it fetches the upstream PNG over HTTPS with an identifying User-Agent, re-encodes it as JPEG, and caches it. Your device then caches each tile to its own flash, so a tile is only downloaded once.\n\nUse Options → Reload tiles to re-download the tiles currently in view if one looks corrupted.
|
||||
Init… Init…
|
||||
Leaving console mode - rebooting... Leaving console mode - rebooting...
|
||||
@@ -1062,8 +1054,6 @@ Map data © OpenStreetMap contributors.\nopenstreetmap.org/copyright\nLicensed u
|
||||
Map style © OpenTopoMap (CC-BY-SA) — opentopomap.org\nMap data © OpenStreetMap contributors (ODbL) + SRTM.\n\n Map style © OpenTopoMap (CC-BY-SA) — opentopomap.org\nMap data © OpenStreetMap contributors (ODbL) + SRTM.\n\n
|
||||
Name (A-Z) Name (A-Z)
|
||||
Nearest first Nearest first
|
||||
Other networksNetworks Other networksNetworks
|
||||
Paste (move)Paste (copy) Paste (move)Paste (copy)
|
||||
Recent message Recent message
|
||||
Reload tiles in view Reload tiles in view
|
||||
Repeaters Repeaters
|
||||
@@ -1076,13 +1066,8 @@ Show contacts Show contacts
|
||||
Show coordinates Show coordinates
|
||||
Show tile z/x/y Show tile z/x/y
|
||||
Starting… Starting…
|
||||
Stop sharing loc Share my loc Stop sharing loc Share my loc
|
||||
Supprimer Supprimer
|
||||
Switch to the external antenna?\n\nMake sure an antenna is actually connected to the external antenna socket first. Transmitting with nothing attached can damage the radio.\n\nResets to the on-board antenna on every reboot.Turn on legacy per-transmit switching?\n\nDiagnostic mode, for comparison only. It transmits on the on-board antenna and listens on the external one, so your outbound signal will be much weaker than your inbound.\n\nResets to the on-board antenna on every reboot. Switch to the external antenna?\n\nMake sure an antenna is actually connected to the external antenna socket first. Transmitting with nothing attached can damage the radio.\n\nResets to the on-board antenna on every reboot.Turn on legacy per-transmit switching?\n\nDiagnostic mode, for comparison only. It transmits on the on-board antenna and listens on the external one, so your outbound signal will be much weaker than your inbound.\n\nResets to the on-board antenna on every reboot.
|
||||
Text console (experimental) Text console (experimental)
|
||||
Tile debug overlay Tile debug overlay
|
||||
Unblock Block Unblock Block
|
||||
Unfav Favorite Unfav Favorite
|
||||
chats chats
|
||||
repeaters repeaters
|
||||
rooms rooms
|
||||
@@ -1098,3 +1083,26 @@ channel busy %.1f%% channel busy %.1f%%
|
||||
sampling... sampling...
|
||||
teal = now blue = average teal = now blue = average
|
||||
window restarted window restarted
|
||||
Filter Filter
|
||||
Hide system files Hide system files
|
||||
Networks Networks
|
||||
Other networks Other networks
|
||||
Show system files Show system files
|
||||
Unfav Unfav
|
||||
Firmware beta_%d\nChecking for updates over Wi-Fi… Firmware beta_%d\nChecking for updates over Wi-Fi…
|
||||
Install beta_%d?\nThis downgrades the firmware and reboots. Install beta_%d?\nThis downgrades the firmware and reboots.
|
||||
Other (hidden) network… Other (hidden) network…
|
||||
Paused Paused
|
||||
Scan again Scan again
|
||||
At a glance At a glance
|
||||
At a glance disabled At a glance disabled
|
||||
At a glance enabled At a glance enabled
|
||||
At a glance while locked At a glance while locked
|
||||
Glance only shows when unlocked Glance only shows when unlocked
|
||||
Glance shows message previews\nwhile the screen is locked Glance shows message previews\nwhile the screen is locked
|
||||
Snake — swipe, roll, or press a direction Snake — swipe, roll, or press a direction
|
||||
N/A N/A
|
||||
This board cannot run this app. This board cannot run this app.
|
||||
Signal Signal
|
||||
Signal (stale) Signal (stale)
|
||||
nothing heard yet nothing heard yet
|
||||
|
||||
+25
-17
@@ -1,7 +1,7 @@
|
||||
# wadamesh language file
|
||||
# canonical source - translators: edit this file and PR it
|
||||
# code: sr
|
||||
# ver: 18
|
||||
# ver: 19
|
||||
# name: Српски
|
||||
# base: sr
|
||||
(device behind) (уређај касни)
|
||||
@@ -826,7 +826,7 @@ TCP on TCP укљ
|
||||
TOUCH BETA TOUCH BETA
|
||||
TX TX
|
||||
Tab hotkeys — tap a row, then press a key Пречице картице — додирни ред, па притисни тастер
|
||||
Tap Use to switch - the device reboots to apply. Додирните „Користи“ да промените — уређај ће се поново покренути.
|
||||
Tap Use to switch - the device reboots to apply. Додирните "Користи" да промените — уређај ће се поново покренути.
|
||||
Tap Use to switch - the device reboots to apply. Language files live in /lang on the storage; edit them or add your own. Додирни «Користи» за промену - уређај се поново покреће. Датотеке језика су у /lang на меморији; уреди их или додај своје.
|
||||
Tap a language; the device reboots to apply. Изаберите језик; уређај ће се рестартовати.
|
||||
Taste the rainbow! Окуси дугу!
|
||||
@@ -977,8 +977,6 @@ tap to exit full screen додирните за излаз из целог ек
|
||||
zoom зум
|
||||
zoom %d зум %d
|
||||
~17 dB amp for quiet/remote areas; turn off in noisy spots. појачало ~17 dB за тиха/удаљена места; искључи у бучним.
|
||||
© OpenStreetMap © OpenStreetMap
|
||||
© OpenTopoMap © OpenTopoMap
|
||||
Scope
|
||||
another region
|
||||
my region
|
||||
@@ -1000,7 +998,6 @@ Overwrite oldest non-favorite
|
||||
1-character messages will be shown 1-character messages will be shown
|
||||
All All
|
||||
Allow Allow
|
||||
Apps that may send messages on the mesh. Anything they send goes out under your node name and cannot be told apart from a message you typed. Turn one off to take the permission back. Apps that may send messages on the mesh. Anything they send goes out under your node name and cannot be told apart from a message you typed. Turn one off to take the permission back.
|
||||
Ignore 1-character messages Ignore 1-character messages
|
||||
Join w/ password Join w/ password
|
||||
Location no longer shared Location no longer shared
|
||||
@@ -1014,9 +1011,7 @@ Never\nChosen contacts only\nAnyone who asks Never\nChosen contacts only\nAnyone
|
||||
No apps installed. No apps installed.
|
||||
No reply. Path hash is %u bytes;\nolder repeaters drop those.\nTry 1 byte (Settings > Radio & Mesh) No reply. Path hash is %u bytes;\nolder repeaters drop those.\nTry 1 byte (Settings > Radio & Mesh)
|
||||
Not supported on this board Not supported on this board
|
||||
Read incoming messages Read incoming messages
|
||||
Reconnected to %.40s Reconnected to %.40s
|
||||
Send messages as me Send messages as me
|
||||
Sent only when that contact asks, encrypted to them. Nothing is broadcast. Needs "Answer telemetry requests" on. Pick contacts in a contact's menu. Sent only when that contact asks, encrypted to them. Nothing is broadcast. Needs "Answer telemetry requests" on. Pick contacts in a contact's menu.
|
||||
Server has forgotten you.\nJoin with the room password. Server has forgotten you.\nJoin with the room password.
|
||||
Share my loc Share my loc
|
||||
@@ -1042,7 +1037,6 @@ App permissions App permissions
|
||||
About / credits About / credits
|
||||
Auth failed Auth failed
|
||||
Auto-add on for %s — new %s land in Contacts automatically. Auto-add on for %s — new %s land in Contacts automatically.
|
||||
Batteryactivityon Batteryactivityon
|
||||
Console mode Console mode
|
||||
Console mode (experimental) Console mode (experimental)
|
||||
Direct (0-hop) only Direct (0-hop) only
|
||||
@@ -1051,8 +1045,6 @@ EXPERIMENTAL. Boots into a text console with no graphical interface: type comman
|
||||
Enter text Enter text
|
||||
Entering console mode - rebooting... Entering console mode - rebooting...
|
||||
Favorite Favorite
|
||||
Geblokkeerde gebruikers Geblokkeerde gebruikers
|
||||
Hide system filesShow system files Hide system filesShow system files
|
||||
How tiles work:\nThe map is built from 256×256 "slippy" tiles. Only the tiles for the area you're viewing are fetched — there is no bulk pre-download.\n\nBecause this device can't do HTTPS (not enough heap after Wi-Fi starts) and decodes JPEG far more cheaply than PNG, tiles come from the wadamesh proxy: it fetches the upstream PNG over HTTPS with an identifying User-Agent, re-encodes it as JPEG, and caches it. Your device then caches each tile to its own flash, so a tile is only downloaded once.\n\nUse Options → Reload tiles to re-download the tiles currently in view if one looks corrupted. How tiles work:\nThe map is built from 256×256 "slippy" tiles. Only the tiles for the area you're viewing are fetched — there is no bulk pre-download.\n\nBecause this device can't do HTTPS (not enough heap after Wi-Fi starts) and decodes JPEG far more cheaply than PNG, tiles come from the wadamesh proxy: it fetches the upstream PNG over HTTPS with an identifying User-Agent, re-encodes it as JPEG, and caches it. Your device then caches each tile to its own flash, so a tile is only downloaded once.\n\nUse Options → Reload tiles to re-download the tiles currently in view if one looks corrupted.
|
||||
Init… Init…
|
||||
Leaving console mode - rebooting... Leaving console mode - rebooting...
|
||||
@@ -1062,8 +1054,6 @@ Map data © OpenStreetMap contributors.\nopenstreetmap.org/copyright\nLicensed u
|
||||
Map style © OpenTopoMap (CC-BY-SA) — opentopomap.org\nMap data © OpenStreetMap contributors (ODbL) + SRTM.\n\n Map style © OpenTopoMap (CC-BY-SA) — opentopomap.org\nMap data © OpenStreetMap contributors (ODbL) + SRTM.\n\n
|
||||
Name (A-Z) Name (A-Z)
|
||||
Nearest first Nearest first
|
||||
Other networksNetworks Other networksNetworks
|
||||
Paste (move)Paste (copy) Paste (move)Paste (copy)
|
||||
Recent message Recent message
|
||||
Reload tiles in view Reload tiles in view
|
||||
Repeaters Repeaters
|
||||
@@ -1076,13 +1066,8 @@ Show contacts Show contacts
|
||||
Show coordinates Show coordinates
|
||||
Show tile z/x/y Show tile z/x/y
|
||||
Starting… Starting…
|
||||
Stop sharing loc Share my loc Stop sharing loc Share my loc
|
||||
Supprimer Supprimer
|
||||
Switch to the external antenna?\n\nMake sure an antenna is actually connected to the external antenna socket first. Transmitting with nothing attached can damage the radio.\n\nResets to the on-board antenna on every reboot.Turn on legacy per-transmit switching?\n\nDiagnostic mode, for comparison only. It transmits on the on-board antenna and listens on the external one, so your outbound signal will be much weaker than your inbound.\n\nResets to the on-board antenna on every reboot. Switch to the external antenna?\n\nMake sure an antenna is actually connected to the external antenna socket first. Transmitting with nothing attached can damage the radio.\n\nResets to the on-board antenna on every reboot.Turn on legacy per-transmit switching?\n\nDiagnostic mode, for comparison only. It transmits on the on-board antenna and listens on the external one, so your outbound signal will be much weaker than your inbound.\n\nResets to the on-board antenna on every reboot.
|
||||
Text console (experimental) Text console (experimental)
|
||||
Tile debug overlay Tile debug overlay
|
||||
Unblock Block Unblock Block
|
||||
Unfav Favorite Unfav Favorite
|
||||
chats chats
|
||||
repeaters repeaters
|
||||
rooms rooms
|
||||
@@ -1098,3 +1083,26 @@ channel busy %.1f%% channel busy %.1f%%
|
||||
sampling... sampling...
|
||||
teal = now blue = average teal = now blue = average
|
||||
window restarted window restarted
|
||||
Filter Filter
|
||||
Hide system files Hide system files
|
||||
Networks Networks
|
||||
Other networks Other networks
|
||||
Show system files Show system files
|
||||
Unfav Unfav
|
||||
Firmware beta_%d\nChecking for updates over Wi-Fi… Firmware beta_%d\nChecking for updates over Wi-Fi…
|
||||
Install beta_%d?\nThis downgrades the firmware and reboots. Install beta_%d?\nThis downgrades the firmware and reboots.
|
||||
Other (hidden) network… Other (hidden) network…
|
||||
Paused Paused
|
||||
Scan again Scan again
|
||||
At a glance At a glance
|
||||
At a glance disabled At a glance disabled
|
||||
At a glance enabled At a glance enabled
|
||||
At a glance while locked At a glance while locked
|
||||
Glance only shows when unlocked Glance only shows when unlocked
|
||||
Glance shows message previews\nwhile the screen is locked Glance shows message previews\nwhile the screen is locked
|
||||
Snake — swipe, roll, or press a direction Snake — swipe, roll, or press a direction
|
||||
N/A N/A
|
||||
This board cannot run this app. This board cannot run this app.
|
||||
Signal Signal
|
||||
Signal (stale) Signal (stale)
|
||||
nothing heard yet nothing heard yet
|
||||
|
||||
+24
-16
@@ -1,7 +1,7 @@
|
||||
# wadamesh language file
|
||||
# canonical source - translators: edit this file and PR it
|
||||
# code: uk
|
||||
# ver: 18
|
||||
# ver: 19
|
||||
# name: Українська
|
||||
# base: uk
|
||||
(device behind) (пристрій відстає)
|
||||
@@ -977,8 +977,6 @@ tap to exit full screen торкніться, щоб вийти з повное
|
||||
zoom масштаб
|
||||
zoom %d масштаб %d
|
||||
~17 dB amp for quiet/remote areas; turn off in noisy spots. підсилювач ~17 дБ для тихих/віддалених місць; вимикайте в галасливих.
|
||||
© OpenStreetMap © OpenStreetMap
|
||||
© OpenTopoMap © OpenTopoMap
|
||||
Scope
|
||||
another region
|
||||
my region
|
||||
@@ -1000,7 +998,6 @@ Overwrite oldest non-favorite
|
||||
1-character messages will be shown 1-character messages will be shown
|
||||
All All
|
||||
Allow Allow
|
||||
Apps that may send messages on the mesh. Anything they send goes out under your node name and cannot be told apart from a message you typed. Turn one off to take the permission back. Apps that may send messages on the mesh. Anything they send goes out under your node name and cannot be told apart from a message you typed. Turn one off to take the permission back.
|
||||
Ignore 1-character messages Ignore 1-character messages
|
||||
Join w/ password Join w/ password
|
||||
Location no longer shared Location no longer shared
|
||||
@@ -1014,9 +1011,7 @@ Never\nChosen contacts only\nAnyone who asks Never\nChosen contacts only\nAnyone
|
||||
No apps installed. No apps installed.
|
||||
No reply. Path hash is %u bytes;\nolder repeaters drop those.\nTry 1 byte (Settings > Radio & Mesh) No reply. Path hash is %u bytes;\nolder repeaters drop those.\nTry 1 byte (Settings > Radio & Mesh)
|
||||
Not supported on this board Not supported on this board
|
||||
Read incoming messages Read incoming messages
|
||||
Reconnected to %.40s Reconnected to %.40s
|
||||
Send messages as me Send messages as me
|
||||
Sent only when that contact asks, encrypted to them. Nothing is broadcast. Needs "Answer telemetry requests" on. Pick contacts in a contact's menu. Sent only when that contact asks, encrypted to them. Nothing is broadcast. Needs "Answer telemetry requests" on. Pick contacts in a contact's menu.
|
||||
Server has forgotten you.\nJoin with the room password. Server has forgotten you.\nJoin with the room password.
|
||||
Share my loc Share my loc
|
||||
@@ -1042,7 +1037,6 @@ App permissions App permissions
|
||||
About / credits About / credits
|
||||
Auth failed Auth failed
|
||||
Auto-add on for %s — new %s land in Contacts automatically. Auto-add on for %s — new %s land in Contacts automatically.
|
||||
Batteryactivityon Batteryactivityon
|
||||
Console mode Console mode
|
||||
Console mode (experimental) Console mode (experimental)
|
||||
Direct (0-hop) only Direct (0-hop) only
|
||||
@@ -1051,8 +1045,6 @@ EXPERIMENTAL. Boots into a text console with no graphical interface: type comman
|
||||
Enter text Enter text
|
||||
Entering console mode - rebooting... Entering console mode - rebooting...
|
||||
Favorite Favorite
|
||||
Geblokkeerde gebruikers Geblokkeerde gebruikers
|
||||
Hide system filesShow system files Hide system filesShow system files
|
||||
How tiles work:\nThe map is built from 256×256 "slippy" tiles. Only the tiles for the area you're viewing are fetched — there is no bulk pre-download.\n\nBecause this device can't do HTTPS (not enough heap after Wi-Fi starts) and decodes JPEG far more cheaply than PNG, tiles come from the wadamesh proxy: it fetches the upstream PNG over HTTPS with an identifying User-Agent, re-encodes it as JPEG, and caches it. Your device then caches each tile to its own flash, so a tile is only downloaded once.\n\nUse Options → Reload tiles to re-download the tiles currently in view if one looks corrupted. How tiles work:\nThe map is built from 256×256 "slippy" tiles. Only the tiles for the area you're viewing are fetched — there is no bulk pre-download.\n\nBecause this device can't do HTTPS (not enough heap after Wi-Fi starts) and decodes JPEG far more cheaply than PNG, tiles come from the wadamesh proxy: it fetches the upstream PNG over HTTPS with an identifying User-Agent, re-encodes it as JPEG, and caches it. Your device then caches each tile to its own flash, so a tile is only downloaded once.\n\nUse Options → Reload tiles to re-download the tiles currently in view if one looks corrupted.
|
||||
Init… Init…
|
||||
Leaving console mode - rebooting... Leaving console mode - rebooting...
|
||||
@@ -1062,8 +1054,6 @@ Map data © OpenStreetMap contributors.\nopenstreetmap.org/copyright\nLicensed u
|
||||
Map style © OpenTopoMap (CC-BY-SA) — opentopomap.org\nMap data © OpenStreetMap contributors (ODbL) + SRTM.\n\n Map style © OpenTopoMap (CC-BY-SA) — opentopomap.org\nMap data © OpenStreetMap contributors (ODbL) + SRTM.\n\n
|
||||
Name (A-Z) Name (A-Z)
|
||||
Nearest first Nearest first
|
||||
Other networksNetworks Other networksNetworks
|
||||
Paste (move)Paste (copy) Paste (move)Paste (copy)
|
||||
Recent message Recent message
|
||||
Reload tiles in view Reload tiles in view
|
||||
Repeaters Repeaters
|
||||
@@ -1076,13 +1066,8 @@ Show contacts Show contacts
|
||||
Show coordinates Show coordinates
|
||||
Show tile z/x/y Show tile z/x/y
|
||||
Starting… Starting…
|
||||
Stop sharing loc Share my loc Stop sharing loc Share my loc
|
||||
Supprimer Supprimer
|
||||
Switch to the external antenna?\n\nMake sure an antenna is actually connected to the external antenna socket first. Transmitting with nothing attached can damage the radio.\n\nResets to the on-board antenna on every reboot.Turn on legacy per-transmit switching?\n\nDiagnostic mode, for comparison only. It transmits on the on-board antenna and listens on the external one, so your outbound signal will be much weaker than your inbound.\n\nResets to the on-board antenna on every reboot. Switch to the external antenna?\n\nMake sure an antenna is actually connected to the external antenna socket first. Transmitting with nothing attached can damage the radio.\n\nResets to the on-board antenna on every reboot.Turn on legacy per-transmit switching?\n\nDiagnostic mode, for comparison only. It transmits on the on-board antenna and listens on the external one, so your outbound signal will be much weaker than your inbound.\n\nResets to the on-board antenna on every reboot.
|
||||
Text console (experimental) Text console (experimental)
|
||||
Tile debug overlay Tile debug overlay
|
||||
Unblock Block Unblock Block
|
||||
Unfav Favorite Unfav Favorite
|
||||
chats chats
|
||||
repeaters repeaters
|
||||
rooms rooms
|
||||
@@ -1098,3 +1083,26 @@ channel busy %.1f%% channel busy %.1f%%
|
||||
sampling... sampling...
|
||||
teal = now blue = average teal = now blue = average
|
||||
window restarted window restarted
|
||||
Filter Filter
|
||||
Hide system files Hide system files
|
||||
Networks Networks
|
||||
Other networks Other networks
|
||||
Show system files Show system files
|
||||
Unfav Unfav
|
||||
Firmware beta_%d\nChecking for updates over Wi-Fi… Firmware beta_%d\nChecking for updates over Wi-Fi…
|
||||
Install beta_%d?\nThis downgrades the firmware and reboots. Install beta_%d?\nThis downgrades the firmware and reboots.
|
||||
Other (hidden) network… Other (hidden) network…
|
||||
Paused Paused
|
||||
Scan again Scan again
|
||||
At a glance At a glance
|
||||
At a glance disabled At a glance disabled
|
||||
At a glance enabled At a glance enabled
|
||||
At a glance while locked At a glance while locked
|
||||
Glance only shows when unlocked Glance only shows when unlocked
|
||||
Glance shows message previews\nwhile the screen is locked Glance shows message previews\nwhile the screen is locked
|
||||
Snake — swipe, roll, or press a direction Snake — swipe, roll, or press a direction
|
||||
N/A N/A
|
||||
This board cannot run this app. This board cannot run this app.
|
||||
Signal Signal
|
||||
Signal (stale) Signal (stale)
|
||||
nothing heard yet nothing heard yet
|
||||
|
||||
+13
-13
@@ -3,67 +3,67 @@
|
||||
{
|
||||
"code": "bg",
|
||||
"name": "Български",
|
||||
"ver": "18"
|
||||
"ver": "19"
|
||||
},
|
||||
{
|
||||
"code": "de",
|
||||
"name": "Deutsch",
|
||||
"ver": "18"
|
||||
"ver": "19"
|
||||
},
|
||||
{
|
||||
"code": "el",
|
||||
"name": "Ελληνικά",
|
||||
"ver": "18"
|
||||
"ver": "19"
|
||||
},
|
||||
{
|
||||
"code": "es",
|
||||
"name": "Español",
|
||||
"ver": "18"
|
||||
"ver": "19"
|
||||
},
|
||||
{
|
||||
"code": "fr",
|
||||
"name": "Français",
|
||||
"ver": "18"
|
||||
"ver": "19"
|
||||
},
|
||||
{
|
||||
"code": "hu",
|
||||
"name": "Magyar",
|
||||
"ver": "18"
|
||||
"ver": "19"
|
||||
},
|
||||
{
|
||||
"code": "it",
|
||||
"name": "Italiano",
|
||||
"ver": "18"
|
||||
"ver": "19"
|
||||
},
|
||||
{
|
||||
"code": "nl",
|
||||
"name": "Nederlands",
|
||||
"ver": "18"
|
||||
"ver": "19"
|
||||
},
|
||||
{
|
||||
"code": "pt-br",
|
||||
"name": "Português (BR)",
|
||||
"ver": "18"
|
||||
"ver": "19"
|
||||
},
|
||||
{
|
||||
"code": "ro",
|
||||
"name": "Română",
|
||||
"ver": "18"
|
||||
"ver": "19"
|
||||
},
|
||||
{
|
||||
"code": "ru",
|
||||
"name": "Русский",
|
||||
"ver": "18"
|
||||
"ver": "19"
|
||||
},
|
||||
{
|
||||
"code": "sr",
|
||||
"name": "Српски",
|
||||
"ver": "18"
|
||||
"ver": "19"
|
||||
},
|
||||
{
|
||||
"code": "uk",
|
||||
"name": "Українська",
|
||||
"ver": "18"
|
||||
"ver": "19"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
{"id":"sdktest","name":"SDK Test","ver":"1.7","desc":"Developer tool. Checks the extended Lua SDK on this board: capabilities, clock, battery, GPS, private files, audio API, read-only SD listing, crypto vectors, packet identity, channels, and opt-in transmit calls."}
|
||||
@@ -0,0 +1,293 @@
|
||||
-- SDK self-test. Exercises the extended SDK so the results can be read off the
|
||||
-- screen instead of inferred from a build log. Published to the store as a
|
||||
-- developer/bench tool.
|
||||
--
|
||||
-- 1.2 adds wada.crypto (checked against published RFC vectors, so a PASS here is
|
||||
-- real evidence and not just "it returned something"), wada.mesh.channels, and
|
||||
-- wada.mesh.send_dm.
|
||||
-- 1.3 adds the discovery surface (wada.mesh.discover / discovered), packet
|
||||
-- identity in rx_log, exact micro-degree coordinates, wada.ui.input and the
|
||||
-- windowed wada.fs.read.
|
||||
-- 1.6 adds wada.sd.list: capability reporting, traversal rejection, root
|
||||
-- metadata, and a nested directory listing when the card contains one.
|
||||
-- 1.7 adds storage-neutral wada.audio capability and API-shape checks. Playback
|
||||
-- stays manual so opening the bench test never emits sound unexpectedly.
|
||||
-- 1.5 uses wada.ui.text_lines() to measure instead of estimating, where the
|
||||
-- firmware has it. That call was added because of the 1.3 bug below: an app
|
||||
-- could ask how tall a line is but not how wide, so laying out rows meant
|
||||
-- guessing whether text would wrap. Falls back to the 1.4 estimate on older
|
||||
-- firmware, so it still lays out correctly there.
|
||||
-- 1.4 fixes rows drawing on top of one another. label:width() turns wrapping
|
||||
-- on, so any line wider than the screen became two lines while the caller still
|
||||
-- advanced by one, and the next row landed on top. Reported with a photo of the
|
||||
-- channels line sitting across the contacts line. row() now owns the cursor and
|
||||
-- advances by the height the text actually needs.
|
||||
local ui, sys, store, timer = wada.ui, wada.sys, wada.store, wada.timer
|
||||
local C = ui.colors
|
||||
|
||||
local app = {}
|
||||
local rows, keyline, sendline, dmline, msgline = {}, nil, nil, nil, nil
|
||||
local discline, inputline = nil, nil
|
||||
local W = 300
|
||||
|
||||
-- row() owns the vertical cursor. Every caller used to pass a y and then add a
|
||||
-- hand-picked constant, which is only correct while the text fits on one line.
|
||||
-- An app cannot measure rendered text, so the wrapped line count is estimated
|
||||
-- from a deliberately pessimistic character width: over-estimating costs a
|
||||
-- little whitespace, under-estimating overlaps the next row.
|
||||
local LH, GAP = 14, 3
|
||||
local cy = 4
|
||||
local function row(text, color, extra_gap)
|
||||
local l = ui.label(text, 6, cy, 12, color or C.text)
|
||||
l:width(W - 12)
|
||||
rows[#rows + 1] = l
|
||||
local n
|
||||
if ui.text_lines then
|
||||
n = ui.text_lines(tostring(text), W - 12, 12) -- measured: exact
|
||||
else
|
||||
local cpl = math.max(16, (W - 12) // 7) -- older firmware: estimate
|
||||
n = 0
|
||||
for seg in (tostring(text) .. "\n"):gmatch("(.-)\n") do
|
||||
n = n + math.max(1, math.ceil(#seg / cpl))
|
||||
end
|
||||
end
|
||||
cy = cy + math.max(1, n or 1) * LH + GAP + (extra_gap or 0)
|
||||
return l
|
||||
end
|
||||
local function yn(v) return v and "yes" or "NO" end
|
||||
|
||||
function app.on_open(w, h)
|
||||
W = w or 300
|
||||
ui.scroll(true)
|
||||
cy = 4
|
||||
local y
|
||||
|
||||
local c = sys.caps()
|
||||
row("caps: sdk_ext=" .. yn(c.sdk_ext) .. " kbd=" .. yn(c.keyboard) ..
|
||||
" touch=" .. yn(c.touch) .. " sd=" .. yn(c.sd), C.accent)
|
||||
row("caps: sd_list=" .. yn(c.sd_list) .. " discover=" .. yn(c.discover) ..
|
||||
" input=" .. yn(c.input) ..
|
||||
" rx_identity=" .. yn(c.rx_identity), C.accent)
|
||||
row("caps: audio=" .. yn(c.audio) .. " wav=" .. yn(c.audio_wav) ..
|
||||
" mp3=" .. yn(c.audio_mp3) .. " audio_sd=" .. yn(c.audio_sd), C.accent)
|
||||
if c.audio then
|
||||
local api_ok = wada.audio and type(wada.audio.play) == "function" and
|
||||
type(wada.audio.pause) == "function" and
|
||||
type(wada.audio.resume) == "function" and
|
||||
type(wada.audio.stop) == "function" and
|
||||
type(wada.audio.status) == "function"
|
||||
local status = api_ok and wada.audio.status() or nil
|
||||
api_ok = api_ok and type(status) == "table" and type(status.state) == "string"
|
||||
row("wada.audio API: " .. (api_ok and ("PASS (" .. status.state .. ")") or "FAIL"),
|
||||
api_ok and C.good or C.bad)
|
||||
else
|
||||
row("wada.audio: unavailable on this board", C.sub)
|
||||
end
|
||||
row("layout: " .. (ui.text_lines and "measured (ui.text_lines)" or "estimated (older firmware)"),
|
||||
ui.text_lines and C.good or C.sub)
|
||||
-- crypto: published test vectors, so this is checkable rather than merely alive
|
||||
if wada.crypto then
|
||||
local hex = wada.crypto.hex
|
||||
local k20 = string.rep(string.char(0x0b), 20)
|
||||
local checks = {
|
||||
{ "sha256('abc')", hex(wada.crypto.sha256("abc")),
|
||||
"ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad" },
|
||||
{ "sha1('abc')", hex(wada.crypto.sha1("abc")),
|
||||
"a9993e364706816aba3e25717850c26c9cd0d89d" },
|
||||
{ "hmac_sha1 RFC2202#1", hex(wada.crypto.hmac_sha1(k20, "Hi There")),
|
||||
"b617318655057264e28bc0b6fb378c8ef146be00" },
|
||||
{ "hmac_sha256 RFC4231#1", hex(wada.crypto.hmac_sha256(k20, "Hi There")),
|
||||
"b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7" },
|
||||
}
|
||||
local allok = true
|
||||
for _, t in ipairs(checks) do
|
||||
local ok = (t[2] == t[3])
|
||||
if not ok then allok = false end
|
||||
row("crypto " .. t[1] .. ": " .. (ok and "PASS" or ("FAIL got " .. tostring(t[2]):sub(1, 16))),
|
||||
ok and C.good or C.bad)
|
||||
end
|
||||
-- The point of having it in C: this loop would blow the instruction budget in Lua.
|
||||
local t0 = sys.millis()
|
||||
for _ = 1, 200 do wada.crypto.hmac_sha1(k20, "Hi There") end
|
||||
row(string.format("crypto: 200 x hmac_sha1 in %d ms%s", sys.millis() - t0,
|
||||
allok and "" or " (VECTORS FAILED)"), allok and C.good or C.bad)
|
||||
else
|
||||
row("wada.crypto: MISSING", C.bad)
|
||||
end
|
||||
|
||||
-- channel discovery
|
||||
local chans = wada.mesh.channels and wada.mesh.channels() or nil
|
||||
if chans then
|
||||
local names = table.concat(chans, ", ")
|
||||
row("channels(): " .. #chans .. " " .. names:sub(1, 60), C.text)
|
||||
else
|
||||
row("wada.mesh.channels: MISSING", C.bad)
|
||||
end
|
||||
|
||||
if not c.sdk_ext then
|
||||
row("extended SDK is OFF on this board - stopping here.", C.sub)
|
||||
return
|
||||
end
|
||||
|
||||
-- contacts, so send_dm has a target to name. 1.3 also checks the pubkey field.
|
||||
local cts = wada.mesh.contacts()
|
||||
local first = cts[1] and cts[1].name or nil
|
||||
row("contacts: " .. #cts .. (first and (" first=" .. first) or ""), C.text)
|
||||
if cts[1] then
|
||||
local pk = cts[1].pubkey
|
||||
row("contacts[1].pubkey: " .. tostring(pk) ..
|
||||
(type(pk) == "string" and #pk == 8 and " PASS" or " FAIL"),
|
||||
(type(pk) == "string" and #pk == 8) and C.good or C.bad)
|
||||
end
|
||||
|
||||
local me = wada.mesh.self()
|
||||
row("self: " .. tostring(me.name) .. " pubkey=" .. tostring(me.pubkey), C.text)
|
||||
-- Exact coordinates. Lua's floats here are 32-bit, so an app that logs a track
|
||||
-- must use the _e6 integers; this proves they are present and consistent.
|
||||
local fix = sys.gps()
|
||||
if fix then
|
||||
local drift = math.abs(fix.lat_e6 / 1e6 - fix.lat)
|
||||
row(string.format("gps: %d,%d e6 alt %dm %d sats drift %.6f %s",
|
||||
fix.lat_e6, fix.lon_e6, fix.alt_m or 0, fix.sats, drift,
|
||||
drift < 0.001 and "PASS" or "FAIL"), drift < 0.001 and C.good or C.bad)
|
||||
else
|
||||
row("gps: no fix (normal indoors)", C.sub)
|
||||
end
|
||||
|
||||
-- rx_log identity: adverts carry a real public key, addressed frames carry
|
||||
-- one-byte hashes, everything else carries nothing. All three are correct.
|
||||
local log = wada.mesh.rx_log()
|
||||
local withpk, withsrc = 0, 0
|
||||
for _, r in ipairs(log) do
|
||||
if r.pubkey then withpk = withpk + 1 end
|
||||
if r.src then withsrc = withsrc + 1 end
|
||||
end
|
||||
row(string.format("rx_log: %d frames, %d with a pubkey (adverts), %d with src/dst",
|
||||
#log, withpk, withsrc), #log > 0 and C.text or C.sub)
|
||||
-- fs: windowed read. Writes once (the 1/sec limit means one write per open).
|
||||
if wada.fs then
|
||||
local probe = "0123456789abcdef"
|
||||
wada.fs.write("sdktest.bin", probe)
|
||||
local part, total = wada.fs.read("sdktest.bin", 4, 4)
|
||||
local ok = (part == "4567" and total == #probe)
|
||||
row("fs.read(name,4,4): " .. tostring(part) .. " total=" .. tostring(total) ..
|
||||
(ok and " PASS" or " FAIL"), ok and C.good or C.bad)
|
||||
end
|
||||
|
||||
-- Physical SD listing is read-only and independently feature-detected. A
|
||||
-- supported board with no inserted card is a normal bench state, not a test
|
||||
-- failure; API shape and path rejection can still be checked separately.
|
||||
if c.sd_list then
|
||||
if not wada.sd or not wada.sd.list then
|
||||
row("wada.sd.list: MISSING despite caps", C.bad)
|
||||
else
|
||||
local guard_ok, guard_failure = true, nil
|
||||
local bad_paths = { "/..", "/.", "/a/../b", "/a//b", "/a/", "relative", "/a\\b" }
|
||||
for _, path in ipairs(bad_paths) do
|
||||
local rejected, baderr = wada.sd.list(path)
|
||||
if rejected ~= nil or baderr ~= "bad path" then
|
||||
guard_ok = false
|
||||
guard_failure = path .. " -> " .. tostring(baderr)
|
||||
break
|
||||
end
|
||||
end
|
||||
row("sd traversal guard: " .. (guard_ok and "PASS" or ("FAIL " .. guard_failure)),
|
||||
guard_ok and C.good or C.bad)
|
||||
|
||||
local entries, err = wada.sd.list("/")
|
||||
if not entries then
|
||||
local note = err == "busy" and " (retry later)" or " (no readable card)"
|
||||
row("sd.list('/'): " .. tostring(err) .. note, C.sub)
|
||||
else
|
||||
local metadata_ok, first_dir = true, nil
|
||||
for _, entry in ipairs(entries) do
|
||||
if type(entry.name) ~= "string" or
|
||||
(entry.type ~= "file" and entry.type ~= "dir") or
|
||||
type(entry.size) ~= "number" or type(entry.mtime) ~= "number" then
|
||||
metadata_ok = false
|
||||
end
|
||||
if not first_dir and entry.type == "dir" then first_dir = entry.name end
|
||||
end
|
||||
row("sd.list('/'): " .. #entries .. " entries, metadata " ..
|
||||
(metadata_ok and "PASS" or "FAIL") ..
|
||||
(entries.truncated and " (truncated)" or ""),
|
||||
metadata_ok and C.good or C.bad)
|
||||
if first_dir then
|
||||
local nested, nested_err = wada.sd.list("/" .. first_dir)
|
||||
row("sd.list('/" .. first_dir .. "'): " ..
|
||||
(nested and (#nested .. " entries PASS") or ("FAIL " .. tostring(nested_err))),
|
||||
nested and C.good or C.bad)
|
||||
else
|
||||
row("sd nested listing: no directory on card to test", C.sub)
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
row("wada.sd.list: unavailable on this board", C.sub)
|
||||
end
|
||||
|
||||
sendline = row("mesh.send: not tried yet", C.sub)
|
||||
dmline = row("mesh.send_dm: not tried yet", C.sub)
|
||||
msgline = row("on_message: waiting (needs the read permissions)", C.sub)
|
||||
y = cy + 4
|
||||
ui.button("Send to Public", 6, y, 120, 30, function()
|
||||
local ok, err = wada.mesh.send("Public", "wadamesh SDK self-test")
|
||||
sendline:set("mesh.send: " .. tostring(ok) .. " " .. tostring(err))
|
||||
sendline:color(ok and C.good or C.bad)
|
||||
end)
|
||||
ui.button("DM first contact", 132, y, 130, 30, function()
|
||||
if not first then dmline:set("mesh.send_dm: no contacts to target"); dmline:color(C.bad); return end
|
||||
local ok, err = wada.mesh.send_dm(first, "wadamesh SDK self-test (DM)")
|
||||
dmline:set("mesh.send_dm -> " .. first .. ": " .. tostring(ok) .. " " .. tostring(err))
|
||||
dmline:color(ok and C.good or C.bad)
|
||||
end)
|
||||
cy = y + 38
|
||||
discline = row("mesh.discover: not tried yet", C.sub)
|
||||
inputline = row("ui.input: not tried yet", C.sub)
|
||||
-- Probing TRANSMITS and makes every neighbour reply, so it is a button, not
|
||||
-- something this app does on open.
|
||||
y = cy + 4
|
||||
ui.button("Probe", 6, y, 90, 30, function()
|
||||
local tag, err = wada.mesh.discover()
|
||||
if not tag then
|
||||
discline:set("mesh.discover: " .. tostring(err)); discline:color(C.bad); return
|
||||
end
|
||||
discline:set("mesh.discover: sent, waiting for replies..."); discline:color(C.text)
|
||||
end)
|
||||
ui.button("Results", 102, y, 90, 30, function()
|
||||
local hits = wada.mesh.discovered()
|
||||
if #hits == 0 then
|
||||
discline:set("discovered(): nothing yet - probe, then wait a few seconds")
|
||||
discline:color(C.sub); return
|
||||
end
|
||||
local h = hits[1]
|
||||
discline:set(string.format("discovered(): %d first %s snr %.1f/%.1f %s",
|
||||
#hits, h.name or h.pubkey, h.snr, h.their_snr, h.direct and "direct" or (h.hops .. "h")))
|
||||
discline:color(C.good)
|
||||
end)
|
||||
y = y + 34
|
||||
ui.button("Beep", 6, y, 70, 30, function() sys.beep() end)
|
||||
ui.button("Input", 82, y, 90, 30, function()
|
||||
ui.input("Type anything", "hello", function(text)
|
||||
inputline:set("ui.input -> " .. (text and ("'" .. text .. "'") or "cancelled"))
|
||||
inputline:color(text and C.good or C.sub)
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
||||
function app.on_input(ev)
|
||||
if ev.type == "key" and keyline then
|
||||
keyline:set("keys: got '" .. tostring(ev.key) .. "'")
|
||||
keyline:color(C.good)
|
||||
end
|
||||
end
|
||||
|
||||
-- Proves the kind field and that DMs/rooms reach an app at all, not just channels.
|
||||
function app.on_message(m)
|
||||
if not msgline then return end
|
||||
msgline:set("on_message: kind=" .. tostring(m.kind) .. " from=" .. tostring(m.sender) ..
|
||||
" ch=" .. tostring(m.channel) .. " text=" .. tostring(m.text):sub(1, 20))
|
||||
msgline:color(C.good)
|
||||
end
|
||||
|
||||
return app
|
||||
@@ -0,0 +1 @@
|
||||
{"id":"wardrive","name":"Wardrive","ver":"1.1","desc":"LoRa coverage survey. Probes every 20s, logs each reply with GPS position, altitude and BOTH link directions to a CSV you can pull off the device. Reference app for wada.mesh.discover."}
|
||||
@@ -0,0 +1,216 @@
|
||||
-- Wardrive (Lua) — a LoRa coverage survey that logs to a CSV you can pull off
|
||||
-- the device afterwards.
|
||||
--
|
||||
-- Reference app for the discovery half of the SDK. It works the way a survey
|
||||
-- has to work: it PROBES rather than listens. A probe is a zero-hop request
|
||||
-- that every node in earshot answers, so a reply proves the link works from
|
||||
-- exactly where you are standing. Listening only ever tells you what happened
|
||||
-- to transmit while you were there, which is a different and much weaker claim.
|
||||
--
|
||||
-- Each reply carries both directions of the link: how well we heard them, and
|
||||
-- how well they heard us. They are rarely equal, and the asymmetry is the point
|
||||
-- -- "I can hear the repeater but it cannot hear me" is not the same fact as
|
||||
-- "no coverage", and only a probe reveals it.
|
||||
local ui, sys, mesh, fs, timer = wada.ui, wada.sys, wada.mesh, wada.fs, wada.timer
|
||||
local C = ui.colors
|
||||
local app = {}
|
||||
|
||||
local SWEEP_MS = 20000 -- above the 15 s floor the firmware enforces on probes
|
||||
local HARVEST_MS = 4000 -- replies land over the few seconds after a probe
|
||||
local TYPE = { [1]="chat", [2]="repeater", [3]="room", [4]="sensor" }
|
||||
|
||||
local run, running, samples, sweeps, last_err = "run", false, 0, 0, nil
|
||||
local node_count = 0
|
||||
local phase, phase_at = "idle", 0
|
||||
local hdr, gps_lbl, stat_lbl, rows = nil, nil, nil, {}
|
||||
local nodes = {} -- pubkey -> { name, type, best, worst, seen }
|
||||
local pending = {} -- lines waiting on the 1 write/sec limit
|
||||
|
||||
local function logname() return run .. ".csv" end
|
||||
|
||||
-- Return at most max_bytes without splitting a UTF-8 sequence. The row's
|
||||
-- fixed-width columns are byte-budgeted, not character-budgeted; a raw
|
||||
-- string.sub(1, 14) cut Ouderkerk + sun + VS16 inside the final codepoint and
|
||||
-- left LVGL unable to advance through the label (#323, same class as #223).
|
||||
local function utf8_prefix_bytes(text, max_bytes)
|
||||
local offset, last, length = 1, 0, #text
|
||||
while offset <= length and offset <= max_bytes do
|
||||
local first = text:byte(offset)
|
||||
local width = first <= 0x7F and 1
|
||||
or (first >= 0xC2 and first <= 0xDF and 2)
|
||||
or (first >= 0xE0 and first <= 0xEF and 3)
|
||||
or (first >= 0xF0 and first <= 0xF4 and 4) or 0
|
||||
if width == 0 or offset + width - 1 > length or offset + width - 1 > max_bytes then break end
|
||||
local valid = true
|
||||
for i = 2, width do
|
||||
local byte = text:byte(offset + i - 1)
|
||||
if byte < 0x80 or byte > 0xBF then valid = false; break end
|
||||
end
|
||||
local second = width > 1 and text:byte(offset + 1) or 0
|
||||
if (first == 0xE0 and second < 0xA0) or (first == 0xED and second > 0x9F) or
|
||||
(first == 0xF0 and second < 0x90) or (first == 0xF4 and second > 0x8F) then valid = false end
|
||||
if not valid then break end
|
||||
last = offset + width - 1
|
||||
offset = last + 1
|
||||
end
|
||||
return text:sub(1, last)
|
||||
end
|
||||
|
||||
-- Lua here is built with 32-bit floats, so fix.lat is good to about a metre and
|
||||
-- no better. fix.lat_e6 is the same reading as an exact integer in
|
||||
-- micro-degrees, which is what belongs in a log: a survey you plot months later
|
||||
-- should not carry rounding the device never had.
|
||||
local HEADER = "epoch,lat_e6,lon_e6,alt_m,pubkey,name,type,rssi,snr,their_snr,hops"
|
||||
local wrote_header = false
|
||||
|
||||
-- The filesystem allows one write a second. Sweeps produce a burst of rows, so
|
||||
-- they queue here and drain a chunk per tick instead of being dropped.
|
||||
local function flush()
|
||||
if #pending == 0 then return end
|
||||
local chunk = table.concat(pending, "\n") .. "\n"
|
||||
if not wrote_header then chunk = HEADER .. "\n" .. chunk end
|
||||
local ok = fs.append(logname(), chunk)
|
||||
if ok then pending, wrote_header = {}, true end
|
||||
end
|
||||
|
||||
local function record(fix, hit)
|
||||
local key = hit.pubkey
|
||||
local n = nodes[key]
|
||||
if not n then
|
||||
n = { name = hit.name or key, type = hit.type, best = hit.snr, worst = hit.snr, seen = 0 }
|
||||
nodes[key] = n
|
||||
node_count = node_count + 1
|
||||
end
|
||||
if hit.snr > n.best then n.best = hit.snr end
|
||||
if hit.snr < n.worst then n.worst = hit.snr end
|
||||
n.seen = n.seen + 1
|
||||
if hit.name then n.name = hit.name end
|
||||
|
||||
samples = samples + 1
|
||||
pending[#pending + 1] = string.format("%d,%d,%d,%d,%s,%s,%d,%d,%.2f,%.2f,%d",
|
||||
fix.time or sys.epoch(), fix.lat_e6, fix.lon_e6, fix.alt_m or 0,
|
||||
key, (hit.name or ""):gsub(",", " "), hit.type,
|
||||
hit.rssi, hit.snr, hit.their_snr, hit.hops)
|
||||
end
|
||||
|
||||
local function sweep()
|
||||
local tag, err = mesh.discover() -- every node type
|
||||
if not tag then last_err = err; return false end
|
||||
last_err = nil
|
||||
sweeps = sweeps + 1
|
||||
return true
|
||||
end
|
||||
|
||||
local function harvest()
|
||||
local fix = sys.gps()
|
||||
if not fix then
|
||||
-- No fix means the sample cannot be placed, so it is discarded rather than
|
||||
-- logged at 0,0. A survey file with phantom points at Null Island is worse
|
||||
-- than a shorter one.
|
||||
mesh.discover_clear()
|
||||
return
|
||||
end
|
||||
for _, hit in ipairs(mesh.discovered()) do record(fix, hit) end
|
||||
mesh.discover_clear() -- next sample must not inherit this one
|
||||
end
|
||||
|
||||
local function redraw()
|
||||
local fix = sys.gps()
|
||||
if fix then
|
||||
gps_lbl:set(string.format("%.5f, %.5f %dm %d sats", fix.lat, fix.lon, fix.alt_m or 0, fix.sats))
|
||||
gps_lbl:color(C.good)
|
||||
else
|
||||
gps_lbl:set("waiting for a GPS fix - samples are discarded until then")
|
||||
gps_lbl:color(C.bad)
|
||||
end
|
||||
|
||||
local state = running and (phase == "probe" and "listening..." or "sweeping") or "stopped"
|
||||
stat_lbl:set(string.format("%s | %s | %d sweeps, %d samples, %d nodes%s",
|
||||
run, state, sweeps, samples, node_count,
|
||||
last_err and (" [" .. last_err .. "]") or ""))
|
||||
stat_lbl:color(last_err and C.bad or C.accent)
|
||||
|
||||
local list = {}
|
||||
for key, n in pairs(nodes) do list[#list + 1] = { key = key, n = n } end
|
||||
table.sort(list, function(a, b) return a.n.best > b.n.best end)
|
||||
for i = 1, #rows do
|
||||
local e = list[i]
|
||||
if e then
|
||||
rows[i]:set(string.format("%-14s %-8s best %5.1f worst %5.1f x%d",
|
||||
utf8_prefix_bytes(e.n.name, 14), TYPE[e.n.type] or "?", e.n.best, e.n.worst, e.n.seen))
|
||||
rows[i]:color(e.n.best > 0 and C.good or C.text)
|
||||
else
|
||||
rows[i]:set(i == 1 and "nothing has answered a probe yet" or "")
|
||||
rows[i]:color(C.sub)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function app.on_open(w, h)
|
||||
if not sys.caps().discover then
|
||||
ui.label("This board does not carry the extended SDK,", 6, 8, 12, C.bad)
|
||||
ui.label("so it cannot send discovery probes.", 6, 26, 12, C.bad)
|
||||
return
|
||||
end
|
||||
ui.scroll(true)
|
||||
local LH = ui.text_h(12)
|
||||
local y = 4
|
||||
|
||||
hdr = ui.label("LoRa coverage survey", 4, y, 12, C.accent); hdr:width(w - 10); y = y + LH + 3
|
||||
gps_lbl = ui.label("", 4, y, 12, C.sub); gps_lbl:width(w - 10); y = y + LH + 3
|
||||
stat_lbl = ui.label("", 4, y, 12, C.text); stat_lbl:width(w - 10); y = y + LH * 2 + 5
|
||||
|
||||
local bw = math.min(96, (w - 20) // 3)
|
||||
ui.button("Start", 4, y, bw, 32, function()
|
||||
running = not running
|
||||
if running then phase, phase_at = "idle", 0 end
|
||||
sys.toast(running and "Survey running" or "Survey stopped", 1200)
|
||||
end)
|
||||
ui.button("Name", 8 + bw, y, bw, 32, function()
|
||||
ui.input("Name this run", run, function(text)
|
||||
if text then
|
||||
run = text:gsub("[^%w%-_]", "_")
|
||||
wrote_header = false -- a new file needs its own header row
|
||||
sys.toast("Logging to " .. logname(), 1500)
|
||||
end
|
||||
end)
|
||||
end)
|
||||
ui.button("Reset", 12 + bw * 2, y, bw, 32, function()
|
||||
nodes, samples, sweeps, pending, node_count = {}, 0, 0, {}, 0
|
||||
mesh.discover_clear()
|
||||
fs.remove(logname())
|
||||
wrote_header = false
|
||||
sys.toast("Cleared " .. logname(), 1200)
|
||||
end)
|
||||
y = y + 38
|
||||
|
||||
ui.label("strongest first", 4, y, 12, C.sub); y = y + LH + 2
|
||||
for i = 1, 12 do
|
||||
rows[i] = ui.label("", 4, y, 12, C.text); rows[i]:width(w - 10); y = y + LH + 2
|
||||
end
|
||||
|
||||
redraw()
|
||||
timer.every(1000)
|
||||
end
|
||||
|
||||
function app.on_tick()
|
||||
if running then
|
||||
local now = sys.millis()
|
||||
if phase == "idle" or (phase == "wait" and now - phase_at >= SWEEP_MS) then
|
||||
-- A refused or rate-limited probe backs off a full sweep interval. Retrying
|
||||
-- every tick would re-enter the permission path once a second for nothing.
|
||||
phase, phase_at = sweep() and "probe" or "wait", now
|
||||
elseif phase == "probe" and now - phase_at >= HARVEST_MS then
|
||||
harvest()
|
||||
phase = "wait" -- phase_at stays at the probe time, so sweeps stay on cadence
|
||||
end
|
||||
end
|
||||
flush()
|
||||
redraw()
|
||||
end
|
||||
|
||||
function app.on_close()
|
||||
flush() -- one last drain; anything queued would otherwise be lost
|
||||
end
|
||||
|
||||
return app
|
||||
+25
-4
@@ -141,6 +141,7 @@
|
||||
<a href="#net">wada.net</a>
|
||||
<a href="#store">wada.store</a>
|
||||
<a href="#fs">wada.fs</a>
|
||||
<a href="#audio">wada.audio</a>
|
||||
<a href="#sd">wada.sd</a>
|
||||
<a href="#sys">wada.sys</a>
|
||||
<a href="#timer">wada.timer</a>
|
||||
@@ -237,6 +238,7 @@ return app</code></pre>
|
||||
<tr><td>wada.ui.button(text, x, y, w, h, fn)</td><td>A tappable button at <code>x, y</code> sized <code>w × h</code>; <code>fn</code> is called with no arguments.</td></tr>
|
||||
<tr><td>wada.ui.text_w(text [, size])</td><td>Rendered width of <code>text</code> in pixels, for the given size class.</td></tr>
|
||||
<tr><td>wada.ui.text_lines(text, width [, size])</td><td>How many lines <code>text</code> wraps to inside <code>width</code>. Counts explicit newlines too. <b>Use this when you lay out your own rows.</b> <code>label:width()</code> turns wrapping on, so a line longer than the screen becomes two while a fixed <code>y</code> step still advances by one, and the next row is drawn on top of it. This is the single most common way an app's layout goes wrong; it happened to the SDK Test app's channel list.</td></tr>
|
||||
<tr><td>wada.ui.clear()</td><td>Remove every widget from the app's page, leaving the page itself. This is how you build an app with more than one screen: clear, then create the next screen's widgets in the same place you built the first. <b>Any handle you still hold from before the clear is dead</b> — calling a method on it does nothing rather than crashing, but it will not come back, so re-create and re-assign. Older firmware has no <code>ui.clear</code>: write <code>local clear = ui.clear or function() end</code> if you need to run on both.</td></tr>
|
||||
<tr><td>wada.ui.list(x, y, w, h)</td><td>A scrollable list of selectable rows — the "pick one of N" widget. Methods: <code>add(text [, fn])</code> returning the row index, <code>set(i, text)</code>, <code>color(i, c)</code>, <code>select(i)</code> (highlights <i>and</i> scrolls it into view), <code>selected()</code>, <code>count()</code>, <code>clear()</code>, <code>pos(x, y)</code>. Rows are real buttons, so keyboard and trackball navigation walks them on touchless boards without you doing anything.</td></tr>
|
||||
<tr><td>wada.ui.input(title, initial, cb)</td><td>A modal text field. <code>cb(text)</code> on OK, <code>cb(nil)</code> on cancel or on the dialog being closed any other way — exactly one call, always, so an app that disabled itself while waiting always gets to re-enable. Uses the firmware's own dialog and keyboard, so it behaves identically on a touchscreen, on the Tanmatsu's physical keyboard and on a trackball board. One prompt at a time.</td></tr>
|
||||
<tr><td>wada.ui.canvas(w, h)</td><td>A drawing surface. See the canvas methods below.</td></tr>
|
||||
@@ -291,7 +293,8 @@ return app</code></pre>
|
||||
<h2>wada.mesh</h2>
|
||||
<div class="row"><div class="prose">
|
||||
<table class="api">
|
||||
<tr><td>wada.mesh.contacts()</td><td>Array of <code>{name, pubkey, type, ago_s, lat, lon, lat_e6, lon_e6}</code>. <code>pubkey</code> is the first 4 bytes as 8 hex characters — the same short form the rest of the interface uses, and what lines a contact up with a discovery hit.</td></tr>
|
||||
<tr><td>wada.mesh.contacts([offset], [limit])</td><td>Array of <code>{name, pubkey, type, ago_s, lat, lon, lat_e6, lon_e6}</code>. <code>pubkey</code> is the first 4 bytes as 8 hex characters — the same short form the rest of the interface uses, and what lines a contact up with a discovery hit. Returns at most <code>limit</code> entries (default 100, maximum 250) starting at <code>offset</code>, because every entry is an eight-field table and building thousands in one call would exhaust the app heap. On a device with more contacts than that, page through them: ask for 100, then 100 from offset 100, and so on.</td></tr>
|
||||
<tr><td>wada.mesh.contact_count()</td><td>How many contacts the device holds, so you can page through <code>contacts()</code> without calling it repeatedly to find where the list ends.</td></tr>
|
||||
<tr><td>wada.mesh.rx_log()</td><td>Recent packets, newest first: <code>{ago_ms, type, rssi, snr, hops, route, len}</code>, plus whatever identity the frame actually carried — see below. The RF Monitor feed.</td></tr>
|
||||
<tr><td>wada.mesh.stats()</td><td><code>{rssi, noise, rx_air_s, tx_air_s, rx_pkts, rx_err, tx_budget_ms, rx_events, rx_dropped, tx_pkts, freq, bw, sf, duty_pct}</code>.</td></tr>
|
||||
<tr><td>wada.mesh.self()</td><td>This node: <code>{name, pubkey, lat, lon, lat_e6, lon_e6}</code>.</td></tr>
|
||||
@@ -409,7 +412,7 @@ end</code></pre>
|
||||
</table>
|
||||
<p><code>http://</code> only, both of them. On-device TLS is not workable at the heap these boards have left once Wi-Fi has associated, so there is no <code>https</code> to offer; put a proxy in front if you need it. One request in flight at a time per app.</p>
|
||||
<p>The fetch is asynchronous and runs on the firmware's existing network worker, so it does not block the UI. Your callback runs on the UI thread once the body is in memory.</p>
|
||||
<div class="note"><p><b>Plain HTTP only, and that is not an oversight.</b> After Wi-Fi associates there is not enough free internal memory on the smaller boards for a TLS handshake — mbedTLS wants around 30 KB and roughly 5 KB is free. If you need an HTTPS source, put a small proxy in front of it, the way the map tiles do. Responses are capped (64 KB by default).</p></div>
|
||||
<div class="note"><p><b>Plain HTTP only, and that is not an oversight.</b> After Wi-Fi associates there is not enough free internal memory on the smaller boards for a TLS handshake — mbedTLS wants around 30 KB and roughly 5 KB is free. If you need an HTTPS source, put a small proxy in front of it, the way the map tiles do. Responses are capped (192 KB by default).</p></div>
|
||||
</div></div>
|
||||
</section>
|
||||
|
||||
@@ -441,6 +444,24 @@ end</code></pre>
|
||||
</div></div>
|
||||
</section>
|
||||
|
||||
<section class="doc" id="audio">
|
||||
<h2>wada.audio <span class="chip">audio</span></h2>
|
||||
<div class="row"><div class="prose">
|
||||
<p>Asynchronous audio playback from the current app's private storage. A plain name such as <code>track.wav</code> resolves inside the same app folder as <a href="#fs">wada.fs</a>, whether that folder lives on internal flash, SD, or SD_MMC. Check <code>wada.sys.caps().audio</code> and <code>audio_wav</code> before using it.</p>
|
||||
<table class="api">
|
||||
<tr><th>Call</th><th>Returns / does</th></tr>
|
||||
<tr><td>wada.audio.play(name)</td><td>Starts or replaces playback and returns <code>true</code>, or <code>nil, error</code>. Plain names use app-private storage. On boards with <code>caps().audio_sd</code>, <code>sd:/Music/track.wav</code> reads directly from the physical card.</td></tr>
|
||||
<tr><td>wada.audio.pause()</td><td>Pauses an active track. Returns whether the command was accepted.</td></tr>
|
||||
<tr><td>wada.audio.resume()</td><td>Resumes a paused track. Returns whether the command was accepted.</td></tr>
|
||||
<tr><td>wada.audio.stop()</td><td>Stops the active track. Returns whether the command was accepted.</td></tr>
|
||||
<tr><td>wada.audio.status()</td><td><code>{state, path, source, format, error}</code>. State is <code>stopped</code>, <code>playing</code>, <code>paused</code>, <code>ended</code>, or <code>error</code>; <code>error</code> is present only after a playback failure.</td></tr>
|
||||
</table>
|
||||
<p>Supported formats are PCM WAV (16-bit mono or stereo at 8–48 kHz) and MPEG Layer III MP3, including CBR, VBR, and ID3-tagged files. Stereo is mixed to mono on the device. Check <code>audio_wav</code> or <code>audio_mp3</code> rather than assuming a format exists on older firmware.</p>
|
||||
<p>Playback follows the user's Sound switch and volume, never blocks the Lua callback, and stops automatically when the app closes. A second <code>play()</code> replaces the current track. Playlist ordering belongs to the app, so there are no ambiguous host-side <code>next()</code> or <code>previous()</code> calls.</p>
|
||||
<p>Plain names follow the same sandbox rules as <code>wada.fs</code>. Explicit <code>sd:</code> paths follow <a href="#sd">wada.sd</a> path validation. Expected errors include <code>bad path</code>, <code>no storage</code>, <code>no sd</code>, <code>not found</code>, <code>busy</code>, <code>muted</code>, and <code>unsupported format</code>.</p>
|
||||
</div></div>
|
||||
</section>
|
||||
|
||||
<section class="doc" id="sd">
|
||||
<h2>wada.sd <span class="chip">sd_list</span></h2>
|
||||
<div class="row"><div class="prose">
|
||||
@@ -466,7 +487,7 @@ end</code></pre>
|
||||
<tr><td>wada.sys.datetime()</td><td><code>{year, month, day, hour, min, sec, wday}</code> in local time. <code>wday</code> is 0 for Sunday.</td></tr>
|
||||
<tr><td>wada.sys.tr(s)</td><td>Translate <code>s</code> through the device's active language, using the same table the firmware's own interface uses. Returns <code>s</code> unchanged when there is no translation, so it is always safe to wrap a string. Add your keys to <code>deploy/apps/lang/<code>.lang</code> alongside the firmware's. Older firmware has no <code>sys.tr</code>: write <code>local tr = sys.tr or function(x) return x end</code> and call <code>tr()</code>.</td></tr>
|
||||
<tr><td>wada.sys.beep()</td><td>Short beep on boards with a buzzer; silent elsewhere, and silent when the user has sound off.</td></tr>
|
||||
<tr><td>wada.sys.caps()</td><td><code>{sdk_ext, keyboard, touch, sd, sd_list, compass, accel, discover, input, rx_identity, list, packets, sensors, map, measure}</code>. Check <code>sdk_ext</code> before using anything marked <span class="chip">ext</span> below, and <code>sd_list</code> before using <code>wada.sd</code>. The rest are feature flags for calls added after the extended SDK first shipped, so an app can degrade on older firmware instead of erroring.</td></tr>
|
||||
<tr><td>wada.sys.caps()</td><td><code>{sdk_ext, keyboard, touch, sd, sd_list, audio, audio_wav, audio_mp3, audio_sd, compass, accel, discover, input, rx_identity, list, packets, sensors, map, measure}</code>. Check <code>sdk_ext</code> before using anything marked <span class="chip">ext</span>, <code>sd_list</code> before using <code>wada.sd</code>, and <code>audio</code> plus the format flag before using <code>wada.audio</code>. <code>audio_sd</code> means direct <code>sd:</code> paths are available; plain audio names use app storage and do not require it.</td></tr>
|
||||
<tr><td>wada.sys.battery() <span class="chip">ext</span></td><td><code>{mv, pct, charging}</code>.</td></tr>
|
||||
<tr><td>wada.sys.env() <span class="chip">sensors</span></td><td><code>{temp_c, humidity, pressure_hpa, alt_m}</code>, or <code>nil</code>. A field is present only when the hardware actually reported it, so you can tell "no humidity sensor" from "0% humidity". Gated on <code>caps().sensors</code> — does this board <i>have</i> the sensor rail — and <b>not</b> on <code>sdk_ext</code>, which is a memory gate. Those are different questions: the plain Heltec V4 has the Expansion Kit but not the extended SDK, and most boards with the extended SDK have no sensors at all.</td></tr>
|
||||
<tr><td>wada.sys.gps() <span class="chip">ext</span></td><td><code>{lat, lon, lat_e6, lon_e6, sats, alt_m, time, speed_kmh, course}</code>, or <code>nil</code> with no fix — which is the normal indoor case, so handle it. <code>lat_e6</code>/<code>lon_e6</code> are exact micro-degrees; <code>lat</code>/<code>lon</code> are single-precision floats, so <a href="#discover">log the integers</a>. <code>alt_m</code> is metres. <code>time</code> is satellite time, absent until the receiver has decoded the date. <code>speed_kmh</code> and <code>course</code> (degrees clockwise from north) appear only on boards whose GPS provider reports them, and <code>course</code> only while actually moving — a stationary receiver has no course, so it is absent rather than 0. Treat those last three as optional.</td></tr>
|
||||
@@ -587,7 +608,7 @@ end</code></pre>
|
||||
<div class="row"><div class="prose">
|
||||
<p>Apps run in a restricted environment. These are removed: <code>io</code>, <code>os</code>, <code>require</code>, <code>dofile</code>, and loading new chunks at runtime. Available: <code>math</code>, <code>string</code>, <code>table</code>, and the usual <code>pairs</code>, <code>ipairs</code>, <code>select</code>, <code>pcall</code>, <code>tostring</code>, <code>tonumber</code>.</p>
|
||||
<p>Memory comes from a capped pool in PSRAM, so an app that allocates without bound fails its own allocation rather than starving the radio or the UI. There is an <a href="#budget">instruction budget</a> on every callback for the same reason.</p>
|
||||
<p><b>The extended calls are not on every board.</b> Anything marked <span class="chip">ext</span> above — <code>wada.fs</code>, <code>wada.mesh.send</code>, <code>wada.mesh.send_dm</code>, <code>wada.mesh.discover</code>, <code>sys.battery</code>, <code>sys.gps</code> — needs a board with the memory to carry it, so the Heltec V4 keeps its RAM for the mesh instead. Call <code>wada.sys.caps().sdk_ext</code> and degrade gracefully rather than assuming. <code>wada.sd</code> additionally needs a physical card interface, reported by <code>caps().sd_list</code>.</p>
|
||||
<p><b>The extended calls are not on every board.</b> Anything marked <span class="chip">ext</span> above — <code>wada.fs</code>, <code>wada.mesh.send</code>, <code>wada.mesh.send_dm</code>, <code>wada.mesh.discover</code>, <code>sys.battery</code>, <code>sys.gps</code> — needs a board with the memory to carry it, so the Heltec V4 keeps its RAM for the mesh instead. Call <code>wada.sys.caps().sdk_ext</code> and degrade gracefully rather than assuming. <code>wada.sd</code> additionally needs a physical card interface, reported by <code>caps().sd_list</code>. <code>wada.audio</code> instead follows <code>caps().audio</code>: storage may be internal, but the device still needs a stream-capable speaker path.</p>
|
||||
<p>Rate limits are part of the contract, not a rainy-day guard: <code>wada.fs</code> writes are about one per second with a 32 KB file cap, and <code>wada.mesh.send</code> has a 5 second floor and a 180-character limit. They return <code>false, "too fast"</code> rather than throwing, and hitting them is expected — handle it.</p>
|
||||
<p>None of this makes a hostile app safe, which is why the catalog is curated. It makes an <i>honest</i> app that has a bug survivable: it gets closed, and the device keeps carrying traffic.</p>
|
||||
</div></div>
|
||||
|
||||
@@ -61,6 +61,13 @@
|
||||
#define LV_FONT_MONTSERRAT_20 1
|
||||
#define LV_FONT_MONTSERRAT_24 1
|
||||
#endif
|
||||
/* T-Deck-only, unrelated to the Large/Huge UI-scale block above: an experiment
|
||||
* to shrink the "at a glance" notification's message body from 28px to 20px
|
||||
* on this board only (see atGlanceEnsureFont() in UITask.cpp). Easy to revert
|
||||
* by dropping this block + the T-Deck branch in atGlanceEnsureFont(). */
|
||||
#if defined(HAS_TDECK_GT911)
|
||||
#define LV_FONT_MONTSERRAT_20 1
|
||||
#endif
|
||||
/* 28 px Montserrat for the boot splash title — keeps the rest of the UI on
|
||||
* the smaller fonts so the .data cost stays modest. */
|
||||
#define LV_FONT_MONTSERRAT_28 1
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
// M9-specific LVGL config wrapper.
|
||||
//
|
||||
// Why this exists:
|
||||
// - LVGL 8.4's lv_hal_indev.h unconditionally defines LV_INDEV_DEF_SCROLL_*.
|
||||
// - Our shared lv_conf defines LV_INDEV_DEF_SCROLL_LIMIT for touch tuning, and
|
||||
// M9's build flags define LV_INDEV_DEF_SCROLL_THROW. That combination emits
|
||||
// macro-redefinition warnings, even though M9 has no touch input path.
|
||||
//
|
||||
// For M9, include the shared project LV config, then drop touch-scroll tuning
|
||||
// macros so LVGL's keypad-only defaults can apply without warnings.
|
||||
#ifndef LV_CONF_H
|
||||
#define LV_CONF_H
|
||||
#endif
|
||||
|
||||
#include "lv_conf.h"
|
||||
|
||||
#ifdef LV_INDEV_DEF_SCROLL_LIMIT
|
||||
#undef LV_INDEV_DEF_SCROLL_LIMIT
|
||||
#endif
|
||||
|
||||
#ifdef LV_INDEV_DEF_SCROLL_THROW
|
||||
#undef LV_INDEV_DEF_SCROLL_THROW
|
||||
#endif
|
||||
@@ -0,0 +1,117 @@
|
||||
CC0 1.0 Universal
|
||||
|
||||
Statement of Purpose
|
||||
|
||||
The laws of most jurisdictions throughout the world automatically confer
|
||||
exclusive Copyright and Related Rights (defined below) upon the creator and
|
||||
subsequent owner(s) (each and all, an "owner") of an original work of
|
||||
authorship and/or a database (each, a "Work").
|
||||
|
||||
Certain owners wish to permanently relinquish those rights to a Work for the
|
||||
purpose of contributing to a commons of creative, cultural and scientific
|
||||
works ("Commons") that the public can reliably and without fear of later
|
||||
claims of infringement build upon, modify, incorporate in other works, reuse
|
||||
and redistribute as freely as possible in any form whatsoever and for any
|
||||
purposes, including without limitation commercial purposes. These owners may
|
||||
contribute to the Commons to promote the ideal of a free culture and the
|
||||
further production of creative, cultural and scientific works, or to gain
|
||||
reputation or greater distribution for their Work in part through the use and
|
||||
efforts of others.
|
||||
|
||||
For these and/or other purposes and motivations, and without any expectation
|
||||
of additional consideration or compensation, the person associating CC0 with a
|
||||
Work (the "Affirmer"), to the extent that he or she is an owner of Copyright
|
||||
and Related Rights in the Work, voluntarily elects to apply CC0 to the Work
|
||||
and publicly distribute the Work under its terms, with knowledge of his or her
|
||||
Copyright and Related Rights in the Work and the meaning and intended legal
|
||||
effect of CC0 on those rights.
|
||||
|
||||
1. Copyright and Related Rights. A Work made available under CC0 may be
|
||||
protected by copyright and related or neighboring rights ("Copyright and
|
||||
Related Rights"). Copyright and Related Rights include, but are not limited
|
||||
to, the following:
|
||||
|
||||
i. the right to reproduce, adapt, distribute, perform, display, communicate,
|
||||
and translate a Work;
|
||||
|
||||
ii. moral rights retained by the original author(s) and/or performer(s);
|
||||
|
||||
iii. publicity and privacy rights pertaining to a person's image or likeness
|
||||
depicted in a Work;
|
||||
|
||||
iv. rights protecting against unfair competition in regards to a Work,
|
||||
subject to the limitations in paragraph 4(a), below;
|
||||
|
||||
v. rights protecting the extraction, dissemination, use and reuse of data in
|
||||
a Work;
|
||||
|
||||
vi. database rights (such as those arising under Directive 96/9/EC of the
|
||||
European Parliament and of the Council of 11 March 1996 on the legal
|
||||
protection of databases, and under any national implementation thereof,
|
||||
including any amended or successor version of such directive); and
|
||||
|
||||
vii. other similar, equivalent or corresponding rights throughout the world
|
||||
based on applicable law or treaty, and any national implementations thereof.
|
||||
|
||||
2. Waiver. To the greatest extent permitted by, but not in contravention of,
|
||||
applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and
|
||||
unconditionally waives, abandons, and surrenders all of Affirmer's Copyright
|
||||
and Related Rights and associated claims and causes of action, whether now
|
||||
known or unknown (including existing as well as future claims and causes of
|
||||
action), in the Work (i) in all territories worldwide, (ii) for the maximum
|
||||
duration provided by applicable law or treaty (including future time
|
||||
extensions), (iii) in any current or future medium and for any number of
|
||||
copies, and (iv) for any purpose whatsoever, including without limitation
|
||||
commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes
|
||||
the Waiver for the benefit of each member of the public at large and to the
|
||||
detriment of Affirmer's heirs and successors, fully intending that such Waiver
|
||||
shall not be subject to revocation, rescission, cancellation, termination, or
|
||||
any other legal or equitable action to disrupt the quiet enjoyment of the Work
|
||||
by the public as contemplated by Affirmer's express Statement of Purpose.
|
||||
|
||||
3. Public License Fallback. Should any part of the Waiver for any reason be
|
||||
judged legally invalid or ineffective under applicable law, then the Waiver
|
||||
shall be preserved to the maximum extent permitted taking into account
|
||||
Affirmer's express Statement of Purpose. In addition, to the extent the Waiver
|
||||
is so judged Affirmer hereby grants to each affected person a royalty-free,
|
||||
non transferable, non sublicensable, non exclusive, irrevocable and
|
||||
unconditional license to exercise Affirmer's Copyright and Related Rights in
|
||||
the Work (i) in all territories worldwide, (ii) for the maximum duration
|
||||
provided by applicable law or treaty (including future time extensions), (iii)
|
||||
in any current or future medium and for any number of copies, and (iv) for any
|
||||
purpose whatsoever, including without limitation commercial, advertising or
|
||||
promotional purposes (the "License"). The License shall be deemed effective as
|
||||
of the date CC0 was applied by Affirmer to the Work. Should any part of the
|
||||
License for any reason be judged legally invalid or ineffective under
|
||||
applicable law, such partial invalidity or ineffectiveness shall not
|
||||
invalidate the remainder of the License, and in such case Affirmer hereby
|
||||
affirms that he or she will not (i) exercise any of his or her remaining
|
||||
Copyright and Related Rights in the Work or (ii) assert any associated claims
|
||||
and causes of action with respect to the Work, in either case contrary to
|
||||
Affirmer's express Statement of Purpose.
|
||||
|
||||
4. Limitations and Disclaimers.
|
||||
|
||||
a. No trademark or patent rights held by Affirmer are waived, abandoned,
|
||||
surrendered, licensed or otherwise affected by this document.
|
||||
|
||||
b. Affirmer offers the Work as-is and makes no representations or warranties
|
||||
of any kind concerning the Work, express, implied, statutory or otherwise,
|
||||
including without limitation warranties of title, merchantability, fitness
|
||||
for a particular purpose, non infringement, or the absence of latent or
|
||||
other defects, accuracy, or the present or absence of errors, whether or not
|
||||
discoverable, all to the greatest extent permissible under applicable law.
|
||||
|
||||
c. Affirmer disclaims responsibility for clearing rights of other persons
|
||||
that may apply to the Work or any use thereof, including without limitation
|
||||
any person's Copyright and Related Rights in the Work. Further, Affirmer
|
||||
disclaims responsibility for obtaining any necessary consents, permissions
|
||||
or other rights required for any use of the Work.
|
||||
|
||||
d. Affirmer understands and acknowledges that Creative Commons is not a
|
||||
party to this document and has no duty or obligation with respect to this
|
||||
CC0 or use of the Work.
|
||||
|
||||
For more information, please see
|
||||
<http://creativecommons.org/publicdomain/zero/1.0/>
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
# minimp3
|
||||
|
||||
This directory vendors `minimp3.h` from
|
||||
[lieff/minimp3](https://github.com/lieff/minimp3) at commit
|
||||
`ea99364f61c14656440e8d77e9c233ccf3124633` under CC0-1.0.
|
||||
|
||||
Pinned upstream and local file hashes:
|
||||
|
||||
- upstream `minimp3.h`: `57e437c5c1f0e8b243885d3929c8973b5e6c778451e0100ab4251d19915cb3ad`
|
||||
- local `minimp3.h` with the hook below: `3f59a7f29de636ca0db0aab8a5b86739c43ac8bf9ac902010e7220f783c0ae0a`
|
||||
- `LICENSE`: `6a1ee543e5282cd9061881edf462e6fdab181f328da71fc2c9a6950a80e94d01`
|
||||
|
||||
Wadamesh adds one opt-in `MINIMP3_SCRATCH` hook around the decoder's local
|
||||
scratch object. The Lua audio worker uses it to place the roughly 16 KB decoder
|
||||
workspace in PSRAM instead of its FreeRTOS task stack. Builds that do not define
|
||||
the hook retain upstream behavior.
|
||||
File diff suppressed because it is too large
Load Diff
+16
-4
@@ -613,6 +613,15 @@ build_flags =
|
||||
-D HAS_TDECK_GT911=1
|
||||
-D HAS_TDECK_TRACKBALL=1
|
||||
-D HAS_TDECK_KEYBOARD=1
|
||||
; Forced here (not just in lv_conf.h) because .pio/libdeps/<env>/MeshCore/include/lv_conf.h
|
||||
; -- a stale snapshot vendored by the pinned core tag -- sits earlier in the -I search
|
||||
; order than this repo's own include/lv_conf.h, so LV_CONF_INCLUDE_SIMPLE's quoted
|
||||
; #include "lv_conf.h" silently resolves to that stale copy for src/ compiles (though
|
||||
; library .c files, e.g. lvgl's own, still see the right one -- inconsistent per TU). A
|
||||
; -D here is immune to that: it's set before any header is processed and isn't
|
||||
; overridden by a plain #define. Only 20px is forced, for the "at a glance" experiment
|
||||
; below (atGlanceEnsureFont() in UITask.cpp) -- not 18/24, which this board doesn't use.
|
||||
-D LV_FONT_MONTSERRAT_20=1
|
||||
-D ENABLE_ADVERT_ON_BOOT=0
|
||||
-D PIN_TB_UP=15
|
||||
-D PIN_TB_DOWN=3
|
||||
@@ -1143,13 +1152,17 @@ board_build.partitions = variants/thinknode_m9/partitions_m9_touch.csv
|
||||
; DriveDiosInSleepMode (0x012A, FW 0x0308+) with CMD_PERR -> begin() = -706.
|
||||
; The pre-script makes RadioLib's config() skip it on old FW (idempotent,
|
||||
; per-env libdeps copy). See scripts/build/patch_radiolib_lr11x0.py.
|
||||
extra_scripts = pre:scripts/build/pre_gen_baked.py, pre:scripts/inject_wifi_env.py, pre:scripts/build/patch_radiolib_lr11x0.py, merge-bin.py
|
||||
extra_scripts = pre:scripts/build/pre_gen_baked.py, pre:scripts/inject_wifi_env.py, pre:scripts/build/patch_radiolib_lr11x0.py, pre:scripts/build/patch_meshcore_rv3028_define.py, merge-bin.py
|
||||
|
||||
build_unflags =
|
||||
-std=gnu++11
|
||||
-std=gnu++14
|
||||
|
||||
build_flags =
|
||||
; 1.17 core: ESP32Board.cpp includes <target.h> -> MicroNMEALocationProvider.h (header-only
|
||||
; provider), so MicroNMEA never chains into the core lib's LDF scope -- hand it the path.
|
||||
-I"${platformio.libdeps_dir}/${this.__env__}/MicroNMEA/src"
|
||||
-Wno-deprecated-declarations -Wno-unused-parameter -DNDEBUG -DRADIOLIB_STATIC_ONLY=1 -DRADIOLIB_GODMODE=1
|
||||
-Wno-deprecated-declarations -Wno-unused-parameter -Wno-cpp -Wno-overflow -Wno-return-type -std=gnu++17 -DNDEBUG -DRADIOLIB_STATIC_ONLY=1 -DRADIOLIB_GODMODE=1
|
||||
-D MC_VENDORED_TOUCH_APP
|
||||
-D LORA_FREQ=869.618
|
||||
-D LORA_BW=62.5
|
||||
@@ -1331,9 +1344,8 @@ build_flags =
|
||||
-D HAS_TOUCH_UI=1
|
||||
-D FIRMWARE_OTA_ENV='"ThinkNode_M9_companion_radio_touch"'
|
||||
-D ENABLE_ADVERT_ON_BOOT=0
|
||||
-D LV_CONF_PATH=lv_conf.h
|
||||
-D LV_CONF_PATH=lv_conf_m9.h
|
||||
-D LV_CONF_INCLUDE_SIMPLE=1
|
||||
-D LV_INDEV_DEF_SCROLL_THROW=7
|
||||
-D MAX_CONTACTS=2000
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D DISPLAY_CLASS=ST7789LCDDisplay
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
# beta_70 - test build. Stable stays beta_65.
|
||||
# One user-facing note per non-blank, non-# line; # lines are section comments.
|
||||
|
||||
# --- The crash on the regions screen ------------------------------------------------------
|
||||
Adding or removing known regions and then leaving that screen crashed the device. If you have been unable to get out of Radio & mesh without resetting, this was why.
|
||||
The cause was general, not specific to regions: when a text field is destroyed, the interface library sends it one last "you lost focus" event, and seven fields in the firmware save their value on losing focus. So a field was reading and rewriting itself from inside its own destructor. All seven now ignore a blur that is really a deletion.
|
||||
Found by decoding the core dump attached to the report, against a rebuilt beta_69 image. Thank you for sending it.
|
||||
|
||||
# --- Translations: undoing damage we caused ------------------------------------------------
|
||||
Language file v18 contained keys that do not exist anywhere in the firmware. Entries like "Paste (move)Paste (copy)" and "Unblock Block" were two separate buttons glued into one, and one entry was a Dutch phrase that appears only inside a source-code comment.
|
||||
That was a bug in the tool that decides which text belongs in a language file, introduced in the last release. It has been fixed twice over: it no longer joins the two halves of a choice into one entry, and it no longer reads text out of comments.
|
||||
202 invented entries are gone from all thirteen files. Nothing a translator had actually translated was touched.
|
||||
The curly quotes in the Hungarian map credits showed as empty boxes because no bundled font contains them. Replaced with straight quotes.
|
||||
Five more strings are translatable: the Wi-Fi rescan and hidden-network rows, the update check, and the downgrade prompt.
|
||||
Language files are at v19. v18 is withdrawn.
|
||||
|
||||
# --- The Store stops offering apps your board cannot run -----------------------------------
|
||||
Wardrive and Nearby need the extended SDK, which the Heltec V4 does not have. The Store offered both anyway, so the most common board in the mesh could download, install and open two apps that then refused to work, with no warning. Apps can now say what they need, and the Store says so on the row instead.
|
||||
|
||||
# --- Community work, by Michael A. Cojocari (@oumike) ---------------------------------------
|
||||
Lua apps can play WAV and MP3 audio, from the SD card or from memory.
|
||||
@-mention autocomplete in the device chat and the web interface, suggesting people whose adverts this device actually heard rather than everyone in your contacts.
|
||||
Symbol and alt keys latch: tap once for the next key, tap twice to lock the layer until you tap again. No more holding a modifier down.
|
||||
The web reader opens a home page from the SD card if you put one there, so a device with no internet still has somewhere to start.
|
||||
Wardrive no longer stops when a node name contains an emoji. Names are cut on character boundaries now, and text coming from an app is checked before it reaches the screen.
|
||||
Screenshots from the Control Center on the ThinkNode M9 and both T-Lora Pagers, with a timer for immediate, 3-second or 10-second capture.
|
||||
The M9's Home key works while you are typing in a text field, and the built-in Snake starts from the first direction you press.
|
||||
Tapping a chat composer that is already focused reopens the on-screen keyboard instead of doing nothing.
|
||||
|
||||
# --- Community work, by Tesso M Costa (@codemonkeybr) --------------------------------------
|
||||
A new "At a glance" notification style: when a message arrives while the screen is dimmed, it shows a short preview without fully waking the device or lighting the keyboard. Optional, and it can be limited to when the device is unlocked.
|
||||
|
||||
# --- Also fixed -----------------------------------------------------------------------------
|
||||
Granting an app permission could silently take a permission away from a different app. The switches packed the app's position and the permission into too few bits, so "Send discovery probes" wrote the wrong app's record, marking an app you never touched as refused. That is why apps reported "permission denied" with every permission granted.
|
||||
|
||||
# --- For testers ------------------------------------------------------------------------------
|
||||
The regions crash is the fix most worth confirming: add and remove a few known regions, leave the screen, and check the device stays up.
|
||||
If you translate, your language file is clean again but has more untranslated entries than before, because strings that were previously invisible to the checker are now listed.
|
||||
Console mode is still an early preview and has not changed this release.
|
||||
@@ -0,0 +1,14 @@
|
||||
# beta_71 - test build. Stable stays beta_65.
|
||||
# One user-facing note per non-blank, non-# line; # lines are section comments.
|
||||
|
||||
# --- For people writing Lua apps ------------------------------------------------------------
|
||||
Apps can clear their page. There was no way to remove the widgets an app had already created, so anything with a second screen drew it on top of the first. wada.ui.clear() takes the page back to empty, and any widget you were still holding from before the clear becomes inert rather than dangerous.
|
||||
Apps can reach all your contacts. The list stopped at 100, and it only ever looked at the first 200 in the first place, so on a device with a lot of contacts most of them were unreachable and nothing said so. wada.mesh.contacts() now takes an offset and a limit, and wada.mesh.contact_count() tells an app how many there are to page through.
|
||||
Apps can be three times larger. The limit was 64 KB, which an ordinary program can reach. It is 192 KB now.
|
||||
|
||||
# --- T-Deck keyboard ---------------------------------------------------------------------------
|
||||
Modifier latching could fail to turn on even with the right keyboard firmware installed. The firmware asked the keyboard what it was capable of exactly once, on the first read after boot, and the first read after boot is the one most likely to come back garbled because the keyboard controller is still starting. One bad answer meant no latching for the rest of the session, and rebooting simply asked again at the same unlucky moment. It now keeps asking for a second and a half before giving up, and says what it actually saw when it does.
|
||||
|
||||
# --- For testers ---------------------------------------------------------------------------------
|
||||
This is a small drop on top of beta_70, which carried the fix for the crash when leaving the known-regions screen. If you have not tried that yet, it is still the thing most worth confirming: add and remove a few regions, leave the screen, check the device stays up.
|
||||
If your screen is not going to sleep, we are still investigating (issue 333) and it is not fixed here.
|
||||
@@ -0,0 +1,104 @@
|
||||
-- Manual WAV/MP3 transport test for wada.audio.
|
||||
-- SD test file: /Music/test.mp3
|
||||
-- App-local file: /apps/audio_test.d/test.mp3 on the active app storage.
|
||||
local ui, sys, timer = wada.ui, wada.sys, wada.timer
|
||||
local audio = wada.audio
|
||||
local C = ui.colors
|
||||
|
||||
local SD_PATH = "sd:/Music/test.mp3"
|
||||
local APP_PATH = "test.mp3"
|
||||
|
||||
local app = {}
|
||||
local source = APP_PATH
|
||||
local source_line, state_line, detail_line, command_line
|
||||
|
||||
local function show_command(ok, err)
|
||||
command_line:set(ok and "Command: accepted" or ("Command: " .. tostring(err or "rejected")))
|
||||
command_line:color(ok and C.good or C.bad)
|
||||
end
|
||||
|
||||
local function refresh()
|
||||
if not audio then return end
|
||||
local status = audio.status()
|
||||
state_line:set("State: " .. tostring(status.state))
|
||||
state_line:color(status.state == "error" and C.bad or
|
||||
status.state == "playing" and C.good or C.text)
|
||||
local detail = "Source: " .. tostring(status.source or "-") ..
|
||||
" Format: " .. tostring(status.format or "-")
|
||||
if status.error then detail = detail .. " Error: " .. tostring(status.error) end
|
||||
detail_line:set(detail)
|
||||
detail_line:color(status.error and C.bad or C.sub)
|
||||
end
|
||||
|
||||
function app.on_open(w, h)
|
||||
local caps = sys.caps()
|
||||
source = APP_PATH
|
||||
if caps.audio_sd and wada.sd and wada.sd.list then
|
||||
local entries = wada.sd.list("/")
|
||||
if entries then source = SD_PATH end
|
||||
end
|
||||
|
||||
ui.label("Audio playback test", 6, 4, 14, C.accent)
|
||||
ui.label("audio=" .. tostring(caps.audio) ..
|
||||
" wav=" .. tostring(caps.audio_wav) ..
|
||||
" mp3=" .. tostring(caps.audio_mp3), 6, 24, 12, C.sub)
|
||||
source_line = ui.label("File: " .. source, 6, 44, 12, C.text)
|
||||
source_line:width(w - 12)
|
||||
state_line = ui.label("State: stopped", 6, 64, 12, C.text)
|
||||
detail_line = ui.label("Source: - Format: -", 6, 82, 12, C.sub)
|
||||
detail_line:width(w - 12)
|
||||
command_line = ui.label("Command: ready", 6, 100, 12, C.sub)
|
||||
command_line:width(w - 12)
|
||||
|
||||
if not caps.audio or not audio then
|
||||
command_line:set("Command: wada.audio unavailable")
|
||||
command_line:color(C.bad)
|
||||
return
|
||||
end
|
||||
|
||||
local gap = 5
|
||||
local button_w = math.max(54, (w - 12 - gap * 3) // 4)
|
||||
local x = 6
|
||||
ui.button("Play", x, 116, button_w, 30, function()
|
||||
local ok, err = audio.play(source)
|
||||
show_command(ok, err)
|
||||
refresh()
|
||||
end)
|
||||
x = x + button_w + gap
|
||||
ui.button("Pause", x, 116, button_w, 30, function()
|
||||
show_command(audio.pause())
|
||||
refresh()
|
||||
end)
|
||||
x = x + button_w + gap
|
||||
ui.button("Resume", x, 116, button_w, 30, function()
|
||||
show_command(audio.resume())
|
||||
refresh()
|
||||
end)
|
||||
x = x + button_w + gap
|
||||
ui.button("Stop", x, 116, button_w, 30, function()
|
||||
show_command(audio.stop())
|
||||
refresh()
|
||||
end)
|
||||
|
||||
if caps.audio_sd then
|
||||
ui.button("Switch SD / app storage", 6, 150, math.min(w - 12, 190), 26, function()
|
||||
source = source == SD_PATH and APP_PATH or SD_PATH
|
||||
source_line:set("File: " .. source)
|
||||
command_line:set("Command: source changed")
|
||||
command_line:color(C.sub)
|
||||
end)
|
||||
end
|
||||
|
||||
timer.every(200)
|
||||
refresh()
|
||||
end
|
||||
|
||||
function app.on_tick()
|
||||
refresh()
|
||||
end
|
||||
|
||||
function app.on_close()
|
||||
if audio then audio.stop() end
|
||||
end
|
||||
|
||||
return app
|
||||
+73
-15
@@ -31,7 +31,7 @@ def source_keys():
|
||||
keys = set()
|
||||
for f in glob.glob(os.path.join(ROOT, 'src/ui-touch/*.cpp')) + \
|
||||
glob.glob(os.path.join(ROOT, 'src/ui-touch/*.h')):
|
||||
src = open(f, encoding='utf-8').read()
|
||||
src = strip_comments(open(f, encoding='utf-8').read())
|
||||
# An optional LV_SYMBOL_* macro may precede the literal: TR(LV_SYMBOL_OK " Text").
|
||||
# Requiring the group to START with a quote silently skipped every such
|
||||
# call - the audit's worst blind spot (the sheet rows never got checked).
|
||||
@@ -55,7 +55,7 @@ def source_keys():
|
||||
# collecting keeps a translation alive, under-collecting deletes one.
|
||||
for f in glob.glob(os.path.join(ROOT, 'src/ui-touch/*.cpp')) + \
|
||||
glob.glob(os.path.join(ROOT, 'src/ui-touch/*.h')):
|
||||
src = open(f, encoding='utf-8').read()
|
||||
src = strip_comments(open(f, encoding='utf-8').read())
|
||||
for name in set(re.findall(r'TR\(\s*([A-Za-z_][A-Za-z0-9_]*)\s*\[', src)):
|
||||
for m in re.finditer(r'\b' + re.escape(name) + r'\s*\[[^\]]*\]\s*=\s*\{', src):
|
||||
i, depth = m.end(), 1
|
||||
@@ -80,24 +80,22 @@ def source_keys():
|
||||
# parameters and note which parameter, then pull the literal out of that
|
||||
# argument position at every call site.
|
||||
for f in _sources():
|
||||
src = open(f, encoding='utf-8').read()
|
||||
src = strip_comments(open(f, encoding='utf-8').read())
|
||||
for name, idx in _tr_wrapping_helpers(src):
|
||||
for arg in _call_args_at(src, name, idx):
|
||||
parts = re.findall(r'"((?:[^"\\]|\\.)*)"', arg)
|
||||
if not parts:
|
||||
continue
|
||||
k = unescape_c(''.join(parts))
|
||||
while k and 0xE000 <= ord(k[0]) <= 0xF8FF:
|
||||
k = k[1:]
|
||||
k = k.lstrip(' ')
|
||||
if k.strip():
|
||||
keys.add(k)
|
||||
for lit in literal_groups(arg):
|
||||
k = unescape_c(lit)
|
||||
while k and 0xE000 <= ord(k[0]) <= 0xF8FF:
|
||||
k = k[1:]
|
||||
k = k.lstrip(' ')
|
||||
if k.strip():
|
||||
keys.add(k)
|
||||
|
||||
# RANGE-FOR over a local table: `struct {...} rows[] = {{"Show contacts", ...}};`
|
||||
# then `for (auto& r : rows) TR(r.label)`. Same blind spot as the helper case --
|
||||
# correct at runtime, invisible to a TR("literal") scan, so untranslatable.
|
||||
for f in _sources():
|
||||
src = open(f, encoding='utf-8').read()
|
||||
src = strip_comments(open(f, encoding='utf-8').read())
|
||||
for m in re.finditer(r'for\s*\(\s*(?:const\s+)?auto\s*&?\s*(\w+)\s*:\s*(\w+)\s*\)', src):
|
||||
var, tbl = m.group(1), m.group(2)
|
||||
brace = src.find('{', m.end())
|
||||
@@ -119,7 +117,7 @@ def source_keys():
|
||||
# TR(someFunc(...)): a helper that RETURNS one of several literals, translated
|
||||
# by the caller. Every literal it can return is a key.
|
||||
for f in _sources():
|
||||
src = open(f, encoding='utf-8').read()
|
||||
src = strip_comments(open(f, encoding='utf-8').read())
|
||||
for name in set(re.findall(r'TR\(\s*([A-Za-z_]\w*)\s*\(', src)):
|
||||
for d in re.finditer(r'\b' + re.escape(name) + r'\s*\([^;{}]*\)\s*\{', src):
|
||||
seg = src[d.end() - 1:_balanced(src, d.end() - 1)]
|
||||
@@ -130,7 +128,7 @@ def source_keys():
|
||||
# LUA APPS: wada.sys.tr("...") (aliased to a local `tr` by convention). The
|
||||
# apps ship from the same catalog and their strings belong in the same files.
|
||||
for f in sorted(glob.glob(os.path.join(ROOT, 'deploy/apps/*/*/*.lua'))):
|
||||
src = open(f, encoding='utf-8').read()
|
||||
src = strip_comments(open(f, encoding='utf-8').read())
|
||||
for lit in re.findall(r'\b(?:sys\.)?tr\(\s*"((?:[^"\\]|\\.)*)"', src):
|
||||
k = unescape_c(lit)
|
||||
if k.strip():
|
||||
@@ -138,6 +136,66 @@ def source_keys():
|
||||
return keys
|
||||
|
||||
|
||||
def strip_comments(src):
|
||||
"""Blank out //... and /*...*/ while preserving string/char literals and line
|
||||
count. The extractor scans raw text, so a quoted phrase inside a comment was
|
||||
being collected as a translatable key -- that is how the Dutch string
|
||||
"Geblokkeerde gebruikers", which only appears in a comment explaining how a
|
||||
long translation degrades, ended up as a KEY in all thirteen files."""
|
||||
out, i, n = [], 0, len(src)
|
||||
while i < n:
|
||||
c = src[i]
|
||||
if c == '"' or c == "'":
|
||||
q = c; out.append(c); i += 1
|
||||
while i < n:
|
||||
out.append(src[i])
|
||||
if src[i] == '\\' and i + 1 < n:
|
||||
out.append(src[i + 1]); i += 2; continue
|
||||
if src[i] == q: i += 1; break
|
||||
i += 1
|
||||
continue
|
||||
if c == '/' and i + 1 < n and src[i + 1] == '/':
|
||||
while i < n and src[i] != '\n': i += 1
|
||||
continue
|
||||
if c == '/' and i + 1 < n and src[i + 1] == '*':
|
||||
i += 2
|
||||
while i + 1 < n and not (src[i] == '*' and src[i + 1] == '/'):
|
||||
if src[i] == '\n': out.append('\n')
|
||||
i += 1
|
||||
i += 2
|
||||
continue
|
||||
out.append(c); i += 1
|
||||
return ''.join(out)
|
||||
|
||||
|
||||
def literal_groups(arg):
|
||||
"""Keys from one call argument.
|
||||
|
||||
A C compiler concatenates ADJACENT string literals, so `"a" "b"` is one
|
||||
string. `cond ? "a" : "b"` is two, and joining them (which this used to do)
|
||||
invented keys that exist nowhere: "Paste (move)Paste (copy)",
|
||||
"Unblock Block", "Unfav Favorite". Those shipped to every translator in
|
||||
v18. Only whitespace and an LV_SYMBOL_* macro may sit between literals of
|
||||
one group; anything else starts a new one."""
|
||||
groups, cur, i, n = [], [], 0, len(arg)
|
||||
while i < n:
|
||||
c = arg[i]
|
||||
if c == '"':
|
||||
j, buf = i + 1, []
|
||||
while j < n:
|
||||
if arg[j] == '\\' and j + 1 < n: buf.append(arg[j:j + 2]); j += 2; continue
|
||||
if arg[j] == '"': break
|
||||
buf.append(arg[j]); j += 1
|
||||
cur.append(''.join(buf)); i = j + 1; continue
|
||||
if c.isspace(): i += 1; continue
|
||||
m = re.match(r'LV_SYMBOL_[A-Z0-9_]+', arg[i:])
|
||||
if m: i += m.end(); continue
|
||||
if cur: groups.append(cur); cur = []
|
||||
i += 1
|
||||
if cur: groups.append(cur)
|
||||
return [''.join(g) for g in groups]
|
||||
|
||||
|
||||
def _sources():
|
||||
return sorted(glob.glob(os.path.join(ROOT, 'src/ui-touch/*.cpp')) +
|
||||
glob.glob(os.path.join(ROOT, 'src/ui-touch/*.h')))
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
Import("env")
|
||||
import os
|
||||
|
||||
MARKER = "wadamesh-rv3028-guard-patch"
|
||||
OLD = "#define RV3028_ADDRESS 0x52"
|
||||
NEW = """#ifndef RV3028_ADDRESS
|
||||
#define RV3028_ADDRESS 0x52
|
||||
#endif // wadamesh-rv3028-guard-patch"""
|
||||
|
||||
path = os.path.join(
|
||||
env.subst("$PROJECT_LIBDEPS_DIR"),
|
||||
env.subst("$PIOENV"),
|
||||
"MeshCore",
|
||||
"src",
|
||||
"helpers",
|
||||
"AutoDiscoverRTCClock.cpp",
|
||||
)
|
||||
|
||||
if not os.path.isfile(path):
|
||||
print("[patch_meshcore_rv3028_define] WARNING: %s not found (libdeps not fetched yet?)" % path)
|
||||
print("[patch_meshcore_rv3028_define] WARNING: MeshCore NOT patched - re-run build once libdeps exist")
|
||||
else:
|
||||
with open(path) as f:
|
||||
src = f.read()
|
||||
|
||||
if MARKER in src:
|
||||
print("[patch_meshcore_rv3028_define] already patched")
|
||||
elif OLD in src:
|
||||
with open(path, "w") as f:
|
||||
f.write(src.replace(OLD, NEW, 1))
|
||||
print("[patch_meshcore_rv3028_define] patched RV3028_ADDRESS guard in AutoDiscoverRTCClock.cpp")
|
||||
else:
|
||||
print("[patch_meshcore_rv3028_define] WARNING: pattern not found - MeshCore version drift?")
|
||||
print("[patch_meshcore_rv3028_define] WARNING: check AutoDiscoverRTCClock.cpp by hand, NOT patched")
|
||||
@@ -5,7 +5,7 @@ it ever touches a device. Not part of the firmware build.
|
||||
|
||||
```sh
|
||||
scripts/lua-harness/run.sh # gpscompass, all scenarios
|
||||
scripts/lua-harness/run.sh deploy/apps/wardrive/1.0/wardrive.lua
|
||||
scripts/lua-harness/run.sh deploy/apps/wardrive/1.1/wardrive.lua
|
||||
SCENARIO=declination scripts/lua-harness/run.sh # one scenario
|
||||
```
|
||||
|
||||
|
||||
@@ -10,6 +10,27 @@ local function checkint(v, what) assert(math.tointeger(v) ~= nil, what .. ": not
|
||||
local function checkcol(v, what) if v ~= nil then checkint(v, what .. " color") end end
|
||||
local function checkstr(v, what) assert(type(v) == "string" or type(v) == "number", what .. ": not a string") end
|
||||
|
||||
local function valid_utf8(text)
|
||||
local i, n = 1, #text
|
||||
while i <= n do
|
||||
local first = text:byte(i)
|
||||
local width = first <= 0x7F and 1
|
||||
or (first >= 0xC2 and first <= 0xDF and 2)
|
||||
or (first >= 0xE0 and first <= 0xEF and 3)
|
||||
or (first >= 0xF0 and first <= 0xF4 and 4) or 0
|
||||
if width == 0 or i + width - 1 > n then return false end
|
||||
for j = 2, width do
|
||||
local byte = text:byte(i + j - 1)
|
||||
if byte < 0x80 or byte > 0xBF then return false end
|
||||
end
|
||||
local second = width > 1 and text:byte(i + 1) or 0
|
||||
if (first == 0xE0 and second < 0xA0) or (first == 0xED and second > 0x9F) or
|
||||
(first == 0xF0 and second < 0x90) or (first == 0xF4 and second > 0x8F) then return false end
|
||||
i = i + width
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
-- instruction budget like guardedCall(): error() out of the hook, pcall catches
|
||||
local function guarded(budget, fn, ...)
|
||||
local count = 0
|
||||
@@ -26,6 +47,13 @@ local cfg = {} -- per-scenario device config
|
||||
local widgets = { canvases = 0, labels = 0, buttons = 0, scroll = false, timer_ms = nil }
|
||||
local drawlog = { text = {}, circles = {}, ops = 0 }
|
||||
local storekv = {}
|
||||
local audio_state = { state = "stopped", path = "", source = "", format = "", error = nil }
|
||||
local audio_log = {}
|
||||
|
||||
local function audio_host_close()
|
||||
audio_state.state = "stopped"
|
||||
audio_log[#audio_log + 1] = "release"
|
||||
end
|
||||
|
||||
local function mkcanvas(w, h)
|
||||
checkint(w, "canvas w"); checkint(h, "canvas h")
|
||||
@@ -77,7 +105,7 @@ local function mkbutton(text, x, y, w, h, fn)
|
||||
end
|
||||
|
||||
local function build_wada()
|
||||
local wada = { ui = {}, sys = {}, mesh = {}, store = {}, timer = {}, net = {} }
|
||||
local wada = { ui = {}, sys = {}, mesh = {}, store = {}, timer = {}, net = {}, fs = {} }
|
||||
wada.ui.colors = { accent = 0x15B6A6, text = 0xE6E9ED, sub = 0x7A7F87, bg = 0x000000, panel = 0x15181B, bad = 0xD7574E, good = 0x53C06B }
|
||||
wada.ui.canvas = mkcanvas
|
||||
wada.ui.label = mklabel
|
||||
@@ -102,7 +130,12 @@ local function build_wada()
|
||||
wada.sys.beep = function() return false end
|
||||
wada.sys.caps = function() return { sdk_ext = cfg.caps.sdk_ext, keyboard = cfg.caps.keyboard,
|
||||
touch = cfg.caps.touch, sd = true, compass = cfg.caps.compass,
|
||||
accel = cfg.caps.accel } end
|
||||
accel = cfg.caps.accel, discover = cfg.caps.discover,
|
||||
sd_list = cfg.caps.sd_list or false,
|
||||
audio = cfg.caps.audio or false,
|
||||
audio_wav = cfg.caps.audio or false,
|
||||
audio_mp3 = cfg.caps.audio or false,
|
||||
audio_sd = cfg.caps.audio_sd or false } end
|
||||
if cfg.caps.sdk_ext then
|
||||
wada.sys.battery = function() return { mv = 3900, pct = 70, charging = false } end
|
||||
wada.sys.gps = function() return cfg.gps and cfg.gps() or nil end
|
||||
@@ -118,6 +151,12 @@ local function build_wada()
|
||||
wada.mesh.self = function() return { name = "me", lat = cfg.self_lat or 0, lon = cfg.self_lon or 0 } end
|
||||
wada.mesh.stats = function() return {} end
|
||||
wada.mesh.rx_log = function() return {} end
|
||||
wada.mesh.discover = function() return "probe-1" end
|
||||
wada.mesh.discovered = function() return cfg.discovered or {} end
|
||||
wada.mesh.discover_clear = function() cfg.discovered = {} end
|
||||
|
||||
wada.fs.append = function(name, data) checkstr(name, "fs.append name"); checkstr(data, "fs.append data"); return true end
|
||||
wada.fs.remove = function(name) checkstr(name, "fs.remove name"); return true end
|
||||
|
||||
wada.store.get = function(k, d) assert(type(k) == "string"); local v = storekv[k]; if v == nil then return d end return v end
|
||||
wada.store.set = function(k, v) assert(type(k) == "string", "store key must be a string")
|
||||
@@ -126,6 +165,56 @@ local function build_wada()
|
||||
|
||||
wada.timer.every = function(ms) checkint(ms, "timer.every"); if ms < 33 then ms = 33 end; widgets.timer_ms = ms end
|
||||
wada.timer.stop = function() widgets.timer_ms = nil end
|
||||
|
||||
if cfg.caps.audio then
|
||||
wada.audio = {}
|
||||
wada.audio.play = function(path)
|
||||
assert(type(path) == "string", "audio.play path must be a string")
|
||||
local source = "app"
|
||||
if path:sub(1, 3) == "sd:" then
|
||||
if not cfg.caps.audio_sd then return nil, "no sd" end
|
||||
local card_path = path:sub(4)
|
||||
if card_path:sub(1, 1) ~= "/" or card_path:find("//", 1, true) or
|
||||
card_path:find("/../", 1, true) or card_path:sub(-3) == "/.." or
|
||||
card_path:sub(-2) == "/." or card_path:sub(-1) == "/" then
|
||||
return nil, "bad path"
|
||||
end
|
||||
source = "sd"
|
||||
elseif #path == 0 or #path > 32 or path:sub(1, 1) == "." or
|
||||
not path:match("^[A-Za-z0-9._-]+$") then
|
||||
return nil, "bad path"
|
||||
end
|
||||
local format = path:lower():sub(-4)
|
||||
if format ~= ".wav" and format ~= ".mp3" then return nil, "unsupported format" end
|
||||
audio_state = { state = "playing", path = path, source = source,
|
||||
format = format:sub(2), error = nil }
|
||||
audio_log[#audio_log + 1] = "play:" .. path
|
||||
return true
|
||||
end
|
||||
wada.audio.pause = function()
|
||||
if audio_state.state ~= "playing" then return false end
|
||||
audio_state.state = "paused"; audio_log[#audio_log + 1] = "pause"; return true
|
||||
end
|
||||
wada.audio.resume = function()
|
||||
if audio_state.state ~= "paused" then return false end
|
||||
audio_state.state = "playing"; audio_log[#audio_log + 1] = "resume"; return true
|
||||
end
|
||||
wada.audio.stop = function()
|
||||
if audio_state.state ~= "playing" and audio_state.state ~= "paused" then return false end
|
||||
audio_state.state = "stopped"; audio_log[#audio_log + 1] = "stop"; return true
|
||||
end
|
||||
wada.audio.status = function()
|
||||
local out = {}
|
||||
for key, value in pairs(audio_state) do out[key] = value end
|
||||
return out
|
||||
end
|
||||
end
|
||||
if cfg.caps.sd_list then
|
||||
wada.sd = { list = function(path)
|
||||
assert(path == "/", "mock SD only exposes the root")
|
||||
return {}
|
||||
end }
|
||||
end
|
||||
return wada
|
||||
end
|
||||
|
||||
@@ -159,6 +248,8 @@ local function reset_world()
|
||||
widgets = { canvases = 0, labels = 0, buttons = 0, scroll = false, timer_ms = nil }
|
||||
labels, buttons, toasts, drawlog = {}, {}, {}, { text = {}, circles = {}, ops = 0 }
|
||||
clock_ms = 1000
|
||||
audio_state = { state = "stopped", path = "", source = "", format = "", error = nil }
|
||||
audio_log = {}
|
||||
end
|
||||
|
||||
-- simulated magnetometer: Earth field 0.45 G at true heading `deg` (device frame ==
|
||||
@@ -204,6 +295,36 @@ local contacts_fixture = {
|
||||
|
||||
local scenarios = {}
|
||||
|
||||
scenarios.wardrive_utf8 = function()
|
||||
cfg = {
|
||||
w = 320, h = 196,
|
||||
caps = { sdk_ext = true, keyboard = true, touch = false, compass = false, accel = false, discover = true },
|
||||
discovered = {
|
||||
{ pubkey = "01020304", name = "Ouderkerk☀️", type = 2,
|
||||
rssi = -72, snr = 7.25, their_snr = 6.5, hops = 0 }
|
||||
}
|
||||
}
|
||||
cfg.gps = function()
|
||||
return { lat = 52.295, lon = 4.907, lat_e6 = 52295000, lon_e6 = 4907000,
|
||||
sats = 9, alt_m = 3, time = 1787620000 }
|
||||
end
|
||||
storekv = {}
|
||||
wada = build_wada()
|
||||
local app = load_app()
|
||||
assert(guarded(BUDGET, app.on_open, cfg.w, cfg.h))
|
||||
assert(buttons[1] and buttons[1].fn, "Wardrive Start button missing")
|
||||
assert(guarded(BUDGET, buttons[1].fn))
|
||||
tick(app, 1, 100)
|
||||
tick(app, 1, 4000)
|
||||
local found = false
|
||||
for _, label in ipairs(labels) do
|
||||
assert(valid_utf8(label.text), "Wardrive rendered invalid UTF-8")
|
||||
if label.text:find("Ouderkerk☀", 1, true) then found = true end
|
||||
end
|
||||
assert(found, "emoji-bearing repeater name was not rendered")
|
||||
if app.on_close then guarded(BUDGET, app.on_close) end
|
||||
end
|
||||
|
||||
-- The declination model, as it actually ships. Rather than testing a copy in
|
||||
-- out/wmm, this pulls the do-block straight out of the app file that gets
|
||||
-- sideloaded, so an inlining mistake fails here instead of on the device.
|
||||
@@ -683,6 +804,61 @@ scenarios.tanmatsu = function()
|
||||
if app.on_close then guarded(BUDGET, app.on_close) end
|
||||
end
|
||||
|
||||
scenarios.audio_api = function()
|
||||
cfg = { w = 320, h = 196,
|
||||
caps = { sdk_ext = true, keyboard = true, touch = false,
|
||||
audio = true, audio_sd = true, sd_list = true } }
|
||||
wada = build_wada()
|
||||
local caps = wada.sys.caps()
|
||||
assert(caps.audio and caps.audio_wav and caps.audio_mp3 and caps.audio_sd,
|
||||
"audio capability flags do not match the WAV/MP3 contract")
|
||||
assert(wada.audio and wada.audio.play and wada.audio.pause and wada.audio.resume and
|
||||
wada.audio.stop and wada.audio.status, "wada.audio API is incomplete")
|
||||
|
||||
assert(wada.audio.play("track.wav"))
|
||||
local status = wada.audio.status()
|
||||
assert(status.state == "playing" and status.path == "track.wav" and
|
||||
status.source == "app" and status.format == "wav", "app-storage play status is wrong")
|
||||
assert(wada.audio.pause() and wada.audio.status().state == "paused", "pause failed")
|
||||
assert(wada.audio.resume() and wada.audio.status().state == "playing", "resume failed")
|
||||
assert(wada.audio.play("next.wav") and wada.audio.status().path == "next.wav",
|
||||
"a second play must replace the active track")
|
||||
assert(wada.audio.play("sd:/Music/card.wav"))
|
||||
assert(wada.audio.status().source == "sd", "sd: source was not reported")
|
||||
|
||||
local value, err = wada.audio.play("../escape.wav")
|
||||
assert(value == nil and err == "bad path", "app sandbox traversal was accepted")
|
||||
value, err = wada.audio.play("sd:/Music/../escape.wav")
|
||||
assert(value == nil and err == "bad path", "SD traversal was accepted")
|
||||
assert(wada.audio.play("track.mp3") and wada.audio.status().format == "mp3",
|
||||
"MP3 playback was not accepted or reported")
|
||||
value, err = wada.audio.play("track.flac")
|
||||
assert(value == nil and err == "unsupported format", "unknown audio format was accepted")
|
||||
|
||||
assert(wada.audio.stop() and wada.audio.status().state == "stopped", "stop failed")
|
||||
assert(wada.audio.play("close.wav"))
|
||||
audio_host_close()
|
||||
assert(wada.audio.status().state == "stopped" and audio_log[#audio_log] == "release",
|
||||
"host close did not release playback")
|
||||
print(" transport + storage sandbox: PASS (" .. #audio_log .. " host calls)")
|
||||
|
||||
if APP_PATH:match("audio_test%.lua$") then
|
||||
local app = load_app()
|
||||
assert(guarded(BUDGET, app.on_open, cfg.w, cfg.h))
|
||||
assert(#buttons == 5, "audio test app did not create all transport/source buttons")
|
||||
buttons[1].fn(); assert(wada.audio.status().state == "playing", "Play button failed")
|
||||
buttons[2].fn(); assert(wada.audio.status().state == "paused", "Pause button failed")
|
||||
buttons[3].fn(); assert(wada.audio.status().state == "playing", "Resume button failed")
|
||||
buttons[4].fn(); assert(wada.audio.status().state == "stopped", "Stop button failed")
|
||||
buttons[5].fn(); buttons[1].fn()
|
||||
assert(wada.audio.status().source == "app", "source switch did not select app storage")
|
||||
guarded(BUDGET, app.on_tick)
|
||||
guarded(BUDGET, app.on_close)
|
||||
assert(wada.audio.status().state == "stopped", "test app close did not stop playback")
|
||||
print(" scripts/audio_test.lua UI: PASS")
|
||||
end
|
||||
end
|
||||
|
||||
-- instruction cost of one heavy tick (redraw forced every tick by spinning the heading)
|
||||
scenarios.cost = function()
|
||||
cfg = { w = 320, h = 196, caps = { sdk_ext = true, keyboard = true, touch = false, compass = true, accel = true },
|
||||
@@ -711,7 +887,9 @@ scenarios.cost = function()
|
||||
assert(worst < BUDGET / 4, "tick too expensive")
|
||||
end
|
||||
|
||||
local order = { "declination", "align_nofix", "bearings_absolute", "m9", "r8", "v4", "pager", "pager_portrait_jumbo", "tanmatsu", "cost" }
|
||||
local order = APP_PATH:find("/wardrive/", 1, true)
|
||||
and { "wardrive_utf8" }
|
||||
or { "declination", "align_nofix", "bearings_absolute", "m9", "r8", "v4", "pager", "pager_portrait_jumbo", "tanmatsu", "audio_api", "cost" }
|
||||
for _, name in ipairs(order) do
|
||||
if SCENARIO == "all" or SCENARIO == name then
|
||||
print("== " .. name)
|
||||
|
||||
+13
-2
@@ -22,6 +22,7 @@ Usage:
|
||||
scripts/sideload_app.py --port /dev/cu.wchusbserial10 deploy/apps/gpscompass/1.0
|
||||
scripts/sideload_app.py --port ... --reboot deploy/apps/gpscompass/1.0/gpscompass.lua
|
||||
scripts/sideload_app.py --port ... --dest /lang deploy/apps/lang/11/de.lang
|
||||
scripts/sideload_app.py --port ... --remote /apps/audio_test.d/test.mp3 ~/Music/test.mp3
|
||||
|
||||
Pass an app VERSION directory to send its <id>.lua + <id>.json, or individual
|
||||
files. --reboot restarts the device afterwards so the drawer/Store rescan
|
||||
@@ -152,6 +153,7 @@ def main():
|
||||
ap.add_argument("--port", required=True)
|
||||
ap.add_argument("--baud", type=int, default=115200)
|
||||
ap.add_argument("--dest", default="/apps", help="/apps (default) or /lang")
|
||||
ap.add_argument("--remote", help="exact remote path for one file, including /apps/<id>.d/<name>")
|
||||
ap.add_argument("--reboot", action="store_true", help="reboot the device afterwards")
|
||||
args = ap.parse_args()
|
||||
|
||||
@@ -170,10 +172,19 @@ def main():
|
||||
else:
|
||||
sys.exit("not found: " + p)
|
||||
|
||||
if args.remote:
|
||||
if len(files) != 1:
|
||||
sys.exit("--remote requires exactly one input file")
|
||||
if not args.remote.startswith(("/apps/", "/lang/")):
|
||||
sys.exit("--remote must begin with /apps/ or /lang/")
|
||||
|
||||
s = open_port(args.port, args.baud)
|
||||
try:
|
||||
for f in files:
|
||||
push(s, f, "%s/%s" % (args.dest.rstrip("/"), os.path.basename(f)))
|
||||
if args.remote:
|
||||
push(s, files[0], args.remote)
|
||||
else:
|
||||
for f in files:
|
||||
push(s, f, "%s/%s" % (args.dest.rstrip("/"), os.path.basename(f)))
|
||||
if args.reboot:
|
||||
s.write(b"reboot\n")
|
||||
s.flush()
|
||||
|
||||
@@ -230,6 +230,13 @@ bool DataStore::mkdirRooted(FILESYSTEM* fs, const char* dir) {
|
||||
return fs->exists(p) || fs->mkdir(p);
|
||||
}
|
||||
|
||||
File DataStore::openWriteRootedFlatSafe(FILESYSTEM* fs, const char* filename) {
|
||||
// Do not pass create=true here. Arduino's VFS implementation interprets it
|
||||
// as "mkdir every parent first", but SPIFFS is flat and returns ENOTSUP for
|
||||
// mkdir while still accepting slash-containing file keys.
|
||||
return fs->open(_rp(filename), "w");
|
||||
}
|
||||
|
||||
bool DataStore::removeRooted(FILESYSTEM* fs, const char* filename) {
|
||||
return fs->remove(_rp(filename));
|
||||
}
|
||||
|
||||
@@ -109,6 +109,7 @@ public:
|
||||
File openAppend(FILESYSTEM* fs, const char* filename); // create if missing
|
||||
bool fileExists(FILESYSTEM* fs, const char* filename);
|
||||
bool mkdirRooted(FILESYSTEM* fs, const char* dir); // true if it exists afterwards
|
||||
File openWriteRootedFlatSafe(FILESYSTEM* fs, const char* filename); // SPIFFS accepts slash keys but no mkdir
|
||||
bool removeRooted(FILESYSTEM* fs, const char* filename);
|
||||
bool renameFile(FILESYSTEM* fs, const char* from, const char* to);
|
||||
#endif
|
||||
|
||||
+46
-13
@@ -5445,7 +5445,7 @@ void MyMesh::checkCLIRescueCmd() {
|
||||
// opens the file, "fadd <off> <len> <sum> <base64>" lines append to it
|
||||
// (self-checking, see cliPutChunk), "fend" closes it.
|
||||
// Same physical-access trust level as "rm" and "erase" above, with a
|
||||
// NARROWER scope: only the two sideload directories the Store writes.
|
||||
// NARROWER scope: Store files plus one app's private <id>.d directory.
|
||||
cliPutBegin(&cli_command[5]);
|
||||
} else if (memcmp(cli_command, "fadd ", 5) == 0) {
|
||||
cliPutChunk(&cli_command[5]);
|
||||
@@ -5470,24 +5470,51 @@ void MyMesh::checkCLIRescueCmd() {
|
||||
// "Error: ...".
|
||||
void MyMesh::cliPutBegin(const char* path) {
|
||||
if (_cli_put) { _cli_put.close(); _cli_put_len = 0; }
|
||||
_cli_put_ended = false;
|
||||
const char* dir = nullptr;
|
||||
if (memcmp(path, "/apps/", 6) == 0) dir = "/apps";
|
||||
else if (memcmp(path, "/lang/", 6) == 0) dir = "/lang";
|
||||
if (!dir) { Serial.println("Error: path must be /apps/<name> or /lang/<name>"); return; }
|
||||
const char* name = path + 6;
|
||||
const size_t nlen = strlen(name);
|
||||
if (nlen == 0 || nlen > 40) { Serial.println("Error: bad file name length"); return; }
|
||||
for (size_t i = 0; i < nlen; i++) {
|
||||
const char c = name[i];
|
||||
const bool ok = (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') ||
|
||||
c == '.' || c == '_' || c == '-';
|
||||
const char* slash = strchr(name, '/');
|
||||
const char* leaf = slash ? slash + 1 : name;
|
||||
const size_t dir_len = slash ? (size_t)(slash - name) : 0;
|
||||
const size_t leaf_len = strlen(leaf);
|
||||
if (!leaf_len || leaf_len > 40 || (slash && leaf_len > 32) ||
|
||||
leaf[0] == '.' || strchr(leaf, '/')) {
|
||||
Serial.println("Error: bad file name");
|
||||
return;
|
||||
}
|
||||
for (size_t i = 0; i < leaf_len; i++) {
|
||||
const char c = leaf[i];
|
||||
const bool ok = (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
|
||||
(c >= '0' && c <= '9') || c == '.' || c == '_' || c == '-';
|
||||
if (!ok) { Serial.println("Error: file name may only use A-Z a-z 0-9 . _ -"); return; }
|
||||
}
|
||||
if (name[0] == '.') { Serial.println("Error: file name may not start with '.'"); return; }
|
||||
|
||||
char app_data_dir[48] = "";
|
||||
if (slash) {
|
||||
// The only nested destination is /apps/<id>.d/<file>, matching wada.fs.
|
||||
if (strcmp(dir, "/apps") != 0 || dir_len < 3 || dir_len > 25 ||
|
||||
name[dir_len - 2] != '.' || name[dir_len - 1] != 'd' || name[0] == '.') {
|
||||
Serial.println("Error: nested path must be /apps/<id>.d/<name>");
|
||||
return;
|
||||
}
|
||||
for (size_t i = 0; i + 2 < dir_len; i++) {
|
||||
const char c = name[i];
|
||||
const bool ok = (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
|
||||
(c >= '0' && c <= '9') || c == '_' || c == '-';
|
||||
if (!ok) { Serial.println("Error: bad app id"); return; }
|
||||
}
|
||||
snprintf(app_data_dir, sizeof app_data_dir, "/apps/%.*s", (int)dir_len, name);
|
||||
}
|
||||
FILESYSTEM* fs = _store->getHotDataFS();
|
||||
if (!fs) { Serial.println("Error: storage not ready"); return; }
|
||||
if (!_store->mkdirRooted(fs, dir)) { Serial.printf("Error: cannot create %s\n", dir); return; }
|
||||
_cli_put = _store->openWrite(fs, path);
|
||||
// Hierarchical filesystems create these directories. SPIFFS returns ENOTSUP
|
||||
// but accepts the complete slash-containing path as a flat file key.
|
||||
_store->mkdirRooted(fs, dir);
|
||||
if (app_data_dir[0]) _store->mkdirRooted(fs, app_data_dir);
|
||||
_cli_put = _store->openWriteRootedFlatSafe(fs, path);
|
||||
if (!_cli_put) { Serial.printf("Error: cannot open %s for writing\n", path); return; }
|
||||
_cli_put_len = 0;
|
||||
Serial.printf("ok fput %s\n", path);
|
||||
@@ -5518,7 +5545,7 @@ void MyMesh::cliPutChunk(const char* args) {
|
||||
if ((s & 0xFFFF) != (sum & 0xFFFF)) { Serial.printf("Error: checksum at %u\n", (unsigned)_cli_put_len); return; }
|
||||
if (olen && _cli_put.write(buf, olen) != olen) {
|
||||
Serial.println("Error: write failed (card full?)");
|
||||
_cli_put.close(); _cli_put_len = 0;
|
||||
_cli_put.close(); _cli_put_len = 0; _cli_put_ended = false;
|
||||
return;
|
||||
}
|
||||
_cli_put_len += olen;
|
||||
@@ -5526,9 +5553,15 @@ void MyMesh::cliPutChunk(const char* args) {
|
||||
}
|
||||
|
||||
void MyMesh::cliPutEnd() {
|
||||
if (!_cli_put) { Serial.println("Error: no file open (fput first)"); return; }
|
||||
if (!_cli_put) {
|
||||
if (_cli_put_ended) Serial.printf("ok fend %u bytes\n", (unsigned)_cli_put_last_len);
|
||||
else Serial.println("Error: no file open (fput first)");
|
||||
return;
|
||||
}
|
||||
_cli_put.close();
|
||||
Serial.printf("ok fend %u bytes\n", (unsigned)_cli_put_len);
|
||||
_cli_put_last_len = _cli_put_len;
|
||||
_cli_put_ended = true;
|
||||
Serial.printf("ok fend %u bytes\n", (unsigned)_cli_put_last_len);
|
||||
_cli_put_len = 0;
|
||||
}
|
||||
#endif // ESP32
|
||||
|
||||
@@ -415,6 +415,17 @@ public:
|
||||
return sendTelemetryRequestForUI(recipient);
|
||||
}
|
||||
|
||||
/** Interactive logins must rediscover the route. MeshCore 1.16 repeaters
|
||||
* answer a direct login by flooding the response without refreshing their
|
||||
* return path; a flooded login returns PATH + LOGIN_OK and refreshes both
|
||||
* sides before the deferred request or first admin command is sent. */
|
||||
void uiResetPathForLogin(ContactInfo& recipient) {
|
||||
if (recipient.out_path_len == OUT_PATH_UNKNOWN) return;
|
||||
uiResetContactPath(recipient.id.pub_key);
|
||||
recipient.out_path_len = OUT_PATH_UNKNOWN;
|
||||
memset(recipient.out_path, 0, sizeof(recipient.out_path));
|
||||
}
|
||||
|
||||
/** Touch-UI manual STATUS/TELEMETRY request that DEFERS the REQ until the
|
||||
* guest LOGIN is acknowledged. The chained helpers above fire LOGIN and REQ
|
||||
* back-to-back, but a repeater drops a PAYLOAD_TYPE_REQ from a sender it
|
||||
@@ -435,6 +446,7 @@ public:
|
||||
return (kind == UiReqKind::Telemetry) ? sendTelemetryRequestForUI(recipient)
|
||||
: sendStatusPingForUI(recipient);
|
||||
}
|
||||
uiResetPathForLogin(recipient);
|
||||
uint32_t login_est = 0;
|
||||
int r = sendLogin(recipient, "", login_est);
|
||||
if (r == MSG_SEND_SENT_FLOOD || r == MSG_SEND_SENT_DIRECT) {
|
||||
@@ -453,6 +465,7 @@ public:
|
||||
* AbstractUITask::onAdminLoginResult so the UI can flip from "logging
|
||||
* in…" to "logged in" (or "failed"). */
|
||||
int uiSendAdminLogin(ContactInfo& recipient, const char* password) {
|
||||
uiResetPathForLogin(recipient);
|
||||
uint32_t est = 0;
|
||||
int r = sendLogin(recipient, password ? password : "", est);
|
||||
if (r == MSG_SEND_SENT_FLOOD || r == MSG_SEND_SENT_DIRECT) {
|
||||
@@ -1296,6 +1309,8 @@ private:
|
||||
// Serial sideload state ("fput" / "fadd" / "fend"): the file being written.
|
||||
File _cli_put;
|
||||
uint32_t _cli_put_len = 0;
|
||||
uint32_t _cli_put_last_len = 0;
|
||||
bool _cli_put_ended = false;
|
||||
void cliPutBegin(const char* path);
|
||||
void cliPutChunk(const char* args);
|
||||
void cliPutEnd();
|
||||
|
||||
@@ -1849,6 +1849,10 @@ bool touchPrefsGetEdgeScroll() { if (!s_begun) touchPrefsBegin(); return s_
|
||||
void touchPrefsSetEdgeScroll(bool on) { if (!s_begun) touchPrefsBegin(); prefsPutUChar("tb_edgesc", on ? 1 : 0); }
|
||||
bool touchPrefsGetLockOnScreenOff() { if (!s_begun) touchPrefsBegin(); return s_prefs.getUChar("lock_off", 0) != 0; }
|
||||
void touchPrefsSetLockOnScreenOff(bool on) { if (!s_begun) touchPrefsBegin(); prefsPutUChar("lock_off", on ? 1 : 0); }
|
||||
bool touchPrefsGetGlanceWhenLocked() { if (!s_begun) touchPrefsBegin(); return s_prefs.getUChar("glance_lck", 0) != 0; }
|
||||
void touchPrefsSetGlanceWhenLocked(bool on) { if (!s_begun) touchPrefsBegin(); prefsPutUChar("glance_lck", on ? 1 : 0); }
|
||||
bool touchPrefsGetGlanceEnabled() { if (!s_begun) touchPrefsBegin(); return s_prefs.getUChar("glance_en", 1) != 0; }
|
||||
void touchPrefsSetGlanceEnabled(bool on) { if (!s_begun) touchPrefsBegin(); prefsPutUChar("glance_en", on ? 1 : 0); }
|
||||
|
||||
#if defined(HAS_TANMATSU) // only the Tanmatsu has the message LED — keep S3 (T-Deck/V4) bins unchanged
|
||||
bool touchPrefsGetMsgLed() { if (!s_begun) touchPrefsBegin(); return s_prefs.getUChar("msg_led", 1) != 0; } // default ON
|
||||
|
||||
@@ -451,6 +451,10 @@ bool touchPrefsGetEdgeScroll(); // push cursor past edge to scrol
|
||||
void touchPrefsSetEdgeScroll(bool on);
|
||||
bool touchPrefsGetLockOnScreenOff(); // idle screen-off auto-locks; only a deliberate hold wakes (default false)
|
||||
void touchPrefsSetLockOnScreenOff(bool on);
|
||||
bool touchPrefsGetGlanceWhenLocked(); // "at a glance" also fires while manually/idle locked, not just unlocked+dimmed (default false)
|
||||
void touchPrefsSetGlanceWhenLocked(bool on);
|
||||
bool touchPrefsGetGlanceEnabled(); // master "at a glance" feature toggle (default true)
|
||||
void touchPrefsSetGlanceEnabled(bool on);
|
||||
|
||||
/** Per-channel mute, keyed by channel name. Bit 0 = mute messages, bit 1 =
|
||||
* mute @-mentions. Suppresses the notification SOUND for that channel (the
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
class LatchedModifier {
|
||||
public:
|
||||
static constexpr uint32_t DOUBLE_TAP_MS = 400;
|
||||
|
||||
void press() {
|
||||
held_ = true;
|
||||
used_while_held_ = false;
|
||||
}
|
||||
|
||||
void release(uint32_t now_ms) {
|
||||
held_ = false;
|
||||
if (discard_release_) {
|
||||
discard_release_ = false;
|
||||
used_while_held_ = false;
|
||||
return;
|
||||
}
|
||||
if (used_while_held_) return;
|
||||
tap(now_ms);
|
||||
}
|
||||
|
||||
void tap(uint32_t now_ms) {
|
||||
if (locked_) {
|
||||
clearLatched();
|
||||
return;
|
||||
}
|
||||
if (one_shot_ && (uint32_t)(now_ms - last_tap_ms_) <= DOUBLE_TAP_MS) {
|
||||
one_shot_ = false;
|
||||
locked_ = true;
|
||||
last_tap_ms_ = 0;
|
||||
return;
|
||||
}
|
||||
one_shot_ = true;
|
||||
last_tap_ms_ = now_ms;
|
||||
}
|
||||
|
||||
bool consumeForKey() {
|
||||
const bool active_now = held_ || one_shot_ || locked_;
|
||||
if (held_) used_while_held_ = true;
|
||||
if (one_shot_) {
|
||||
one_shot_ = false;
|
||||
last_tap_ms_ = 0;
|
||||
}
|
||||
return active_now;
|
||||
}
|
||||
|
||||
void markHeldUsed() {
|
||||
if (!held_) return;
|
||||
used_while_held_ = true;
|
||||
one_shot_ = false;
|
||||
last_tap_ms_ = 0;
|
||||
}
|
||||
|
||||
void clearLatched() {
|
||||
one_shot_ = false;
|
||||
locked_ = false;
|
||||
last_tap_ms_ = 0;
|
||||
}
|
||||
|
||||
void discard() {
|
||||
clearLatched();
|
||||
if (held_) {
|
||||
used_while_held_ = true;
|
||||
discard_release_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
void baseline(bool physically_held) {
|
||||
clearLatched();
|
||||
held_ = physically_held;
|
||||
used_while_held_ = physically_held;
|
||||
discard_release_ = physically_held;
|
||||
}
|
||||
|
||||
bool held() const { return held_; }
|
||||
bool latched() const { return one_shot_ || locked_; }
|
||||
bool active() const { return held_ || one_shot_ || locked_; }
|
||||
bool locked() const { return locked_; }
|
||||
|
||||
private:
|
||||
bool held_ = false;
|
||||
bool used_while_held_ = false;
|
||||
bool one_shot_ = false;
|
||||
bool locked_ = false;
|
||||
bool discard_release_ = false;
|
||||
uint32_t last_tap_ms_ = 0;
|
||||
};
|
||||
@@ -6,7 +6,7 @@
|
||||
#include <Arduino.h>
|
||||
#include <Wire.h>
|
||||
#include <Adafruit_TCA8418.h>
|
||||
#include <cctype>
|
||||
#include "PagerKeyboardState.h"
|
||||
|
||||
#ifndef KB_INT
|
||||
#define KB_INT 6
|
||||
@@ -14,30 +14,10 @@
|
||||
#ifndef KB_BACKLIGHT
|
||||
#define KB_BACKLIGHT 46
|
||||
#endif
|
||||
#define KB_ROWS 4
|
||||
#define KB_COLS 10
|
||||
|
||||
// Matrix legend, s_keymap[row][col] — same physical keyboard PCB as
|
||||
// trail-mate's working LR1121 pager build, cross-checked there. '\0' = no
|
||||
// character at that position (dead cell, or intercepted as a modifier below).
|
||||
static constexpr char s_keymap[KB_ROWS][KB_COLS] = {
|
||||
{'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p'},
|
||||
{'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', '\r'},
|
||||
{'\0', 'z', 'x', 'c', 'v', 'b', 'n', 'm', '\0', '\0'},
|
||||
{' ', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0'},
|
||||
};
|
||||
// Alt layer (symbols/numbers) — this hardware has no separate physical Symbol
|
||||
// key, so Alt alone drives it (matches trail-mate's has_symbol_key=false path).
|
||||
static constexpr char s_symbolMap[KB_ROWS][KB_COLS] = {
|
||||
{'1', '2', '3', '4', '5', '6', '7', '8', '9', '0'},
|
||||
{'*', '/', '+', '-', '=', ':', '\'', '"', '@', '\0'},
|
||||
{'\0', '_', '$', ';', '?', '!', ',', '.', '\0', '\0'},
|
||||
{' ', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0'},
|
||||
};
|
||||
|
||||
// Modifier/special-key positions: 0-based (row*KB_COLS + col), matching the
|
||||
// TCA8418 raw event's (code & 0x7F) - 1. Alt is a hold (symbol layer while
|
||||
// held); Shift is a hold too (momentary uppercase on the base layer, real
|
||||
// TCA8418 raw event's (code & 0x7F) - 1. Alt selects symbols while held, a
|
||||
// solo tap selects them for one key, and a double tap locks the layer. Shift
|
||||
// is a hold too (momentary uppercase on the base layer, real
|
||||
// Shift-key semantics) — held Alt THEN a Shift press instead chords into
|
||||
// Alt+Shift, reported via s_alt_shift_chord_pending. What that chord DOES is
|
||||
// a UI-level decision (UITask.cpp): Caps Lock toggle while editing a text
|
||||
@@ -55,29 +35,21 @@ static constexpr char s_symbolMap[KB_ROWS][KB_COLS] = {
|
||||
// harmless in practice since the intended gesture is
|
||||
// hold-Alt-tap-Shift-release-both, not holding all three simultaneously.
|
||||
// Backspace (row2,col9) sits one column over from Shift, so Alt+Backspace
|
||||
// doesn't share this exact ghosting risk with any base-layer letter.
|
||||
static constexpr uint8_t kAltPos = 2 * KB_COLS + 0; // row2,col0 ('\0' in both layers)
|
||||
static constexpr uint8_t kShiftPos = 2 * KB_COLS + 8; // row2,col8 ('\0' in both layers)
|
||||
static constexpr uint8_t kBackspacePos = 2 * KB_COLS + 9; // row2,col9 ('\0' in both layers)
|
||||
static constexpr uint8_t kSpacePos = 3 * KB_COLS + 0; // row3,col0 (' ' in both layers)
|
||||
// doesn't share this exact ghosting risk with any base-layer letter. The maps
|
||||
// and positions live in PagerKeyboardState so host tests exercise the exact
|
||||
// production translation logic.
|
||||
|
||||
static Adafruit_TCA8418 s_kb;
|
||||
static bool s_inited = false;
|
||||
static bool s_alt = false;
|
||||
static bool s_alt_used = false; // Alt consumed as a modifier since it was last pressed
|
||||
static bool s_alt_tap_pending = false; // Alt pressed+released with nothing else happening meanwhile
|
||||
static bool s_caps = false;
|
||||
static bool s_shift_held = false; // momentary Shift, mirrors s_backspace_held/s_space_held
|
||||
static bool s_alt_shift_chord_pending = false; // one-shot, see pagerKeyboardConsumeAltShiftChord()
|
||||
static bool s_alt_backspace_chord_pending = false; // one-shot, see pagerKeyboardConsumeAltBackspaceChord()
|
||||
static bool s_backspace_held = false;
|
||||
static bool s_space_held = false;
|
||||
static PagerKeyboardState s_state;
|
||||
|
||||
// Single-producer (poll) / single-consumer (UI thread) ring — same pattern as
|
||||
// TDeckKeyboard.cpp; byte indices are atomic enough for SPSC without a lock.
|
||||
static volatile uint8_t s_ring[16];
|
||||
static volatile uint8_t s_head = 0;
|
||||
static volatile uint8_t s_tail = 0;
|
||||
// Single-producer (poll) / single-consumer ring. Polling currently happens on
|
||||
// the UI task, but the short critical section preserves the header's contract
|
||||
// if a future board moves I2C polling to another core.
|
||||
static portMUX_TYPE s_ring_mux = portMUX_INITIALIZER_UNLOCKED;
|
||||
static uint8_t s_ring[16];
|
||||
static uint8_t s_head = 0;
|
||||
static uint8_t s_tail = 0;
|
||||
|
||||
static bool s_bl_ready = false;
|
||||
// This framework's Arduino-ESP32 core only has the channel-based LEDC API
|
||||
@@ -88,16 +60,19 @@ static bool s_bl_ready = false;
|
||||
static constexpr uint8_t kKbBacklightPwmChannel = 0;
|
||||
|
||||
static void ringPush(uint8_t c) {
|
||||
portENTER_CRITICAL(&s_ring_mux);
|
||||
const uint8_t nh = (uint8_t)((s_head + 1) & 15);
|
||||
if (nh != s_tail) { // drop if the ring is full
|
||||
s_ring[s_head] = c;
|
||||
s_head = nh;
|
||||
}
|
||||
portEXIT_CRITICAL(&s_ring_mux);
|
||||
}
|
||||
|
||||
void pagerKeyboardBegin() {
|
||||
if (s_inited) return;
|
||||
s_inited = s_kb.begin(TCA8418_DEFAULT_ADDR, &Wire) && s_kb.matrix(KB_ROWS, KB_COLS);
|
||||
s_inited = s_kb.begin(TCA8418_DEFAULT_ADDR, &Wire) &&
|
||||
s_kb.matrix(PagerKeyboardState::ROWS, PagerKeyboardState::COLS);
|
||||
if (!s_inited) return;
|
||||
s_kb.flush();
|
||||
pinMode(KB_INT, INPUT_PULLUP); // TCA8418 INT is open-drain active-low; not ISR-driven here (see .h)
|
||||
@@ -115,58 +90,20 @@ void pagerKeyboardPoll() {
|
||||
const bool pressed = (raw & 0x80) != 0;
|
||||
const uint8_t code = (uint8_t)((raw & 0x7F) - 1);
|
||||
|
||||
if (code == kAltPos) {
|
||||
// Solo tap (press+release, nothing else in between) vs. a modifier hold
|
||||
// (symbol-layer typing, or the rotary encoder's Alt+turn via
|
||||
// pagerKeyboardMarkAltUsed()) — only the former queues a pending tap.
|
||||
if (pressed) { s_alt = true; s_alt_used = false; }
|
||||
else { if (!s_alt_used) s_alt_tap_pending = true; s_alt = false; }
|
||||
continue;
|
||||
}
|
||||
// Any other key event while Alt is held means Alt is being used as a
|
||||
// modifier, not tapped solo — cancels the pending-tap interpretation.
|
||||
if (s_alt && pressed) s_alt_used = true;
|
||||
if (code == kShiftPos) {
|
||||
if (pressed) {
|
||||
if (s_alt) s_alt_shift_chord_pending = true; // Alt (Fn) held + Shift press = chord (UI decides the effect)
|
||||
else s_shift_held = true;
|
||||
} else {
|
||||
s_shift_held = false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (code == kBackspacePos) {
|
||||
if (pressed) {
|
||||
// Alt+Backspace is a distinct chord (jump Home, everywhere -- see
|
||||
// pagerKeyboardConsumeAltBackspaceChord()), not a delete: suppress
|
||||
// both the '\b' ring-push AND s_backspace_held, so the plain-
|
||||
// Backspace hold gestures (back / unlock) never also see this press.
|
||||
if (s_alt) s_alt_backspace_chord_pending = true;
|
||||
else { s_backspace_held = true; ringPush('\b'); }
|
||||
} else {
|
||||
s_backspace_held = false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (code == kSpacePos) { s_space_held = pressed; if (pressed) ringPush(' '); continue; }
|
||||
if (!pressed) continue; // base/symbol keys only emit on press
|
||||
|
||||
const uint8_t row = code / KB_COLS;
|
||||
const uint8_t col = code % KB_COLS;
|
||||
if (row >= KB_ROWS) continue; // a GPIO event outside the matrix, not a key
|
||||
|
||||
char c = s_alt ? s_symbolMap[row][col] : s_keymap[row][col];
|
||||
if (c == '\0') continue;
|
||||
// Caps Lock and a held Shift both mean "uppercase," base layer only.
|
||||
if ((s_caps || s_shift_held) && !s_alt) c = (char)toupper((unsigned char)c);
|
||||
ringPush((uint8_t)c);
|
||||
const uint8_t key = s_state.event(code, pressed, millis());
|
||||
if (key) ringPush(key);
|
||||
}
|
||||
}
|
||||
|
||||
int pagerKeyboardReadKey() {
|
||||
if (s_tail == s_head) return 0;
|
||||
portENTER_CRITICAL(&s_ring_mux);
|
||||
if (s_tail == s_head) {
|
||||
portEXIT_CRITICAL(&s_ring_mux);
|
||||
return 0;
|
||||
}
|
||||
const uint8_t c = s_ring[s_tail];
|
||||
s_tail = (uint8_t)((s_tail + 1) & 15);
|
||||
portEXIT_CRITICAL(&s_ring_mux);
|
||||
return c;
|
||||
}
|
||||
|
||||
@@ -179,32 +116,26 @@ void pagerKeyboardSetBacklight(uint8_t level) {
|
||||
ledcWrite(kKbBacklightPwmChannel, level);
|
||||
}
|
||||
|
||||
bool pagerKeyboardAltHeld() { return s_alt; }
|
||||
bool pagerKeyboardAltHeld() { return s_state.altHeld(); }
|
||||
|
||||
void pagerKeyboardMarkAltUsed() { s_alt_used = true; }
|
||||
void pagerKeyboardMarkAltUsed() { s_state.markAltUsed(); }
|
||||
|
||||
bool pagerKeyboardConsumeAltTap() {
|
||||
if (!s_alt_tap_pending) return false;
|
||||
s_alt_tap_pending = false;
|
||||
return true;
|
||||
void pagerKeyboardDiscardAlt() {
|
||||
s_state.discardAlt();
|
||||
}
|
||||
|
||||
bool pagerKeyboardBackspaceHeld() { return s_backspace_held; }
|
||||
bool pagerKeyboardBackspaceHeld() { return s_state.backspaceHeld(); }
|
||||
|
||||
bool pagerKeyboardSpaceHeld() { return s_space_held; }
|
||||
bool pagerKeyboardSpaceHeld() { return s_state.spaceHeld(); }
|
||||
|
||||
bool pagerKeyboardConsumeAltShiftChord() {
|
||||
if (!s_alt_shift_chord_pending) return false;
|
||||
s_alt_shift_chord_pending = false;
|
||||
return true;
|
||||
return s_state.consumeAltShiftChord();
|
||||
}
|
||||
|
||||
void pagerKeyboardToggleCaps() { s_caps = !s_caps; }
|
||||
void pagerKeyboardToggleCaps() { s_state.toggleCaps(); }
|
||||
|
||||
bool pagerKeyboardConsumeAltBackspaceChord() {
|
||||
if (!s_alt_backspace_chord_pending) return false;
|
||||
s_alt_backspace_chord_pending = false;
|
||||
return true;
|
||||
return s_state.consumeAltBackspaceChord();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
|
||||
// T-LoRa Pager physical QWERTY keyboard: a TCA8418 I2C matrix controller (4
|
||||
// rows x 10 cols, addr 0x34) on the shared I2C bus (SDA 3 / SCL 2). Unlike
|
||||
// the T-Deck's keyboard (a second MCU that resolves ASCII itself before we
|
||||
// ever see a byte), the TCA8418 only reports raw row/col matrix events — the
|
||||
// the T-Deck's keyboard (a second MCU, with raw mode only on newer firmware),
|
||||
// the TCA8418 always reports raw row/col matrix events — the
|
||||
// keymap + shift/sym/alt state machine lives HERE, so pagerKeyboardReadKey()
|
||||
// produces the same final ASCII/control-code stream handleHwKey() already
|
||||
// expects from the T-Deck; no UI-side changes needed to consume it.
|
||||
@@ -13,7 +13,7 @@
|
||||
// must be called from a single, consistent context each tick (whichever task
|
||||
// ends up owning it — wired in a later milestone; this board has no
|
||||
// pre-existing shared-bus task the way the T-Deck's touch poll does).
|
||||
// pagerKeyboardReadKey() only pops from a lock-free ring and is safe to call
|
||||
// pagerKeyboardReadKey() only pops from a critical-section-protected ring and is safe to call
|
||||
// from the UI thread regardless of which context polls.
|
||||
#if defined(HAS_PAGER_KEYBOARD) && defined(ESP32)
|
||||
|
||||
@@ -46,15 +46,12 @@ bool pagerKeyboardAltHeld();
|
||||
|
||||
/** Mark the currently-held Alt as "used as a modifier" — call this when some
|
||||
* other gesture (e.g. the rotary encoder's Alt+turn) consumes the hold, so
|
||||
* releasing Alt afterward isn't also read as a solo tap by
|
||||
* pagerKeyboardConsumeAltTap(). */
|
||||
* releasing Alt afterward does not arm the one-shot symbol layer. */
|
||||
void pagerKeyboardMarkAltUsed();
|
||||
|
||||
/** One-shot: true exactly once if Alt was pressed and released without being
|
||||
* used as a modifier for anything else in between (no key typed, no
|
||||
* pagerKeyboardMarkAltUsed() call) — a "solo tap", distinct from a
|
||||
* symbol-layer or Alt+turn hold. Consumes the pending flag on read. */
|
||||
bool pagerKeyboardConsumeAltTap();
|
||||
/** Cancel one-shot/locked Alt, suppress a later release from a held Alt, and
|
||||
* discard pending Alt chords. Used when input is intentionally discarded. */
|
||||
void pagerKeyboardDiscardAlt();
|
||||
|
||||
/** True while Backspace is physically held WITHOUT Alt (raw state, mirrors
|
||||
* pagerKeyboardAltHeld()). A plain press still immediately ring-pushes '\b'
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "LatchedModifier.h"
|
||||
|
||||
class PagerKeyboardState {
|
||||
public:
|
||||
static constexpr uint8_t ROWS = 4;
|
||||
static constexpr uint8_t COLS = 10;
|
||||
static constexpr uint8_t ALT_POS = 2 * COLS;
|
||||
static constexpr uint8_t SHIFT_POS = 2 * COLS + 8;
|
||||
static constexpr uint8_t BACKSPACE_POS = 2 * COLS + 9;
|
||||
static constexpr uint8_t SPACE_POS = 3 * COLS;
|
||||
|
||||
uint8_t event(uint8_t code, bool pressed, uint32_t now_ms) {
|
||||
static const char base[ROWS][COLS] = {
|
||||
{'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p'},
|
||||
{'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', '\r'},
|
||||
{'\0', 'z', 'x', 'c', 'v', 'b', 'n', 'm', '\0', '\0'},
|
||||
{' ', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0'},
|
||||
};
|
||||
static const char symbols[ROWS][COLS] = {
|
||||
{'1', '2', '3', '4', '5', '6', '7', '8', '9', '0'},
|
||||
{'*', '/', '+', '-', '=', ':', '\'', '"', '@', '\0'},
|
||||
{'\0', '_', '$', ';', '?', '!', ',', '.', '\0', '\0'},
|
||||
{' ', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0'},
|
||||
};
|
||||
|
||||
if (code == ALT_POS) {
|
||||
if (pressed) alt_.press();
|
||||
else alt_.release(now_ms);
|
||||
return 0;
|
||||
}
|
||||
if (code == SHIFT_POS) {
|
||||
if (pressed) {
|
||||
if (alt_.held()) {
|
||||
alt_.markHeldUsed();
|
||||
alt_shift_chord_pending_ = true;
|
||||
} else {
|
||||
shift_held_ = true;
|
||||
}
|
||||
} else {
|
||||
shift_held_ = false;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
if (code == BACKSPACE_POS) {
|
||||
if (pressed) {
|
||||
if (alt_.held()) {
|
||||
alt_.markHeldUsed();
|
||||
alt_backspace_chord_pending_ = true;
|
||||
} else {
|
||||
alt_.consumeForKey();
|
||||
backspace_held_ = true;
|
||||
return '\b';
|
||||
}
|
||||
} else {
|
||||
backspace_held_ = false;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
if (code == SPACE_POS) {
|
||||
space_held_ = pressed;
|
||||
if (pressed) {
|
||||
alt_.consumeForKey();
|
||||
return ' ';
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
if (!pressed) return 0;
|
||||
|
||||
const uint8_t row = code / COLS;
|
||||
const uint8_t col = code % COLS;
|
||||
if (row >= ROWS) return 0;
|
||||
const bool symbol_layer = alt_.consumeForKey();
|
||||
char key = symbol_layer ? symbols[row][col] : base[row][col];
|
||||
if (key == '\0') return 0;
|
||||
if ((caps_ || shift_held_) && !symbol_layer && key >= 'a' && key <= 'z') key -= 32;
|
||||
return (uint8_t)key;
|
||||
}
|
||||
|
||||
bool altHeld() const { return alt_.held(); }
|
||||
void markAltUsed() { alt_.markHeldUsed(); }
|
||||
void discardAlt() {
|
||||
alt_.discard();
|
||||
alt_shift_chord_pending_ = false;
|
||||
alt_backspace_chord_pending_ = false;
|
||||
}
|
||||
bool backspaceHeld() const { return backspace_held_; }
|
||||
bool spaceHeld() const { return space_held_; }
|
||||
bool consumeAltShiftChord() {
|
||||
const bool pending = alt_shift_chord_pending_;
|
||||
alt_shift_chord_pending_ = false;
|
||||
return pending;
|
||||
}
|
||||
void toggleCaps() { caps_ = !caps_; }
|
||||
bool consumeAltBackspaceChord() {
|
||||
const bool pending = alt_backspace_chord_pending_;
|
||||
alt_backspace_chord_pending_ = false;
|
||||
return pending;
|
||||
}
|
||||
|
||||
private:
|
||||
LatchedModifier alt_;
|
||||
bool caps_ = false;
|
||||
bool shift_held_ = false;
|
||||
bool alt_shift_chord_pending_ = false;
|
||||
bool alt_backspace_chord_pending_ = false;
|
||||
bool backspace_held_ = false;
|
||||
bool space_held_ = false;
|
||||
};
|
||||
@@ -2,6 +2,7 @@
|
||||
#if defined(HAS_TDECK_KEYBOARD) && defined(ESP32)
|
||||
|
||||
#include "TDeckKeyboard.h"
|
||||
#include "TDeckKeyboardState.h"
|
||||
#include <Arduino.h>
|
||||
#include <Wire.h>
|
||||
|
||||
@@ -9,19 +10,96 @@
|
||||
#define PIN_KB_ADDR 0x55
|
||||
#endif
|
||||
|
||||
// Single-producer (core-0 poll) / single-consumer (UI thread) ring. Byte indices
|
||||
// on a 32-bit MCU are atomic enough for SPSC without a lock.
|
||||
static volatile uint8_t s_ring[16];
|
||||
static volatile uint8_t s_head = 0; // written by producer
|
||||
static volatile uint8_t s_tail = 0; // written by consumer
|
||||
// Core-0 produces and the UI task consumes. One short cross-core critical
|
||||
// section publishes each byte together with its index and also protects the
|
||||
// desired modifier-input mode below.
|
||||
static portMUX_TYPE s_keyboard_mux = portMUX_INITIALIZER_UNLOCKED;
|
||||
static uint8_t s_ring[16];
|
||||
static uint8_t s_head = 0;
|
||||
static uint8_t s_tail = 0;
|
||||
static bool s_inited = false;
|
||||
enum class KeyboardMode : uint8_t { Probe, Raw, Legacy };
|
||||
// How long to keep asking the C3 for raw frames before settling for legacy mode.
|
||||
// Generous on purpose: getting this wrong costs the user modifier latching for
|
||||
// the entire session, and the only cost of waiting is that the first second of
|
||||
// keystrokes goes through the legacy path, which still works.
|
||||
static const uint32_t kProbeWindowMs = 1500;
|
||||
static KeyboardMode s_mode = KeyboardMode::Probe;
|
||||
static TDeckKeyboardState s_raw_state;
|
||||
static bool s_modifier_input_allowed = true; // guarded by s_keyboard_mux
|
||||
static uint32_t s_modifier_mode_generation = 0; // guarded by s_keyboard_mux
|
||||
static uint32_t s_modifier_generation_applied = 0; // core-0 owner only
|
||||
|
||||
// Backlight: the UI thread requests a level; the actual I2C write happens in the
|
||||
// poll (core 0). The keyboard's C3 firmware sets the backlight on an I2C write.
|
||||
static volatile uint8_t s_bl_desired = 0;
|
||||
static volatile bool s_bl_dirty = false;
|
||||
|
||||
static void ringPushLocked(uint8_t key) {
|
||||
if (!key) return;
|
||||
const uint8_t next = (uint8_t)((s_head + 1) & 15);
|
||||
if (next != s_tail) {
|
||||
s_ring[s_head] = key;
|
||||
s_head = next;
|
||||
}
|
||||
}
|
||||
|
||||
static void keyboardCommand(uint8_t command) {
|
||||
Wire.beginTransmission(PIN_KB_ADDR);
|
||||
Wire.write(command);
|
||||
Wire.endTransmission();
|
||||
}
|
||||
|
||||
static int keyboardRead(uint8_t* out, size_t count) {
|
||||
Wire.requestFrom((int)PIN_KB_ADDR, (int)count);
|
||||
int read = 0;
|
||||
while (Wire.available() && read < (int)count) out[read++] = (uint8_t)Wire.read();
|
||||
while (Wire.available()) Wire.read();
|
||||
return read;
|
||||
}
|
||||
|
||||
static void processRawFrame(const uint8_t frame[TDeckKeyboardState::COLS], uint32_t now_ms) {
|
||||
uint8_t keys[16];
|
||||
portENTER_CRITICAL(&s_keyboard_mux);
|
||||
const uint32_t generation = s_modifier_mode_generation;
|
||||
if (generation != s_modifier_generation_applied) {
|
||||
// Transition frame: establish current physical state, publish nothing.
|
||||
// The mode setter already cleared older queued bytes under this same lock.
|
||||
s_raw_state.baseline(frame);
|
||||
s_modifier_generation_applied = generation;
|
||||
} else {
|
||||
const size_t key_count = s_raw_state.update(frame, now_ms, keys, sizeof keys,
|
||||
s_modifier_input_allowed);
|
||||
for (size_t i = 0; i < key_count; ++i) ringPushLocked(keys[i]);
|
||||
}
|
||||
portEXIT_CRITICAL(&s_keyboard_mux);
|
||||
}
|
||||
|
||||
static void processLegacyKey(uint8_t key) {
|
||||
portENTER_CRITICAL(&s_keyboard_mux);
|
||||
const uint32_t generation = s_modifier_mode_generation;
|
||||
if (generation != s_modifier_generation_applied) {
|
||||
// No raw modifier state exists, and the C3 exposes one latest-byte mailbox
|
||||
// (comdata/comdata_flag), not one response per host mode transition. Any
|
||||
// number of transitions before this read therefore coalesce deliberately:
|
||||
// drop the sole pending byte once so it cannot cross into the final mode.
|
||||
s_modifier_generation_applied = generation;
|
||||
} else {
|
||||
ringPushLocked(key);
|
||||
}
|
||||
portEXIT_CRITICAL(&s_keyboard_mux);
|
||||
}
|
||||
|
||||
void tdeckKeyboardBegin() {
|
||||
s_mode = KeyboardMode::Probe;
|
||||
s_raw_state = TDeckKeyboardState{};
|
||||
portENTER_CRITICAL(&s_keyboard_mux);
|
||||
s_head = s_tail = 0;
|
||||
// Keep any desired mode the UI published immediately after starting this
|
||||
// task. Setting applied one generation behind forces the first response to
|
||||
// baseline (or drain the legacy controller's one-byte mailbox).
|
||||
s_modifier_generation_applied = s_modifier_mode_generation - 1;
|
||||
portEXIT_CRITICAL(&s_keyboard_mux);
|
||||
s_inited = true; // Wire was configured (18/8, 400k, 20ms timeout) by the touch driver
|
||||
}
|
||||
|
||||
@@ -49,21 +127,92 @@ void tdeckKeyboardFlushBacklight() {
|
||||
void tdeckKeyboardPoll() {
|
||||
if (!s_inited) return;
|
||||
tdeckKeyboardFlushBacklight();
|
||||
if (Wire.requestFrom((int)PIN_KB_ADDR, 1) != 1) return;
|
||||
uint8_t key = Wire.read();
|
||||
if (key == 0) return; // no key this scan
|
||||
const uint8_t nh = (uint8_t)((s_head + 1) & 15);
|
||||
if (nh != s_tail) { // drop if the ring is full
|
||||
s_ring[s_head] = key;
|
||||
s_head = nh;
|
||||
if (s_mode == KeyboardMode::Probe) {
|
||||
// LilyGO keyboard firmware from June 2025 onward accepts 0x03 and returns
|
||||
// five column-state bytes. Older controllers ignore it and keep returning
|
||||
// one resolved ASCII byte; restore 0x04 key mode when that is what we have.
|
||||
//
|
||||
// KEEP RETRYING before concluding "old firmware". This used to decide on a
|
||||
// SINGLE read, and the first I2C read after boot is the least trustworthy
|
||||
// one there is: the C3 is still coming up, so a short or garbled frame is
|
||||
// ordinary. One such frame committed the device to legacy mode for the whole
|
||||
// session, with no recovery short of a reboot -- reported as latching simply
|
||||
// not working on a T-Deck carrying the June 2025 firmware (#332).
|
||||
static uint32_t s_probe_start_ms = 0;
|
||||
const uint32_t now = millis();
|
||||
if (!s_probe_start_ms) s_probe_start_ms = now;
|
||||
|
||||
keyboardCommand(0x03);
|
||||
uint8_t frame[TDeckKeyboardState::COLS] = {};
|
||||
const int count = keyboardRead(frame, sizeof frame);
|
||||
bool valid_raw = count == (int)sizeof frame;
|
||||
size_t bad_col = 0;
|
||||
for (size_t col = 0; col < sizeof frame && valid_raw; ++col) {
|
||||
if (frame[col] & 0x80) { valid_raw = false; bad_col = col; }
|
||||
}
|
||||
if (valid_raw) {
|
||||
s_mode = KeyboardMode::Raw;
|
||||
Serial.println("[keyboard] T-Deck raw mode: modifier latching enabled");
|
||||
processRawFrame(frame, millis());
|
||||
return;
|
||||
}
|
||||
// A legacy controller answers with one resolved byte. Deliver it either way
|
||||
// so nothing typed during the probe window is dropped.
|
||||
if (count == 1) { keyboardCommand(0x04); processLegacyKey(frame[0]); }
|
||||
if ((uint32_t)(now - s_probe_start_ms) < kProbeWindowMs) return; // try again next poll
|
||||
|
||||
keyboardCommand(0x04);
|
||||
s_mode = KeyboardMode::Legacy;
|
||||
// Say WHY, so the next report of "latching does not work" is one line to
|
||||
// diagnose instead of a guess about which firmware someone has.
|
||||
Serial.printf("[keyboard] T-Deck legacy mode: update keyboard C3 firmware for modifier "
|
||||
"latching (probe read %d bytes, wanted %d%s)\n",
|
||||
count, (int)sizeof frame,
|
||||
(count == (int)sizeof frame) ? ", high bit set in a column" : "");
|
||||
(void)bad_col;
|
||||
return;
|
||||
}
|
||||
if (s_mode == KeyboardMode::Raw) {
|
||||
uint8_t frame[TDeckKeyboardState::COLS] = {};
|
||||
if (keyboardRead(frame, sizeof frame) != (int)sizeof frame) return;
|
||||
for (size_t col = 0; col < sizeof frame; ++col) if (frame[col] & 0x80) return;
|
||||
processRawFrame(frame, millis());
|
||||
return;
|
||||
}
|
||||
uint8_t key = 0;
|
||||
if (keyboardRead(&key, 1) == 1) processLegacyKey(key);
|
||||
}
|
||||
|
||||
int tdeckKeyboardReadKey() {
|
||||
if (s_tail == s_head) return 0; // empty
|
||||
portENTER_CRITICAL(&s_keyboard_mux);
|
||||
if (s_tail == s_head) {
|
||||
portEXIT_CRITICAL(&s_keyboard_mux);
|
||||
return 0;
|
||||
}
|
||||
const uint8_t key = s_ring[s_tail];
|
||||
s_tail = (uint8_t)((s_tail + 1) & 15);
|
||||
portEXIT_CRITICAL(&s_keyboard_mux);
|
||||
return key;
|
||||
}
|
||||
|
||||
void tdeckKeyboardDiscardModifiers() {
|
||||
portENTER_CRITICAL(&s_keyboard_mux);
|
||||
if (s_modifier_input_allowed) {
|
||||
s_modifier_input_allowed = false;
|
||||
++s_modifier_mode_generation;
|
||||
s_tail = s_head;
|
||||
}
|
||||
portEXIT_CRITICAL(&s_keyboard_mux);
|
||||
}
|
||||
|
||||
void tdeckKeyboardAllowModifiers() {
|
||||
portENTER_CRITICAL(&s_keyboard_mux);
|
||||
if (!s_modifier_input_allowed) {
|
||||
s_modifier_input_allowed = true;
|
||||
++s_modifier_mode_generation;
|
||||
s_tail = s_head;
|
||||
}
|
||||
portEXIT_CRITICAL(&s_keyboard_mux);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
// LilyGo T-Deck physical keyboard. It's a separate ESP32-C3 running the
|
||||
// T-Keyboard firmware on the shared I2C bus (SDA 18 / SCL 8, addr 0x55): each
|
||||
// 1-byte read returns the ASCII code of the last key press (0 = none). The C3
|
||||
// resolves shift / sym layers itself, so we just receive final characters.
|
||||
// T-Keyboard firmware on the shared I2C bus (SDA 18 / SCL 8, addr 0x55).
|
||||
// Current controller firmware supports a five-byte raw matrix mode, which lets
|
||||
// this driver preserve held modifiers and add tap/double-tap latching. Older
|
||||
// firmware returns one resolved ASCII byte; startup detects that and restores
|
||||
// the original controller-side key mode automatically.
|
||||
//
|
||||
// CRITICAL: the keyboard shares the I2C bus with the GT911 touch controller,
|
||||
// which is polled from a core-0 task. To avoid two cores hitting Wire at once,
|
||||
// tdeckKeyboardPoll() must be called from THAT task; the UI thread only drains
|
||||
// the lock-free ring via tdeckKeyboardReadKey().
|
||||
// the critical-section-protected ring via tdeckKeyboardReadKey().
|
||||
#if defined(HAS_TDECK_KEYBOARD) && defined(ESP32)
|
||||
|
||||
#include <stdint.h>
|
||||
@@ -23,6 +25,14 @@ void tdeckKeyboardPoll();
|
||||
/** Pop the next buffered key (ASCII), or 0 if none. Safe from the UI thread. */
|
||||
int tdeckKeyboardReadKey();
|
||||
|
||||
/** Cancel one-shot/locked modifiers and suppress a later release from a
|
||||
* currently held modifier. Call when queued input is intentionally discarded. */
|
||||
void tdeckKeyboardDiscardModifiers();
|
||||
|
||||
/** Stop continuously discarding raw modifier state after the UI returns to a
|
||||
* mode where keyboard input is meaningful. */
|
||||
void tdeckKeyboardAllowModifiers();
|
||||
|
||||
/** Request a keyboard-backlight level (0 = off, 0xFF = on). Safe from the UI
|
||||
* thread — the I2C write happens inside tdeckKeyboardPoll() (core 0), which
|
||||
* owns the bus. Only re-sent when the value changes. */
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "LatchedModifier.h"
|
||||
|
||||
class TDeckKeyboardState {
|
||||
public:
|
||||
static constexpr size_t COLS = 5;
|
||||
|
||||
size_t update(const uint8_t current[COLS], uint32_t now_ms,
|
||||
uint8_t* out, size_t out_capacity, bool modifiers_enabled = true) {
|
||||
static const char base[COLS][7] = {
|
||||
{'q', 'w', '\0', 'a', '\0', ' ', '\0'},
|
||||
{'e', 's', 'd', 'p', 'x', 'z', '\0'},
|
||||
{'r', 'g', 't', '\0', 'v', 'c', 'f'},
|
||||
{'u', 'h', 'y', '\0', 'b', 'n', 'j'},
|
||||
{'o', 'l', 'i', '\0', '$', 'm', 'k'},
|
||||
};
|
||||
static const char symbols[COLS][7] = {
|
||||
{'#', '1', '\0', '*', '\0', '\0', '0'},
|
||||
{'2', '4', '5', '@', '8', '7', '\0'},
|
||||
{'3', '/', '(', '\0', '?', '9', '6'},
|
||||
{'_', ':', ')', '\0', '!', ',', ';'},
|
||||
{'+', '"', '-', '\0', '\0', '.', '\''},
|
||||
};
|
||||
auto down = [&](size_t col, uint8_t row) {
|
||||
return (current[col] & (uint8_t)(1U << row)) != 0;
|
||||
};
|
||||
auto pressed = [&](size_t col, uint8_t row) {
|
||||
return down(col, row) && (previous_[col] & (uint8_t)(1U << row)) == 0;
|
||||
};
|
||||
auto released = [&](size_t col, uint8_t row) {
|
||||
return !down(col, row) && (previous_[col] & (uint8_t)(1U << row)) != 0;
|
||||
};
|
||||
|
||||
if (!modifiers_enabled) {
|
||||
symbol_.baseline(down(0, 2));
|
||||
alt_.baseline(down(0, 4));
|
||||
}
|
||||
if (modifiers_enabled && pressed(0, 2)) symbol_.press();
|
||||
if (modifiers_enabled && pressed(0, 4)) alt_.press();
|
||||
if (modifiers_enabled && released(0, 2)) symbol_.release(now_ms);
|
||||
if (modifiers_enabled && released(0, 4)) alt_.release(now_ms);
|
||||
|
||||
const bool shift = modifiers_enabled && (down(1, 6) || down(2, 3));
|
||||
size_t written = 0;
|
||||
auto emit = [&](uint8_t key) {
|
||||
if (written < out_capacity) out[written++] = key;
|
||||
};
|
||||
for (uint8_t row = 0; row < 7; ++row) {
|
||||
for (size_t col = 0; col < COLS; ++col) {
|
||||
if (!pressed(col, row)) continue;
|
||||
if ((col == 0 && (row == 2 || row == 4)) ||
|
||||
(col == 1 && row == 6) || (col == 2 && row == 3)) continue;
|
||||
const bool physical_alt = modifiers_enabled && alt_.held();
|
||||
if (physical_alt) alt_.markHeldUsed();
|
||||
// Preserve LilyGO controller shortcuts while raw mode moves ordinary
|
||||
// matrix translation into the host. The C3 still handles Alt+B's
|
||||
// backlight toggle; suppress its character exactly as key mode does.
|
||||
if (physical_alt && col == 3 && row == 4) {
|
||||
symbol_.consumeForKey();
|
||||
continue;
|
||||
}
|
||||
if (physical_alt && col == 2 && row == 5) {
|
||||
symbol_.consumeForKey();
|
||||
emit(0x0C);
|
||||
continue;
|
||||
}
|
||||
if (col == 3 && row == 3) {
|
||||
symbol_.consumeForKey(); alt_.consumeForKey(); emit('\r'); continue;
|
||||
}
|
||||
if (col == 4 && row == 3) {
|
||||
symbol_.consumeForKey(); alt_.consumeForKey(); emit('\b'); continue;
|
||||
}
|
||||
|
||||
const bool use_symbols = modifiers_enabled && (symbol_.active() || alt_.latched());
|
||||
const char key = use_symbols ? symbols[col][row] : base[col][row];
|
||||
if (modifiers_enabled) {
|
||||
symbol_.consumeForKey();
|
||||
if (!physical_alt) alt_.consumeForKey();
|
||||
}
|
||||
if (key == '\0') continue;
|
||||
char resolved = key;
|
||||
if (!use_symbols && shift && resolved >= 'a' && resolved <= 'z') resolved -= 32;
|
||||
emit((uint8_t)resolved);
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t col = 0; col < COLS; ++col) previous_[col] = current[col] & 0x7F;
|
||||
return written;
|
||||
}
|
||||
|
||||
void baseline(const uint8_t current[COLS]) {
|
||||
symbol_.baseline((current[0] & (uint8_t)(1U << 2)) != 0);
|
||||
alt_.baseline((current[0] & (uint8_t)(1U << 4)) != 0);
|
||||
for (size_t col = 0; col < COLS; ++col) previous_[col] = current[col] & 0x7F;
|
||||
}
|
||||
|
||||
void discardModifiers() {
|
||||
symbol_.discard();
|
||||
alt_.discard();
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t previous_[COLS] = {};
|
||||
LatchedModifier symbol_;
|
||||
LatchedModifier alt_;
|
||||
};
|
||||
+262
-26
@@ -18,6 +18,7 @@
|
||||
#include "AppPage.h"
|
||||
#include "device_caps.h"
|
||||
#include "i18n.h" // wada.sys.tr: apps can use the same translation table the UI does
|
||||
#include "Utf8Text.h"
|
||||
#include "helpers/esp32/WdtHeavyGuard.h" // wada.fs writes can trigger SPIFFS GC
|
||||
|
||||
extern "C" {
|
||||
@@ -35,6 +36,18 @@ extern bool luaHostScreenOn(); // false = display asleep; app tick
|
||||
extern void luaHostKeepAwake(bool on); // hold the screen + ticks for a measuring app // notification chime; false = no sounder / muted
|
||||
extern fs::FS* luaHostAppFs(); // /apps storage root FS (may be null)
|
||||
extern void luaHostAppPath(char* out, size_t cap, const char* rel); // prefixes the store root
|
||||
#if CAP_LUA_AUDIO
|
||||
extern bool luaHostAudioPlay(fs::FS* fs, const char* path, const char* shown,
|
||||
const char* source, uint32_t owner,
|
||||
char* error, size_t error_cap);
|
||||
extern bool luaHostAudioPause(uint32_t owner, bool pause);
|
||||
extern bool luaHostAudioStop(uint32_t owner, bool release);
|
||||
extern void luaHostAudioStatus(uint32_t owner, char* state, size_t state_cap,
|
||||
char* path, size_t path_cap,
|
||||
char* source, size_t source_cap,
|
||||
char* format, size_t format_cap,
|
||||
char* error, size_t error_cap);
|
||||
#endif
|
||||
#if CAP_LUA_SD_LIST
|
||||
extern fs::FS* luaHostSdFs(bool* busy); // mounted physical SD, or null
|
||||
extern bool luaHostSdReadFailed(); // failed open was a dead card
|
||||
@@ -157,10 +170,25 @@ void budgetHook(lua_State* L, lua_Debug*) {
|
||||
// host state
|
||||
// ---------------------------------------------------------------------------
|
||||
constexpr size_t kHeapCap = 256 * 1024; // per-app PSRAM cap
|
||||
constexpr size_t kMaxSrc = 64 * 1024; // app source size limit
|
||||
// App source size limit. Was 64 KB, which a real app can reach: reported from a
|
||||
// 2000-contact device whose author hit it writing an ordinary Lua program, not
|
||||
// anything pathological. The source lives in PSRAM only until luaL_loadbuffer
|
||||
// has compiled it, so this is a transient allocation rather than a per-app cost,
|
||||
// and the compiled chunk still has to fit the 256 KB per-app heap below.
|
||||
constexpr size_t kMaxSrc = 192 * 1024; // app source size limit
|
||||
constexpr size_t kStoreMax = 2048; // per-app persisted KV budget (bytes, serialized)
|
||||
constexpr int kMinTickMs = 33; // fastest on_tick cadence (~30 fps)
|
||||
|
||||
// Bumped by wada.ui.clear(). Every widget handle records the generation it was
|
||||
// created in; a handle from an older one has already been destroyed, so the
|
||||
// accessors below null its pointer and the `if (u->obj)` guard that every
|
||||
// method already has does the rest.
|
||||
//
|
||||
// Without this, clear() would be a use-after-free generator: WidgetUd holds a
|
||||
// raw lv_obj_t*, so a Lua variable still referring to a label from the previous
|
||||
// screen would sail past the null check straight into freed memory.
|
||||
static uint32_t s_ui_gen = 1;
|
||||
|
||||
struct Host {
|
||||
lua_State* L = nullptr;
|
||||
LuaHeap heap;
|
||||
@@ -184,10 +212,12 @@ struct Host {
|
||||
AppTimer timers[kMaxTimers];
|
||||
bool in_lua = false; // re-entrancy guard (dismiss from inside a callback)
|
||||
bool want_close = false;
|
||||
uint32_t generation = 0; // resource ownership across close/reopen
|
||||
char id[24] = "";
|
||||
char title[32] = "";
|
||||
};
|
||||
Host* s_h = nullptr;
|
||||
uint32_t s_host_generation = 0;
|
||||
|
||||
char s_bar_title[40]; // appPageBegin keeps the pointer — must outlive the page
|
||||
|
||||
@@ -246,8 +276,21 @@ uint32_t argColor(lua_State* L, int idx, uint32_t def = 0xFFFFFF) {
|
||||
return (uint32_t)luaL_optinteger(L, idx, (lua_Integer)def) & 0xFFFFFF;
|
||||
}
|
||||
|
||||
// LVGL assumes structurally valid UTF-8 and can stop advancing when an app
|
||||
// hands it a partial codepoint. Keep one reusable repair buffer: valid strings
|
||||
// stay zero-copy, while malformed runs become one ASCII '?'. Every call below
|
||||
// is synchronous and LVGL copies label text before this buffer can be reused.
|
||||
const char* safeUiText(const char* text, size_t length, size_t* safe_length = nullptr,
|
||||
unsigned scratch_slot = 0) {
|
||||
static std::string scratch_slots[2];
|
||||
std::string& scratch = scratch_slots[scratch_slot & 1U];
|
||||
const char* safe = Utf8Text::sanitize(text, length, scratch);
|
||||
if (safe_length) *safe_length = safe == text ? length : scratch.size();
|
||||
return safe;
|
||||
}
|
||||
|
||||
// ---- canvas userdata ----
|
||||
struct CanvasUd { lv_obj_t* obj; lv_color_t* buf; int w, h; };
|
||||
struct CanvasUd { lv_obj_t* obj; lv_color_t* buf; int w, h; uint32_t gen; };
|
||||
|
||||
CanvasUd* checkCanvas(lua_State* L) {
|
||||
return (CanvasUd*)luaL_checkudata(L, 1, "wada.canvas");
|
||||
@@ -298,11 +341,14 @@ int cvCircle(lua_State* L) {
|
||||
int cvText(lua_State* L) {
|
||||
CanvasUd* c = checkCanvas(L);
|
||||
if (!c->obj) return 0;
|
||||
const lv_coord_t x = (lv_coord_t)luaL_checkinteger(L, 2);
|
||||
const lv_coord_t y = (lv_coord_t)luaL_checkinteger(L, 3);
|
||||
size_t text_length = 0;
|
||||
const char* text = luaL_checklstring(L, 4, &text_length);
|
||||
lv_draw_label_dsc_t d; lv_draw_label_dsc_init(&d);
|
||||
d.color = lv_color_hex(argColor(L, 5));
|
||||
d.font = luaHostFontForSize((int)luaL_optinteger(L, 6, 14));
|
||||
lv_canvas_draw_text(c->obj, (lv_coord_t)luaL_checkinteger(L, 2), (lv_coord_t)luaL_checkinteger(L, 3),
|
||||
c->w, &d, luaL_checkstring(L, 4));
|
||||
lv_canvas_draw_text(c->obj, x, y, c->w, &d, safeUiText(text, text_length));
|
||||
return 0;
|
||||
}
|
||||
int cvPos(lua_State* L) {
|
||||
@@ -337,6 +383,7 @@ int uiCanvas(lua_State* L) {
|
||||
lv_canvas_set_buffer(cv, buf, w, h, LV_IMG_CF_TRUE_COLOR);
|
||||
lv_canvas_fill_bg(cv, lv_color_hex(0x000000), LV_OPA_COVER);
|
||||
CanvasUd* ud = (CanvasUd*)lua_newuserdatauv(L, sizeof(CanvasUd), 0);
|
||||
ud->gen = s_ui_gen;
|
||||
ud->obj = cv; ud->buf = buf; ud->w = w; ud->h = h;
|
||||
luaL_setmetatable(L, "wada.canvas");
|
||||
// Pin the handle in the registry for the app's lifetime. LVGL draws straight
|
||||
@@ -352,12 +399,18 @@ int uiCanvas(lua_State* L) {
|
||||
}
|
||||
|
||||
// ---- label userdata ----
|
||||
struct WidgetUd { lv_obj_t* obj; };
|
||||
WidgetUd* checkLabel(lua_State* L) { return (WidgetUd*)luaL_checkudata(L, 1, "wada.label"); }
|
||||
struct WidgetUd { lv_obj_t* obj; uint32_t gen; };
|
||||
WidgetUd* checkLabel(lua_State* L) {
|
||||
WidgetUd* u = (WidgetUd*)luaL_checkudata(L, 1, "wada.label");
|
||||
if (u->gen != s_ui_gen) u->obj = nullptr; // cleared out from under this handle
|
||||
return u;
|
||||
}
|
||||
|
||||
int lbSet(lua_State* L) {
|
||||
WidgetUd* u = checkLabel(L);
|
||||
if (u->obj) lv_label_set_text(u->obj, luaL_checkstring(L, 2));
|
||||
size_t length = 0;
|
||||
const char* text = luaL_checklstring(L, 2, &length);
|
||||
if (u->obj) lv_label_set_text(u->obj, safeUiText(text, length));
|
||||
return 0;
|
||||
}
|
||||
int lbPos(lua_State* L) {
|
||||
@@ -390,20 +443,31 @@ int lbWidth(lua_State* L) { // label:width(px [, "left"|"center"|"right"]) —
|
||||
|
||||
int uiLabel(lua_State* L) {
|
||||
if (!s_h || !s_h->body) return luaL_error(L, "no app body");
|
||||
size_t length = 0;
|
||||
const char* text = luaL_checklstring(L, 1, &length);
|
||||
const lv_coord_t x = (lv_coord_t)luaL_optinteger(L, 2, 0);
|
||||
const lv_coord_t y = (lv_coord_t)luaL_optinteger(L, 3, 0);
|
||||
const lv_font_t* font = luaHostFontForSize((int)luaL_optinteger(L, 4, 14));
|
||||
const uint32_t color = argColor(L, 5, 0xE6E9ED);
|
||||
lv_obj_t* l = lv_label_create(s_h->body);
|
||||
lv_label_set_text(l, luaL_checkstring(L, 1));
|
||||
lv_obj_set_pos(l, (lv_coord_t)luaL_optinteger(L, 2, 0), (lv_coord_t)luaL_optinteger(L, 3, 0));
|
||||
lv_obj_set_style_text_font(l, luaHostFontForSize((int)luaL_optinteger(L, 4, 14)), LV_PART_MAIN);
|
||||
lv_obj_set_style_text_color(l, lv_color_hex(argColor(L, 5, 0xE6E9ED)), LV_PART_MAIN);
|
||||
lv_label_set_text(l, safeUiText(text, length));
|
||||
lv_obj_set_pos(l, x, y);
|
||||
lv_obj_set_style_text_font(l, font, LV_PART_MAIN);
|
||||
lv_obj_set_style_text_color(l, lv_color_hex(color), LV_PART_MAIN);
|
||||
WidgetUd* ud = (WidgetUd*)lua_newuserdatauv(L, sizeof(WidgetUd), 0);
|
||||
ud->gen = s_ui_gen;
|
||||
ud->obj = l;
|
||||
luaL_setmetatable(L, "wada.label");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// ---- chart userdata (the primitive the native Monitor/Airtime pages use) ----
|
||||
struct ChartUd { lv_obj_t* obj; lv_chart_series_t* ser[2]; int n_ser; };
|
||||
ChartUd* checkChart(lua_State* L) { return (ChartUd*)luaL_checkudata(L, 1, "wada.chart"); }
|
||||
struct ChartUd { lv_obj_t* obj; lv_chart_series_t* ser[2]; int n_ser; uint32_t gen; };
|
||||
ChartUd* checkChart(lua_State* L) {
|
||||
ChartUd* u = (ChartUd*)luaL_checkudata(L, 1, "wada.chart");
|
||||
if (u->gen != s_ui_gen) { u->obj = nullptr; u->n_ser = 0; }
|
||||
return u;
|
||||
}
|
||||
|
||||
int chPush(lua_State* L) { // chart:push(series_idx, value)
|
||||
ChartUd* c = checkChart(L);
|
||||
@@ -470,6 +534,7 @@ int uiChart(lua_State* L) { // wada.ui.chart(w, h, points, color1 [, color2] [
|
||||
lv_obj_set_style_line_color(ch, lv_color_hex(0x1A1D1F), LV_PART_MAIN); // grid
|
||||
lv_obj_set_style_line_width(ch, 2, LV_PART_ITEMS);
|
||||
ChartUd* ud = (ChartUd*)lua_newuserdatauv(L, sizeof(ChartUd), 0);
|
||||
ud->gen = s_ui_gen;
|
||||
ud->obj = ch;
|
||||
ud->n_ser = 0;
|
||||
ud->ser[0] = lv_chart_add_series(ch, lv_color_hex(argColor(L, 4, 0x15B6A6)), LV_CHART_AXIS_PRIMARY_Y);
|
||||
@@ -497,12 +562,17 @@ void btnEventCb(lv_event_t* e) {
|
||||
|
||||
int uiButton(lua_State* L) {
|
||||
if (!s_h || !s_h->body) return luaL_error(L, "no app body");
|
||||
const char* txt = luaL_checkstring(L, 1);
|
||||
size_t text_length = 0;
|
||||
const char* text = luaL_checklstring(L, 1, &text_length);
|
||||
const lv_coord_t x = (lv_coord_t)luaL_checkinteger(L, 2);
|
||||
const lv_coord_t y = (lv_coord_t)luaL_checkinteger(L, 3);
|
||||
const lv_coord_t w = (lv_coord_t)luaL_optinteger(L, 4, 90);
|
||||
const lv_coord_t h = (lv_coord_t)luaL_optinteger(L, 5, 34);
|
||||
lv_obj_t* b = lv_btn_create(s_h->body);
|
||||
lv_obj_set_pos(b, (lv_coord_t)luaL_checkinteger(L, 2), (lv_coord_t)luaL_checkinteger(L, 3));
|
||||
lv_obj_set_size(b, (lv_coord_t)luaL_optinteger(L, 4, 90), (lv_coord_t)luaL_optinteger(L, 5, 34));
|
||||
lv_obj_set_pos(b, x, y);
|
||||
lv_obj_set_size(b, w, h);
|
||||
lv_obj_t* bl = lv_label_create(b);
|
||||
lv_label_set_text(bl, txt);
|
||||
lv_label_set_text(bl, safeUiText(text, text_length));
|
||||
lv_obj_center(bl);
|
||||
if (lua_isfunction(L, 6)) {
|
||||
lua_rawgeti(L, LUA_REGISTRYINDEX, s_h->ref_btncb);
|
||||
@@ -513,6 +583,7 @@ int uiButton(lua_State* L) {
|
||||
lv_obj_add_event_cb(b, btnEventCb, LV_EVENT_CLICKED, nullptr);
|
||||
}
|
||||
WidgetUd* ud = (WidgetUd*)lua_newuserdatauv(L, sizeof(WidgetUd), 0);
|
||||
ud->gen = s_ui_gen;
|
||||
ud->obj = b;
|
||||
luaL_setmetatable(L, "wada.label"); // shares set/pos/color methods
|
||||
return 1;
|
||||
@@ -529,8 +600,12 @@ int uiButton(lua_State* L) {
|
||||
// keyboard/trackball focus navigation walks them on touchless boards for free,
|
||||
// and a tap works on the ones with a screen. Per-row callbacks ride the same
|
||||
// button-callback table uiButton uses.
|
||||
struct ListUd { lv_obj_t* obj; int sel; };
|
||||
ListUd* checkList(lua_State* L) { return (ListUd*)luaL_checkudata(L, 1, "wada.list"); }
|
||||
struct ListUd { lv_obj_t* obj; int sel; uint32_t gen; };
|
||||
ListUd* checkList(lua_State* L) {
|
||||
ListUd* u = (ListUd*)luaL_checkudata(L, 1, "wada.list");
|
||||
if (u->gen != s_ui_gen) u->obj = nullptr;
|
||||
return u;
|
||||
}
|
||||
|
||||
lv_obj_t* listRowAt(ListUd* u, int i) { // i is 1-based, as everywhere in Lua
|
||||
if (!u->obj || i < 1) return nullptr;
|
||||
@@ -554,6 +629,7 @@ int uiTextW(lua_State* L) {
|
||||
size_t len = 0;
|
||||
const char* txt = luaL_checklstring(L, 1, &len);
|
||||
const lv_font_t* f = luaHostFontForSize((int)luaL_optinteger(L, 2, 14));
|
||||
txt = safeUiText(txt, len, &len);
|
||||
lua_pushinteger(L, (lua_Integer)lv_txt_get_width(txt, (uint32_t)len, f, 0, LV_TEXT_FLAG_NONE));
|
||||
return 1;
|
||||
}
|
||||
@@ -566,6 +642,7 @@ int uiTextLines(lua_State* L) {
|
||||
const char* txt = luaL_checklstring(L, 1, &len);
|
||||
const int w = (int)luaL_checkinteger(L, 2);
|
||||
const lv_font_t* f = luaHostFontForSize((int)luaL_optinteger(L, 3, 14));
|
||||
txt = safeUiText(txt, len, &len);
|
||||
if (w <= 0) { lua_pushinteger(L, 1); return 1; }
|
||||
int lines = 0;
|
||||
const char* p = txt;
|
||||
@@ -580,6 +657,24 @@ int uiTextLines(lua_State* L) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// wada.ui.clear() -- remove every widget from the app's page.
|
||||
//
|
||||
// The SDK documented building widgets in on_open() and gave no way to take them
|
||||
// down again, so an app with more than one screen drew the second on top of the
|
||||
// first. Reported by pisti87, who had been working around it with buttons.
|
||||
//
|
||||
// Bumping the generation is what makes this safe. A Lua variable still holding a
|
||||
// label from the screen just cleared keeps a raw pointer to freed memory; the
|
||||
// accessors compare generations and null it, so the existing `if (u->obj)` guard
|
||||
// in every method turns a stale call into a no-op instead of a crash.
|
||||
static int uiClear(lua_State* L) {
|
||||
(void)L;
|
||||
if (!s_h || !s_h->body) return 0;
|
||||
lv_obj_clean(s_h->body); // deletes the children, keeps the page itself
|
||||
++s_ui_gen;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int uiList(lua_State* L) {
|
||||
if (!s_h || !s_h->body) return luaL_error(L, "no app body");
|
||||
const int x = (int)luaL_checkinteger(L, 1), y = (int)luaL_checkinteger(L, 2);
|
||||
@@ -597,6 +692,7 @@ int uiList(lua_State* L) {
|
||||
lv_obj_set_scroll_dir(c, LV_DIR_VER);
|
||||
lv_obj_add_flag(c, LV_OBJ_FLAG_SCROLLABLE);
|
||||
ListUd* ud = (ListUd*)lua_newuserdatauv(L, sizeof(ListUd), 0);
|
||||
ud->gen = s_ui_gen;
|
||||
ud->obj = c; ud->sel = 0;
|
||||
luaL_setmetatable(L, "wada.list");
|
||||
return 1;
|
||||
@@ -605,7 +701,8 @@ int uiList(lua_State* L) {
|
||||
// list:add(text [, fn]) -> index of the new row
|
||||
int lsAdd(lua_State* L) {
|
||||
ListUd* u = checkList(L);
|
||||
const char* txt = luaL_checkstring(L, 2);
|
||||
size_t text_length = 0;
|
||||
const char* text = luaL_checklstring(L, 2, &text_length);
|
||||
if (!u->obj || !s_h) return 0;
|
||||
lv_obj_t* row = lv_btn_create(u->obj);
|
||||
lv_obj_set_width(row, LV_PCT(100));
|
||||
@@ -614,7 +711,7 @@ int lsAdd(lua_State* L) {
|
||||
lv_obj_set_style_pad_all(row, 6, LV_PART_MAIN);
|
||||
listPaintRow(row, false);
|
||||
lv_obj_t* lb = lv_label_create(row);
|
||||
lv_label_set_text(lb, txt);
|
||||
lv_label_set_text(lb, safeUiText(text, text_length));
|
||||
lv_label_set_long_mode(lb, LV_LABEL_LONG_DOT); // a long name truncates instead of reflowing the row
|
||||
lv_obj_set_width(lb, LV_PCT(100));
|
||||
lv_obj_set_style_text_font(lb, luaHostFontForSize(12), LV_PART_MAIN);
|
||||
@@ -633,9 +730,11 @@ int lsAdd(lua_State* L) {
|
||||
int lsSet(lua_State* L) {
|
||||
ListUd* u = checkList(L);
|
||||
lv_obj_t* row = listRowAt(u, (int)luaL_checkinteger(L, 2));
|
||||
size_t text_length = 0;
|
||||
const char* text = luaL_checklstring(L, 3, &text_length);
|
||||
if (!row) return 0;
|
||||
lv_obj_t* lb = lv_obj_get_child(row, 0);
|
||||
if (lb) lv_label_set_text(lb, luaL_checkstring(L, 3));
|
||||
if (lb) lv_label_set_text(lb, safeUiText(text, text_length));
|
||||
return 0;
|
||||
}
|
||||
int lsColor(lua_State* L) {
|
||||
@@ -764,12 +863,17 @@ int sysBeep(lua_State* L) { lua_pushboolean(L, luaHostBeep()); return 1; }
|
||||
// rather than assume: the extended SDK is absent on low-resource boards (see
|
||||
// CAP_LUA_SDK_EXT in device_caps.h), and a store app runs on all of them.
|
||||
int sysCaps(lua_State* L) {
|
||||
lua_createtable(L, 0, 14);
|
||||
lua_createtable(L, 0, 20);
|
||||
lua_pushboolean(L, CAP_LUA_SDK_EXT); lua_setfield(L, -2, "sdk_ext");
|
||||
lua_pushboolean(L, CAP_KEYBOARD); lua_setfield(L, -2, "keyboard");
|
||||
lua_pushboolean(L, CAP_TOUCH); lua_setfield(L, -2, "touch");
|
||||
lua_pushboolean(L, CAP_SD); lua_setfield(L, -2, "sd");
|
||||
lua_pushboolean(L, CAP_LUA_SD_LIST); lua_setfield(L, -2, "sd_list");
|
||||
lua_pushboolean(L, CAP_LUA_AUDIO); lua_setfield(L, -2, "audio");
|
||||
lua_pushboolean(L, CAP_LUA_AUDIO); lua_setfield(L, -2, "audio_wav");
|
||||
lua_pushboolean(L, CAP_LUA_AUDIO); lua_setfield(L, -2, "audio_mp3");
|
||||
lua_pushboolean(L, CAP_LUA_AUDIO && CAP_LUA_SD_LIST);
|
||||
lua_setfield(L, -2, "audio_sd");
|
||||
// Feature flags for the calls added after the first extended SDK shipped, so
|
||||
// an app can degrade instead of erroring on firmware that predates them.
|
||||
lua_pushboolean(L, CAP_LUA_SDK_EXT); lua_setfield(L, -2, "discover"); // wada.mesh.discover
|
||||
@@ -1563,6 +1667,93 @@ int sdList(lua_State* L) {
|
||||
#endif
|
||||
#endif // CAP_LUA_SDK_EXT
|
||||
|
||||
#if CAP_LUA_AUDIO
|
||||
static bool audioSafeName(const char* name, size_t len) {
|
||||
if (!name || len == 0 || len > 32 || name[0] == '.') return false;
|
||||
for (size_t i = 0; i < len; ++i) {
|
||||
const char c = name[i];
|
||||
const bool ok = (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
|
||||
(c >= '0' && c <= '9') || c == '.' || c == '_' || c == '-';
|
||||
if (!ok) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static int audioError(lua_State* L, const char* error) {
|
||||
lua_pushnil(L);
|
||||
lua_pushstring(L, error);
|
||||
return 2;
|
||||
}
|
||||
|
||||
// wada.audio.play(name) resolves inside /apps/<id>.d on the board's active
|
||||
// storage backend. An explicit sd:/... path is available only with sd_list.
|
||||
int audioPlay(lua_State* L) {
|
||||
if (!s_h) return audioError(L, "closed");
|
||||
size_t requested_len = 0;
|
||||
const char* requested = luaL_checklstring(L, 1, &requested_len);
|
||||
fs::FS* fs = nullptr;
|
||||
char path[224] = "";
|
||||
const char* source = "app";
|
||||
|
||||
if (requested_len >= 3 && !memcmp(requested, "sd:", 3)) {
|
||||
#if CAP_LUA_SD_LIST
|
||||
const char* card_path = requested + 3;
|
||||
const size_t card_len = requested_len - 3;
|
||||
if (!sdSafePath(card_path, card_len, path, sizeof path)) return audioError(L, "bad path");
|
||||
bool busy = false;
|
||||
fs = luaHostSdFs(&busy);
|
||||
if (!fs) return audioError(L, busy ? "busy" : "no sd");
|
||||
source = "sd";
|
||||
#else
|
||||
return audioError(L, "no sd");
|
||||
#endif
|
||||
} else {
|
||||
if (!audioSafeName(requested, requested_len)) return audioError(L, "bad path");
|
||||
fs = luaHostAppFs();
|
||||
if (!fs) return audioError(L, "no storage");
|
||||
char rel[96];
|
||||
snprintf(rel, sizeof rel, "/apps/%s.d/%s", s_h->id, requested);
|
||||
luaHostAppPath(path, sizeof path, rel);
|
||||
}
|
||||
|
||||
char error[40] = "";
|
||||
if (!luaHostAudioPlay(fs, path, requested, source, s_h->generation, error, sizeof error))
|
||||
return audioError(L, error[0] ? error : "playback failed");
|
||||
lua_pushboolean(L, 1);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int audioPause(lua_State* L) {
|
||||
lua_pushboolean(L, s_h && luaHostAudioPause(s_h->generation, true));
|
||||
return 1;
|
||||
}
|
||||
|
||||
int audioResume(lua_State* L) {
|
||||
lua_pushboolean(L, s_h && luaHostAudioPause(s_h->generation, false));
|
||||
return 1;
|
||||
}
|
||||
|
||||
int audioStop(lua_State* L) {
|
||||
lua_pushboolean(L, s_h && luaHostAudioStop(s_h->generation, false));
|
||||
return 1;
|
||||
}
|
||||
|
||||
int audioStatus(lua_State* L) {
|
||||
char state[12], path[192], source[8], format[8], error[40];
|
||||
luaHostAudioStatus(s_h ? s_h->generation : 0,
|
||||
state, sizeof state, path, sizeof path,
|
||||
source, sizeof source, format, sizeof format,
|
||||
error, sizeof error);
|
||||
lua_createtable(L, 0, 5);
|
||||
lua_pushstring(L, state); lua_setfield(L, -2, "state");
|
||||
lua_pushstring(L, path); lua_setfield(L, -2, "path");
|
||||
lua_pushstring(L, source); lua_setfield(L, -2, "source");
|
||||
lua_pushstring(L, format); lua_setfield(L, -2, "format");
|
||||
if (error[0]) { lua_pushstring(L, error); lua_setfield(L, -2, "error"); }
|
||||
return 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
void storePath(char* out, size_t cap) {
|
||||
char rel[40];
|
||||
snprintf(rel, sizeof rel, "/apps/%s.sav", s_h->id);
|
||||
@@ -1694,10 +1885,35 @@ void pressCb(lv_event_t* e) {
|
||||
}
|
||||
|
||||
// ---- wada.mesh (read-only) ----
|
||||
// wada.mesh.contacts([offset], [limit])
|
||||
//
|
||||
// Used to scan the first 200 contacts and return at most 100, with no way to see
|
||||
// past that and nothing to say it had truncated. On a device with 2000 contacts
|
||||
// an app simply could not reach most of them (reported by Jade).
|
||||
//
|
||||
// It takes an offset and a limit now. The default limit stays 100 because every
|
||||
// entry is an 8-field Lua table and building thousands of them in one call would
|
||||
// blow the per-app heap -- but the window can be moved, so all contacts are
|
||||
// reachable by asking twice. wada.mesh.contact_count() says how many there are.
|
||||
// How many contacts the device holds, so an app can page through them without
|
||||
// calling contacts() repeatedly just to discover where the list ends.
|
||||
static int meshContactCount(lua_State* L) {
|
||||
char name[36], pk[12]; int type; uint32_t ago; double lat, lon; int32_t lat6, lon6;
|
||||
int n = 0;
|
||||
while (n < 4096 && luaHostContactAt(n, name, sizeof name, &type, &ago, &lat, &lon,
|
||||
pk, sizeof pk, &lat6, &lon6)) ++n;
|
||||
lua_pushinteger(L, n);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int meshContacts(lua_State* L) {
|
||||
const int offset = (int)luaL_optinteger(L, 1, 0);
|
||||
int limit = (int)luaL_optinteger(L, 2, 100);
|
||||
if (limit < 1) limit = 1;
|
||||
if (limit > 250) limit = 250; // one call still has to fit the app's heap
|
||||
lua_newtable(L);
|
||||
char name[36], pk[12]; int type; uint32_t ago; double lat, lon; int32_t lat6, lon6;
|
||||
for (int i = 0, out = 0; i < 200 && out < 100; i++) {
|
||||
for (int i = (offset > 0 ? offset : 0), out = 0; out < limit; i++) {
|
||||
if (!luaHostContactAt(i, name, sizeof name, &type, &ago, &lat, &lon, pk, sizeof pk,
|
||||
&lat6, &lon6)) break;
|
||||
lua_createtable(L, 0, 8);
|
||||
@@ -1939,11 +2155,14 @@ void promptDeliver(const char* text) {
|
||||
}
|
||||
|
||||
int uiInput(lua_State* L) {
|
||||
const char* title = luaL_optstring(L, 1, "");
|
||||
const char* initial = luaL_optstring(L, 2, "");
|
||||
size_t title_length = 0, initial_length = 0;
|
||||
const char* title_raw = luaL_optlstring(L, 1, "", &title_length);
|
||||
const char* initial_raw = luaL_optlstring(L, 2, "", &initial_length);
|
||||
luaL_checktype(L, 3, LUA_TFUNCTION);
|
||||
if (!s_h) return luaL_error(L, "no app");
|
||||
if (s_h->prompt_cb != LUA_NOREF) return luaL_error(L, "an input prompt is already open");
|
||||
const char* title = safeUiText(title_raw, title_length, nullptr, 0);
|
||||
const char* initial = safeUiText(initial_raw, initial_length, nullptr, 1);
|
||||
lua_pushvalue(L, 3);
|
||||
s_h->prompt_cb = luaL_ref(L, LUA_REGISTRYINDEX);
|
||||
luaHostTextPrompt(title, initial, promptDeliver);
|
||||
@@ -2105,6 +2324,7 @@ void openWada(lua_State* L) {
|
||||
lua_setfield(L, -2, "colors");
|
||||
lua_pushcfunction(L, uiInput); lua_setfield(L, -2, "input"); // modal text entry
|
||||
lua_pushcfunction(L, uiList); lua_setfield(L, -2, "list"); // scrollable selectable rows
|
||||
lua_pushcfunction(L, uiClear); lua_setfield(L, -2, "clear"); // wipe the page (#318 / Discord)
|
||||
lua_setfield(L, -2, "ui");
|
||||
|
||||
lua_newtable(L); // wada.sys
|
||||
@@ -2172,8 +2392,19 @@ void openWada(lua_State* L) {
|
||||
lua_setfield(L, -2, "sd");
|
||||
#endif
|
||||
|
||||
#if CAP_LUA_AUDIO
|
||||
lua_newtable(L); // wada.audio (app storage + optional SD)
|
||||
lua_pushcfunction(L, audioPlay); lua_setfield(L, -2, "play");
|
||||
lua_pushcfunction(L, audioPause); lua_setfield(L, -2, "pause");
|
||||
lua_pushcfunction(L, audioResume); lua_setfield(L, -2, "resume");
|
||||
lua_pushcfunction(L, audioStop); lua_setfield(L, -2, "stop");
|
||||
lua_pushcfunction(L, audioStatus); lua_setfield(L, -2, "status");
|
||||
lua_setfield(L, -2, "audio");
|
||||
#endif
|
||||
|
||||
lua_newtable(L); // wada.mesh (read-only)
|
||||
lua_pushcfunction(L, meshContacts); lua_setfield(L, -2, "contacts");
|
||||
lua_pushcfunction(L, meshContactCount); lua_setfield(L, -2, "contact_count");
|
||||
lua_pushcfunction(L, meshRxLog); lua_setfield(L, -2, "rx_log");
|
||||
lua_pushcfunction(L, meshStats); lua_setfield(L, -2, "stats");
|
||||
lua_pushcfunction(L, meshSelf); lua_setfield(L, -2, "self");
|
||||
@@ -2397,6 +2628,9 @@ void hostTeardown() {
|
||||
Host* h = s_h;
|
||||
if (!h) return;
|
||||
s_h = nullptr; // bindings see "closed" from here on
|
||||
#if CAP_LUA_AUDIO
|
||||
luaHostAudioStop(h->generation, true);
|
||||
#endif
|
||||
// A wada.ui.input dialog lives on lv_layer_top, so it would outlive the app
|
||||
// that opened it. Drop it before the state goes, not after.
|
||||
luaHostTextPromptDismiss();
|
||||
@@ -2465,6 +2699,8 @@ bool luaAppLaunch(const char* id, const char* title, const char* src, size_t len
|
||||
|
||||
Host* h = new Host();
|
||||
h->heap.cap = kHeapCap;
|
||||
h->generation = ++s_host_generation;
|
||||
if (!h->generation) h->generation = ++s_host_generation;
|
||||
snprintf(h->id, sizeof h->id, "%s", id);
|
||||
snprintf(h->title, sizeof h->title, "%s", title ? title : id);
|
||||
|
||||
|
||||
@@ -0,0 +1,287 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "ReaderContent.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
|
||||
namespace ReaderContent {
|
||||
namespace {
|
||||
|
||||
bool ciPrefix(const char* text, size_t text_len, const char* prefix) {
|
||||
const size_t prefix_len = strlen(prefix);
|
||||
if (text_len < prefix_len) return false;
|
||||
for (size_t i = 0; i < prefix_len; ++i) {
|
||||
char actual = text[i];
|
||||
char expected = prefix[i];
|
||||
if (actual >= 'A' && actual <= 'Z') actual += 32;
|
||||
if (expected >= 'A' && expected <= 'Z') expected += 32;
|
||||
if (actual != expected) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t decodeEntity(const char* text, size_t text_len, char* out, int* wrote) {
|
||||
static const struct { const char* name; const char* utf8; } named[] = {
|
||||
{"amp;","&"},{"lt;","<"},{"gt;",">"},{"quot;","\""},{"apos;","'"},{"nbsp;"," "},
|
||||
{"mdash;","\xe2\x80\x94"},{"ndash;","\xe2\x80\x93"},{"hellip;","\xe2\x80\xa6"},
|
||||
{"lsquo;","\xe2\x80\x98"},{"rsquo;","\xe2\x80\x99"},{"ldquo;","\xe2\x80\x9c"},
|
||||
{"rdquo;","\xe2\x80\x9d"},{"copy;","\xc2\xa9"},{"reg;","\xc2\xae"},{"euro;","\xe2\x82\xac"},
|
||||
{"deg;","\xc2\xb0"},{"middot;","\xc2\xb7"},{"bull;","\xe2\x80\xa2"},{"trade;","\xe2\x84\xa2"},
|
||||
};
|
||||
for (const auto& entity : named) {
|
||||
const size_t name_len = strlen(entity.name);
|
||||
if (text_len > name_len && strncmp(text + 1, entity.name, name_len) == 0) {
|
||||
const int width = strlen(entity.utf8);
|
||||
memcpy(out, entity.utf8, width);
|
||||
*wrote = width;
|
||||
return name_len + 1;
|
||||
}
|
||||
}
|
||||
if (text_len > 3 && text[1] == '#') {
|
||||
long codepoint = 0;
|
||||
const bool hex = text[2] == 'x' || text[2] == 'X';
|
||||
size_t i = hex ? 3 : 2;
|
||||
const size_t start = i;
|
||||
for (; i < text_len && text[i] != ';'; ++i) {
|
||||
const char c = text[i];
|
||||
int digit;
|
||||
if (c >= '0' && c <= '9') digit = c - '0';
|
||||
else if (hex && c >= 'a' && c <= 'f') digit = c - 'a' + 10;
|
||||
else if (hex && c >= 'A' && c <= 'F') digit = c - 'A' + 10;
|
||||
else { i = start; break; }
|
||||
codepoint = codepoint * (hex ? 16 : 10) + digit;
|
||||
}
|
||||
if (i > start && i < text_len && text[i] == ';' && codepoint > 0 && codepoint <= 0x10FFFF) {
|
||||
int width = 0;
|
||||
if (codepoint < 0x80) out[width++] = static_cast<char>(codepoint);
|
||||
else if (codepoint < 0x800) {
|
||||
out[width++] = 0xC0 | (codepoint >> 6);
|
||||
out[width++] = 0x80 | (codepoint & 0x3F);
|
||||
} else if (codepoint < 0x10000) {
|
||||
out[width++] = 0xE0 | (codepoint >> 12);
|
||||
out[width++] = 0x80 | ((codepoint >> 6) & 0x3F);
|
||||
out[width++] = 0x80 | (codepoint & 0x3F);
|
||||
} else {
|
||||
out[width++] = 0xF0 | (codepoint >> 18);
|
||||
out[width++] = 0x80 | ((codepoint >> 12) & 0x3F);
|
||||
out[width++] = 0x80 | ((codepoint >> 6) & 0x3F);
|
||||
out[width++] = 0x80 | (codepoint & 0x3F);
|
||||
}
|
||||
*wrote = width;
|
||||
return i + 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool tagAttribute(const char* tag, size_t tag_len, const char* attribute,
|
||||
char* out, size_t cap) {
|
||||
const size_t attribute_len = strlen(attribute);
|
||||
for (size_t i = 0; i + attribute_len + 1 < tag_len; ++i) {
|
||||
if (!ciPrefix(tag + i, tag_len - i, attribute)) continue;
|
||||
if (i > 0 && tag[i - 1] != ' ' && tag[i - 1] != '\t' &&
|
||||
tag[i - 1] != '\r' && tag[i - 1] != '\n') continue;
|
||||
size_t cursor = i + attribute_len;
|
||||
while (cursor < tag_len && (tag[cursor] == ' ' || tag[cursor] == '\t')) ++cursor;
|
||||
if (cursor >= tag_len || tag[cursor] != '=') continue;
|
||||
++cursor;
|
||||
while (cursor < tag_len && (tag[cursor] == ' ' || tag[cursor] == '\t')) ++cursor;
|
||||
char quote = 0;
|
||||
if (cursor < tag_len && (tag[cursor] == '"' || tag[cursor] == '\'')) {
|
||||
quote = tag[cursor++];
|
||||
}
|
||||
size_t written = 0;
|
||||
while (cursor < tag_len && written + 1 < cap) {
|
||||
const char c = tag[cursor];
|
||||
if (quote ? c == quote : (c == ' ' || c == '>' || c == '\t')) break;
|
||||
out[written++] = c;
|
||||
++cursor;
|
||||
}
|
||||
out[written] = 0;
|
||||
return written > 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool resolveUrl(const char* base, const char* href, char* out, size_t cap) {
|
||||
if (!base || !href || !out || cap == 0) return false;
|
||||
while (*href == ' ') ++href;
|
||||
const size_t href_len = strlen(href);
|
||||
if (!href_len || href[0] == '#') return false;
|
||||
if (ciPrefix(href, href_len, "javascript:") || ciPrefix(href, href_len, "mailto:") ||
|
||||
ciPrefix(href, href_len, "tel:") || ciPrefix(href, href_len, "data:")) return false;
|
||||
|
||||
if (ciPrefix(href, href_len, "http://") || ciPrefix(href, href_len, "https://") ||
|
||||
ciPrefix(href, href_len, "sd:/")) {
|
||||
snprintf(out, cap, "%s", href);
|
||||
} else if (ciPrefix(base, strlen(base), "sd:/")) {
|
||||
if (href[0] == '/' && href[1] == '/') {
|
||||
snprintf(out, cap, "https:%s", href);
|
||||
} else if (href[0] == '/') {
|
||||
snprintf(out, cap, "sd:%s", href);
|
||||
} else {
|
||||
const char* last_slash = strrchr(base + 3, '/');
|
||||
if (last_slash) snprintf(out, cap, "%.*s%s", static_cast<int>(last_slash - base + 1), base, href);
|
||||
else snprintf(out, cap, "sd:/%s", href);
|
||||
}
|
||||
} else {
|
||||
const char* scheme_end = strstr(base, "://");
|
||||
if (!scheme_end) return false;
|
||||
const int scheme_len = static_cast<int>(scheme_end - base);
|
||||
const char* host = scheme_end + 3;
|
||||
const char* host_end = strchr(host, '/');
|
||||
const int host_len = host_end ? static_cast<int>(host_end - host) : static_cast<int>(strlen(host));
|
||||
if (href[0] == '/' && href[1] == '/') {
|
||||
snprintf(out, cap, "%.*s:%s", scheme_len, base, href);
|
||||
} else if (href[0] == '/') {
|
||||
snprintf(out, cap, "%.*s://%.*s%s", scheme_len, base, host_len, host, href);
|
||||
} else {
|
||||
const char* last_slash = strrchr(base, '/');
|
||||
if (last_slash && last_slash > scheme_end + 2)
|
||||
snprintf(out, cap, "%.*s%s", static_cast<int>(last_slash - base + 1), base, href);
|
||||
else
|
||||
snprintf(out, cap, "%.*s://%.*s/%s", scheme_len, base, host_len, host, href);
|
||||
}
|
||||
}
|
||||
char* fragment = strchr(out, '#');
|
||||
if (fragment) *fragment = 0;
|
||||
return out[0] != 0;
|
||||
}
|
||||
|
||||
size_t htmlToText(const char* html, size_t html_len,
|
||||
char* out, size_t out_cap, const char* base,
|
||||
Link* links, size_t link_capacity, size_t* link_count) {
|
||||
if (link_count) *link_count = 0;
|
||||
if (!html || !out || out_cap == 0) return 0;
|
||||
|
||||
size_t out_len = 0;
|
||||
size_t links_len = 0;
|
||||
int pending_newlines = 0;
|
||||
bool pending_space = false;
|
||||
bool started = false;
|
||||
bool in_anchor = false;
|
||||
uint32_t anchor_start = 0;
|
||||
char anchor_href[HREF_CAPACITY] = "";
|
||||
auto emit = [&](char c) { if (out_len + 1 < out_cap) out[out_len++] = c; };
|
||||
auto flush = [&]() {
|
||||
if (!started) {
|
||||
pending_newlines = 0;
|
||||
pending_space = false;
|
||||
return;
|
||||
}
|
||||
while (pending_newlines > 0) { emit('\n'); --pending_newlines; }
|
||||
if (pending_space) { emit(' '); pending_space = false; }
|
||||
};
|
||||
auto finishAnchor = [&]() {
|
||||
if (in_anchor && out_len > anchor_start && links && links_len < link_capacity) {
|
||||
links[links_len].start = anchor_start;
|
||||
links[links_len].end = static_cast<uint32_t>(out_len);
|
||||
snprintf(links[links_len].href, sizeof links[links_len].href, "%s", anchor_href);
|
||||
++links_len;
|
||||
}
|
||||
in_anchor = false;
|
||||
};
|
||||
static const char* blocks[] = {
|
||||
"p","div","br","li","ul","ol","tr","h1","h2","h3","h4","h5","h6",
|
||||
"section","article","header","footer","table","blockquote","pre","hr","nav","title","body", nullptr
|
||||
};
|
||||
|
||||
size_t i = 0;
|
||||
while (i < html_len && out_len + 5 < out_cap) {
|
||||
const char c = html[i];
|
||||
if (c == '<') {
|
||||
if (html_len - i >= 4 && html[i + 1] == '!' && html[i + 2] == '-' && html[i + 3] == '-') {
|
||||
size_t end = i + 4;
|
||||
while (end + 2 < html_len && !(html[end] == '-' && html[end + 1] == '-' && html[end + 2] == '>')) ++end;
|
||||
i = end + 2 < html_len ? end + 3 : html_len;
|
||||
continue;
|
||||
}
|
||||
const bool script = ciPrefix(html + i, html_len - i, "<script");
|
||||
const bool style = !script && ciPrefix(html + i, html_len - i, "<style");
|
||||
if (script || style) {
|
||||
const char* closing_tag = script ? "</script" : "</style";
|
||||
size_t end = i + 1;
|
||||
while (end < html_len && !(html[end] == '<' && ciPrefix(html + end, html_len - end, closing_tag))) ++end;
|
||||
while (end < html_len && html[end] != '>') ++end;
|
||||
i = end < html_len ? end + 1 : html_len;
|
||||
if (pending_newlines < 2) ++pending_newlines;
|
||||
continue;
|
||||
}
|
||||
|
||||
size_t name_cursor = i + 1;
|
||||
const bool closing = name_cursor < html_len && html[name_cursor] == '/';
|
||||
if (closing) ++name_cursor;
|
||||
char name[12];
|
||||
int name_len = 0;
|
||||
while (name_cursor < html_len && name_len < 11) {
|
||||
char value = html[name_cursor];
|
||||
if ((value >= 'a' && value <= 'z') || (value >= 'A' && value <= 'Z') ||
|
||||
(value >= '0' && value <= '9')) {
|
||||
name[name_len++] = value >= 'A' && value <= 'Z' ? value + 32 : value;
|
||||
++name_cursor;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
name[name_len] = 0;
|
||||
size_t tag_end = i + 1;
|
||||
while (tag_end < html_len && html[tag_end] != '>') ++tag_end;
|
||||
if (name[0] == 'a' && name[1] == 0) {
|
||||
finishAnchor();
|
||||
if (!closing) {
|
||||
char raw_href[300];
|
||||
if (base && links &&
|
||||
tagAttribute(html + i, (tag_end < html_len ? tag_end : html_len) - i,
|
||||
"href", raw_href, sizeof raw_href) &&
|
||||
resolveUrl(base, raw_href, anchor_href, sizeof anchor_href)) {
|
||||
flush();
|
||||
in_anchor = true;
|
||||
anchor_start = static_cast<uint32_t>(out_len);
|
||||
}
|
||||
}
|
||||
}
|
||||
i = tag_end < html_len ? tag_end + 1 : html_len;
|
||||
for (int block = 0; blocks[block]; ++block) {
|
||||
if (strcmp(name, blocks[block]) == 0) {
|
||||
pending_space = false;
|
||||
if (pending_newlines < 2) ++pending_newlines;
|
||||
break;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (c == '&') {
|
||||
char entity[8];
|
||||
int width = 0;
|
||||
const size_t used = decodeEntity(html + i, html_len - i, entity, &width);
|
||||
if (used) {
|
||||
for (int byte = 0; byte < width; ++byte) {
|
||||
if (entity[byte] == ' ') {
|
||||
if (started) pending_space = true;
|
||||
} else {
|
||||
flush(); emit(entity[byte]); started = true;
|
||||
}
|
||||
}
|
||||
i += used;
|
||||
continue;
|
||||
}
|
||||
flush(); emit('&'); started = true; ++i;
|
||||
continue;
|
||||
}
|
||||
if (c == ' ' || c == '\t' || c == '\r' || c == '\n') {
|
||||
if (started) pending_space = true;
|
||||
++i;
|
||||
continue;
|
||||
}
|
||||
flush(); emit(c); started = true; ++i;
|
||||
}
|
||||
finishAnchor();
|
||||
out[out_len < out_cap ? out_len : out_cap - 1] = 0;
|
||||
if (link_count) *link_count = links_len;
|
||||
return out_len;
|
||||
}
|
||||
|
||||
} // namespace ReaderContent
|
||||
@@ -0,0 +1,24 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
namespace ReaderContent {
|
||||
|
||||
constexpr size_t HREF_CAPACITY = 240;
|
||||
|
||||
struct Link {
|
||||
uint32_t start;
|
||||
uint32_t end;
|
||||
char href[HREF_CAPACITY];
|
||||
};
|
||||
|
||||
bool resolveUrl(const char* base, const char* href, char* out, size_t cap);
|
||||
|
||||
size_t htmlToText(const char* html, size_t html_len,
|
||||
char* out, size_t out_cap, const char* base,
|
||||
Link* links, size_t link_capacity, size_t* link_count);
|
||||
|
||||
} // namespace ReaderContent
|
||||
@@ -25,9 +25,12 @@ SnakeGame* SnakeGame::s_active = nullptr;
|
||||
bool SnakeGame::isOpen() { return s_active != nullptr && s_active->root_ != nullptr; }
|
||||
|
||||
void SnakeGame::steer(int dx, int dy) {
|
||||
if (!s_active || !s_active->started_ || s_active->over_) return;
|
||||
if (!s_active || s_active->over_) return;
|
||||
// Trackball deltas -> one cardinal direction (dominant axis).
|
||||
if (dx == 0 && dy == 0) return;
|
||||
// Keyboard-only boards (M9) have no touch "New game" tap target; first
|
||||
// directional input should start immediately.
|
||||
if (!s_active->started_) s_active->startGame();
|
||||
const int adx = dx < 0 ? -dx : dx, ady = dy < 0 ? -dy : dy;
|
||||
if (adx >= ady) s_active->setDir(dx > 0 ? 1 : -1, 0);
|
||||
else s_active->setDir(0, dy > 0 ? 1 : -1);
|
||||
@@ -105,7 +108,7 @@ void SnakeGame::updateScoreLabel() {
|
||||
if (over_) lv_label_set_text_fmt(score_, TR("Game over \xe2\x80\x94 score %d (tap to restart)"), score_val_);
|
||||
else if (paused_) lv_label_set_text_fmt(score_, TR("Paused \xe2\x80\x94 score %d"), score_val_);
|
||||
else if (started_) lv_label_set_text_fmt(score_, TR("score %d"), score_val_);
|
||||
else lv_label_set_text(score_, TR("Snake \xe2\x80\x94 swipe or roll the trackball"));
|
||||
else lv_label_set_text(score_, TR("Snake \xe2\x80\x94 swipe, roll, or press a direction"));
|
||||
}
|
||||
|
||||
void SnakeGame::step() {
|
||||
|
||||
@@ -6,9 +6,10 @@
|
||||
// Self-contained Snake mini-game launched from the Apps drawer.
|
||||
//
|
||||
// Owns a full-screen overlay on lv_layer_top: a canvas playfield filling the
|
||||
// unoccupied screen, a score line, and a close button. The game waits on a
|
||||
// "New game" button (doesn't start moving on open). Steer by swipe OR trackball
|
||||
// (UITask routes the trackball here via isOpen()/steer() and hides the cursor);
|
||||
// unoccupied screen, a score line, and a close button. The game can start from
|
||||
// the "New game" button OR from the first directional steer input. Steer by
|
||||
// swipe/gesture, trackball, or hardware directional keys (UITask routes board
|
||||
// input here via isOpen()/steer());
|
||||
// tap to restart after a game over; the X closes it. One instance at a time.
|
||||
//
|
||||
// Decoupled from UITask internals — depends only on LVGL and lvglPsramAlloc.
|
||||
|
||||
+1639
-300
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,78 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <string>
|
||||
|
||||
namespace Utf8Text {
|
||||
|
||||
inline bool continuation(uint8_t byte) {
|
||||
return (byte & 0xC0U) == 0x80U;
|
||||
}
|
||||
|
||||
inline size_t sequenceLength(const uint8_t* text, size_t remaining) {
|
||||
if (!remaining) return 0;
|
||||
const uint8_t first = text[0];
|
||||
if (first <= 0x7F) return 1;
|
||||
if (first >= 0xC2 && first <= 0xDF) {
|
||||
return remaining >= 2 && continuation(text[1]) ? 2 : 0;
|
||||
}
|
||||
if (first == 0xE0) {
|
||||
return remaining >= 3 && text[1] >= 0xA0 && text[1] <= 0xBF && continuation(text[2]) ? 3 : 0;
|
||||
}
|
||||
if ((first >= 0xE1 && first <= 0xEC) || (first >= 0xEE && first <= 0xEF)) {
|
||||
return remaining >= 3 && continuation(text[1]) && continuation(text[2]) ? 3 : 0;
|
||||
}
|
||||
if (first == 0xED) {
|
||||
return remaining >= 3 && text[1] >= 0x80 && text[1] <= 0x9F && continuation(text[2]) ? 3 : 0;
|
||||
}
|
||||
if (first == 0xF0) {
|
||||
return remaining >= 4 && text[1] >= 0x90 && text[1] <= 0xBF &&
|
||||
continuation(text[2]) && continuation(text[3]) ? 4 : 0;
|
||||
}
|
||||
if (first >= 0xF1 && first <= 0xF3) {
|
||||
return remaining >= 4 && continuation(text[1]) && continuation(text[2]) && continuation(text[3]) ? 4 : 0;
|
||||
}
|
||||
if (first == 0xF4) {
|
||||
return remaining >= 4 && text[1] >= 0x80 && text[1] <= 0x8F &&
|
||||
continuation(text[2]) && continuation(text[3]) ? 4 : 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
inline bool valid(const char* text, size_t length) {
|
||||
const uint8_t* bytes = reinterpret_cast<const uint8_t*>(text);
|
||||
size_t offset = 0;
|
||||
while (offset < length) {
|
||||
const size_t sequence = sequenceLength(bytes + offset, length - offset);
|
||||
if (!sequence) return false;
|
||||
offset += sequence;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Returns the original pointer when it is already valid. Malformed runs are
|
||||
// collapsed to one ASCII '?' in scratch so LVGL never receives a partial codepoint.
|
||||
inline const char* sanitize(const char* text, size_t length, std::string& scratch) {
|
||||
scratch.clear();
|
||||
if (valid(text, length)) return text;
|
||||
|
||||
scratch.reserve(length);
|
||||
const uint8_t* bytes = reinterpret_cast<const uint8_t*>(text);
|
||||
size_t offset = 0;
|
||||
while (offset < length) {
|
||||
const size_t sequence = sequenceLength(bytes + offset, length - offset);
|
||||
if (sequence) {
|
||||
scratch.append(text + offset, sequence);
|
||||
offset += sequence;
|
||||
continue;
|
||||
}
|
||||
scratch.push_back('?');
|
||||
++offset;
|
||||
while (offset < length && continuation(bytes[offset])) ++offset;
|
||||
}
|
||||
return scratch.c_str();
|
||||
}
|
||||
|
||||
} // namespace Utf8Text
|
||||
@@ -273,6 +273,15 @@
|
||||
#define CAP_SOUND_FILES 0
|
||||
#endif
|
||||
|
||||
// Sustained PCM output for media playback. This is deliberately independent
|
||||
// of storage: Lua app files may live on internal flash, SD, or SD_MMC.
|
||||
#if defined(HAS_TDECK_GT911) || defined(TLORA_PAGER) || \
|
||||
defined(HAS_TDISPLAY_P4) || defined(HAS_TANMATSU)
|
||||
#define CAP_AUDIO_STREAM 1
|
||||
#else
|
||||
#define CAP_AUDIO_STREAM 0
|
||||
#endif
|
||||
|
||||
// ---- On-device web browser (the "Web" reader app) ---------------------------
|
||||
// The reader fetches pages over on-device HTTPS, and a TLS handshake needs ~30 KB of
|
||||
// free INTERNAL heap. Only the 8 MB-PSRAM boards (T-Deck, Tanmatsu, ThinkNode M9, RAK
|
||||
@@ -294,6 +303,14 @@
|
||||
#define CAP_LUA_APPS 1
|
||||
#endif
|
||||
|
||||
#ifndef CAP_LUA_AUDIO
|
||||
#if CAP_LUA_APPS && CAP_AUDIO_STREAM
|
||||
#define CAP_LUA_AUDIO 1
|
||||
#else
|
||||
#define CAP_LUA_AUDIO 0
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// ---- Extended Lua SDK ------------------------------------------------------
|
||||
// The BASE SDK (drawing, timers, key/value store, read-only mesh + radio stats,
|
||||
// http_get) is board-agnostic and ships everywhere CAP_LUA_APPS is on.
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
#include "device_caps.h"
|
||||
#if defined(HAS_TANMATSU)
|
||||
// Was HAS_TANMATSU-only (Large/Huge UI-scale accented-Latin fallback); the T-Deck
|
||||
// now also builds this for the "at a glance" notification's 20 px message body
|
||||
// (see atGlanceEnsureFont() in UITask.cpp) -- an experiment to see whether a
|
||||
// smaller-than-28px glance body is still legible on that panel.
|
||||
#if defined(HAS_TANMATSU) || defined(HAS_TDECK_GT911)
|
||||
|
||||
/*******************************************************************************
|
||||
* Size: 20 px
|
||||
@@ -2563,4 +2567,4 @@ lv_font_t extras_lat_20 = {
|
||||
#endif /*#if EXTRAS_LAT_20*/
|
||||
|
||||
|
||||
#endif /* HAS_TANMATSU */
|
||||
#endif /* HAS_TANMATSU || HAS_TDECK_GT911 */
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
#include "device_caps.h"
|
||||
#if defined(HAS_TANMATSU)
|
||||
// Was HAS_TANMATSU-only (only consumer used to be the Tanmatsu's Large/Huge UI-scale
|
||||
// accented-Latin fallback); now compiled on every board too for the "at a glance"
|
||||
// notification's 28 px message body (see atGlanceEnsureFont() in UITask.cpp), which
|
||||
// needs accented Latin / em-dash / ellipsis glyph coverage at that size on any board.
|
||||
|
||||
/*******************************************************************************
|
||||
* Size: 28 px
|
||||
@@ -3637,6 +3640,3 @@ lv_font_t extras_lat_28 = {
|
||||
|
||||
|
||||
#endif /*#if EXTRAS_LAT_28*/
|
||||
|
||||
|
||||
#endif /* HAS_TANMATSU */
|
||||
|
||||
@@ -363,7 +363,7 @@ static const I18nPair kBuiltin_hu[] = {
|
||||
{ "History limit off", "Előzménykorlát kikapcsolva" },
|
||||
{ "Home", "Kezdőlap" },
|
||||
{ "Hotkey saved", "Gyorsbillentyű mentése" },
|
||||
{ "How tiles work:\nThe map is built from 256×256 \"slippy\" tiles. Only the tiles for the area you're viewing are fetched — there is no bulk pre-download.\n\nBecause this device can't do HTTPS (not enough heap after Wi-Fi starts) and decodes JPEG far more cheaply than PNG, tiles come from the wadamesh proxy: it fetches the upstream PNG over HTTPS with an identifying User-Agent, re-encodes it as JPEG, and caches it. Your device then caches each tile to its own flash, so a tile is only downloaded once.\n\nUse Options → Reload tiles to re-download the tiles currently in view if one looks corrupted.", "A csempék működése:\nA térkép 256×256 képpontos „slippy” csempékből épül fel. Csak a megtekintett területhez szükséges csempék töltődnek le — nincs tömeges előzetes letöltés.\n\nMivel az eszköz nem támogatja a HTTPS-t (a Wi-Fi elindítása után nincs elegendő heap memória), és a JPEG képek dekódolása sokkal kevesebb erőforrást igényel, mint a PNG-é, a csempék a wadamesh proxyn keresztül érkeznek: a proxy HTTPS-en keresztül lekéri a forrásból a PNG-t egy azonosító User-Agent használatával, JPEG formátumba kódolja át, majd eltárolja a gyorsítótárban. Az eszköz ezután minden csempét a saját flash memóriájában is eltárol, így egy csempét csak egyszer kell letölteni.\n\nAz Options → Reload tiles lehetőséggel újra letöltheted a jelenleg megtekintett terület csempéit, ha valamelyik hibásan jelenik meg." },
|
||||
{ "How tiles work:\nThe map is built from 256×256 \"slippy\" tiles. Only the tiles for the area you're viewing are fetched — there is no bulk pre-download.\n\nBecause this device can't do HTTPS (not enough heap after Wi-Fi starts) and decodes JPEG far more cheaply than PNG, tiles come from the wadamesh proxy: it fetches the upstream PNG over HTTPS with an identifying User-Agent, re-encodes it as JPEG, and caches it. Your device then caches each tile to its own flash, so a tile is only downloaded once.\n\nUse Options → Reload tiles to re-download the tiles currently in view if one looks corrupted.", "A csempék működése:\nA térkép 256×256 képpontos \"slippy\" csempékből épül fel. Csak a megtekintett területhez szükséges csempék töltődnek le — nincs tömeges előzetes letöltés.\n\nMivel az eszköz nem támogatja a HTTPS-t (a Wi-Fi elindítása után nincs elegendő heap memória), és a JPEG képek dekódolása sokkal kevesebb erőforrást igényel, mint a PNG-é, a csempék a wadamesh proxyn keresztül érkeznek: a proxy HTTPS-en keresztül lekéri a forrásból a PNG-t egy azonosító User-Agent használatával, JPEG formátumba kódolja át, majd eltárolja a gyorsítótárban. Az eszköz ezután minden csempét a saját flash memóriájában is eltárol, így egy csempét csak egyszer kell letölteni.\n\nAz Options → Reload tiles lehetőséggel újra letöltheted a jelenleg megtekintett terület csempéit, ha valamelyik hibásan jelenik meg." },
|
||||
{ "How you'll appear to other nodes. You can change this later in Settings.", "Hogyan fogsz megjelenni más csomópontok számára. Ezt később módosíthatod a Beállításokban." },
|
||||
{ "Humidity", "Páratartalom" },
|
||||
{ "I accept the privacy risk", "Elfogadom az adatvédelmi kockázatot" },
|
||||
@@ -386,6 +386,7 @@ static const I18nPair kBuiltin_hu[] = {
|
||||
{ "Install", "Telepít" },
|
||||
{ "Install a previous version", "Telepítse az előző verziót" },
|
||||
{ "Install an earlier version", "Telepítse egy korábbi verziót" },
|
||||
{ "Install beta_%d?\nThis downgrades the firmware and reboots.", "A beta_%d telepítése?\nEz leminősíti a firmware-t és újraindul." },
|
||||
{ "Install failed (network?)", "A telepítés nem sikerült (hálózat?)" },
|
||||
{ "Install update", "Frissítés telepítése" },
|
||||
{ "Installed", "Telepítve" },
|
||||
@@ -723,7 +724,7 @@ static const I18nPair kBuiltin_hu[] = {
|
||||
{ "SD card removed", "SD-kártya eltávolítva" },
|
||||
{ "SD data is unavailable - contacts and channels cannot be saved until the card is reinserted.", "Az SD-kártya adatai nem érhetők el – a névjegyek és a csatornák nem menthetők a kártya újbóli behelyezéséig." },
|
||||
{ "SD data is unavailable - identity, settings, contacts and channels cannot be saved until the card is reinserted.", "Az SD-kártya adatai nem érhetők el – az azonosító, a beállítások, a névjegyek és a csatornák nem menthetők a kártya újbóli behelyezéséig." },
|
||||
{ "SD data migration is incomplete. Identity and settings remain internal; use Copy internal data to SD to retry.", "Az SD-kártya adatainak áthelyezése nem teljes. Az azonosító és a beállítások továbbra is a belső tárhelyen vannak; a folytatáshoz használd a „Belső adatok másolása SD-kártyára” lehetőséget." },
|
||||
{ "SD data migration is incomplete. Identity and settings remain internal; use Copy internal data to SD to retry.", "Az SD-kártya adatainak áthelyezése nem teljes. Az azonosító és a beállítások továbbra is a belső tárhelyen vannak; a folytatáshoz használd a \"Belső adatok másolása SD-kártyára\" lehetőséget." },
|
||||
{ "SD download failed: %s", "SD-letöltés sikertelen: %s" },
|
||||
{ "SD formatted - %s (MESHCOMOD)", "SD formázás - %s (MESHCOMOD)" },
|
||||
{ "SD is busy - close tools and retry", "Az SD-kártya foglalt – zárd be az eszközöket, majd próbáld újra." },
|
||||
@@ -835,6 +836,7 @@ static const I18nPair kBuiltin_hu[] = {
|
||||
{ "Sightline", "Rálátás" },
|
||||
{ "Signal", "Jelerősség" },
|
||||
{ "Signal & traffic", "Jelzés és forgalom" },
|
||||
{ "Signal (stale)", "Jel (elavult)" },
|
||||
{ "Signal probe", "Jelmérés" },
|
||||
{ "Size", "Méret" },
|
||||
{ "Skip", "Kihagyás" },
|
||||
@@ -1010,6 +1012,7 @@ static const I18nPair kBuiltin_hu[] = {
|
||||
{ "my region", "saját régió" },
|
||||
{ "n/a in this build", "n/a ebben a buildben" },
|
||||
{ "not asked yet", "Még nem kérték" },
|
||||
{ "nothing heard yet", "Még semmi hír." },
|
||||
{ "now on channel %s", "most a %s csatornán" },
|
||||
{ "now talking to %s", "most %s-szel beszélgetek" },
|
||||
{ "off", "ki" },
|
||||
@@ -2606,7 +2609,7 @@ static const I18nPair kBuiltin_de[] = {
|
||||
{ "SD card removed", "SD-Karte entfernt" },
|
||||
{ "SD data is unavailable - contacts and channels cannot be saved until the card is reinserted.", "SD-Daten nicht verfügbar – Kontakte und Kanäle können erst nach Einsetzen der Karte gespeichert werden." },
|
||||
{ "SD data is unavailable - identity, settings, contacts and channels cannot be saved until the card is reinserted.", "SD-Daten nicht verfügbar – Identität, Einstellungen, Kontakte und Kanäle können erst nach Einsetzen der Karte gespeichert werden." },
|
||||
{ "SD data migration is incomplete. Identity and settings remain internal; use Copy internal data to SD to retry.", "SD-Datenübertragung unvollständig. Identität und Einstellungen bleiben intern; „Interne Daten auf SD kopieren“ zum erneuten Versuch verwenden." },
|
||||
{ "SD data migration is incomplete. Identity and settings remain internal; use Copy internal data to SD to retry.", "SD-Datenübertragung unvollständig. Identität und Einstellungen bleiben intern; \"Interne Daten auf SD kopieren\" zum erneuten Versuch verwenden." },
|
||||
{ "SD download failed: %s", "SD-Download fehlgeschlagen: %s" },
|
||||
{ "SD formatted - %s (MESHCOMOD)", "SD formatiert - %s (MESHCOMOD)" },
|
||||
{ "SD is busy - close tools and retry", "SD ist belegt – Tools schließen und erneut versuchen" },
|
||||
@@ -8070,7 +8073,7 @@ static const I18nPair kBuiltin_bg[] = {
|
||||
{ "TCP off", "TCP изкл" },
|
||||
{ "TCP on", "TCP вкл" },
|
||||
{ "Tab hotkeys — tap a row, then press a key", "Клавиши за раздела — докосни ред, после натисни клавиш" },
|
||||
{ "Tap Use to switch - the device reboots to apply.", "Натиснете „Избери“, за да превключите — устройството ще се рестартира." },
|
||||
{ "Tap Use to switch - the device reboots to apply.", "Натиснете \"Избери\", за да превключите — устройството ще се рестартира." },
|
||||
{ "Tap Use to switch - the device reboots to apply. Language files live in /lang on the storage; edit them or add your own.", "Докосни «Избери» за смяна - устройството се рестартира. Езиковите файлове са в /lang в паметта; редактирай ги или добави свои." },
|
||||
{ "Tap a language; the device reboots to apply.", "Изберете език; устройството ще се рестартира." },
|
||||
{ "Taste the rainbow!", "Вкуси дъгата!" },
|
||||
@@ -8965,7 +8968,7 @@ static const I18nPair kBuiltin_sr[] = {
|
||||
{ "TCP off", "TCP искљ" },
|
||||
{ "TCP on", "TCP укљ" },
|
||||
{ "Tab hotkeys — tap a row, then press a key", "Пречице картице — додирни ред, па притисни тастер" },
|
||||
{ "Tap Use to switch - the device reboots to apply.", "Додирните „Користи“ да промените — уређај ће се поново покренути." },
|
||||
{ "Tap Use to switch - the device reboots to apply.", "Додирните \"Користи\" да промените — уређај ће се поново покренути." },
|
||||
{ "Tap Use to switch - the device reboots to apply. Language files live in /lang on the storage; edit them or add your own.", "Додирни «Користи» за промену - уређај се поново покреће. Датотеке језика су у /lang на меморији; уреди их или додај своје." },
|
||||
{ "Tap a language; the device reboots to apply.", "Изаберите језик; уређај ће се рестартовати." },
|
||||
{ "Taste the rainbow!", "Окуси дугу!" },
|
||||
|
||||
@@ -340,6 +340,8 @@ static const char kLuaSrc_sdktest[] = R"WADALUA(-- SDK self-test. Exercises the
|
||||
-- windowed wada.fs.read.
|
||||
-- 1.6 adds wada.sd.list: capability reporting, traversal rejection, root
|
||||
-- metadata, and a nested directory listing when the card contains one.
|
||||
-- 1.7 adds storage-neutral wada.audio capability and API-shape checks. Playback
|
||||
-- stays manual so opening the bench test never emits sound unexpectedly.
|
||||
-- 1.5 uses wada.ui.text_lines() to measure instead of estimating, where the
|
||||
-- firmware has it. That call was added because of the 1.3 bug below: an app
|
||||
-- could ask how tall a line is but not how wide, so laying out rows meant
|
||||
@@ -396,6 +398,21 @@ function app.on_open(w, h)
|
||||
row("caps: sd_list=" .. yn(c.sd_list) .. " discover=" .. yn(c.discover) ..
|
||||
" input=" .. yn(c.input) ..
|
||||
" rx_identity=" .. yn(c.rx_identity), C.accent)
|
||||
row("caps: audio=" .. yn(c.audio) .. " wav=" .. yn(c.audio_wav) ..
|
||||
" mp3=" .. yn(c.audio_mp3) .. " audio_sd=" .. yn(c.audio_sd), C.accent)
|
||||
if c.audio then
|
||||
local api_ok = wada.audio and type(wada.audio.play) == "function" and
|
||||
type(wada.audio.pause) == "function" and
|
||||
type(wada.audio.resume) == "function" and
|
||||
type(wada.audio.stop) == "function" and
|
||||
type(wada.audio.status) == "function"
|
||||
local status = api_ok and wada.audio.status() or nil
|
||||
api_ok = api_ok and type(status) == "table" and type(status.state) == "string"
|
||||
row("wada.audio API: " .. (api_ok and ("PASS (" .. status.state .. ")") or "FAIL"),
|
||||
api_ok and C.good or C.bad)
|
||||
else
|
||||
row("wada.audio: unavailable on this board", C.sub)
|
||||
end
|
||||
row("layout: " .. (ui.text_lines and "measured (ui.text_lines)" or "estimated (older firmware)"),
|
||||
ui.text_lines and C.good or C.sub)
|
||||
-- crypto: published test vectors, so this is checkable rather than merely alive
|
||||
@@ -1473,6 +1490,34 @@ local pending = {} -- lines waiting on the 1 write/sec limit
|
||||
|
||||
local function logname() return run .. ".csv" end
|
||||
|
||||
-- Return at most max_bytes without splitting a UTF-8 sequence. The row's
|
||||
-- fixed-width columns are byte-budgeted, not character-budgeted; a raw
|
||||
-- string.sub(1, 14) cut Ouderkerk + sun + VS16 inside the final codepoint and
|
||||
-- left LVGL unable to advance through the label (#323, same class as #223).
|
||||
local function utf8_prefix_bytes(text, max_bytes)
|
||||
local offset, last, length = 1, 0, #text
|
||||
while offset <= length and offset <= max_bytes do
|
||||
local first = text:byte(offset)
|
||||
local width = first <= 0x7F and 1
|
||||
or (first >= 0xC2 and first <= 0xDF and 2)
|
||||
or (first >= 0xE0 and first <= 0xEF and 3)
|
||||
or (first >= 0xF0 and first <= 0xF4 and 4) or 0
|
||||
if width == 0 or offset + width - 1 > length or offset + width - 1 > max_bytes then break end
|
||||
local valid = true
|
||||
for i = 2, width do
|
||||
local byte = text:byte(offset + i - 1)
|
||||
if byte < 0x80 or byte > 0xBF then valid = false; break end
|
||||
end
|
||||
local second = width > 1 and text:byte(offset + 1) or 0
|
||||
if (first == 0xE0 and second < 0xA0) or (first == 0xED and second > 0x9F) or
|
||||
(first == 0xF0 and second < 0x90) or (first == 0xF4 and second > 0x8F) then valid = false end
|
||||
if not valid then break end
|
||||
last = offset + width - 1
|
||||
offset = last + 1
|
||||
end
|
||||
return text:sub(1, last)
|
||||
end
|
||||
|
||||
-- Lua here is built with 32-bit floats, so fix.lat is good to about a metre and
|
||||
-- no better. fix.lat_e6 is the same reading as an exact integer in
|
||||
-- micro-degrees, which is what belongs in a log: a survey you plot months later
|
||||
@@ -1554,7 +1599,7 @@ local function redraw()
|
||||
local e = list[i]
|
||||
if e then
|
||||
rows[i]:set(string.format("%-14s %-8s best %5.1f worst %5.1f x%d",
|
||||
e.n.name:sub(1, 14), TYPE[e.n.type] or "?", e.n.best, e.n.worst, e.n.seen))
|
||||
utf8_prefix_bytes(e.n.name, 14), TYPE[e.n.type] or "?", e.n.best, e.n.worst, e.n.seen))
|
||||
rows[i]:color(e.n.best > 0 and C.good or C.text)
|
||||
else
|
||||
rows[i]:set(i == 1 and "nothing has answered a probe yet" or "")
|
||||
@@ -1775,9 +1820,9 @@ static const LuaBuiltinApp kLuaBuiltin[] = {
|
||||
{ "monitor", "RF Monitor", "1.3", kLuaSrc_monitor },
|
||||
{ "airtime", "Airtime", "1.4", kLuaSrc_airtime },
|
||||
{ "snake", "Snake", "1.0", kLuaSrc_snake },
|
||||
{ "sdktest", "SDK Test", "1.6", kLuaSrc_sdktest },
|
||||
{ "sdktest", "SDK Test", "1.7", kLuaSrc_sdktest },
|
||||
{ "2048", "2048", "1.2", kLuaSrc_2048 },
|
||||
{ "wardrive", "Wardrive", "1.0", kLuaSrc_wardrive },
|
||||
{ "wardrive", "Wardrive", "1.1", kLuaSrc_wardrive },
|
||||
{ "nearby", "Nearby", "1.0", kLuaSrc_nearby },
|
||||
};
|
||||
static const int kLuaBuiltinCount = (int)(sizeof(kLuaBuiltin)/sizeof(kLuaBuiltin[0]));
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
#include "helpers/input/LatchedModifier.h"
|
||||
|
||||
int main() {
|
||||
LatchedModifier modifier;
|
||||
|
||||
modifier.press();
|
||||
assert(modifier.consumeForKey());
|
||||
modifier.release(100);
|
||||
assert(!modifier.consumeForKey());
|
||||
|
||||
modifier.press();
|
||||
modifier.release(200);
|
||||
assert(modifier.consumeForKey());
|
||||
assert(!modifier.consumeForKey());
|
||||
|
||||
modifier.press();
|
||||
modifier.release(400);
|
||||
modifier.press();
|
||||
modifier.release(700);
|
||||
assert(modifier.locked());
|
||||
assert(modifier.consumeForKey());
|
||||
assert(modifier.consumeForKey());
|
||||
|
||||
modifier.press();
|
||||
modifier.release(800);
|
||||
assert(!modifier.active());
|
||||
|
||||
modifier.tap(1000);
|
||||
modifier.tap(1500);
|
||||
assert(!modifier.locked());
|
||||
assert(modifier.consumeForKey());
|
||||
|
||||
modifier.tap(2000);
|
||||
modifier.clearLatched();
|
||||
assert(!modifier.active());
|
||||
|
||||
modifier.tap(2100);
|
||||
modifier.discard();
|
||||
assert(!modifier.active());
|
||||
|
||||
modifier.press();
|
||||
modifier.discard();
|
||||
modifier.release(2200);
|
||||
assert(!modifier.active());
|
||||
|
||||
LatchedModifier wrapped;
|
||||
wrapped.tap(UINT32_MAX - 100);
|
||||
wrapped.tap(100);
|
||||
assert(wrapped.locked());
|
||||
|
||||
wrapped.baseline(true);
|
||||
assert(wrapped.held());
|
||||
wrapped.release(200);
|
||||
assert(!wrapped.active());
|
||||
wrapped.baseline(false);
|
||||
assert(!wrapped.active());
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
#include <assert.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
static void* testMp3Scratch();
|
||||
#define MINIMP3_ONLY_MP3
|
||||
#define MINIMP3_NO_SIMD
|
||||
#define MINIMP3_SCRATCH ((mp3dec_scratch_t*)testMp3Scratch())
|
||||
#define MINIMP3_IMPLEMENTATION
|
||||
#include "../lib/minimp3/minimp3.h"
|
||||
|
||||
static mp3dec_scratch_t g_scratch;
|
||||
static void* testMp3Scratch() { return &g_scratch; }
|
||||
|
||||
struct DecodeBuffer {
|
||||
uint8_t input[16 * 1024];
|
||||
mp3d_sample_t pcm[MINIMP3_MAX_SAMPLES_PER_FRAME];
|
||||
};
|
||||
|
||||
static uint32_t readLe32(const uint8_t* value) {
|
||||
return (uint32_t)value[0] | ((uint32_t)value[1] << 8) |
|
||||
((uint32_t)value[2] << 16) | ((uint32_t)value[3] << 24);
|
||||
}
|
||||
|
||||
static long mp3DataEnd(FILE* file) {
|
||||
assert(fseek(file, 0, SEEK_END) == 0);
|
||||
long end = ftell(file);
|
||||
assert(end >= 0);
|
||||
uint8_t footer[128];
|
||||
if (end >= 128 && fseek(file, end - 128, SEEK_SET) == 0 &&
|
||||
fread(footer, 1, 3, file) == 3 && !memcmp(footer, "TAG", 3)) {
|
||||
end -= 128;
|
||||
}
|
||||
if (end >= 32 && fseek(file, end - 32, SEEK_SET) == 0 &&
|
||||
fread(footer, 1, 32, file) == 32 && !memcmp(footer, "APETAGEX", 8)) {
|
||||
const uint32_t version = readLe32(footer + 8);
|
||||
const uint32_t tag_size = readLe32(footer + 12);
|
||||
const uint32_t flags = readLe32(footer + 20);
|
||||
const uint64_t total_size = (uint64_t)tag_size + ((flags & 0x80000000u) ? 32u : 0u);
|
||||
if ((version == 1000 || version == 2000) && tag_size >= 32 && total_size <= (uint64_t)end)
|
||||
end -= (long)total_size;
|
||||
}
|
||||
rewind(file);
|
||||
return end;
|
||||
}
|
||||
|
||||
static bool decodeFile(const char* path) {
|
||||
FILE* file = fopen(path, "rb");
|
||||
if (!file) {
|
||||
fprintf(stderr, "%s: could not open\n", path);
|
||||
return false;
|
||||
}
|
||||
const long data_end = mp3DataEnd(file);
|
||||
if (data_end <= 0) { fclose(file); return false; }
|
||||
|
||||
DecodeBuffer buffer;
|
||||
mp3dec_t decoder;
|
||||
mp3dec_init(&decoder);
|
||||
size_t input_start = 0, input_count = 0;
|
||||
uint64_t samples_total = 0;
|
||||
int frames = 0, sample_rate = 0, channels = 0, bitrate_changes = 0;
|
||||
int last_bitrate = 0;
|
||||
bool eof = false, ok = true;
|
||||
|
||||
while (ok) {
|
||||
if (!eof && input_count < 4096) {
|
||||
if (input_start && input_start + input_count + 4096 > sizeof buffer.input) {
|
||||
memmove(buffer.input, buffer.input + input_start, input_count);
|
||||
input_start = 0;
|
||||
}
|
||||
size_t room = sizeof buffer.input - (input_start + input_count);
|
||||
const long position = ftell(file);
|
||||
const size_t remaining = position < data_end ? (size_t)(data_end - position) : 0;
|
||||
if (room > remaining) room = remaining;
|
||||
const size_t got = fread(buffer.input + input_start + input_count, 1, room, file);
|
||||
if (got > 0) {
|
||||
input_count += got;
|
||||
eof = ftell(file) >= data_end;
|
||||
}
|
||||
else if (ferror(file)) ok = false;
|
||||
else eof = true;
|
||||
}
|
||||
|
||||
if (!ok || !input_count) break;
|
||||
|
||||
mp3dec_frame_info_t info = {};
|
||||
const int samples = mp3dec_decode_frame(&decoder, buffer.input + input_start,
|
||||
(int)input_count, buffer.pcm, &info);
|
||||
if (info.frame_bytes < 0 || (size_t)info.frame_bytes > input_count) {
|
||||
ok = false;
|
||||
break;
|
||||
}
|
||||
if (info.frame_bytes > 0) {
|
||||
input_start += (size_t)info.frame_bytes;
|
||||
input_count -= (size_t)info.frame_bytes;
|
||||
if (!input_count) input_start = 0;
|
||||
} else if (eof) {
|
||||
break;
|
||||
} else if (input_start + input_count == sizeof buffer.input) {
|
||||
ok = false;
|
||||
break;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (samples <= 0) continue;
|
||||
if (info.layer != 3 || (info.channels != 1 && info.channels != 2) ||
|
||||
info.hz < 8000 || info.hz > 48000) {
|
||||
ok = false;
|
||||
break;
|
||||
}
|
||||
if (!sample_rate) {
|
||||
sample_rate = info.hz;
|
||||
channels = info.channels;
|
||||
} else if (sample_rate != info.hz) {
|
||||
ok = false;
|
||||
break;
|
||||
}
|
||||
if (last_bitrate && last_bitrate != info.bitrate_kbps) ++bitrate_changes;
|
||||
last_bitrate = info.bitrate_kbps;
|
||||
samples_total += (uint64_t)samples;
|
||||
++frames;
|
||||
}
|
||||
|
||||
fclose(file);
|
||||
if (!ok || frames == 0 || samples_total == 0) {
|
||||
fprintf(stderr, "%s: decode failed after %d frames\n", path, frames);
|
||||
return false;
|
||||
}
|
||||
printf("%s: %d frames, %llu samples/channel, %d Hz, %d channel(s), %d bitrate changes\n",
|
||||
path, frames, (unsigned long long)samples_total, sample_rate, channels,
|
||||
bitrate_changes);
|
||||
return true;
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
if (argc < 2) {
|
||||
fprintf(stderr, "usage: %s file.mp3 [file.mp3 ...]\n", argv[0]);
|
||||
return 2;
|
||||
}
|
||||
for (int i = 1; i < argc; ++i) assert(decodeFile(argv[i]));
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
#include "helpers/input/PagerKeyboardState.h"
|
||||
|
||||
int main() {
|
||||
PagerKeyboardState keyboard;
|
||||
|
||||
assert(keyboard.event(0, true, 10) == 'q');
|
||||
keyboard.event(0, false, 20);
|
||||
|
||||
keyboard.event(PagerKeyboardState::ALT_POS, true, 100);
|
||||
assert(keyboard.altHeld());
|
||||
assert(keyboard.event(0, true, 110) == '1');
|
||||
keyboard.event(0, false, 120);
|
||||
keyboard.event(PagerKeyboardState::ALT_POS, false, 130);
|
||||
assert(keyboard.event(0, true, 140) == 'q');
|
||||
keyboard.event(0, false, 150);
|
||||
|
||||
keyboard.event(PagerKeyboardState::ALT_POS, true, 200);
|
||||
keyboard.event(PagerKeyboardState::ALT_POS, false, 220);
|
||||
assert(keyboard.event(1, true, 230) == '2');
|
||||
keyboard.event(1, false, 240);
|
||||
assert(keyboard.event(1, true, 250) == 'w');
|
||||
keyboard.event(1, false, 260);
|
||||
|
||||
keyboard.event(PagerKeyboardState::ALT_POS, true, 300);
|
||||
keyboard.event(PagerKeyboardState::ALT_POS, false, 320);
|
||||
keyboard.event(PagerKeyboardState::ALT_POS, true, 400);
|
||||
keyboard.event(PagerKeyboardState::ALT_POS, false, 420);
|
||||
assert(keyboard.event(0, true, 430) == '1');
|
||||
keyboard.event(0, false, 440);
|
||||
assert(keyboard.event(1, true, 450) == '2');
|
||||
keyboard.event(1, false, 460);
|
||||
keyboard.event(PagerKeyboardState::ALT_POS, true, 470);
|
||||
keyboard.event(PagerKeyboardState::ALT_POS, false, 480);
|
||||
assert(keyboard.event(0, true, 490) == 'q');
|
||||
keyboard.event(0, false, 500);
|
||||
|
||||
keyboard.event(PagerKeyboardState::SHIFT_POS, true, 510);
|
||||
assert(keyboard.event(0, true, 520) == 'Q');
|
||||
keyboard.event(0, false, 530);
|
||||
keyboard.event(PagerKeyboardState::SHIFT_POS, false, 540);
|
||||
|
||||
keyboard.event(PagerKeyboardState::ALT_POS, true, 600);
|
||||
keyboard.event(PagerKeyboardState::SHIFT_POS, true, 610);
|
||||
assert(keyboard.consumeAltShiftChord());
|
||||
assert(!keyboard.consumeAltShiftChord());
|
||||
keyboard.toggleCaps();
|
||||
keyboard.event(PagerKeyboardState::SHIFT_POS, false, 620);
|
||||
keyboard.event(PagerKeyboardState::ALT_POS, false, 630);
|
||||
assert(keyboard.event(0, true, 640) == 'Q');
|
||||
keyboard.event(0, false, 650);
|
||||
keyboard.toggleCaps();
|
||||
|
||||
keyboard.event(PagerKeyboardState::ALT_POS, true, 700);
|
||||
assert(keyboard.event(PagerKeyboardState::BACKSPACE_POS, true, 710) == 0);
|
||||
assert(keyboard.consumeAltBackspaceChord());
|
||||
assert(!keyboard.backspaceHeld());
|
||||
keyboard.event(PagerKeyboardState::BACKSPACE_POS, false, 720);
|
||||
keyboard.event(PagerKeyboardState::ALT_POS, false, 730);
|
||||
assert(keyboard.event(0, true, 740) == 'q');
|
||||
keyboard.event(0, false, 750);
|
||||
|
||||
keyboard.event(PagerKeyboardState::ALT_POS, true, 800);
|
||||
keyboard.discardAlt();
|
||||
keyboard.event(PagerKeyboardState::ALT_POS, false, 810);
|
||||
assert(keyboard.event(0, true, 820) == 'q');
|
||||
keyboard.event(0, false, 830);
|
||||
|
||||
keyboard.event(PagerKeyboardState::ALT_POS, true, 900);
|
||||
keyboard.event(PagerKeyboardState::ALT_POS, false, 920);
|
||||
keyboard.markAltUsed(); // no effect without a physical hold
|
||||
assert(keyboard.event(PagerKeyboardState::SPACE_POS, true, 930) == ' ');
|
||||
assert(keyboard.spaceHeld());
|
||||
keyboard.event(PagerKeyboardState::SPACE_POS, false, 940);
|
||||
assert(!keyboard.spaceHeld());
|
||||
assert(keyboard.event(0, true, 950) == 'q');
|
||||
keyboard.event(0, false, 960);
|
||||
|
||||
keyboard.event(PagerKeyboardState::ALT_POS, true, 1000);
|
||||
keyboard.markAltUsed(); // physical Alt+encoder equivalent
|
||||
keyboard.event(PagerKeyboardState::ALT_POS, false, 1010);
|
||||
assert(keyboard.event(0, true, 1020) == 'q');
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "ui-touch/ReaderContent.h"
|
||||
|
||||
int main() {
|
||||
static const char html[] =
|
||||
"<!doctype html><html><body><h1>Bookmarks</h1>"
|
||||
"<p><a href='https://lite.cnn.com'>CNN</a></p>"
|
||||
"<p><a href='news.htm'>Local news</a> & weather</p>"
|
||||
"<script>12345</script></body></html>";
|
||||
char text[256];
|
||||
ReaderContent::Link links[4] = {};
|
||||
size_t link_count = 0;
|
||||
const size_t text_len = ReaderContent::htmlToText(
|
||||
html, strlen(html), text, sizeof text, "sd:/home.htm",
|
||||
links, 4, &link_count);
|
||||
|
||||
assert(text_len == strlen(text));
|
||||
assert(strstr(text, "Bookmarks") != nullptr);
|
||||
assert(strstr(text, "CNN") != nullptr);
|
||||
assert(strstr(text, "Local news & weather") != nullptr);
|
||||
assert(strstr(text, "12345") == nullptr);
|
||||
assert(link_count == 2);
|
||||
assert(strcmp(links[0].href, "https://lite.cnn.com") == 0);
|
||||
assert(strcmp(links[1].href, "sd:/news.htm") == 0);
|
||||
assert(strncmp(text + links[0].start, "CNN", links[0].end - links[0].start) == 0);
|
||||
assert(strncmp(text + links[1].start, "Local news", links[1].end - links[1].start) == 0);
|
||||
|
||||
char resolved[240];
|
||||
assert(ReaderContent::resolveUrl("sd:/bookmarks/home.htm", "../index.htm",
|
||||
resolved, sizeof resolved));
|
||||
assert(strcmp(resolved, "sd:/bookmarks/../index.htm") == 0);
|
||||
assert(ReaderContent::resolveUrl("sd:/home.htm", "//text.npr.org",
|
||||
resolved, sizeof resolved));
|
||||
assert(strcmp(resolved, "https://text.npr.org") == 0);
|
||||
assert(!ReaderContent::resolveUrl("sd:/home.htm", "javascript:alert(1)",
|
||||
resolved, sizeof resolved));
|
||||
|
||||
static const char attribute_html[] =
|
||||
"<a data-href='sd:/wrong.htm' xhref='sd:/also-wrong.htm' href='right.htm'>Right</a>";
|
||||
link_count = 0;
|
||||
ReaderContent::htmlToText(attribute_html, strlen(attribute_html), text, sizeof text,
|
||||
"sd:/home.htm", links, 4, &link_count);
|
||||
assert(link_count == 1);
|
||||
assert(strcmp(links[0].href, "sd:/right.htm") == 0);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "helpers/input/TDeckKeyboardState.h"
|
||||
|
||||
static size_t update(TDeckKeyboardState& keyboard, uint8_t state[5], uint32_t now,
|
||||
uint8_t* out) {
|
||||
return keyboard.update(state, now, out, 8);
|
||||
}
|
||||
|
||||
static void setKey(uint8_t state[5], int col, int row, bool down) {
|
||||
if (down) state[col] |= (uint8_t)(1U << row);
|
||||
else state[col] &= (uint8_t)~(1U << row);
|
||||
}
|
||||
|
||||
int main() {
|
||||
TDeckKeyboardState keyboard;
|
||||
uint8_t state[5] = {};
|
||||
uint8_t out[8] = {};
|
||||
|
||||
setKey(state, 0, 2, true); // hold Sym
|
||||
assert(update(keyboard, state, 10, out) == 0);
|
||||
setKey(state, 0, 0, true); // q -> #
|
||||
assert(update(keyboard, state, 20, out) == 1 && out[0] == '#');
|
||||
setKey(state, 0, 0, false);
|
||||
update(keyboard, state, 30, out);
|
||||
setKey(state, 0, 2, false);
|
||||
update(keyboard, state, 40, out);
|
||||
setKey(state, 0, 0, true); // hold use must not latch
|
||||
assert(update(keyboard, state, 50, out) == 1 && out[0] == 'q');
|
||||
setKey(state, 0, 0, false);
|
||||
update(keyboard, state, 60, out);
|
||||
|
||||
setKey(state, 0, 2, true); // tap Sym
|
||||
update(keyboard, state, 100, out);
|
||||
setKey(state, 0, 2, false);
|
||||
update(keyboard, state, 120, out);
|
||||
setKey(state, 0, 1, true); // w -> 1 once
|
||||
assert(update(keyboard, state, 130, out) == 1 && out[0] == '1');
|
||||
setKey(state, 0, 1, false);
|
||||
update(keyboard, state, 140, out);
|
||||
setKey(state, 0, 1, true);
|
||||
assert(update(keyboard, state, 150, out) == 1 && out[0] == 'w');
|
||||
setKey(state, 0, 1, false);
|
||||
update(keyboard, state, 160, out);
|
||||
|
||||
setKey(state, 0, 2, true); // an unmapped symbol still uses the hold
|
||||
update(keyboard, state, 170, out);
|
||||
setKey(state, 0, 5, true); // Space has no T-Deck symbol mapping
|
||||
assert(update(keyboard, state, 175, out) == 0);
|
||||
setKey(state, 0, 5, false);
|
||||
update(keyboard, state, 180, out);
|
||||
setKey(state, 0, 2, false);
|
||||
update(keyboard, state, 185, out);
|
||||
setKey(state, 0, 0, true);
|
||||
assert(update(keyboard, state, 190, out) == 1 && out[0] == 'q');
|
||||
setKey(state, 0, 0, false);
|
||||
update(keyboard, state, 195, out);
|
||||
|
||||
setKey(state, 0, 4, true); // held Alt preserves base layer
|
||||
update(keyboard, state, 200, out);
|
||||
setKey(state, 0, 0, true);
|
||||
assert(update(keyboard, state, 210, out) == 1 && out[0] == 'q');
|
||||
setKey(state, 0, 0, false);
|
||||
update(keyboard, state, 215, out);
|
||||
setKey(state, 3, 4, true); // Alt+B stays controller-only
|
||||
assert(update(keyboard, state, 216, out) == 0);
|
||||
setKey(state, 3, 4, false);
|
||||
update(keyboard, state, 217, out);
|
||||
setKey(state, 2, 5, true); // Alt+C keeps its legacy control code
|
||||
assert(update(keyboard, state, 218, out) == 1 && out[0] == 0x0C);
|
||||
setKey(state, 2, 5, false);
|
||||
update(keyboard, state, 219, out);
|
||||
setKey(state, 0, 4, false);
|
||||
update(keyboard, state, 220, out);
|
||||
|
||||
setKey(state, 0, 4, true); // solo Alt is a one-shot symbol latch
|
||||
update(keyboard, state, 225, out);
|
||||
setKey(state, 0, 4, false);
|
||||
update(keyboard, state, 230, out);
|
||||
setKey(state, 0, 0, true);
|
||||
assert(update(keyboard, state, 235, out) == 1 && out[0] == '#');
|
||||
setKey(state, 0, 0, false);
|
||||
update(keyboard, state, 240, out);
|
||||
|
||||
setKey(state, 0, 4, true); // double-tap Alt locks symbols
|
||||
update(keyboard, state, 250, out);
|
||||
setKey(state, 0, 4, false);
|
||||
update(keyboard, state, 270, out);
|
||||
setKey(state, 0, 4, true);
|
||||
update(keyboard, state, 300, out);
|
||||
setKey(state, 0, 4, false);
|
||||
update(keyboard, state, 320, out);
|
||||
setKey(state, 0, 0, true);
|
||||
assert(update(keyboard, state, 330, out) == 1 && out[0] == '#');
|
||||
setKey(state, 0, 0, false);
|
||||
update(keyboard, state, 340, out);
|
||||
setKey(state, 0, 1, true);
|
||||
assert(update(keyboard, state, 350, out) == 1 && out[0] == '1');
|
||||
setKey(state, 0, 1, false);
|
||||
update(keyboard, state, 360, out);
|
||||
setKey(state, 0, 4, true); // tap Alt unlocks
|
||||
update(keyboard, state, 400, out);
|
||||
setKey(state, 0, 4, false);
|
||||
update(keyboard, state, 420, out);
|
||||
|
||||
setKey(state, 0, 2, true); // double-tap Sym locks symbols
|
||||
update(keyboard, state, 430, out);
|
||||
setKey(state, 0, 2, false);
|
||||
update(keyboard, state, 440, out);
|
||||
setKey(state, 0, 2, true);
|
||||
update(keyboard, state, 450, out);
|
||||
setKey(state, 0, 2, false);
|
||||
update(keyboard, state, 460, out);
|
||||
setKey(state, 0, 1, true);
|
||||
assert(update(keyboard, state, 470, out) == 1 && out[0] == '1');
|
||||
setKey(state, 0, 1, false);
|
||||
update(keyboard, state, 475, out);
|
||||
setKey(state, 0, 2, true);
|
||||
update(keyboard, state, 480, out);
|
||||
setKey(state, 0, 2, false);
|
||||
update(keyboard, state, 490, out);
|
||||
|
||||
setKey(state, 1, 6, true); // held Shift still uppercases base
|
||||
update(keyboard, state, 500, out);
|
||||
setKey(state, 0, 0, true);
|
||||
assert(update(keyboard, state, 510, out) == 1 && out[0] == 'Q');
|
||||
|
||||
setKey(state, 0, 0, false);
|
||||
setKey(state, 1, 6, false);
|
||||
update(keyboard, state, 520, out);
|
||||
setKey(state, 0, 2, true); // discarded physical hold
|
||||
update(keyboard, state, 600, out);
|
||||
keyboard.discardModifiers();
|
||||
setKey(state, 0, 2, false);
|
||||
update(keyboard, state, 620, out);
|
||||
setKey(state, 0, 0, true);
|
||||
assert(update(keyboard, state, 630, out) == 1 && out[0] == 'q');
|
||||
setKey(state, 0, 0, false);
|
||||
update(keyboard, state, 640, out);
|
||||
|
||||
setKey(state, 0, 4, true); // discarded Alt hold cannot latch on release
|
||||
update(keyboard, state, 650, out);
|
||||
keyboard.discardModifiers();
|
||||
setKey(state, 0, 4, false);
|
||||
update(keyboard, state, 660, out);
|
||||
setKey(state, 0, 0, true);
|
||||
assert(update(keyboard, state, 670, out) == 1 && out[0] == 'q');
|
||||
|
||||
setKey(state, 0, 0, false);
|
||||
update(keyboard, state, 680, out);
|
||||
setKey(state, 0, 2, true); // modifiers disabled: base key still routes
|
||||
update(keyboard, state, 690, out);
|
||||
setKey(state, 0, 0, true);
|
||||
assert(keyboard.update(state, 700, out, 8, false) == 1 && out[0] == 'q');
|
||||
|
||||
uint8_t baseline[5] = {};
|
||||
setKey(baseline, 0, 1, true); // transition frame produces no edge later
|
||||
keyboard.baseline(baseline);
|
||||
assert(keyboard.update(baseline, 710, out, 8) == 0);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include <string>
|
||||
|
||||
#include "ui-touch/Utf8Text.h"
|
||||
|
||||
int main() {
|
||||
const char full[] = "Ouderkerk\xE2\x98\x80\xEF\xB8\x8F";
|
||||
assert(strlen(full) == 15);
|
||||
assert(Utf8Text::valid(full, strlen(full)));
|
||||
|
||||
std::string broken(full, 14); // Wardrive's former name:sub(1, 14)
|
||||
assert(!Utf8Text::valid(broken.data(), broken.size()));
|
||||
|
||||
std::string scratch;
|
||||
const char* clean = Utf8Text::sanitize(broken.data(), broken.size(), scratch);
|
||||
assert(scratch == "Ouderkerk\xE2\x98\x80?");
|
||||
assert(Utf8Text::valid(clean, scratch.size()));
|
||||
|
||||
scratch = "allocated";
|
||||
const char* unchanged = Utf8Text::sanitize(full, strlen(full), scratch);
|
||||
assert(unchanged == full);
|
||||
assert(scratch.empty());
|
||||
|
||||
const char overlong[] = { (char)0xC0, (char)0xAF };
|
||||
assert(!Utf8Text::valid(overlong, sizeof overlong));
|
||||
Utf8Text::sanitize(overlong, sizeof overlong, scratch);
|
||||
assert(scratch == "?");
|
||||
return 0;
|
||||
}
|
||||
@@ -89,6 +89,7 @@ bool p4AudioReady() {
|
||||
if (s_ready) return true;
|
||||
if (s_failed) return false;
|
||||
if (!s_mtx) s_mtx = xSemaphoreCreateMutex();
|
||||
if (!s_mtx) return false;
|
||||
xSemaphoreTake(s_mtx, portMAX_DELAY);
|
||||
if (s_ready || s_failed) { xSemaphoreGive(s_mtx); return s_ready; }
|
||||
|
||||
@@ -125,6 +126,9 @@ bool p4AudioReady() {
|
||||
if (i2s_channel_init_std_mode(s_tx, &std_cfg) != ESP_OK ||
|
||||
i2s_channel_enable(s_tx) != ESP_OK) {
|
||||
printf("[P4AUDIO] I2S init failed\n");
|
||||
i2s_channel_disable(s_tx);
|
||||
i2s_del_channel(s_tx);
|
||||
s_tx = nullptr;
|
||||
s_failed = true;
|
||||
xSemaphoreGive(s_mtx);
|
||||
return false;
|
||||
@@ -139,7 +143,7 @@ void p4AudioTone(int freq_hz, int duration_ms, int amplitude) {
|
||||
if (amplitude <= 0 || freq_hz <= 0 || duration_ms <= 0) return;
|
||||
if (!p4AudioReady()) return;
|
||||
if (amplitude > 30000) amplitude = 30000;
|
||||
xSemaphoreTake(s_mtx, portMAX_DELAY);
|
||||
if (xSemaphoreTake(s_mtx, 0) != pdTRUE) return;
|
||||
const int total = P4A_RATE * duration_ms / 1000;
|
||||
const int fade = P4A_RATE * 4 / 1000; // 4 ms in/out fade kills the click
|
||||
static int16_t buf[256 * 2]; // stereo frames, chunked
|
||||
@@ -163,4 +167,36 @@ void p4AudioTone(int freq_hz, int duration_ms, int amplitude) {
|
||||
xSemaphoreGive(s_mtx);
|
||||
}
|
||||
|
||||
uint32_t p4AudioStreamRate() { return P4A_RATE; }
|
||||
|
||||
bool p4AudioStreamBegin() {
|
||||
if (!p4AudioReady()) return false;
|
||||
return xSemaphoreTake(s_mtx, portMAX_DELAY) == pdTRUE;
|
||||
}
|
||||
|
||||
bool p4AudioStreamWrite(const int16_t* samples, size_t frames) {
|
||||
if (!samples || !frames || !s_tx) return false;
|
||||
static int16_t stereo[256 * 2];
|
||||
while (frames) {
|
||||
const size_t count = frames > 256 ? 256 : frames;
|
||||
for (size_t i = 0; i < count; ++i)
|
||||
stereo[2 * i] = stereo[2 * i + 1] = samples[i];
|
||||
size_t written = 0;
|
||||
const size_t bytes = count * 2 * sizeof(int16_t);
|
||||
if (i2s_channel_write(s_tx, stereo, bytes, &written, pdMS_TO_TICKS(300)) != ESP_OK ||
|
||||
written != bytes) return false;
|
||||
samples += count;
|
||||
frames -= count;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void p4AudioStreamEnd() {
|
||||
if (!s_mtx) return;
|
||||
static const int16_t silence[256 * 2] = {};
|
||||
size_t written = 0;
|
||||
i2s_channel_write(s_tx, silence, sizeof silence, &written, pdMS_TO_TICKS(300));
|
||||
xSemaphoreGive(s_mtx);
|
||||
}
|
||||
|
||||
#endif // HAS_TDISPLAY_P4
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
// DOUT=10. Fixed 16 kHz / 16-bit stereo — plenty for notification chimes, tiny buffers.
|
||||
// All calls are safe from any task; init is lazy on first use (~ms, I²C + I2S bring-up).
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
// Bring the codec + I2S up (idempotent). Returns false if the codec never answered.
|
||||
bool p4AudioReady();
|
||||
@@ -12,3 +13,10 @@ bool p4AudioReady();
|
||||
// Blocking sine tone: `amplitude` is the 16-bit peak (0..~30000; the UI uses pct*130).
|
||||
// No-op (fast) when amplitude <= 0 or the codec is absent.
|
||||
void p4AudioTone(int freq_hz, int duration_ms, int amplitude);
|
||||
|
||||
// Exclusive fixed-rate PCM stream used by media playback. Samples are mono;
|
||||
// the backend duplicates them to the codec's stereo I2S slots.
|
||||
uint32_t p4AudioStreamRate();
|
||||
bool p4AudioStreamBegin();
|
||||
bool p4AudioStreamWrite(const int16_t* samples, size_t frames);
|
||||
void p4AudioStreamEnd();
|
||||
|
||||
Reference in New Issue
Block a user