PicoFox file contract
PicoFox file contract
Source of truth for this build: firmware at c69af3a, inspected October 20, 2026. The product manual is the user-facing reference. The app uses conservative validation where they differ.
Volume contents
| Filename | Owner | App behavior |
|---|---|---|
settings.txt |
User configuration | Read/edit/write UTF-8 text, LF output |
audio.wav |
User audio | Read if present; replace only on explicit save |
.callsign.wav |
Firmware-generated ID | Never edit |
.settings_crc.bin |
Firmware settings-change bookkeeping | Never edit |
No custom folder, backup, or draft is stored on the radio. The app does not reformat, delete, flash or eject the volume. The radio’s FAT16 filesystem is exposed via TinyUSB mass storage. Device settings/audio become effective after USB disconnect and power cycle.
Published firmware has USB serial diagnostics but no application command parser for reboot or settings reload. MSC flush synchronizes flash; it does not apply new settings. The inspected USB stack’s 1200-baud touch enters the upload bootloader, not a normal restart. Installed firmware identity and startup persistence need verification before adding control commands. See serial-control findings and extension contract.
Settings schema
Input is case-sensitive KEY=value, one per line. Whitespace around keys and values is trimmed. Last recognized duplicate wins on import. Invalid numeric known values fail import. Blank/comment/unknown lines survive export; duplicate known keys collapse to one. Known keys absent from input are appended. Existing UTF-8 text must decode successfully. Output is LF-terminated UTF-8 without BOM and six decimal places for MHz, independent of locale.
| Key | App default | App accepted values |
|---|---|---|
CALLSIGN |
empty (cannot export until valid) | 1–11 ASCII uppercase letters/digits |
ITU_ZONE |
2 | 1, 2, 3 |
FREQ_MHZ |
146.565 | Finite; 144.020–145.980 in zone 1, 144.020–147.980 in zones 2/3, inclusive |
DUTY_CYCLE |
100 | Integer 0–100 |
ATTENUATION |
0 | Integer 0–127; approximately 0.25 dB/step; r3 only |
MORSE_WPM |
15 | Integer 1–30 |
MORSE_FARNSWORTH_WPM |
15 | Integer Morse speed through 255 |
MORSE_TONE |
600 | Integer 100–2000 Hz (conservative manual range) |
MORSE_TONE_VOL |
70 | Integer 1–100 |
Draft JSON uses the Swift model property names: callsign, ituZone, frequencyMHz, dutyCycle, attenuation, morseWPM, farnsworthWPM, toneHz, toneVolume. It is app persistence rather than a versioned exchange format. Device text and WAV are the cross-platform exchange formats.
Firmware differences that matter
- Callsign: the manual advertises 12 characters;
char callsign[12]plusstrncpy(..., sizeof(...) - 1)allows 11 plus a terminator. App enforces 11 to avoid silent truncation. - Band edges:
FM_DEVIATION_HZis 10,000; the loader reserves twice that at each band edge. The app validates the resulting 20 kHz margins. - Farnsworth: firmware default is 10, but loader raises values below Morse WPM to Morse WPM. The app starts at effective default 15 and reports invalid spacing without silently rewriting the user’s value. This is the current firmware behavior, not conventional slower Farnsworth timing.
- Tone: loader accepts 100–2499 Hz, while manual recommends 100–2000. App uses the manual’s narrower range.
- Duty cycle: firmware uses
offTime = ((100 - dutyCyclePercent) * audioLengthMs) / 100. It does not compute a standard on/(on+off) percentage; the app stores the configured number and does not promise a true duty-cycle estimate. - WAV: playback reads 44 header bytes, checks RIFF/WAVE magic, then reads every subsequent pair as a sample. Chunk metadata or trailing bytes can become transmitted sound. Output must be a canonical, metadata-free header and payload.
Canonical audio.wav byte layout
All integers little endian. No extension chunks or trailing metadata.
| Offset | Length | Value |
|---|---|---|
| 0 | 4 | ASCII RIFF |
| 4 | 4 | 36 + payload byte count |
| 8 | 4 | ASCII WAVE |
| 12 | 4 | ASCII fmt + space |
| 16 | 4 | 16 |
| 20 | 2 | Format 1 (PCM) |
| 22 | 2 | Channels 1 |
| 24 | 4 | Rate 5000 |
| 28 | 4 | Byte rate 10000 |
| 32 | 2 | Block alignment 2 |
| 34 | 2 | Bits 16 |
| 36 | 4 | ASCII data |
| 40 | 4 | Payload byte count |
| 44 | N | Signed PCM16 mono samples |
Duration = payloadBytes / 10000 seconds. Device file size = 44 + payloadBytes. Available device capacity must be evaluated by actual filesystem writes; the app’s 12 MB limit is a resource policy, not a promise about free flash.
Power slider
The UI presents power instead of attenuation. It keeps the firmware ATTENUATION field and maps 128 discrete slider positions with attenuation = 127 - sliderStep. Nominal base output is 65 × 10^(-attenuation × 0.25 / 10) mW. Thus steps 0, 40 and 80 correspond to nominal 65, 6.5 and 0.65 mW. The increasing slider runs approximately 0.0434–65 mW.
This is a calculation from the published maximum and approximate attenuation step, not an RF measurement. Values carry ≈ in the UI. Hardware revision, frequency, supply, device variation and firmware attenuation cycling can change actual output. r2 ignores attenuation entirely; the slider is an r3 control. Exact power requires measured calibration data, which is not available in this delivery.
Firmware-generated length fields
At the inspected firmware commit, audio.h declares 300,000 PCM data bytes while six 48,000-byte repetitions create 288,000 bytes. The resulting default file is 288,044 bytes but its RIFF/data lengths advertise 300,044/300,000. decodePicoFox first tries strict WAV parsing, then accepts only the proven factory case: actual length 288,044 bytes, declared RIFF file length 300,044 bytes and declared PCM payload 300,000 bytes, with the canonical PCM16 mono 5 kHz layout. Other truncations are rejected. Lengths are corrected in a local memory copy; the source file is unchanged. Regular WAV import remains strict. Canonical export always writes correct lengths.
A readable header mismatch is separate from a filesystem read failure. Lost FAT cluster links cannot be repaired by WAV parsing. The folder adapter reports unreadable audio separately and still opens settings; it refuses direct replacement when the original cannot be read/backed up.
Firmware-generated callsign audio
At the documented firmware baseline, the Morse generator writes a fixed WAV header before producing variable-length samples. FoxProgrammer does not read or write the hidden .callsign.wav file. Explicit import uses strict WAV validation; the factory-audio compatibility exception does not apply to arbitrary short files.