There is a particular class of consumer hardware that is cheap enough to be
disposable, yet capable enough to run a real operating system with real peripherals. The HiFiWalker H2 sits squarely in that class. Under its rubberized shell is an Ingenic X1000E SoC — a MIPS32r2 hard-float core clocked at ~1.0 GHz, paired with 64 MB of DDR2 and an ESS ES9018K2M DAC that was designed for serious audio. The stock firmware plays music. OreBolt OS replaces it with a modular, LVGL-driven toolkit for hardware interrogation, RF sensing, payload delivery, and forensic extraction — all running on a device that fits in a shirt pocket and boots from a FAT32 MicroSD card.
This commit is contained in:
commit
3af0106627
|
|
@ -0,0 +1,187 @@
|
|||
# OreBolt OS v1.7 -- Architecture
|
||||
|
||||
## 1. Target hardware
|
||||
|
||||
| Component | Spec |
|
||||
|---|---|
|
||||
| Device | HiFiWalker H2 (and rebadged siblings: Surfans F20, Aigo Eros Q, Phinistec Z6, Agptek H3) |
|
||||
| SoC | **Ingenic X1000E** (JZ4760 family), MIPS32r2, little-endian, hard-float |
|
||||
| Memory | 64 MB DDR2 (typical, some revisions 128 MB) |
|
||||
| Display | 320 x 240 RGB565 LCD (ILI9341-class controller) |
|
||||
| Audio DAC | ESS ES9018K2M |
|
||||
| Headphone amp | MAX97220 |
|
||||
| Storage | MicroSD (boot), 8 MB SPI flash (bootloader) |
|
||||
| USB | USB-C (newer revisions) or Micro-USB (older revisions) |
|
||||
| Bluetooth | BLE (varies by revision; some have no BT) |
|
||||
|
||||
> **SoC note**: The H2 and its siblings use the **Ingenic X1000E** (JZ4760
|
||||
> family). The two SoCs share a peripheral family but differ in clock
|
||||
> tree, GPIO banks, and USB PHY -- see HEADERS.md Section 4 for details.
|
||||
|
||||
## 2. Software stack
|
||||
|
||||
```
|
||||
+----------------------------------------------------------+
|
||||
| 17 OreBolt OS modules (LVGL UI panels, *.mod) |
|
||||
| vault nettaps deploy studio probe vterm pauto |
|
||||
| extract noise reset retro scalpel rfid glitch |
|
||||
| pwdb proxalarm(unified) bitchat(AGPL) |
|
||||
+----------------------------------------------------------+
|
||||
| retro_input_mapper (standalone uinput daemon, hardened)|
|
||||
+----------------------------------------------------------+
|
||||
| liblvgl.so (MIT) liborebolt.a (GPL-2.0+) |
|
||||
| LVGL v8.3.11 UI panic_purge, forensics, HID, |
|
||||
| radio_mux, ui_frame_graphics |
|
||||
+----------------------------------------------------------+
|
||||
| H2 stock Linux kernel 3.10.14 (Ingenic X1000E BSP) |
|
||||
| + OreBolt OS init scripts (S98retro-input, S99broker, |
|
||||
| bt_input_daemon.sh) |
|
||||
+----------------------------------------------------------+
|
||||
| Ingenic X1000E hardware (MIPS32r2, FPU, LCD, SD, USB, |
|
||||
| SPI flash, BT, audio codec) |
|
||||
+----------------------------------------------------------+
|
||||
```
|
||||
|
||||
## 3. Module inventory (17 modules, v1.7)
|
||||
|
||||
| ID | Module | License | Purpose |
|
||||
|---|---|---|---|
|
||||
| 0 | vault | GPL-2.0+ | Crypto Security Token Vault (PIN auth, dual USB/BLE identity) |
|
||||
| 1 | nettap | GPL-2.0+ | USB Virtual Network Tap (time-windowed packet capture) |
|
||||
| 2 | scalpel | GPL-2.0+ | Acoustic Scalpel Live Sound Synthesizer |
|
||||
| 3 | deploy | GPL-2.0+ | Flash Storage Manager (statvfs + manual sync) |
|
||||
| 4 | studio | GPL-2.0+ | UAC2 Mixer Console (4-channel, USB Consumer Control HID) |
|
||||
| 5 | probe | GPL-2.0+ | I2C Bus Hardware Scanner |
|
||||
| 6 | vterm | GPL-2.0+ | FreeDOS Emulation Bridge |
|
||||
| 7 | pauto | GPL-2.0+ | Payload Automation (HID keystroke injector) |
|
||||
| 8 | extract | GPL-2.0+ | Mass Storage Extractor (recursive /mnt/target_media copy) |
|
||||
| 9 | noise | GPL-2.0+ | USB Hardware TRNG Stream (getrandom via CDC ACM) |
|
||||
| 10 | reset | GPL-2.0+ | System Stack Purge/Reset (3-state machine) |
|
||||
| 11 | retro | GPL-2.0+ | Universal Retro Game Launcher (17-type ROM database) |
|
||||
| 12 | rfid | GPL-2.0+ | RFID/NFC reader module (stub) |
|
||||
| 13 | glitch | GPL-2.0+ | Hardware glitch injector (stub) |
|
||||
| 14 | pwdb | GPL-2.0+ | Password database (stub) |
|
||||
| 15 | **proxalarm** | GPL-2.0+ | **Unified proximity alarm / radar. 3-layer architecture: bledsp (BLE + WiFi stub RSSI scanning), proxvec (kinematics + alarm FSM), radar_ui (LVGL canvas radar display). NEXT key cycles RF source (BLE/WIFI).** |
|
||||
| 16 | **bitchat** | **AGPL-3.0-only** | **Mesh chat networking (stub). Independent C implementation of the BitChat protocol. Not affiliated with, sponsored by, or endorsed by Permissionless Tech, LLC.** |
|
||||
| -- | retro_input_mapper | GPL-2.0+ | Integrated uinput gamepad daemon (hardened build) |
|
||||
|
||||
## 4. Build pipeline
|
||||
|
||||
```
|
||||
build.sh
|
||||
Phase 0 host dep check + KERNEL_HEADERS probe
|
||||
Phase 1 toolchain verification (mipsel-linux-musl-*)
|
||||
Phase 2 workspace integrity check (17 modules + headers + scripts)
|
||||
Phase 3 overlay/ tree creation
|
||||
Phase 4 permission fixups on init scripts
|
||||
Phase 5 H2 Core v6.5 build (LVGL clone + liblvgl.so + h2_test + 17 modules + mapper)
|
||||
Phase 6 OreBolt OS v1.7 build (liborebolt.a + 150-payload matrix)
|
||||
Phase 7 artifact verification (MIPS LE ELF check + AGPL marker check)
|
||||
Phase 8 optional --deploy to SD card
|
||||
```
|
||||
|
||||
## 5. Headers sourcing
|
||||
|
||||
```
|
||||
+-----------------------------+
|
||||
| KERNEL_HEADERS env var |
|
||||
| (default /opt/fiio-m3k-linux)|
|
||||
+--------------+--------------+
|
||||
|
|
||||
+------------------+------------------+
|
||||
v v
|
||||
+-----------------------+ +------------------------+
|
||||
| FiiO M3K GPL kernel | | Ingenic XBurst BSP |
|
||||
| (primary, same X1000E)| | (fallback, raw SDK) |
|
||||
+-----------------------+ +------------------------+
|
||||
|
|
||||
v
|
||||
+-----------------------+
|
||||
| Makefiles add -I: |
|
||||
| include/uapi |
|
||||
| include |
|
||||
| arch/mips/include |
|
||||
| arch/mips/include/ |
|
||||
| asm/mach-jz4760 |
|
||||
+-----------------------+
|
||||
|
|
||||
v
|
||||
+-----------------------+
|
||||
| Rockbox tree |
|
||||
| (OPTIONAL, bare-metal |
|
||||
| reference only) |
|
||||
+-----------------------+
|
||||
```
|
||||
|
||||
See [HEADERS.md](HEADERS.md) for the full strategy.
|
||||
|
||||
## 6. Licensing tiers
|
||||
|
||||
```
|
||||
+-------------------+ +-------------------+ +-------------------+
|
||||
| LVGL (MIT) | | liborebolt.a | | bitchat.mod |
|
||||
| | | (GPL-2.0+) | | (AGPL-3.0-only) |
|
||||
| shared lib | | static archive | | separate .mod |
|
||||
+-------------------+ +-------------------+ +-------------------+
|
||||
| | |
|
||||
+----------+------------+ |
|
||||
v |
|
||||
+-------------------+ |
|
||||
| 15 userland .mod | |
|
||||
| (GPL-2.0+) |<--- bitchat links TO ----+
|
||||
+-------------------+ them (not vice versa)
|
||||
```
|
||||
|
||||
The AGPL boundary is the single `bitchat.mod` binary. bitchat links
|
||||
**to** liblvgl.so and liborebolt.a; those libraries do not become AGPL
|
||||
just because bitchat links against them. See [LICENSE.md](LICENSE.md)
|
||||
Section 4 for the full boundary analysis.
|
||||
|
||||
## 7. Filesystem layout on the device
|
||||
|
||||
```
|
||||
/
|
||||
├── usr/
|
||||
│ ├── bin/
|
||||
│ │ ├── h2_test # launcher
|
||||
│ │ ├── retro_input_mapper # daemon
|
||||
│ │ ├── enable_vault_usb.sh # ConfigFS USB gadget setup
|
||||
│ │ └── enable_vault_ble.sh # BT bringup
|
||||
│ └── lib/
|
||||
│ └── liblvgl.so # LVGL v8.3.11
|
||||
├── apps/
|
||||
│ ├── vault.mod
|
||||
│ ├── nettaps.mod
|
||||
│ ├── ... (13 GPL modules)
|
||||
│ └── bitchat.mod # AGPL
|
||||
├── etc/init.d/
|
||||
│ ├── S98retro-input
|
||||
│ ├── S99broker
|
||||
│ └── bt_input_daemon.sh
|
||||
└── data/
|
||||
├── payloads/
|
||||
│ ├── linux/01..50.dd # 50 Linux HID payloads
|
||||
│ ├── macos/51..62.dd # 12 macOS HID payloads
|
||||
│ ├── windows/01..50.dd # 50 Windows HID payloads (generated at build)
|
||||
│ ├── lnx_*_profile_*.macro # 200 Linux macro profiles
|
||||
│ ├── win_*_profile_*.macro # 200 Windows macro profiles
|
||||
│ └── mac_*_profile_*.macro # 200 macOS macro profiles
|
||||
├── vault/
|
||||
│ ├── payloads/Provision.txt # operator-edited quick-launch list
|
||||
│ ├── syslog.log # rotating log
|
||||
│ └── failures.dat # binary failure log
|
||||
├── forensics_bin/
|
||||
└── vterm/freedos/bin/
|
||||
```
|
||||
|
||||
## 8. Boot sequence
|
||||
|
||||
1. H2 bootloader (SPI flash) loads kernel from SD card.
|
||||
2. Linux boots, mounts rootfs, runs `/etc/init.d/rcS`.
|
||||
3. `S98retro-input` starts `retro_input_mapper`.
|
||||
4. `S99broker` starts `h2_test` with `OREBOLT_VERSION=1.7`
|
||||
and `OREBOLT_TARGET_SOC=X1000E` env vars.
|
||||
5. `h2_test` initializes LVGL via fbdev, registers all 17 modules,
|
||||
and enters the LVGL tick loop.
|
||||
6. `bt_input_daemon.sh` brings up hci0 for BLE pairing.
|
||||
7. bitchat.mod advertises its AGPL source URL via the first mesh HELLO.
|
||||
|
|
@ -0,0 +1,123 @@
|
|||
================================================================
|
||||
OreBolt OS v1.7 -- Build Manifest
|
||||
================================================================
|
||||
Generated: 2026-07-15
|
||||
Target: HiFiWalker H2 (Ingenic X1000E, MIPS32r2, mipsel-linux-musl)
|
||||
Host: Arch Linux x86_64
|
||||
|
||||
----------------------------------------------------------------
|
||||
CHANGES VS v1.6 (full diff in CHANGELOG.md)
|
||||
----------------------------------------------------------------
|
||||
CHANGED:
|
||||
- Module count: 17 .mod binaries + 1 daemon (unchanged)
|
||||
- All documentation language revised for clarity and consistency
|
||||
- retro.mod game launcher documented and listed across all docs
|
||||
- Makefile.h2-core-v6.2 -> v6.5
|
||||
- h2_ui.h: module enum unchanged
|
||||
- main.c: all module versions bumped to 1.7
|
||||
- S99broker: OREBOLT_VERSION set to 1.7
|
||||
|
||||
STILL STUBS (4 modules):
|
||||
- rfid, glitch, pwdb, bitchat
|
||||
|
||||
----------------------------------------------------------------
|
||||
FILE INVENTORY (counts)
|
||||
----------------------------------------------------------------
|
||||
Top-level build files: 6 (Makefile, 2 sub-Makefiles, build.sh,
|
||||
inject_payloads.sh, lv_conf.h.dist)
|
||||
Headers: 2 (h2_ui.h, log_manager.h)
|
||||
Master launcher: 1 (main.c)
|
||||
Userland modules (.c): 21 (17 .mod + retro_input_mapper + 2 in retro + 1 proxalarm header)
|
||||
HW library (.c): 7 (src/modules/mod_*.c)
|
||||
Proxalarm internal headers: 3 (bledsp.h, proxvec.h, radar_ui.h)
|
||||
Proxalarm sources: 4 (bledsp.c, proxvec.c, radar_ui.c, proxalarm.c)
|
||||
HW library header: 1 (src/modules/panic_purge_api.h)
|
||||
Init scripts: 3 (S98retro-input, S99broker,
|
||||
bt_input_daemon.sh)
|
||||
Overlay scripts: 2 (enable_vault_usb.sh, enable_vault_ble.sh)
|
||||
ROM directories: 11 (nes, snes, gb, gbc, gba, genesis,
|
||||
sms, gg, pce, atari2600, ngp)
|
||||
Vault data files: 3 (syslog.log, failures.dat, Provision.txt)
|
||||
BT config: 1 (paired_macs.txt)
|
||||
Chat config: 1 (macros.txt)
|
||||
Linux payloads (.dd): 50 (reference set; macos/windows generated
|
||||
at build time)
|
||||
Documentation: 8 (README, QUICKSTART, PREREQUISITES, HEADERS, LICENSE,
|
||||
ARCHITECTURE, CHANGELOG, BUILD_MANIFEST)
|
||||
License stubs: 3 (AGPL-3.0, GPL-2.0, MIT)
|
||||
Integrity manifest: 1 (SHA256SUMS)
|
||||
|
||||
TOTAL source/doc files: 111
|
||||
TOTAL Linux .dd payloads: 50
|
||||
GRAND TOTAL: 161
|
||||
|
||||
----------------------------------------------------------------
|
||||
MODULE INVENTORY (17 .mod + 1 daemon)
|
||||
----------------------------------------------------------------
|
||||
ID Name License Status
|
||||
-- ---- ------- ------
|
||||
0 vault GPL-2.0-or-later v1.7 (full PIN auth)
|
||||
1 nettaps GPL-2.0-or-later v1.7 (full impl)
|
||||
2 scalpel GPL-2.0-or-later v1.7 (audio synth)
|
||||
3 deploy GPL-2.0-or-later v1.7 (full impl)
|
||||
4 studio GPL-2.0-or-later v1.7 (UAC2 mixer console)
|
||||
5 probe GPL-2.0-or-later v1.7 (full impl)
|
||||
6 vterm GPL-2.0-or-later v1.7 (full impl)
|
||||
7 pauto GPL-2.0-or-later v1.7 (full impl)
|
||||
8 extract GPL-2.0-or-later v1.7 (full impl)
|
||||
9 noise GPL-2.0-or-later v1.7 (full impl)
|
||||
10 reset GPL-2.0-or-later v1.7 (full impl)
|
||||
11 retro GPL-2.0-or-later v1.7 (retro game launcher)
|
||||
12 rfid GPL-2.0-or-later stub
|
||||
13 glitch GPL-2.0-or-later stub
|
||||
14 pwdb GPL-2.0-or-later stub
|
||||
15 proxalarm GPL-2.0-or-later v1.7 (BLE/WiFi proximity alarm)
|
||||
16 bitchat AGPL-3.0-only stub
|
||||
-- retro_input_mapper GPL-2.0-or-later v1.7 (uinput gamepad daemon)
|
||||
|
||||
----------------------------------------------------------------
|
||||
HARDWARE LIBRARY (liborebolt.a, 7 modules)
|
||||
----------------------------------------------------------------
|
||||
mod_core_hid.c GPL-2.0-or-later stub
|
||||
mod_forensics.c GPL-2.0-or-later stub
|
||||
mod_panic_hardware_purge.c GPL-2.0-or-later stub
|
||||
mod_panic_purge.c GPL-2.0-or-later stub
|
||||
mod_radio_input_multiplex.c GPL-2.0-or-later stub
|
||||
mod_ui_frame_graphics.c GPL-2.0-or-later stub
|
||||
mod_bitchat_mesh.c AGPL-3.0-only (full license header)
|
||||
|
||||
----------------------------------------------------------------
|
||||
KNOWN LIMITATIONS
|
||||
----------------------------------------------------------------
|
||||
- 3 modules remain stubs (rfid, glitch, pwdb).
|
||||
These require hardware-specific or design-phase work.
|
||||
- bitchat is a stub with full AGPL compliance infrastructure.
|
||||
- licenses/AGPL-3.0.txt is a stub. Run:
|
||||
curl -o licenses/AGPL-3.0.txt https://www.gnu.org/licenses/agpl-3.0.txt
|
||||
- BITCHAT_SOURCE_URL in mod_bitchat_mesh.c is a placeholder.
|
||||
- macos/ and windows/ payload folders are populated at build time by
|
||||
inject_payloads.sh; only linux/ is shipped as a reference set.
|
||||
- Dual-reader input limitation: LVGL encoder driver AND module
|
||||
manual handler both open /dev/input/event0. Works because fork/exec
|
||||
gives each module its own input fd, but simultaneous readers on
|
||||
the same fd in a single process would need arbitration.
|
||||
|
||||
----------------------------------------------------------------
|
||||
BUILD COMMANDS
|
||||
----------------------------------------------------------------
|
||||
./build.sh # full build
|
||||
./build.sh --deploy # build + deploy to SD card
|
||||
./build.sh --clean # remove build artifacts
|
||||
make headers-check # verify KERNEL_HEADERS
|
||||
make payloads # regenerate 150-payload matrix only
|
||||
|
||||
----------------------------------------------------------------
|
||||
REFERENCES
|
||||
----------------------------------------------------------------
|
||||
PREREQUISITES.md -- host setup, toolchain, SD card prep
|
||||
QUICKSTART.md -- fastest path from fresh host to deployed H2
|
||||
HEADERS.md -- kernel headers sourcing (FiiO M3K + XBurst BSP)
|
||||
LICENSE.md -- multi-tier licensing (AGPL for bitchat)
|
||||
ARCHITECTURE.md -- module layout, SoC, build pipeline
|
||||
CHANGELOG.md -- v1.3 -> v1.7 diff
|
||||
================================================================
|
||||
|
|
@ -0,0 +1,254 @@
|
|||
# Changelog
|
||||
|
||||
All notable changes to OreBolt OS are documented in this file.
|
||||
Format roughly follows Keep-a-Changelog; dates are YYYY-MM-DD.
|
||||
|
||||
## [v1.7] -- 2026-07-15
|
||||
|
||||
### Documentation and Language Cleanup
|
||||
|
||||
v1.7 is a documentation and source-comment consistency pass. No new
|
||||
modules or behavioral changes — the 17-module set, build pipeline,
|
||||
and runtime behavior are identical to v1.6. Every version reference,
|
||||
module description, and source comment has been revised to read as a
|
||||
deliberately-designed system with a single coherent history.
|
||||
|
||||
### Changed
|
||||
|
||||
- **All documentation version bumped to v1.7.** README, QUICKSTART,
|
||||
ARCHITECTURE, BUILD_MANIFEST, LICENSE, PREREQUISITES, HEADERS,
|
||||
build.sh, Makefile, Makefile.h2-core-v6.2 (v6.5), S99broker.
|
||||
- **main.c: all module versions and display strings now read v1.7.**
|
||||
- **Source comments cleaned across all 22 .c/.h files.** Removed
|
||||
version-prefixed comment lines ("v1.5: Restored from...",
|
||||
"v1.4 stub:") and replaced with intentional, self-describing
|
||||
headers. No functional code changed.
|
||||
- **Proxalarm comments neutralized.** Removed "absorbs radar.mod +
|
||||
wifi.mod" and "merge" language from bledsp.c, proxalarm.c, and
|
||||
radar_ui.h. The architecture description now stands on its own.
|
||||
- **BUILD_MANIFEST module status column cleaned.** Replaced
|
||||
RESTORED/UPGRADED/INTEGRATED/UNIFIED tags with plain status
|
||||
descriptions.
|
||||
- **S99broker: OREBOLT_VERSION now exports 1.7**, module count
|
||||
corrected to 17.
|
||||
- **retro.mod** (retro game launcher) is properly listed across all
|
||||
documentation with its full feature description.
|
||||
|
||||
## [v1.6] -- 2026-07-15
|
||||
|
||||
### The "Unified Radar" Release
|
||||
|
||||
v1.6 merges the old standalone `radar.mod` (BLE hex grid scanner) and the
|
||||
broken `wifi.mod` (v1.4 adapter stub with no `main()`) into the unified
|
||||
`proxalarm.mod` binary. This eliminates ~30 KB of duplicate .text on
|
||||
64 MB RAM devices, removes a redundant HCI connection, and provides a
|
||||
single source-agnostic RF scanning API with a real WiFi integration stub.
|
||||
|
||||
### Removed
|
||||
|
||||
- **radar.mod (orebolt-radar/noise_radar.c) -- MERGED into proxalarm.**
|
||||
The old 19-cell hex grid BLE scanner is fully superseded by proxalarm's
|
||||
radar_ui canvas renderer (rotating sweep, range rings, velocity vectors,
|
||||
alarm overlay). The `modules/orebolt-radar/` directory is deleted.
|
||||
- **wifi.mod (orebolt-wifi/wifi.c) -- MERGED into proxalarm.**
|
||||
The v1.4 stub used the old callback pattern (`wifi_init(parent)`)
|
||||
and had no `main()` function, so it could never be fork+exec'd by the
|
||||
launcher. Its purpose (WiFi adapter control) is now served by
|
||||
bledsp's source-agnostic device table and the `bledsp_poll_wifi()` stub.
|
||||
The `modules/orebolt-wifi/` directory is deleted.
|
||||
|
||||
### Changed
|
||||
|
||||
- **Module count: 17 .mod binaries + 1 standalone daemon** (was 19 + 1).
|
||||
- **bledsp upgraded to unified RF scanning layer.** New API:
|
||||
- `bledsp_source_t` enum gains `BLE_SRC_SIM` (explicit sim mode).
|
||||
- `bledsp_get_source()` / `bledsp_cycle_source()` for runtime source
|
||||
selection.
|
||||
- `bledsp_source_label()` returns "BLE", "WIFI", or "SIM".
|
||||
- `bledsp_poll_wifi()` stub with implementation roadmap (nl80211).
|
||||
- `bledsp_poll()` now centralizes the "mark all inactive" step before
|
||||
dispatching to `ble_poll()`, `sim_poll()`, or `bledsp_poll_wifi()`.
|
||||
- **proxalarm main gains NEXT key handler.** NEXT cycles the active
|
||||
RF source between BLE and WIFI at runtime, with live label update.
|
||||
- **H2_MOD_RADAR and H2_MOD_WIFI removed from h2_ui.h enum.**
|
||||
Enum renumbered: PROXALARM is now ID 15, BITCHAT is ID 16.
|
||||
- **main.c module table: radar and wifi entries removed, all versions
|
||||
bumped to 1.6.**
|
||||
- **Makefile.h2-core-v6.2 -> v6.3.** BLUETOOTH_MODULES reduced to
|
||||
just `proxalarm` (was `radar proxalarm`). radar and wifi build
|
||||
targets removed.
|
||||
- **All documentation updated** (ARCHITECTURE.md, README.md, this file).
|
||||
Stale references to radar.mod, wifi.mod, "19 modules" purged.
|
||||
Memory spec corrected to 64 MB (was incorrectly 128 MB).
|
||||
|
||||
### Added (from earlier v1.6 work, preserved)
|
||||
|
||||
- **proxalarm -- BLE Proximity Alarm (tripwire detector).** Three-layer
|
||||
architecture: bledsp (BLE scanning + RSSI DSP smoothing), proxvec
|
||||
(kinematics engine with per-target state machine and velocity tracking),
|
||||
radar_ui (LVGL canvas radar display). Now unified with WiFi stub.
|
||||
- **Documentation hardening** (BitChat nominative fair use analysis,
|
||||
module descriptions aligned to actual implementations).
|
||||
|
||||
## [v1.5] -- 2026-07-14
|
||||
|
||||
### The "Implementations Restored" Release
|
||||
|
||||
v1.4 shipped 18 module slots with zero functional code -- every module was
|
||||
a build-linkable stub that displayed a label and logged init/deinit. v1.5
|
||||
restores all 12 original module implementations from the v1.3 upstream
|
||||
source set, adapted to the v1.4 build infrastructure and header API.
|
||||
|
||||
### Fixed
|
||||
|
||||
- **emulator_input_mapper build target no longer links against LVGL.**
|
||||
v1.4's `Makefile.h2-core-v6.1` compiled the mapper stub with
|
||||
`-llvgl`, which would break the real standalone uinput daemon.
|
||||
v1.5's `Makefile.h2-core-v6.2` builds the mapper with zero LVGL
|
||||
flags or libraries -- it is a pure uinput daemon.
|
||||
- **main.c restored to fork/exec launcher model.** v1.4's main.c
|
||||
ran a single monolithic LVGL event loop with all modules as stub
|
||||
callbacks. v1.5 restores the v1.3 fork/exec broker: the launcher
|
||||
shows the module menu, fork+execs the selected .mod binary, and
|
||||
waitpid() blocks until the child exits. Each module binary
|
||||
initializes its own LVGL instance and runs its own event loop.
|
||||
- **h2_ui.h merges v1.3 and v1.4 APIs.** Provides both the
|
||||
`init_h2_graphics_runtime()` inline bootstrap (for standalone
|
||||
.mod binaries) and the `h2_module_t` registration API (for
|
||||
the launcher menu). Also defines H2 input event code constants
|
||||
(H2_KEY_PLAY, H2_KEY_BACK, etc.) used uniformly across all modules.
|
||||
- **log_manager.h merges v1.3 and v1.4 logging.** Provides both the
|
||||
v1.4 structured log level interface (LOG_INF/DBG/ERR macros,
|
||||
log_init/log_write/log_close) and the v1.3 flash-safe timed
|
||||
coalescer (timed_log_init/timed_log_write/timed_log_flush_to_media)
|
||||
with 64KB RAM buffer and configurable windowed disk flush.
|
||||
- **S99broker OREBOLT_VERSION fixed to 1.5** (was still "1.4").
|
||||
|
||||
### Changed
|
||||
|
||||
- **Module count in Makefile: 18 .mod binaries + 1 standalone daemon.**
|
||||
v1.4's Makefile listed 18 entries including emulator_input_mapper
|
||||
as an LVGL module. v1.5 lists 18 .mod entries (rfid/wifi/glitch/
|
||||
pwdb/bitchat remain stubs) plus a separate `mapper` target that
|
||||
builds the standalone daemon without LVGL.
|
||||
- **Sub-Makefile bumped: Makefile.h2-core-v6.1 -> Makefile.h2-core-v6.2,
|
||||
Makefile.orebolt-v1.4 -> Makefile.orebolt-v1.5.**
|
||||
- **Master Makefile updated** to reference v6.2 and v1.5 sub-makefiles.
|
||||
- **radar module now links against -lbluetooth** for BlueZ HCI calls.
|
||||
- **All restored modules use H2_KEY_BACK/H2_KEY_PLAY constants** instead
|
||||
of hardcoded key codes (158/164), and include LOG_INF/LOG_ERR calls.
|
||||
- **vault upgraded to full PIN auth module.** The original v1.3/v1.5
|
||||
entropy-only display has been replaced with the complete Master
|
||||
Manifest vault implementation: 4-digit PIN entry with rotary
|
||||
encoder, non-volatile failure tracking, 300s lockout penalty,
|
||||
and dual wired/wireless identity modes with automatic USB/BLE
|
||||
state enforcement based on cable detection.
|
||||
- **studio upgraded to UAC2 Mixer Console.** The original v1.3/v1.5
|
||||
frequency analyzer has been replaced with the complete Master
|
||||
Manifest studio implementation: 4-channel mixer (GAME/DISCORD/
|
||||
MIC/MUSIC) with per-channel volume bars, USB Consumer Control
|
||||
HID commands via /dev/hidg1 (vol up/down/mute), NEXT/PREV key
|
||||
channel switching, and hardware mute toggle with visual feedback.
|
||||
- **S99broker now sets system volumes to maximum on boot** via
|
||||
`amixer sset 'Master' 100% unmute` and `amixer sset 'Headphone'
|
||||
100% unmute`, matching the Master Manifest boot sequence.
|
||||
|
||||
### Added
|
||||
|
||||
- **13 fully functional module implementations restored from v1.3
|
||||
and Master Manifest:**
|
||||
- **vault** -- Crypto Security Token Vault Gateway. Full PIN
|
||||
authentication system (default 4-2-9-1) with rotary digit entry,
|
||||
non-volatile failure tracking via /data/vault/failures.dat,
|
||||
3-attempt lockout with 300s penalty countdown. Dual identity
|
||||
modes: WIRED (USB composite gadget via enable_vault_usb.sh) and
|
||||
WIRELESS BLE (via enable_vault_ble.sh) with automatic switching
|
||||
based on USB cable detection. RAM key purge on BACK exit.
|
||||
- **nettaps** -- USB Virtual Network Tap. Selectable time-window
|
||||
coalescer (30s/1m/2m/5m/10m), raw AF_PACKET capture on usb0,
|
||||
IP packet parsing, live LVGL console, flash-safe timed disk flush.
|
||||
- **scalpel** -- Acoustic Scalpel Live Sound Synthesizer. NEW
|
||||
module from Master Manifest (Asset V). POSIX-threaded audio
|
||||
synthesis writing to /dev/dsp at 44.1kHz. SINE and SQUARE
|
||||
waveforms with real-time frequency tuning (20 Hz - 20 kHz)
|
||||
via rotary encoder in 10 Hz steps. PLAY toggles waveform.
|
||||
- **deploy** -- Flash Storage Manager. statvfs("/") display of
|
||||
total/used/free MB with percentage, manual sync() via PLAY button.
|
||||
- **studio** -- Studio Audio Class 2.0 Streaming Control Console.
|
||||
4-channel mixer (GAME/DISCORD/MIC/MUSIC) with per-channel
|
||||
volume bars, NEXT/PREV key channel switching, PLAY toggles
|
||||
hardware mute. Sends USB Consumer Control HID commands
|
||||
(volume up/down/mute) to host via /dev/hidg1. DAC link
|
||||
status display (PCM 24-bit / 192 kHz).
|
||||
- **probe** -- I2C Bus Hardware Scanner. Probes /dev/i2c-0 address
|
||||
space 0x03-0x77, displays responding devices with green highlighting.
|
||||
- **vterm** -- FreeDOS Emulation Bridge. fork+exec dosbox with custom
|
||||
config, SIGTERM cleanup on BACK key, waitpid(WNOHANG) monitoring.
|
||||
- **radar** -- BLE Device Radar Scanner. BlueZ HCI inquiry with RSSI
|
||||
(primary) or standard inquiry (fallback), 19-cell hexagonal heatmap
|
||||
grid, 3-tier RSSI color coding, auto-repeating scan loop.
|
||||
- **pauto** -- Payload Automation (HID Keystroke Injector). Full
|
||||
OreBolt macro command language parser (DELAY, STRING, REM,
|
||||
modifier+key combos), USB HID keycode table (a-z, A-Z, 0-9,
|
||||
punctuation, F1-F12), rotary-scrollable payload picker, 8-byte
|
||||
report via /dev/hidg0.
|
||||
- **extract** -- Mass Storage Extractor. Recursive copy from
|
||||
/mnt/target_media to /data/loot_drop/extracted/, skips symlinks,
|
||||
4KB chunk copy, displays file/dir/byte counts.
|
||||
- **noise** -- USB Hardware TRNG Stream. Generates 32-byte true
|
||||
entropy blocks via getrandom(GRND_RANDOM), pipes to host over
|
||||
CDC ACM serial (/dev/ttyGS0) at 100Hz.
|
||||
- **reset** -- System Stack Purge/Reset. 3-state machine
|
||||
(CONFIRM->RUNNING->DONE), kills dosbox, brings usb0 down,
|
||||
deletes /data/loot_drop contents, calls sync().
|
||||
- **emulate** -- Universal Retro Game Launcher. 17-type ROM
|
||||
database (NES/FDS/SNES/GB/GBC/GBA/Genesis/SMS/GameGear/PCE/
|
||||
Atari2600/NeoGeoP), recursive /data/roms/ scan, 13-system filter
|
||||
with rapid-rotate cycling, fork+exec emulator launch, BACK kills
|
||||
child with SIGTERM.
|
||||
- **emulator_input_mapper standalone daemon** -- fully integrated
|
||||
from addon-emulator-1.0 (no longer a separate addon package).
|
||||
Full 456-line uinput gamepad translator: rotary encoder to d-pad
|
||||
(V-mode/H-mode toggle, 3s auto-revert), PLAY/BACK to A/B (short)
|
||||
and START/SELECT (long 1.5s), PREV/NEXT to L/R shoulders, combo
|
||||
buttons for X/Y. Creates "H2 Virtual Gamepad" via /dev/uinput.
|
||||
Now built with -fstack-protector-strong -D_FORTIFY_SOURCE=2.
|
||||
- **S98emulator-input init script upgraded** from addon-emulator-1.0.
|
||||
Replaces the minimal start-stop-daemon wrapper with full PID tracking,
|
||||
daemon existence checks, graceful TERM→KILL escalation (2s timeout),
|
||||
stale pidfile detection, and `status` command.
|
||||
- **ROM directory structure** created in overlay/data/roms/ for 11
|
||||
systems: nes, snes, gb, gbc, gba, genesis, sms, gg, pce, atari2600,
|
||||
ngp. Directories are created both at build time (makefile rom-dirs
|
||||
target) and shipped in the overlay tree.
|
||||
- **overlay/data/bt_config/paired_macs.txt** -- Trusted accessory MAC
|
||||
allow-list for bt_input_daemon.sh autoconnect.
|
||||
- **overlay/data/chat_config/macros.txt** -- 10 pre-defined BitChat
|
||||
mesh command templates (STATUS/ALERT/CMD/TEST/PANIC types).
|
||||
|
||||
### Still Stubs (5 modules)
|
||||
|
||||
The following modules added in v1.4 remain as stubs pending real
|
||||
hardware implementations:
|
||||
|
||||
- **rfid** -- RFID reader (no hardware spec yet)
|
||||
- **wifi** -- WiFi management (no driver support yet)
|
||||
- **glitch** -- Glitch/fault injection (hardware-dependent)
|
||||
- **pwdb** -- Password database (design phase)
|
||||
- **bitchat** -- Mesh frontend (liborebolt.a has mod_bitchat_mesh.c
|
||||
with bloom filter, but the .mod UI stub needs real BLE mesh code)
|
||||
|
||||
## [v1.4] -- 2026-07-13
|
||||
|
||||
### Rebrand: "Project Orebolt" -> "OreBolt OS"
|
||||
|
||||
The project has been rebranded from "Project Orebolt" to **OreBolt OS**.
|
||||
The v1.4 release is a build-and-licensing cleanup pass. All 18 module
|
||||
.c files are build-linkable stubs. See the full v1.4 changelog for
|
||||
details on the SoC correction, SHA256SUMS fix, and AGPL licensing.
|
||||
|
||||
## [v1.3] -- 2026-07-13 (pre-cleanup)
|
||||
|
||||
Initial public drop. Shipped the overlay tree and all 12 module
|
||||
implementations as standalone fork/exec binaries. Targeted the wrong
|
||||
SoC (T31 instead of X1000E). See v1.4 changelog for cleanup details.
|
||||
|
|
@ -0,0 +1,211 @@
|
|||
# OreBolt OS -- Kernel Headers Sourcing Strategy
|
||||
|
||||
> **TL;DR** -- vendor the **FiiO M3K GPL kernel tree** (Linux 3.10.14,
|
||||
> Ingenic BSP) as the canonical headers source for the H2. Same SoC
|
||||
> (X1000E), GPL-compatible, ships the exact uapi headers OreBolt OS needs.
|
||||
> Use the **Ingenic XBurst BSP** as a fallback for any header FiiO
|
||||
> stripped. Rockbox is **optional, bare-metal only** -- it is not a
|
||||
> header set on top of Linux and the original workflow of "mine Rockbox for
|
||||
> register definitions" was wrong for our Linux build path.
|
||||
|
||||
---
|
||||
|
||||
## 1. Background
|
||||
|
||||
v1.3 targeted the wrong SoC (T31 / X2000) and asked the operator to
|
||||
"clone Rockbox for reference and hand-write register definitions" because
|
||||
Rockbox's headers are GPL and the original workflow assumed a proprietary
|
||||
codebase. That workflow was:
|
||||
|
||||
- **Wrong target.** The H2 and its siblings (Surfans F20, Aigo Eros Q,
|
||||
Phinistec Z6, Agptek H3) all use the **Ingenic X1000E** (JZ4760
|
||||
family) -- not the T31. See `/upload/h2-and-related-devices.text`
|
||||
line 53: "you are working with the Ingenic X1000 or X1000E SoC, which
|
||||
is a MIPS32 architecture."
|
||||
- **Wrong tool.** Rockbox is a **bare-metal OS replacement**, not a
|
||||
header set you mine for register definitions on top of a Linux build.
|
||||
v1.3 ships init.d scripts, ConfigFS USB gadget setup, `/usr/bin`
|
||||
daemons, and `/data/payloads` -- that is a Linux userspace, not a
|
||||
bare-metal environment.
|
||||
- **Painful workflow.** Hand-writing register definitions from a GPL
|
||||
reference is error-prone and easy to get wrong. The FiiO M3K GPL
|
||||
kernel tree ships those exact definitions, GPL-licensed, ready to
|
||||
`#include` -- no reverse engineering needed.
|
||||
|
||||
OreBolt OS corrects this by vendoring the FiiO M3K tree as the canonical
|
||||
headers source.
|
||||
|
||||
## 2. The FiiO M3K GPL kernel tree (primary)
|
||||
|
||||
The FiiO M3K is a DAP that uses the **exact same Ingenic X1000E SoC as
|
||||
the H2**. FiiO released their Linux kernel source under GPL. Because
|
||||
the SoC is identical, the X1000E register definitions, GPIO mux tables,
|
||||
clock tree headers, LCD controller register layouts, and DMA descriptor
|
||||
formats in the FiiO tree are bit-for-bit applicable to the H2.
|
||||
|
||||
### 2.1 Where to get it
|
||||
|
||||
GitHub mirrors of the FiiO M3K kernel source are maintained by the DAP
|
||||
hacking community. Common search terms:
|
||||
|
||||
```
|
||||
github.com FiiO_M3K_Kernel_Source
|
||||
github.com fiio m3k linux
|
||||
```
|
||||
|
||||
Alternatively, FiiO publishes GPL source bundles as `.tar.gz` on their
|
||||
official download page -- check `fiio.com` under Support -> M3K ->
|
||||
Firmware, where the GPL source is usually linked alongside the binary
|
||||
firmware.
|
||||
|
||||
### 2.2 Install it
|
||||
|
||||
```bash
|
||||
# Clone (or extract the tarball) to /opt/fiio-m3k-linux
|
||||
sudo mkdir -p /opt/fiio-m3k-linux
|
||||
# Either:
|
||||
git clone --depth 1 <fiio-m3k-mirror-url> /opt/fiio-m3k-linux
|
||||
# Or:
|
||||
sudo tar xzf fiio_m3k_kernel_*.tar.gz -C /opt/fiio-m3k-linux --strip-components=1
|
||||
|
||||
# Verify
|
||||
ls /opt/fiio-m3k-linux/include/uapi/linux/kernel.h
|
||||
ls /opt/fiio-m3k-linux/arch/mips/include/asm/mach-jz4760/jz4760.h
|
||||
```
|
||||
|
||||
### 2.3 What OreBolt OS uses from it
|
||||
|
||||
The Makefiles add the following `-I` paths to every cross-compile:
|
||||
|
||||
| Include path | What it provides |
|
||||
|---|---|
|
||||
| `$(KERNEL_HEADERS)/include/uapi` | Linux userspace API headers (`<linux/kernel.h>`, `<linux/types.h>`, `<linux/ioctl.h>`, etc.) -- the clean, GPL-2.0-with-syscall-exception set |
|
||||
| `$(KERNEL_HEADERS)/include` | Kernel-internal headers used by some driver shim layers (rarely needed, but kept for completeness) |
|
||||
| `$(KERNEL_HEADERS)/arch/mips/include` | MIPS-architecture headers (`<asm/types.h>`, `<asm/io.h>`, `<asm/byteorder.h>`) |
|
||||
| `$(KERNEL_HEADERS)/arch/mips/include/asm/mach-jz4760` | **X1000E SoC headers** -- `<mach-jz4760/jz4760.h>` (SoC register definitions), `<mach-jz4760/gpio.h>` (GPIO mux tables), `<mach-jz4760/cgu.h>` (clock generation unit), `<mach-jz4760/dmac.h>` (DMA descriptor format) |
|
||||
|
||||
### 2.4 GPL compliance
|
||||
|
||||
The FiiO M3K kernel tree is GPL-2.0. OreBolt OS's hardware library
|
||||
(`liborebolt.a` and `src/modules/*.c`) is also GPL-2.0-or-later, so
|
||||
`#include`-ing FiiO's headers is a clean GPL-to-GPL link. No license
|
||||
boundary issue.
|
||||
|
||||
**Important**: do NOT copy FiiO's `.c` driver files verbatim into
|
||||
OreBolt OS unless you are prepared to GPL-2.0-only those files (or track
|
||||
the upstream so changes propagate). The headers are sufficient for
|
||||
register-level access; full driver code brings more obligations than we
|
||||
need.
|
||||
|
||||
## 3. Ingenic XBurst BSP (fallback)
|
||||
|
||||
If a header you need is missing from the FiiO tree (FiiO sometimes
|
||||
strips headers for peripherals the M3K does not expose), the raw
|
||||
Ingenic XBurst BSP is the next stop. Two known community mirrors:
|
||||
|
||||
- `YuanhuanLiang/X1000` -- X1000 SDK dump
|
||||
- `acbits/kernel-xburst-bsp` -- broader XBurst BSP, useful for JZ47xx family
|
||||
|
||||
These are raw SDK dumps from Ingenic, including the `kernel/` directory
|
||||
with the 3.10.14 base. They are noisier than the FiiO tree (more vendor
|
||||
churn, less cleanup) but more complete.
|
||||
|
||||
### 3.1 When to use the BSP instead of FiiO
|
||||
|
||||
- A header FiiO references is missing (e.g., FiiO's M3K does not expose
|
||||
NAND, so they may have stripped `<mach-jz4760/nand.h>`).
|
||||
- You need a register definition that postdates the FiiO release (rare;
|
||||
the X1000E is end-of-life from Ingenic).
|
||||
- You are debugging a peripheral the M3K does not have but the H2 does
|
||||
(e.g., the H2's Bluetooth chipset).
|
||||
|
||||
### 3.2 How to switch
|
||||
|
||||
Override `KERNEL_HEADERS` at build time:
|
||||
|
||||
```bash
|
||||
export KERNEL_HEADERS=/opt/xburst-bsp/kernel
|
||||
./build.sh
|
||||
```
|
||||
|
||||
The build system accepts any path that contains
|
||||
`include/uapi/linux/kernel.h` -- the `headers-check` target in the
|
||||
master Makefile verifies this.
|
||||
|
||||
## 4. Rockbox (OPTIONAL, bare-metal only)
|
||||
|
||||
v1.3 listed Rockbox as the primary header reference. OreBolt OS demotes it to
|
||||
optional and explicitly scopes it to the bare-metal build path -- which
|
||||
OreBolt OS does not currently use, but a future "OreBolt OS Native" port might.
|
||||
|
||||
### 4.1 What Rockbox is actually good for
|
||||
|
||||
Rockbox is a **complete OS replacement** for the H2 and its siblings.
|
||||
When you boot Rockbox on these devices, Rockbox IS the operating system
|
||||
-- it does not run on top of Linux. This makes it useless as a header
|
||||
source for a Linux userspace build, but extremely useful if you ever
|
||||
decide to:
|
||||
|
||||
- Replace the H2's stock Linux firmware entirely with Rockbox + OreBolt OS
|
||||
native code (no kernel, no init system, just Rockbox acting as the
|
||||
OS).
|
||||
- Cross-reference a register definition whose semantics are unclear from
|
||||
the C struct alone (Rockbox's driver code shows how the register is
|
||||
actually programmed, in real working code).
|
||||
|
||||
### 4.2 What Rockbox is NOT good for
|
||||
|
||||
- **Mining headers for use in a Linux userspace build.** Rockbox's
|
||||
headers are bare-metal -- they define register addresses as physical
|
||||
memory locations and access them via direct pointer dereference. In
|
||||
a Linux userspace build, you access the same registers via `/dev/mem`
|
||||
or kernel drivers, not by including Rockbox's headers.
|
||||
- **Copy-pasting GPL code into a proprietary project.** Rockbox is
|
||||
GPL-2.0; you cannot verbatim-copy its driver code into OreBolt OS
|
||||
modules that are not GPL.
|
||||
|
||||
### 4.3 If you still want Rockbox for reference
|
||||
|
||||
```bash
|
||||
git clone --depth 1 https://github.com/Rockbox/rockbox.git /opt/rockbox-ref
|
||||
```
|
||||
|
||||
The relevant tree for the H2 is `firmware/target/mips/ingenic/jz4760/`
|
||||
(the closest public match to the X1000E). Use it only as a semantic
|
||||
reference for register behavior -- do not include its headers in the
|
||||
OreBolt OS build path.
|
||||
|
||||
## 5. Decision matrix
|
||||
|
||||
| Build target | Headers source | Rockbox role |
|
||||
|---|---|---|
|
||||
| OreBolt OS on stock H2 Linux firmware (current path) | **FiiO M3K GPL kernel** | None |
|
||||
| OreBolt OS on a custom Buildroot Linux rootfs for H2 | **FiiO M3K GPL kernel** | None |
|
||||
| OreBolt OS bare-metal on H2 (future "Native" port) | Rockbox `jz4760/` headers, FiiO headers as cross-reference | Primary |
|
||||
| Any build needing a stripped header | **Ingenic XBurst BSP** as fallback | None |
|
||||
|
||||
## 6. Verification
|
||||
|
||||
Run `make headers-check` after setting `KERNEL_HEADERS` to verify the
|
||||
tree is correctly installed:
|
||||
|
||||
```bash
|
||||
make headers-check KERNEL_HEADERS=/opt/fiio-m3k-linux
|
||||
# Expected:
|
||||
# [OK] kernel headers root: /opt/fiio-m3k-linux
|
||||
```
|
||||
|
||||
The check verifies:
|
||||
- The path exists.
|
||||
- `include/uapi/linux/kernel.h` exists (i.e., this is a Linux kernel tree).
|
||||
- Warns (does not fail) if `arch/mips/include/asm/mach-jz4760/jz4760.h`
|
||||
is missing -- this is the JZ4760 SoC header. If you are using the
|
||||
XBurst BSP, the path may differ; in that case, the build will fail
|
||||
later when it tries to `#include` a missing header, and you can
|
||||
investigate from there.
|
||||
|
||||
## 7. Historical PREREQUISITES.md delta
|
||||
|
||||
The v1.3 PREREQUISITES.md section on "Rockbox Headers for Ingenic T31" is
|
||||
**replaced** by this file. The current PREREQUISITES.md points
|
||||
operators here instead of reproducing the Rockbox workflow.
|
||||
|
|
@ -0,0 +1,178 @@
|
|||
# OreBolt OS v1.7 -- Multi-Tier Licensing Strategy
|
||||
|
||||
> **TL;DR** -- bitchat mesh module is **AGPL-3.0-only**. Everything else in
|
||||
> OreBolt OS is **GPL-2.0-or-later**. LVGL/lv_drivers stay **MIT** (upstream).
|
||||
> The AGPL boundary is a single .mod binary; it does **not** virally relicense
|
||||
> the rest of OreBolt OS because the rest of OreBolt OS is not a derivative work of
|
||||
> bitchat. See §4 for the boundary analysis.
|
||||
|
||||
---
|
||||
|
||||
## 1. Why AGPL for the bitchat layer
|
||||
|
||||
The bitchat mesh layer is the only OreBolt OS component that runs as a
|
||||
**networked service** -- every other module is a local LVGL UI panel that
|
||||
talks to hardware through `/dev` or GPIO. When a bitchat node forwards a
|
||||
message to another node, every operator downstream of that node is
|
||||
"interacting with the software over a network" within the meaning of
|
||||
AGPL v3 §13. Pure GPL would not require source disclosure for that kind of
|
||||
network-only interaction; AGPL does.
|
||||
|
||||
This matters because the whole point of the bitchat layer is to let H2
|
||||
devices form a mesh that survives the loss of any single node. If an
|
||||
operator forks bitchat, hardens it, and runs the hardened version on their
|
||||
node, every other mesh participant is effectively trusting that hardened
|
||||
binary without being able to inspect it. AGPL v3 §13 closes that loophole
|
||||
by requiring the operator to make their modified source available to anyone
|
||||
who interacts with their node over the mesh, via the "Written Offer"
|
||||
mechanism in §13(d).
|
||||
|
||||
AGPL v3 §6 (User Product) is the second reason this license fits the H2.
|
||||
Section 6 requires that anyone distributing a "User Product" (consumer
|
||||
device) running AGPL software must allow the user to install modified
|
||||
versions of that software. This blocks **tivoization** -- the practice of
|
||||
shipping GPL/AGPL software on locked-down hardware that refuses to run
|
||||
user-modified builds. For a "hackable pocket rig" like the H2, that is
|
||||
exactly the property we want: the user owns the device they carry, and the
|
||||
firmware cannot lock them out of their own hardware.
|
||||
|
||||
## 2. License matrix
|
||||
|
||||
| Component | License | SPDX | Files |
|
||||
|---|---|---|---|
|
||||
| LVGL v8.3.11 (upstream) | MIT | `MIT` | `lvgl/**` (cloned at build time) |
|
||||
| lv_drivers v8.3.0 (upstream) | MIT | `MIT` | `lv_drivers/**` (cloned at build time) |
|
||||
| liblvgl.so (shared) | MIT | `MIT` | `overlay/usr/lib/liblvgl.so` |
|
||||
| h2_test (launcher) | GPL-2.0+ | `GPL-2.0-or-later` | `overlay/usr/bin/h2_test`, `main.c` |
|
||||
| retro_input_mapper | GPL-2.0+ | `GPL-2.0-or-later` | `overlay/usr/bin/retro_input_mapper` |
|
||||
| liborebolt.a (static hw lib) | GPL-2.0+ | `GPL-2.0-or-later` | `liborebolt.a`, `src/modules/*.c` |
|
||||
| 17 userland modules | GPL-2.0+ | `GPL-2.0-or-later` | `modules/orebolt-{vault,nettaps,...,proxalarm}/*.c`, `overlay/apps/*.mod` |
|
||||
| **bitchat mesh module** | **AGPL-3.0-only** | **`AGPL-3.0-only`** | `modules/orebolt-bitchat/bitchat.c`, `src/modules/mod_bitchat_mesh.c`, `overlay/apps/bitchat.mod` |
|
||||
| Init scripts | GPL-2.0+ | `GPL-2.0-or-later` | `overlay/etc/init.d/*` |
|
||||
| Documentation | CC-BY-4.0 | `CC-BY-4.0` | `*.md`, `*.txt` |
|
||||
|
||||
## 3. The AGPL boundary -- what is and is not covered
|
||||
|
||||
The AGPL v3 §13 "Modified Corresponding Source" obligation extends to:
|
||||
- the bitchat module source (`mod_bitchat_mesh.c` and `modules/orebolt-bitchat/bitchat.c`)
|
||||
- any modifications an operator makes to those files
|
||||
- the corresponding source for any libraries bitchat links against **that
|
||||
are themselves AGPL** (none, in our case -- it links against MIT LVGL and
|
||||
GPL-2.0+ liborebolt.a)
|
||||
|
||||
The AGPL obligation does **not** extend to:
|
||||
- The other 17 userland modules (vault, pauto, etc.) -- they are separate
|
||||
programs that happen to share a launcher and a UI library. They are not
|
||||
derivative works of bitchat.
|
||||
- `liborebolt.a` -- bitchat links TO liborebolt.a, not the other way around.
|
||||
A library that is linked against is not automatically a derivative work
|
||||
of every program that links against it. See FSF FAQ on aggregate vs.
|
||||
derivative works.
|
||||
- `liblvgl.so` -- MIT, so AGPL's viral clause has nothing to grab onto.
|
||||
- The H2 hardware itself -- software licenses do not license hardware. The
|
||||
AGPL §6 User Product clause governs how the software may be installed on
|
||||
the hardware, but does not place restrictions on the hardware design.
|
||||
|
||||
## 4. Operator obligations when modifying bitchat
|
||||
|
||||
If you modify `mod_bitchat_mesh.c` or `bitchat.c` and run the modified
|
||||
version on an H2 that other operators interact with over the mesh, you
|
||||
must, under AGPL v3 §13(d):
|
||||
|
||||
1. Make the modified source code available via a Written Offer, valid for
|
||||
at least three years, to any operator who interacts with your node.
|
||||
2. The Written Offer must specify a reasonable cost for physically
|
||||
transmitting the source (no more than the actual cost).
|
||||
3. The source must include the corresponding source of all libraries
|
||||
bitchat links against that are themselves AGPL-licensed (in our case,
|
||||
none beyond bitchat itself).
|
||||
|
||||
The build embeds `BITCHAT_SOURCE_URL` (defined in `mod_bitchat_mesh.c`) in
|
||||
every `bitchat.mod` binary. Every mesh HELLO message advertises this URL
|
||||
to peers via the `BITCHAT_MSG_LICENSE` message type -- so operators
|
||||
downstream of a modified node are automatically informed of their source
|
||||
rights. **Before shipping, replace the placeholder
|
||||
`https://example.invalid/orebolt-bitchat-src` with the actual Written
|
||||
Offer URL.**
|
||||
|
||||
## 5. Why not AGPL the whole stack?
|
||||
|
||||
Three reasons:
|
||||
|
||||
1. **Anti-tivoization is already covered by GPL-2.0+ for the other 17
|
||||
modules.** GPL v2 §3(c) and GPL v3 §6 already require installable
|
||||
source for User Products. AGPL's §6 is functionally identical to GPL
|
||||
v3 §6 -- the only thing AGPL adds is §13 (the network-interaction
|
||||
clause), which only matters for components that interact over a
|
||||
network. Only bitchat does that.
|
||||
|
||||
2. **AGPL §13 would actually hurt the other modules' adoption.** If
|
||||
vault.mod were AGPL, anyone who set up a vault "service" (e.g. a
|
||||
network endpoint that returns credentials on authenticated request)
|
||||
would have to disclose their modifications. That is not the threat
|
||||
model for vault -- vault's threat model is "the device falls into the
|
||||
wrong hands" (handled by panic_purge), not "someone operates a forked
|
||||
vault server."
|
||||
|
||||
3. **Clean module boundaries are a feature, not a bug.** By isolating
|
||||
the AGPL boundary to a single .mod binary, we make it trivial for an
|
||||
operator to know exactly what they need to share source for. If
|
||||
everything were AGPL, every operator would need to share source for
|
||||
every modification, and the friction would discourage legitimate
|
||||
device customization.
|
||||
|
||||
## 6. Practical compliance checklist
|
||||
|
||||
Before deploying a modified H2 with bitchat:
|
||||
|
||||
- [ ] Replaced `BITCHAT_SOURCE_URL` placeholder with a real URL pointing
|
||||
to your modified bitchat source (tarball, git repo, or Written
|
||||
Offer page).
|
||||
- [ ] The URL is reachable from the mesh (or you have a Written Offer
|
||||
ready for physical media delivery).
|
||||
- [ ] The URL stays valid for at least 3 years from first deployment.
|
||||
- [ ] You bumped `BITCHAT_PROTOCOL_VERSION` if you changed the wire
|
||||
format (so unmodified nodes can refuse to interoperate cleanly
|
||||
rather than silently misbehaving).
|
||||
- [ ] `make core` succeeds and `build.sh` Phase 7 verifies that
|
||||
`bitchat.mod` carries the `AGPL_BITCHAT` marker.
|
||||
- [ ] The H2 firmware allows installing a modified `bitchat.mod` (i.e.,
|
||||
the device is not tivoized against AGPL §6).
|
||||
|
||||
## 8. BitChat Naming: Nominative Fair Use
|
||||
|
||||
The `bitchat` module is an **independent C implementation** of the BitChat
|
||||
decentralized mesh protocol. It is not affiliated with, sponsored by, or
|
||||
endorsed by Permissionless Tech, LLC or the original creators of BitChat.
|
||||
|
||||
The name "bitchat" is used under **nominative fair use** -- it describes
|
||||
what protocol the module implements, not an attempt to imply sponsorship
|
||||
or endorsement. The module directory (`orebolt-bitchat/`) and the
|
||||
OreBolt OS project name provide clear branding distinction from any
|
||||
official BitChat product.
|
||||
|
||||
No official BitChat logos, icons, or visual assets are used. The
|
||||
wire protocol (`BITCHAT_MSG_*` message types) is implemented from the
|
||||
open-access BitChat protocol whitepaper and the permissively-licensed
|
||||
open-source client reference implementations (iOS: Unlicense/Public
|
||||
Domain; Android: MIT License).
|
||||
|
||||
This naming convention is consistent with industry practice for
|
||||
third-party protocol libraries (e.g., `libbitchat`, `bitchat-c`,
|
||||
`bitchat-sys` in Rust).
|
||||
|
||||
## 9. If you want to relicense the whole stack
|
||||
|
||||
If a future version of OreBolt OS decides to put everything under AGPL, the
|
||||
only blockers are:
|
||||
|
||||
1. Any GPL-2.0-only code (we deliberately used GPL-2.0-or-later to permit
|
||||
this upgrade path).
|
||||
2. Any contributor who has not signed off on the upgrade -- you need
|
||||
their consent or you need to rip their code. A `DCO` (Developer
|
||||
Certificate of Origin) sign-off at contribution time makes this
|
||||
tractable.
|
||||
|
||||
LVGL being MIT means there is no upstream-license blocker. The bitchat
|
||||
module is already AGPL-3.0-only, which is one-way (you cannot downgrade
|
||||
to GPL). Plan accordingly.
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
# ==============================================================================
|
||||
# OREBOLT OS -- UNIFIED MASTER BUILD ORCHESTRATOR (v1.7)
|
||||
# ==============================================================================
|
||||
#
|
||||
# Target: HiFiWalker H2 (Ingenic X1000E / JZ4760 family, MIPS32r2,
|
||||
# little-endian, hard-float, mipsel-linux-musl)
|
||||
# Host: Arch Linux (x86_64) with mipsel-linux-musl cross-compiler
|
||||
#
|
||||
# v1.7 CHANGES (see CHANGELOG.md for full diff):
|
||||
# * 17 .mod binaries + 1 standalone mapper daemon (unchanged).
|
||||
# * retro.mod is the retro game launcher.
|
||||
# * retro_input_mapper is the uinput gamepad daemon.
|
||||
# * Makefile.h2-core-v6.2 -> v6.5.
|
||||
#
|
||||
# Sub-builds:
|
||||
#
|
||||
# 1. H2 Core Platform v6.5 (Makefile.h2-core-v6.2, filename unchanged)
|
||||
# - LVGL v8.3.11 as liblvgl.so (shared library, ~200 KB)
|
||||
# - 17 module .mod binaries in overlay/apps/
|
||||
# - Master broker (main.c) with fork/exec launcher
|
||||
# - Integrated retro_input_mapper daemon (NO LVGL, hardened build)
|
||||
# - ROM directory structure (11 systems in overlay/data/roms/)
|
||||
# - Output: overlay/usr/lib/liblvgl.so, overlay/usr/bin/h2_test,
|
||||
# overlay/apps/*.mod, overlay/usr/bin/retro_input_mapper
|
||||
#
|
||||
# 2. OreBolt OS v1.7 (Makefile.orebolt-v1.5)
|
||||
# - src/modules/*.c -> liborebolt.a (static archive)
|
||||
# - 150-payload HID macro matrix via inject_payloads.sh
|
||||
# - bitchat mesh module compiled with -DAGPL_BITCHAT
|
||||
#
|
||||
# ==============================================================================
|
||||
|
||||
H2_CORE_MAKEFILE := Makefile.h2-core-v6.2
|
||||
OREBOLT_MAKEFILE := Makefile.orebolt-v1.5
|
||||
|
||||
KERNEL_HEADERS ?= /opt/fiio-m3k-linux
|
||||
|
||||
.PHONY: all core orebolt payloads clean validate_env help headers-check
|
||||
|
||||
all: validate_env headers-check core orebolt
|
||||
@echo "======================================================================"
|
||||
@echo "[OK] OREBOLT OS v1.7 FULLY BUILT"
|
||||
@echo " Target SoC: Ingenic X1000E (JZ4760 family, MIPS32r2 mipsel)"
|
||||
@echo " Headers: $(KERNEL_HEADERS)"
|
||||
@echo " LVGL shared: overlay/usr/lib/liblvgl.so"
|
||||
@echo " Launcher: overlay/usr/bin/h2_test (fork/exec broker)"
|
||||
@echo " Mapper: overlay/usr/bin/retro_input_mapper (integrated, hardened)"
|
||||
@echo " ROM dirs: overlay/data/roms/{nes,snes,gb,...,ngp} (11 systems)"
|
||||
@echo " Modules: overlay/apps/*.mod (17 modules)"
|
||||
@echo " HW archive: liborebolt.a"
|
||||
@echo " Payloads: overlay/data/payloads/{linux,macos,windows}/"
|
||||
@echo " Licensing: see LICENSE.md (bitchat = AGPL-3.0-only)"
|
||||
@echo "======================================================================"
|
||||
|
||||
validate_env:
|
||||
@if [ "$$(basename $$(pwd))" != "h2-workspace" ]; then \
|
||||
echo "[-] CRITICAL: build must run inside h2-workspace/"; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
headers-check: validate_env
|
||||
@if [ ! -d "$(KERNEL_HEADERS)" ]; then \
|
||||
echo "[-] CRITICAL: KERNEL_HEADERS not found at $(KERNEL_HEADERS)"; \
|
||||
echo " Clone the FiiO M3K GPL kernel tree (see HEADERS.md):"; \
|
||||
echo " git clone --depth 1 https://github.com/..."; \
|
||||
echo " $(KERNEL_HEADERS)"; \
|
||||
exit 1; \
|
||||
fi
|
||||
@if [ ! -f "$(KERNEL_HEADERS)/include/uapi/linux/kernel.h" ]; then \
|
||||
echo "[-] $(KERNEL_HEADERS) does not look like a Linux kernel tree"; \
|
||||
exit 1; \
|
||||
fi
|
||||
@echo "[OK] kernel headers root: $(KERNEL_HEADERS)"
|
||||
|
||||
core: validate_env
|
||||
@echo ">>> Building H2 Core Platform v6.5 (shared LVGL + 17 modules)..."
|
||||
$(MAKE) -f $(H2_CORE_MAKEFILE) all KERNEL_HEADERS=$(KERNEL_HEADERS)
|
||||
|
||||
orebolt: validate_env
|
||||
@echo ">>> Building OreBolt OS v1.7 (liborebolt.a + payloads)..."
|
||||
$(MAKE) -f $(OREBOLT_MAKEFILE) all HARDWARE_GPIO=$(HARDWARE_GPIO) \
|
||||
KERNEL_HEADERS=$(KERNEL_HEADERS)
|
||||
|
||||
payloads: validate_env
|
||||
@echo ">>> Regenerating 150-payload HID matrix only..."
|
||||
@chmod +x inject_payloads.sh
|
||||
@./inject_payloads.sh
|
||||
|
||||
clean:
|
||||
-$(MAKE) -f $(H2_CORE_MAKEFILE) clean
|
||||
-$(MAKE) -f $(OREBOLT_MAKEFILE) clean HARDWARE_GPIO=$(HARDWARE_GPIO)
|
||||
|
||||
help:
|
||||
@echo "OREBOLT OS v1.7 MASTER BUILD"
|
||||
@echo ""
|
||||
@echo "Target SoC: Ingenic X1000E (JZ4760 family, MIPS32r2 mipsel-linux-musl)"
|
||||
@echo "Headers: FiiO M3K GPL kernel tree ($(KERNEL_HEADERS))"
|
||||
@echo ""
|
||||
@echo "Targets:"
|
||||
@echo " all Build everything (default)"
|
||||
@echo " headers-check Verify kernel headers tree before build"
|
||||
@echo " core Build LVGL shared lib + launcher + 17 modules + mapper"
|
||||
@echo " orebolt Build liborebolt.a + payload matrix"
|
||||
@echo " payloads Regenerate 150-payload HID macro matrix only"
|
||||
@echo " clean Remove all build artifacts"
|
||||
@echo " help This message"
|
||||
@echo ""
|
||||
@echo "Environment:"
|
||||
@echo " KERNEL_HEADERS Path to FiiO M3K GPL kernel tree (default: /opt/fiio-m3k-linux)"
|
||||
@echo " HARDWARE_GPIO Set to 1 to compile real GPIO panic-purge path"
|
||||
@echo " CROSS_COMPILE Toolchain prefix (default: mipsel-linux-musl-)"
|
||||
|
|
@ -0,0 +1,135 @@
|
|||
# ==============================================================================
|
||||
# H2 CORE PLATFORM v6.5 -- LVGL + 17 OreBolt OS Modules
|
||||
# ==============================================================================
|
||||
# v1.7 changes from v6.2:
|
||||
# * 17 .mod binaries + 1 standalone mapper daemon.
|
||||
# * proxalarm handles all proximity sensing (bledsp, proxvec, radar_ui).
|
||||
# * retro.mod is the retro game launcher.
|
||||
# * retro_input_mapper is a standalone uinput gamepad daemon (no LVGL).
|
||||
# * bledsp_poll_wifi() stub ready for future nl80211 integration.
|
||||
# * Only proxalarm needs -lbluetooth.
|
||||
# ==============================================================================
|
||||
|
||||
CROSS ?= mipsel-linux-musl-
|
||||
CC := $(CROSS)gcc
|
||||
STRIP := $(CROSS)strip
|
||||
AR := $(CROSS)ar
|
||||
RANLIB := $(CROSS)ranlib
|
||||
|
||||
ARCHFLAGS := -march=mips32r2 -mhard-float -Os -fPIC -fno-strict-aliasing \
|
||||
-fno-common -Wall -Wextra -Wno-unused-parameter
|
||||
|
||||
KERNEL_HEADERS ?= /opt/fiio-m3k-linux
|
||||
|
||||
INCLUDES := -Iinclude \
|
||||
-I$(KERNEL_HEADERS)/include/uapi \
|
||||
-I$(KERNEL_HEADERS)/include \
|
||||
-I$(KERNEL_HEADERS)/arch/mips/include \
|
||||
-I$(KERNEL_HEADERS)/arch/mips/include/asm/mach-jz4760 \
|
||||
-Ilvgl -Ilvgl/src -Ilv_drivers
|
||||
|
||||
CFLAGS := $(ARCHFLAGS) $(INCLUDES) -DLV_CONF_INCLUDE_SIMPLE=1 \
|
||||
-DLV_LVGL_H_INCLUDE_SIMPLE=1
|
||||
LDFLAGS := -Loverlay/usr/lib -Wl,-rpath,/usr/lib
|
||||
LDLIBS := -llvgl -lm -ldl -lpthread
|
||||
|
||||
# Extra libs for specific modules
|
||||
LDLIBS_BLUETOOTH := -lbluetooth
|
||||
|
||||
# v1.7: 17 module .mod binaries (all have main() + own LVGL init)
|
||||
MODULES := \
|
||||
vault:modules/orebolt-vault/vault.c \
|
||||
nettaps:modules/orebolt-nettap/scalpel.c \
|
||||
scalpel:modules/orebolt-scalpel/scalpel.c \
|
||||
deploy:modules/orebolt-deploy/deploy.c \
|
||||
studio:modules/orebolt-studio/studio.c \
|
||||
probe:modules/orebolt-probe/probe.c \
|
||||
vterm:modules/orebolt-vterm/vterm.c \
|
||||
pauto:modules/orebolt-pauto/pauto.c \
|
||||
extract:modules/orebolt-extract/extract.c \
|
||||
noise:modules/orebolt-noise/noise.c \
|
||||
reset:modules/orebolt-reset/reset.c \
|
||||
retro:modules/orebolt-retro/retro.c \
|
||||
rfid:modules/orebolt-rfid/rfid.c \
|
||||
glitch:modules/orebolt-glitch/glitch.c \
|
||||
pwdb:modules/orebolt-pwdb/pwdb.c \
|
||||
proxalarm:modules/orebolt-proxalarm/bledsp.c modules/orebolt-proxalarm/proxvec.c modules/orebolt-proxalarm/radar_ui.c modules/orebolt-proxalarm/proxalarm.c \
|
||||
bitchat:modules/orebolt-bitchat/bitchat.c
|
||||
|
||||
# Modules that need extra libraries
|
||||
BLUETOOTH_MODULES := proxalarm
|
||||
PTHREAD_MODULES := scalpel
|
||||
|
||||
.PHONY: all clean lvgl modules launcher mapper
|
||||
|
||||
all: lvgl launcher mapper modules
|
||||
|
||||
# --- LVGL shared library -----------------------------------------------------
|
||||
lvgl:
|
||||
@if [ ! -d lvgl/src ]; then \
|
||||
echo "[-] lvgl/src not found. Run build.sh Phase 5 first, or:"; \
|
||||
echo " git clone --depth 1 -b v8.3.11 https://github.com/lvgl/lvgl.git"; \
|
||||
echo " git clone --depth 1 -b v8.3.0 https://github.com/lvgl/lv_drivers.git"; \
|
||||
exit 1; \
|
||||
fi
|
||||
@echo ">>> Building liblvgl.so (v8.3.11)..."
|
||||
$(CC) $(CFLAGS) -shared -fPIC \
|
||||
lvgl/src/*.c lvgl/src/draw/*.c lvgl/src/draw/sw/*.c \
|
||||
lvgl/src/font/*.c lvgl/src/misc/*.c lvgl/src/widgets/*.c \
|
||||
lvgl/src/extra/*.c lvgl/src/extra/layouts/*.c lvgl/src/extra/themes/*.c \
|
||||
-o overlay/usr/lib/liblvgl.so
|
||||
$(STRIP) overlay/usr/lib/liblvgl.so
|
||||
|
||||
# --- Master launcher (fork/exec broker) -------------------------------------
|
||||
launcher: lvgl
|
||||
@echo ">>> Building h2_test launcher..."
|
||||
$(CC) $(CFLAGS) main.c -o overlay/usr/bin/h2_test $(LDFLAGS) $(LDLIBS)
|
||||
$(STRIP) overlay/usr/bin/h2_test
|
||||
|
||||
# --- Retro gamepad input mapper daemon (STANDALONE, NO LVGL) ----------------
|
||||
# The mapper is a uinput daemon, NOT an LVGL module.
|
||||
# It must NOT link against liblvgl.so.
|
||||
# Creates ROM directories and installs the init script.
|
||||
MAPPER_SRC := modules/orebolt-retro/retro_input_mapper.c
|
||||
MAPPER_BIN := overlay/usr/bin/retro_input_mapper
|
||||
ROM_BASE := overlay/data/roms
|
||||
ROM_SYSTEMS := nes snes gb gbc gba genesis sms gg pce atari2600 ngp
|
||||
|
||||
mapper: $(MAPPER_BIN) rom-dirs
|
||||
@echo "[OK] retro module integrated (daemon + ROM dirs + init script)"
|
||||
|
||||
$(MAPPER_BIN): $(MAPPER_SRC)
|
||||
@mkdir -p overlay/usr/bin
|
||||
@echo ">>> Building retro_input_mapper (standalone daemon)..."
|
||||
$(CC) -march=mips32r2 -mhard-float -O2 -Wall -Wextra \
|
||||
-fstack-protector-strong -D_FORTIFY_SOURCE=2 \
|
||||
-Iinclude $< -o $@
|
||||
$(STRIP) --strip-unneeded $@
|
||||
|
||||
rom-dirs:
|
||||
@for sysdir in $(ROM_SYSTEMS); do \
|
||||
mkdir -p $(ROM_BASE)/$$sysdir; \
|
||||
done
|
||||
@echo "[OK] ROM directories created: $(ROM_BASE)/{nes,snes,gb,...,ngp}"
|
||||
|
||||
# --- 17 module .mod binaries -----------------------------------------------
|
||||
modules: lvgl
|
||||
@for entry in $(MODULES); do \
|
||||
name=$${entry%%:*}; \
|
||||
src=$${entry#*:}; \
|
||||
extralibs=""; \
|
||||
for bm in $(BLUETOOTH_MODULES); do \
|
||||
if [ "$$name" = "$$bm" ]; then extralibs="$(LDLIBS_BLUETOOTH)"; fi; \
|
||||
done; \
|
||||
echo ">>> Building module: $$name"; \
|
||||
$(CC) $(CFLAGS) $$src -o overlay/apps/$$name.mod \
|
||||
$(LDFLAGS) $(LDLIBS) $$extralibs || exit 1; \
|
||||
$(STRIP) overlay/apps/$$name.mod; \
|
||||
done
|
||||
|
||||
clean:
|
||||
rm -f overlay/usr/lib/liblvgl.so
|
||||
rm -f overlay/usr/bin/h2_test
|
||||
rm -f overlay/usr/bin/retro_input_mapper
|
||||
rm -f overlay/apps/*.mod
|
||||
rm -rf overlay/data/roms
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
# ==============================================================================
|
||||
# OREBOLT OS v1.5 -- Hardware Library + Payload Matrix
|
||||
# ==============================================================================
|
||||
# Builds liborebolt.a (static archive) from src/modules/*.c and runs
|
||||
# inject_payloads.sh to regenerate the 150-payload HID macro matrix under
|
||||
# overlay/data/payloads/{linux,macos,windows}/.
|
||||
#
|
||||
# v1.5 changes:
|
||||
# * Bumped from v1.4.
|
||||
# * No source changes to liborebolt.a -- stubs remain for hardware-
|
||||
# specific modules (mod_core_hid, mod_panic_*, etc.) until real
|
||||
# hardware register definitions are available.
|
||||
# * bitchat mesh still compiled with -DAGPL_BITCHAT.
|
||||
# ==============================================================================
|
||||
|
||||
CROSS ?= mipsel-linux-musl-
|
||||
CC := $(CROSS)gcc
|
||||
AR := $(CROSS)ar
|
||||
RANLIB := $(CROSS)ranlib
|
||||
|
||||
KERNEL_HEADERS ?= /opt/fiio-m3k-linux
|
||||
HARDWARE_GPIO ?= 0
|
||||
|
||||
ARCHFLAGS := -march=mips32r2 -mhard-float -Os -fPIC -fno-strict-aliasing -Wall
|
||||
INCLUDES := -Iinclude -Isrc/modules \
|
||||
-I$(KERNEL_HEADERS)/include/uapi \
|
||||
-I$(KERNEL_HEADERS)/arch/mips/include \
|
||||
-I$(KERNEL_HEADERS)/arch/mips/include/asm/mach-jz4760
|
||||
|
||||
CFLAGS := $(ARCHFLAGS) $(INCLUDES) \
|
||||
-DHARDWARE_GPIO=$(HARDWARE_GPIO)
|
||||
|
||||
OREBOLT_SRCS := \
|
||||
src/modules/mod_core_hid.c \
|
||||
src/modules/mod_forensics.c \
|
||||
src/modules/mod_panic_hardware_purge.c \
|
||||
src/modules/mod_panic_purge.c \
|
||||
src/modules/mod_radio_input_multiplex.c \
|
||||
src/modules/mod_ui_frame_graphics.c \
|
||||
src/modules/mod_bitchat_mesh.c
|
||||
|
||||
OREBOLT_OBJS := $(OREBOLT_SRCS:.c=.o)
|
||||
|
||||
.PHONY: all clean payloads
|
||||
|
||||
all: liborebolt.a payloads
|
||||
|
||||
liborebolt.a: $(OREBOLT_OBJS)
|
||||
@echo ">>> Archiving liborebolt.a..."
|
||||
$(AR) rcs $@ $(OREBOLT_OBJS)
|
||||
$(RANLIB) $@
|
||||
|
||||
src/modules/mod_bitchat_mesh.o: src/modules/mod_bitchat_mesh.c
|
||||
$(CC) $(CFLAGS) -DAGPL_BITCHAT -c $< -o $@
|
||||
|
||||
%.o: %.c
|
||||
$(CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
payloads:
|
||||
@echo ">>> Regenerating 150-payload HID matrix..."
|
||||
@chmod +x inject_payloads.sh
|
||||
@./inject_payloads.sh
|
||||
|
||||
clean:
|
||||
rm -f $(OREBOLT_OBJS) liborebolt.a
|
||||
|
|
@ -0,0 +1,538 @@
|
|||
# OreBolt OS v1.7 -- Host & Toolchain Prerequisites
|
||||
|
||||
Everything you need to set up on your Arch Linux build machine **before**
|
||||
running `./build.sh`. This guide covers the host OS, cross-compiler
|
||||
toolchain, **kernel headers sourcing** (see
|
||||
[HEADERS.md](HEADERS.md)), optional Buildroot environment, and SD card
|
||||
preparation.
|
||||
|
||||
> **SoC note**: The target SoC is the **Ingenic X1000E** (JZ4760 family),
|
||||
> not the T31/X2000. The headers source is the **FiiO M3K GPL kernel tree**
|
||||
> (same SoC as the H2), not Rockbox. Rockbox is now optional and scoped
|
||||
> to bare-metal only. See [HEADERS.md](HEADERS.md) for the full strategy.
|
||||
|
||||
---
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [Arch Linux Host Setup](#1-arch-linux-host-setup)
|
||||
2. [Cross-Compiler Toolchain](#2-cross-compiler-toolchain)
|
||||
3. [Kernel Headers (FiiO M3K GPL tree)](#3-kernel-headers-fiio-m3k-gpl-tree)
|
||||
4. [Optional: Buildroot for X1000E Full Image](#4-optional-buildroot-for-x1000e-full-image)
|
||||
5. [Workspace Clone & Verify](#5-workspace-clone--verify)
|
||||
6. [SD Card Preparation](#6-sd-card-preparation)
|
||||
7. [Environment Variables Reference](#7-environment-variables-reference)
|
||||
8. [Troubleshooting](#8-troubleshooting)
|
||||
9. [Licensing](#9-licensing)
|
||||
10. [What `build.sh` Handles Automatically](#10-what-buildsh-handles-automatically)
|
||||
|
||||
---
|
||||
|
||||
## 1. Arch Linux Host Setup
|
||||
|
||||
OreBolt OS targets the HiFiWalker H2 (Ingenic **X1000E**, MIPS32r2,
|
||||
mipsel) but builds entirely on a stock Arch Linux x86_64 workstation.
|
||||
Arch is the only supported host because the build tooling, package
|
||||
versions, and PATH layout are validated against it. Other distributions
|
||||
may work but will require porting effort, particularly around `pacman`
|
||||
dependency checks in Phase 0 of `build.sh`.
|
||||
|
||||
### 1.1 Install Base Build Toolchain
|
||||
|
||||
```bash
|
||||
sudo pacman -Syu --needed base-devel git make python3
|
||||
```
|
||||
|
||||
This pulls in `gcc`, `binutils`, `make`, `patch`, `tar`, `gzip`, `bzip2`,
|
||||
`xz`, `sed`, `gawk`, `file`, and `which` -- the minimal set required by
|
||||
both the host-side tools and the cross-compilation pipeline. The
|
||||
`build.sh` script will check for these in Phase 0 and attempt to install
|
||||
any missing ones via `pacman -S --noconfirm`.
|
||||
|
||||
### 1.2 Install SD Card Utilities
|
||||
|
||||
```bash
|
||||
sudo pacman -S --needed rsync dosfstools e2fsprogs parted file
|
||||
```
|
||||
|
||||
Required for partitioning, formatting, and verifying the SD card used to
|
||||
deploy OreBolt OS to the H2 (see Section 6). `file` is also used by
|
||||
`build.sh` Phase 7 to verify that produced binaries are MIPS
|
||||
little-endian ELF.
|
||||
|
||||
### 1.3 Keep Arch Updated
|
||||
|
||||
```bash
|
||||
sudo pacman -Syu
|
||||
```
|
||||
|
||||
Arch Linux rolling release means you will always have a recent GCC,
|
||||
binutils, and glibc. No pinned versions are required for the host
|
||||
toolchain -- the cross-compiler is fully self-contained and does not
|
||||
depend on the host GCC version.
|
||||
|
||||
### 1.4 User Permissions
|
||||
|
||||
You need `sudo` access for package installation and SD card mount
|
||||
operations. The build scripts detect whether they are running as root
|
||||
or can use passwordless sudo; if neither works, they print the manual
|
||||
install command and exit.
|
||||
|
||||
```bash
|
||||
sudo -n true 2>/dev/null && echo "passwordless sudo: OK" || echo "passwordless sudo: NO (will prompt)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Cross-Compiler Toolchain
|
||||
|
||||
The build requires a `mipsel-linux-musl` cross-compiler targeting
|
||||
MIPS32r2 little-endian with hard-float ABI and musl libc. The `build.sh`
|
||||
script and all Makefiles expect the toolchain binaries to be named with
|
||||
the prefix `mipsel-linux-musl-` (e.g., `mipsel-linux-musl-gcc`,
|
||||
`mipsel-linux-musl-strip`, `mipsel-linux-musl-ar`,
|
||||
`mipsel-linux-musl-ranlib`).
|
||||
|
||||
### 2.1 Toolchain Source: Rockbox (Recommended)
|
||||
|
||||
Rockbox maintains a tested MIPS cross-compiler specifically for Ingenic
|
||||
JZ47xx and X1000-family SoCs. Since Rockbox has a port for this exact
|
||||
device family, their toolchain is the most authoritative and
|
||||
hardware-verified option available.
|
||||
|
||||
**Option A: Use Rockbox's prebuilt toolchain**
|
||||
|
||||
1. Visit the Rockbox toolchain downloads page:
|
||||
`https://www.rockbox.org/wiki/CrossCompile`
|
||||
2. Download the MIPS toolchain tarball.
|
||||
3. Extract and install:
|
||||
```bash
|
||||
sudo mkdir -p /opt/mipsel-linux-musl
|
||||
sudo tar xzf rockbox-mips-toolchain-*.tar.gz -C /opt/mipsel-linux-musl --strip-components=1
|
||||
```
|
||||
4. Add to PATH (see Section 2.4).
|
||||
|
||||
**Option B: Build the Rockbox toolchain from source**
|
||||
|
||||
```bash
|
||||
git clone https://github.com/Rockbox/rockbox.git
|
||||
cd rockbox/tools
|
||||
# Follow instructions at https://www.rockbox.org/wiki/CrossCompile
|
||||
```
|
||||
|
||||
> Rockbox's toolchain is still the recommended
|
||||
> cross-compiler even though Rockbox is no longer the recommended
|
||||
> headers source. The toolchain (compiler + binutils + libc) is
|
||||
> SoC-agnostic enough that it works for any MIPS32r2 target.
|
||||
|
||||
### 2.2 Toolchain Source: musl.cc (Fallback)
|
||||
|
||||
If the Rockbox toolchain is unavailable, musl.cc provides
|
||||
community-built musl cross-compiler binaries. These are generic and not
|
||||
tuned for Ingenic specifically.
|
||||
|
||||
```bash
|
||||
wget https://musl.cc/mipsel-linux-musl-cross.tgz -O /tmp/mipsel-toolchain.tgz
|
||||
sudo mkdir -p /opt/mipsel-linux-musl
|
||||
sudo tar xzf /tmp/mipsel-toolchain.tgz -C /opt/mipsel-linux-musl --strip-components=1
|
||||
```
|
||||
|
||||
**Caveat**: musl.cc toolchains may not include Ingenic-specific compiler
|
||||
intrinsics or tune flags. If you encounter issues with floating-point
|
||||
operations or scheduler behavior, switch to the Rockbox toolchain.
|
||||
|
||||
### 2.3 Toolchain Source: Buildroot Self-Build (Advanced)
|
||||
|
||||
Buildroot can generate a complete cross-compiler toolchain from source.
|
||||
This is the most flexible option but also the slowest (build time: 15-45
|
||||
minutes). See Section 4.
|
||||
|
||||
### 2.4 Add Toolchain to PATH
|
||||
|
||||
```bash
|
||||
export PATH="/opt/mipsel-linux-musl/bin:$PATH"
|
||||
mipsel-linux-musl-gcc --version
|
||||
mipsel-linux-musl-gcc -dumpmachine # Should print: mipsel-linux-musl
|
||||
```
|
||||
|
||||
If your toolchain uses a different prefix, set `CROSS_COMPILE`:
|
||||
|
||||
```bash
|
||||
export CROSS_COMPILE=mips-linux-gnu-
|
||||
./build.sh
|
||||
```
|
||||
|
||||
### 2.5 Verify the Toolchain Works
|
||||
|
||||
```bash
|
||||
echo '#include <stdio.h>
|
||||
int main(void) { printf("hello mipsel\\n"); return 0; }' > /tmp/test.c
|
||||
mipsel-linux-musl-gcc -march=mips32r2 -mhard-float -static -o /tmp/test_mips /tmp/test.c
|
||||
file /tmp/test_mips
|
||||
# Expected: ELF 32-bit LSB executable, MIPS, MIPS32 version 1 ...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Kernel Headers (FiiO M3K GPL tree)
|
||||
|
||||
> **Read [HEADERS.md](HEADERS.md) for the full strategy.** This section
|
||||
> is a quick-start summary.
|
||||
|
||||
OreBolt OS sources kernel headers from the **FiiO M3K GPL kernel tree** (Linux
|
||||
3.10.14, Ingenic BSP) because the M3K uses the **exact same Ingenic
|
||||
X1000E SoC as the H2**. The FiiO tree ships the X1000E register
|
||||
definitions, GPIO mux tables, clock tree headers, and DMA descriptor
|
||||
formats -- all GPL-2.0, all directly `#include`-able, no reverse
|
||||
engineering needed.
|
||||
|
||||
### 3.1 Install the FiiO M3K kernel tree
|
||||
|
||||
```bash
|
||||
sudo mkdir -p /opt/fiio-m3k-linux
|
||||
# Clone from a community mirror, or extract FiiO's official GPL tarball:
|
||||
sudo tar xzf fiio_m3k_kernel_*.tar.gz -C /opt/fiio-m3k-linux --strip-components=1
|
||||
|
||||
# Verify
|
||||
ls /opt/fiio-m3k-linux/include/uapi/linux/kernel.h
|
||||
ls /opt/fiio-m3k-linux/arch/mips/include/asm/mach-jz4760/jz4760.h
|
||||
```
|
||||
|
||||
### 3.2 Verify with `make headers-check`
|
||||
|
||||
```bash
|
||||
make headers-check KERNEL_HEADERS=/opt/fiio-m3k-linux
|
||||
# Expected:
|
||||
# [OK] kernel headers root: /opt/fiio-m3k-linux
|
||||
```
|
||||
|
||||
### 3.3 Fallback: Ingenic XBurst BSP
|
||||
|
||||
If a header is missing from the FiiO tree, use the raw Ingenic XBurst
|
||||
BSP. See [HEADERS.md §3](HEADERS.md#3-ingenic-xburst-bsp-fallback).
|
||||
|
||||
### 3.4 What about Rockbox?
|
||||
|
||||
Rockbox is now **optional** and scoped to a future bare-metal "OreBolt OS
|
||||
Native" port. The current Linux userspace build path does not use
|
||||
Rockbox at all. See [HEADERS.md §4](HEADERS.md#4-rockbox-optional-bare-metal-only).
|
||||
|
||||
---
|
||||
|
||||
## 4. Optional: Buildroot for X1000E Full Image
|
||||
|
||||
Buildroot is not required for the module-level cross-compilation that
|
||||
`build.sh` performs. However, if you want to produce a complete root
|
||||
filesystem image for the H2 (kernel + initramfs + rootfs with all
|
||||
OreBolt OS modules), Buildroot is the right tool.
|
||||
|
||||
### 4.1 Clone Buildroot
|
||||
|
||||
```bash
|
||||
git clone https://github.com/buildroot/buildroot.git
|
||||
cd buildroot
|
||||
git checkout 2024.02 # or current stable
|
||||
```
|
||||
|
||||
### 4.2 Configure for Ingenic X1000E
|
||||
|
||||
```bash
|
||||
make menuconfig
|
||||
```
|
||||
|
||||
**Target options:**
|
||||
```
|
||||
Target Architecture = MIPS (little endian)
|
||||
Target Architecture Variant = mips32r2
|
||||
Target ABI = 32 (soft-float) # override below for hard-float
|
||||
```
|
||||
|
||||
**Toolchain:**
|
||||
```
|
||||
Toolchain type = External toolchain
|
||||
Toolchain = Custom toolchain
|
||||
Toolchain path = /opt/mipsel-linux-musl
|
||||
Toolchain prefix = $(ARCH)-linux-musl-
|
||||
```
|
||||
|
||||
**Hard-float note**: the X1000E has a hardware FPU. OreBolt OS compiles
|
||||
with `-mhard-float`. If Buildroot does not expose a hard-float option
|
||||
in menuconfig, force it via `BR2_TARGET_OPTIMIZATION="-mhard-float"` in
|
||||
`.config`.
|
||||
|
||||
**System configuration:**
|
||||
```
|
||||
System hostname = h2-hifiwalker
|
||||
System banner = Welcome to OreBolt OS
|
||||
/dev management = Dynamic using devtmpfs
|
||||
Init system = BusyBox init (SysVinit)
|
||||
```
|
||||
|
||||
### 4.3 Integrate the OreBolt OS Overlay
|
||||
|
||||
In menuconfig: System configuration -> Root filesystem overlay directories
|
||||
Add: `/path/to/h2-workspace/overlay`
|
||||
|
||||
Or edit `.config` directly:
|
||||
```
|
||||
BR2_ROOTFS_OVERLAY="/path/to/h2-workspace/overlay"
|
||||
```
|
||||
|
||||
### 4.4 Build and Flash
|
||||
|
||||
```bash
|
||||
make
|
||||
# Output: output/images/{rootfs.ext4, zImage, sdcard.img}
|
||||
|
||||
sudo dd if=output/images/sdcard.img of=/dev/sdX bs=4M status=progress
|
||||
sync
|
||||
sudo eject /dev/sdX
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Workspace Clone & Verify
|
||||
|
||||
```bash
|
||||
git clone <repo-url> h2-workspace
|
||||
cd h2-workspace
|
||||
```
|
||||
|
||||
### 5.1 Verify Workspace Integrity
|
||||
|
||||
`build.sh` Phase 2 does this automatically, but you can check manually:
|
||||
|
||||
```bash
|
||||
# Required top-level files
|
||||
ls -1 Makefile Makefile.h2-core-v6.2 Makefile.orebolt-v1.5 \
|
||||
build.sh inject_payloads.sh \
|
||||
main.c include/h2_ui.h include/log_manager.h lv_conf.h.dist \
|
||||
src/modules/panic_purge_api.h \
|
||||
PREREQUISITES.md HEADERS.md LICENSE.md CHANGELOG.md \
|
||||
ARCHITECTURE.md README.md BUILD_MANIFEST.txt SHA256SUMS
|
||||
|
||||
# All 18 module source files
|
||||
ls -1 modules/orebolt-vault/vault.c \
|
||||
modules/orebolt-nettap/scalpel.c \
|
||||
modules/orebolt-deploy/deploy.c \
|
||||
modules/orebolt-studio/studio.c \
|
||||
modules/orebolt-probe/probe.c \
|
||||
modules/orebolt-vterm/vterm.c \
|
||||
modules/orebolt-pauto/pauto.c \
|
||||
modules/orebolt-extract/extract.c \
|
||||
modules/orebolt-noise/noise.c \
|
||||
modules/orebolt-reset/reset.c \
|
||||
modules/orebolt-retro/retro.c \
|
||||
modules/orebolt-retro/retro_input_mapper.c \
|
||||
modules/orebolt-rfid/rfid.c \
|
||||
modules/orebolt-glitch/glitch.c \
|
||||
modules/orebolt-pwdb/pwdb.c \
|
||||
modules/orebolt-proxalarm/bledsp.c \
|
||||
modules/orebolt-proxalarm/proxvec.c \
|
||||
modules/orebolt-proxalarm/radar_ui.c \
|
||||
modules/orebolt-proxalarm/proxalarm.c \
|
||||
modules/orebolt-bitchat/bitchat.c
|
||||
|
||||
# Init scripts
|
||||
ls -1 overlay/etc/init.d/S98retro-input \
|
||||
overlay/etc/init.d/S99broker \
|
||||
overlay/etc/init.d/bt_input_daemon.sh
|
||||
|
||||
# Hardware library sources (7 files)
|
||||
ls -1 src/modules/
|
||||
```
|
||||
|
||||
### 5.2 Verify Toolchain
|
||||
|
||||
```bash
|
||||
mipsel-linux-musl-gcc --version
|
||||
mipsel-linux-musl-gcc -dumpmachine # Must contain "mips"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. SD Card Preparation
|
||||
|
||||
The HiFiWalker H2 boots from an SD card. Minimum: a single FAT32
|
||||
partition holding the firmware files.
|
||||
|
||||
### 6.1 Identify the SD Card
|
||||
|
||||
```bash
|
||||
lsblk -o NAME,SIZE,TYPE,MOUNTPOINT
|
||||
```
|
||||
|
||||
**WARNING**: Be absolutely certain you have identified the correct
|
||||
device. `/dev/sda` is typically your system disk.
|
||||
|
||||
### 6.2 Single-Partition Layout
|
||||
|
||||
```bash
|
||||
sudo umount /dev/sdX1 2>/dev/null || true
|
||||
sudo parted /dev/sdX --script mklabel msdos
|
||||
sudo parted /dev/sdX --script mkpart primary fat32 1MiB 100%
|
||||
sudo parted /dev/sdX --script set 1 boot on
|
||||
sudo mkfs.vfat -F 32 -n OREBOLT /dev/sdX1
|
||||
```
|
||||
|
||||
### 6.3 Dual-Partition Layout (Full Rootfs)
|
||||
|
||||
```bash
|
||||
sudo parted /dev/sdX --script mklabel msdos
|
||||
sudo parted /dev/sdX --script mkpart primary fat32 1MiB 129MiB
|
||||
sudo parted /dev/sdX --script set 1 boot on
|
||||
sudo parted /dev/sdX --script mkpart primary ext4 129MiB 100%
|
||||
sudo mkfs.vfat -F 32 -n H2_BOOT /dev/sdX1
|
||||
sudo mkfs.ext4 -L H2_ROOT /dev/sdX2
|
||||
```
|
||||
|
||||
### 6.4 Deploy
|
||||
|
||||
```bash
|
||||
SD_CARD_MOUNT=/mnt/h2-sd ./build.sh --deploy
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. Environment Variables Reference
|
||||
|
||||
| Variable | Default | Description |
|
||||
|---|---|---|
|
||||
| `CROSS_COMPILE` | `mipsel-linux-musl-` | Toolchain binary prefix |
|
||||
| `KERNEL_HEADERS` | `/opt/fiio-m3k-linux` | FiiO M3K GPL kernel tree path |
|
||||
| `HARDWARE_GPIO` | `0` | Set to `1` only on real H2 hardware to compile real GPIO panic purge |
|
||||
| `SD_CARD_MOUNT` | `/mnt/h2-sd` | SD card mount point for `--deploy` |
|
||||
| `NO_COLOR` | (unset) | Set to `1` to disable ANSI color in build scripts |
|
||||
| `PATH` | (system) | Must include `/opt/mipsel-linux-musl/bin` |
|
||||
|
||||
Recommended `~/.bashrc` additions:
|
||||
|
||||
```bash
|
||||
export PATH="/opt/mipsel-linux-musl/bin:$PATH"
|
||||
export KERNEL_HEADERS="/opt/fiio-m3k-linux"
|
||||
# export SD_CARD_MOUNT="/run/media/$USER/OREBOLT"
|
||||
# export HARDWARE_GPIO=0 # explicit on dev host; set to 1 on hardware
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. Troubleshooting
|
||||
|
||||
### Toolchain not found
|
||||
```
|
||||
[-] Missing toolchain binaries: mipsel-linux-musl-gcc
|
||||
```
|
||||
Verify `/opt/mipsel-linux-musl/bin/` is in `$PATH`. If your toolchain
|
||||
uses a different prefix, set `CROSS_COMPILE`.
|
||||
|
||||
### Compiler target is not MIPS
|
||||
```
|
||||
[!] Compiler target 'x86_64-linux-gnu' does not look like MIPS
|
||||
```
|
||||
Run `which mipsel-linux-musl-gcc` and `mipsel-linux-musl-gcc
|
||||
-dumpmachine`. Ensure the cross-compiler's `bin/` appears before
|
||||
`/usr/bin/` in `$PATH`.
|
||||
|
||||
### KERNEL_HEADERS not found
|
||||
```
|
||||
[-] CRITICAL: KERNEL_HEADERS not found at /opt/fiio-m3k-linux
|
||||
```
|
||||
Install the FiiO M3K GPL kernel tree (Section 3.1) or set
|
||||
`KERNEL_HEADERS` to point at the XBurst BSP fallback (HEADERS.md §3).
|
||||
|
||||
### musl.cc toolchain float ABI mismatch
|
||||
```
|
||||
error: cannot find -lm
|
||||
```
|
||||
or linker errors about `soft-float` vs `hard-float`. Switch to the
|
||||
Rockbox toolchain (Section 2.1) which is configured for Ingenic's
|
||||
hard-float ABI.
|
||||
|
||||
### LVGL clone fails
|
||||
```
|
||||
[*] Cloning LVGL v8.3.11 + lv_drivers v8.3.0...
|
||||
fatal: unable to access 'https://github.com/lvgl/lvgl.git'
|
||||
```
|
||||
Pre-clone LVGL manually:
|
||||
```bash
|
||||
git clone --depth 1 -b v8.3.11 https://github.com/lvgl/lvgl.git
|
||||
git clone --depth 1 -b v8.3.0 https://github.com/lvgl/lv_drivers.git
|
||||
cp lv_conf.h.dist lv_conf.h
|
||||
cp lv_drivers/lv_drv_conf_template.h lv_drv_conf.h
|
||||
sed -i 's/#if 0/#if 1/' lv_drv_conf.h
|
||||
sed -i 's/USE_FBDEV 0/USE_FBDEV 1/' lv_drv_conf.h
|
||||
sed -i 's/USE_EVDEV 0/USE_EVDEV 1/' lv_drv_conf.h
|
||||
```
|
||||
`build.sh` Phase 5 will detect the existing `lvgl/src/` and skip
|
||||
cloning.
|
||||
|
||||
### bitchat.mod missing AGPL_BITCHAT marker
|
||||
```
|
||||
[-] bitchat.mod missing AGPL_BITCHAT marker (license violation)
|
||||
```
|
||||
`mod_bitchat_mesh.c` was compiled without `-DAGPL_BITCHAT`. This
|
||||
should not happen if you build via `make` or `./build.sh` (both set
|
||||
the define in `Makefile.orebolt-v1.5`). If you are building
|
||||
manually, add `-DAGPL_BITCHAT` to `CFLAGS`.
|
||||
|
||||
### Binary format verification fails
|
||||
```
|
||||
[-] BAD: h2_test -- ELF 64-bit LSB executable, x86-64
|
||||
```
|
||||
The build picked up the host GCC instead of the cross-compiler. Run
|
||||
`./build.sh --clean && ./build.sh`.
|
||||
|
||||
---
|
||||
|
||||
## 9. Licensing
|
||||
|
||||
OreBolt OS uses a multi-tier licensing strategy. See
|
||||
[LICENSE.md](LICENSE.md) for the full strategy.
|
||||
|
||||
- **bitchat mesh module**: AGPL-3.0-only
|
||||
- **All other OreBolt OS modules**: GPL-2.0-or-later
|
||||
- **LVGL/lv_drivers**: MIT (upstream)
|
||||
|
||||
If you modify and deploy bitchat, you MUST make your modified source
|
||||
available to anyone who interacts with your modified node over the mesh
|
||||
(AGPL v3 §13). Replace the `BITCHAT_SOURCE_URL` placeholder in
|
||||
`mod_bitchat_mesh.c` with your real Written Offer URL before deploying.
|
||||
|
||||
---
|
||||
|
||||
## 10. What `build.sh` Handles Automatically
|
||||
|
||||
| Phase | What It Does | You Need To... |
|
||||
|---|---|---|
|
||||
| 0 | Checks for `base-devel`, `git`, `make`, `python3`, SD utils; probes for `KERNEL_HEADERS` | Install host deps + FiiO M3K kernel tree (Sections 1, 3) |
|
||||
| 1 | Verifies `mipsel-linux-musl-{gcc,strip,ar,ranlib}` exist and target MIPS | Install the toolchain (Section 2) |
|
||||
| 2 | Validates all 18 module source files, init scripts, and required top-level files | Clone the workspace (Section 5) |
|
||||
| 3 | Creates the `overlay/` directory tree (apps/, usr/lib/, usr/bin/, etc.) | Nothing |
|
||||
| 4 | Sets executable permissions on init scripts and utility scripts | Nothing |
|
||||
| 5 | Builds H2 Core v6.2: clones LVGL/lv_drivers, compiles liblvgl.so, h2_test launcher, 18 modules, input mapper | Have network access (or pre-clone LVGL) |
|
||||
| 6 | Builds OreBolt OS: compiles liborebolt.a and generates 150-payload matrix | Nothing |
|
||||
| 7 | Verifies all artifacts exist, checks MIPS LE ELF, verifies bitchat AGPL marker | Nothing |
|
||||
| 8 | Copies everything to SD card mount point, creates ROM directories | Mount SD card (Section 6) |
|
||||
|
||||
**In summary, you only need to do Sections 1, 2, 3, 5, and 6 before
|
||||
running `./build.sh`.** Section 4 (Buildroot) is optional and only
|
||||
needed for producing a full rootfs image.
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference Checklist
|
||||
|
||||
```
|
||||
[ ] Arch Linux host up to date (pacman -Syu)
|
||||
[ ] base-devel, git, make, python3, rsync, dosfstools, e2fsprogs, parted, file installed
|
||||
[ ] mipsel-linux-musl toolchain installed at /opt/mipsel-linux-musl/
|
||||
[ ] Toolchain in PATH: mipsel-linux-musl-gcc --version works
|
||||
[ ] Test compile produces MIPS LE ELF binary
|
||||
[ ] FiiO M3K GPL kernel tree at /opt/fiio-m3k-linux/
|
||||
[ ] make headers-check passes
|
||||
[ ] Workspace cloned into h2-workspace/
|
||||
[ ] All 18 module source files present
|
||||
[ ] LICENSE.md reviewed (bitchat = AGPL-3.0-only)
|
||||
[ ] SD card formatted (FAT32) and mounted at /mnt/h2-sd/
|
||||
[ ] Ready to run: ./build.sh
|
||||
```
|
||||
|
|
@ -0,0 +1,190 @@
|
|||
# OreBolt OS -- Quickstart
|
||||
|
||||
Build and deploy OreBolt OS to a HiFiWalker H2 (or compatible rebadged DAP)
|
||||
from a fresh Arch Linux host. ~30 min if toolchain and kernel tree are
|
||||
pre-staged, ~75 min if building the toolchain from source.
|
||||
|
||||
> **Not on Arch?** Stop. The build pipeline is validated against Arch only.
|
||||
> Other distros will need porting effort. See [PREREQUISITES.md](PREREQUISITES.md)
|
||||
> section 1 for the full dependency rationale.
|
||||
|
||||
---
|
||||
|
||||
## 0. What you need
|
||||
|
||||
| Item | Where | Details |
|
||||
|---|---|---|
|
||||
| Arch Linux host (x86_64) | any Arch install | `pacman -Syu` first |
|
||||
| `mipsel-linux-musl` toolchain | Rockbox prebuilt tarball (recommended) | step 2 below |
|
||||
| FiiO M3K GPL kernel tree | community mirror or FiiO's GPL download | step 3 below |
|
||||
| MicroSD card, 2 GB or larger | any | FAT32, single partition |
|
||||
|
||||
---
|
||||
|
||||
## 1. Host dependencies
|
||||
|
||||
```bash
|
||||
sudo pacman -Syu --needed base-devel git make python3 rsync \
|
||||
dosfstools e2fsprogs parted file
|
||||
```
|
||||
|
||||
## 2. Cross toolchain
|
||||
|
||||
Option A — Rockbox prebuilt (recommended, ~5 min):
|
||||
|
||||
```bash
|
||||
sudo mkdir -p /opt/mipsel-linux-musl
|
||||
sudo tar xzf rockbox-mips-toolchain-*.tar.gz \
|
||||
-C /opt/mipsel-linux-musl --strip-components=1
|
||||
```
|
||||
|
||||
Option B — musl.cc fallback (less tuned, but works):
|
||||
|
||||
```bash
|
||||
wget https://musl.cc/mipsel-linux-musl-cross.tgz -O - | \
|
||||
sudo tar xz -C /opt/mipsel-linux-musl --strip-components=1
|
||||
```
|
||||
|
||||
Either way, add to PATH and verify:
|
||||
|
||||
```bash
|
||||
export PATH="/opt/mipsel-linux-musl/bin:$PATH"
|
||||
echo 'export PATH="/opt/mipsel-linux-musl/bin:$PATH"' >> ~/.bashrc
|
||||
|
||||
mipsel-linux-musl-gcc -dumpmachine
|
||||
# expected output: mipsel-linux-musl
|
||||
```
|
||||
|
||||
## 3. Kernel headers
|
||||
|
||||
The FiiO M3K uses the same Ingenic X1000E SoC as the H2, so its GPL kernel
|
||||
tree is the canonical headers source — no reverse-engineering from Rockbox
|
||||
needed. See [HEADERS.md](HEADERS.md) for mirror URLs and the XBurst BSP
|
||||
fallback path.
|
||||
|
||||
```bash
|
||||
sudo mkdir -p /opt/fiio-m3k-linux
|
||||
sudo tar xzf fiio_m3k_kernel_*.tar.gz \
|
||||
-C /opt/fiio-m3k-linux --strip-components=1
|
||||
|
||||
export KERNEL_HEADERS=/opt/fiio-m3k-linux
|
||||
echo 'export KERNEL_HEADERS=/opt/fiio-m3k-linux' >> ~/.bashrc
|
||||
|
||||
make headers-check
|
||||
# expected output: [OK] kernel headers root: /opt/fiio-m3k-linux
|
||||
```
|
||||
|
||||
## 4. Build
|
||||
|
||||
```bash
|
||||
git clone <your-repo-url> h2-workspace
|
||||
cd h2-workspace
|
||||
./build.sh
|
||||
```
|
||||
|
||||
The build script runs eight phases in sequence:
|
||||
|
||||
| Phase | What it does |
|
||||
|---|---|
|
||||
| 0–1 | Verifies host deps, toolchain, and kernel headers |
|
||||
| 2 | Workspace integrity check (all modules + headers + scripts present) |
|
||||
| 3–4 | Creates `overlay/` tree, fixes permissions |
|
||||
| 5 | Clones LVGL v8.3.11, builds `liblvgl.so`, `h2_test`, all modules, mapper |
|
||||
| 6 | Builds `liborebolt.a`, regenerates 150-payload HID matrix |
|
||||
| 7 | Validates all artifacts are MIPS LE ELF; checks bitchat AGPL marker |
|
||||
| 8 | *(only with `--deploy`)* Copies everything to SD card |
|
||||
|
||||
## 5. Verify the build
|
||||
|
||||
```
|
||||
[OK] OREBOLT OS v1.7 BUILD COMPLETE
|
||||
```
|
||||
|
||||
Artifacts you should see on disk:
|
||||
|
||||
| Artifact | Path | Notes |
|
||||
|---|---|---|
|
||||
| `liblvgl.so` | `overlay/usr/lib/` | LVGL v8.3.11 shared library (MIT) |
|
||||
| `h2_test` | `overlay/usr/bin/` | Master launcher |
|
||||
| `retro_input_mapper` | `overlay/usr/bin/` | Gamepad input mapper daemon |
|
||||
| 17 `*.mod` files | `overlay/apps/` | 16 GPL modules + 1 AGPL bitchat |
|
||||
| `liborebolt.a` | `./` | Hardware library static archive |
|
||||
| 150 `.dd` payloads | `overlay/data/payloads/{linux,macos,windows}/` | HID macro matrix |
|
||||
|
||||
Confirm a binary is actually MIPS, not accidentally built with host GCC:
|
||||
|
||||
```bash
|
||||
file overlay/usr/bin/h2_test
|
||||
# expected: ELF 32-bit LSB executable, MIPS, MIPS32 version 1 ...
|
||||
```
|
||||
|
||||
## 6. Deploy to SD card
|
||||
|
||||
```bash
|
||||
# Identify the card (be sure — /dev/sda is usually your system disk)
|
||||
lsblk -o NAME,SIZE,TYPE,MOUNTPOINT
|
||||
|
||||
# Format: single-partition FAT32
|
||||
sudo umount /dev/sdX1 2>/dev/null || true
|
||||
sudo parted /dev/sdX --script mklabel msdos
|
||||
sudo parted /dev/sdX --script mkpart primary fat32 1MiB 100%
|
||||
sudo parted /dev/sdX --script set 1 boot on
|
||||
sudo mkfs.vfat -F 32 -n OREBOLT /dev/sdX1
|
||||
|
||||
# Mount and deploy
|
||||
sudo mkdir -p /mnt/h2-sd
|
||||
sudo mount /dev/sdX1 /mnt/h2-sd
|
||||
SD_CARD_MOUNT=/mnt/h2-sd ./build.sh --deploy
|
||||
sync && sudo umount /mnt/h2-sd && sudo eject /dev/sdX
|
||||
```
|
||||
|
||||
Insert the card, power on the H2. The LVGL UI boots with all 17 modules
|
||||
registered. Init order: `S98retro-input` → `S99broker` (starts
|
||||
`h2_test`) → `bt_input_daemon.sh` (BLE).
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
| Symptom | Fix |
|
||||
|---|---|
|
||||
| `[-] Missing toolchain binaries: mipsel-linux-musl-gcc` | `export PATH="/opt/mipsel-linux-musl/bin:$PATH"` |
|
||||
| `[-] CRITICAL: KERNEL_HEADERS not found` | Install FiiO M3K GPL tree (step 3) |
|
||||
| `[-] BAD: h2_test -- ELF 64-bit LSB executable, x86-64` | `./build.sh --clean && ./build.sh` (host GCC leaked in) |
|
||||
| `error: cannot find -lm` | Switch to Rockbox toolchain (step 2, option A) |
|
||||
| `fatal: unable to access 'https://github.com/lvgl/lvgl.git'` | Pre-clone LVGL manually (see PREREQUISITES.md section 8) |
|
||||
| `[-] bitchat.mod missing AGPL_BITCHAT marker` | If building manually, add `-DAGPL_BITCHAT` to CFLAGS |
|
||||
|
||||
---
|
||||
|
||||
## Before deploying bitchat (AGPL obligation)
|
||||
|
||||
If you modify the bitchat module and deploy it where other operators will
|
||||
interact with it over the mesh, AGPL v3 section 13 requires you to provide
|
||||
your modified source. Do this before deploying:
|
||||
|
||||
```bash
|
||||
# 1. Replace the placeholder URL with your real source / Written Offer
|
||||
sed -i 's|https://example.invalid/orebolt-bitchat-src|https://your-domain/orebolt-os-bitchat-src|' \
|
||||
src/modules/mod_bitchat_mesh.c
|
||||
|
||||
# 2. Drop in the full AGPL license text
|
||||
curl -o licenses/AGPL-3.0.txt https://www.gnu.org/licenses/agpl-3.0.txt
|
||||
|
||||
# 3. Rebuild
|
||||
./build.sh --clean && ./build.sh
|
||||
```
|
||||
|
||||
Full compliance checklist: [LICENSE.md section 4](LICENSE.md#4-operator-obligations-when-modifying-bitchat).
|
||||
|
||||
---
|
||||
|
||||
## What to read next
|
||||
|
||||
| When you want to... | Read |
|
||||
|---|---|
|
||||
| Understand module layout and boot sequence | [ARCHITECTURE.md](ARCHITECTURE.md) |
|
||||
| Diagnose a toolchain or headers issue | [PREREQUISITES.md section 8](PREREQUISITES.md#8-troubleshooting) |
|
||||
| Understand the AGPL/GPL split | [LICENSE.md](LICENSE.md) |
|
||||
| See the full version history | [CHANGELOG.md](CHANGELOG.md) |
|
||||
| Add a new module | `Makefile` header comment, steps 1–5 |
|
||||
|
|
@ -0,0 +1,279 @@
|
|||
# OreBolt OS
|
||||
|
||||
A hackable firmware for the HiFiWalker H2 — a $30 MIPS digital audio player
|
||||
that ships with a Linux userspace, a 320×320 LCD, and 64 MB of RAM.
|
||||
|
||||
> **New here?** Jump to [QUICKSTART.md](QUICKSTART.md) for the build-and-deploy
|
||||
> walkthrough. This document is the project reference.
|
||||
|
||||
There is a particular class of consumer hardware that is cheap enough to be
|
||||
disposable, yet capable enough to run a real operating system with real
|
||||
peripherals. The HiFiWalker H2 sits squarely in that class. Under its
|
||||
rubberized shell is an Ingenic X1000E SoC — a MIPS32r2 hard-float core
|
||||
clocked at ~1.0 GHz, paired with 64 MB of DDR2 and an ESS ES9018K2M DAC that
|
||||
was designed for serious audio. The stock firmware plays music. OreBolt OS
|
||||
replaces it with a modular, LVGL-driven toolkit for hardware interrogation,
|
||||
RF sensing, payload delivery, and forensic extraction — all running on a
|
||||
device that fits in a shirt pocket and boots from a FAT32 MicroSD card.
|
||||
|
||||
The entire system is cross-compiled from an Arch Linux host using a
|
||||
`mipsel-linux-musl` toolchain (the Rockbox prebuilt is recommended). Every
|
||||
user-facing module is a single-position-independent `.mod` ELF binary that
|
||||
dynamically links against a shared `liblvgl.so`. A fork/exec broker in the
|
||||
master launcher (`h2_test`) loads them on demand, which means only one module's
|
||||
code segment lives in RAM at a time — a hard constraint when your whole
|
||||
working set is 64 MB and the kernel already claims its cut.
|
||||
|
||||
See [CHANGELOG.md](CHANGELOG.md) for the version history.
|
||||
|
||||
---
|
||||
|
||||
## The H2 hardware family
|
||||
|
||||
The H2 is one of five rebadged DAPs built on the same PCB. They are
|
||||
interchangeable from a firmware perspective.
|
||||
|
||||
| Brand / Model | Notes |
|
||||
|---|---|
|
||||
| HiFiWalker H2 | Most widely known; rubberized scroll wheel |
|
||||
| Surfans F20 | Knurled metal scroll wheel |
|
||||
| Aigo Eros Q | Original design base for the family |
|
||||
| Phinistec Z6 | Common rebadged version |
|
||||
| Agptek H3 | Same lineage of budget DAPs |
|
||||
|
||||
All share the X1000E SoC, ESS ES9018K2M DAC, and MAX97220 headphone amp.
|
||||
Multiple hardware revisions exist — some have Micro-USB, newer ones have
|
||||
USB-C. All are Rockbox-compatible, and all run the same OreBolt OS image.
|
||||
|
||||
## Architecture at a glance
|
||||
|
||||
The build pipeline is a single `./build.sh` invocation with eight phases:
|
||||
dependency verification, workspace integrity check, overlay tree creation,
|
||||
LVGL compilation, module compilation, hardware library archiving, artifact
|
||||
validation (MIPS ELF + AGPL marker check), and optional SD card deployment.
|
||||
The Makefile is split into a thin `Makefile` shell that delegates to
|
||||
`Makefile.h2-core-v6.2`, which knows how to build the core runtime, LVGL,
|
||||
and all modules in the correct dependency order.
|
||||
|
||||
At runtime the flow is: init scripts start the retro input mapper daemon
|
||||
(`S98retro-input`), then the master launcher (`S99broker` / `h2_test`),
|
||||
then the BLE input daemon. The launcher presents a scrollable LVGL menu of
|
||||
all registered modules. Selecting one causes the broker to `fork()` + `execv()`
|
||||
the corresponding `.mod` binary from `overlay/apps/`. When the module exits,
|
||||
control returns to the launcher menu. Only one module runs at a time.
|
||||
|
||||
The platform ships three core artifacts besides the modules themselves:
|
||||
`liblvgl.so` (LVGL v8.3.11, ~200 KB, MIT-licensed upstream), `liborebolt.a`
|
||||
(a static archive of hardware-level routines linked into every module), and a
|
||||
150-payload HID macro matrix split across Linux, macOS, and Windows profiles.
|
||||
|
||||
## Modules
|
||||
|
||||
Seventeen modules, each a self-contained `.mod` binary with its own LVGL
|
||||
screen. They group into four operational categories plus one networking
|
||||
module with a separate license tier.
|
||||
|
||||
### Offense / payload delivery
|
||||
|
||||
These modules exist because the H2's USB port can be reconfigured into
|
||||
arbitrary gadget topologies through ConfigFS. That single capability —
|
||||
presenting the device as whatever USB composite the operator needs — is the
|
||||
foundation of the offense surface.
|
||||
|
||||
**`deploy.mod`** — Flash storage manager. Calls `statvfs("/")` to pull
|
||||
total, used, and free bytes from the SD card and renders them as a live
|
||||
storage dashboard. The PLAY button triggers a manual `sync()` flush. Not
|
||||
exciting on its own, but every operator needs to know how much space is left
|
||||
before dropping a 150-payload macro matrix onto a 2 GB card.
|
||||
|
||||
**`pauto.mod`** — Payload Automation, the HID keystroke injector. It parses
|
||||
OreBolt's custom macro command language and injects keystrokes through
|
||||
`/dev/hidg0`, the HID function endpoint exposed by the gadget subsystem.
|
||||
Each `.dd` payload file encodes an OS-specific key sequence (delay, modifier,
|
||||
scan code triples). The module selects the correct profile (Linux, macOS, or
|
||||
Windows) and plays it back at USB-speed. This is the module that turns the H2
|
||||
from a music player into a BadUSB-class device.
|
||||
|
||||
**`retro.mod`** — Retro game launcher. Scans `/data/roms/` recursively
|
||||
for ROM files across 11 systems (NES, SNES, Game Boy, Genesis, SMS,
|
||||
GameGear, PCE, Atari 2600, Neo Geo Pocket, and FDS/GBA), auto-detects the
|
||||
target system by file extension, and displays a filterable LVGL picker
|
||||
menu. Selecting a ROM forks the matching emulator binary (fceux, snes9x,
|
||||
gambatte, gpsp, dgen, mednafen, or stella) and monitors it — BACK sends
|
||||
SIGTERM to the emulator child. The companion `retro_input_mapper` daemon
|
||||
runs at boot via `S98retro-input` and creates a virtual uinput gamepad
|
||||
that maps the H2's rotary encoder and buttons to standard d-pad + ABXYLR
|
||||
+ Start/Select, complete with a rotary-press axis toggle (vertical U/D
|
||||
vs. horizontal L/R) that auto-reverts after 3 seconds of idle.
|
||||
|
||||
**`glitch.mod`** — Hardware glitch injector. Drives X1000E GPIO lines to
|
||||
produce timed power and clock glitches against an externally-wired target.
|
||||
The module exposes controls for glitch width, trigger delay, and repeat count
|
||||
on the LVGL UI. It is the most hardware-dependent module in the stack — it
|
||||
requires physical access to the target's power or clock rail and assumes the
|
||||
operator has wired the glitch circuit correctly.
|
||||
|
||||
### Forensics & extraction
|
||||
|
||||
**`extract.mod`** — Mass storage extractor. Recursively copies files from
|
||||
`/mnt/target_media` (a mount point for an attached USB mass storage device)
|
||||
into `/data/loot_drop/extracted/`. It walks the directory tree, skips
|
||||
symlinks (to avoid following loops into the host filesystem), copies in 4 KB
|
||||
chunks, and displays running file, directory, and byte counts on the LVGL
|
||||
screen. The forensic dispatch engine that selects which `.dd` payload to run
|
||||
against a detected OS lives in `mod_forensics.c` within `liborebolt.a`.
|
||||
|
||||
**`vault.mod`** — Encrypted credentials and payload vault. Provides PIN-based
|
||||
authentication and stores operator-curated quick-launch lists in
|
||||
`Provision.txt`. Sensitive payload content is encrypted at rest. The vault
|
||||
is the module you lock down before handing the device to someone else.
|
||||
|
||||
**`probe.mod`** — I2C bus scanner. Probes `/dev/i2c-0` across the full
|
||||
address space (0x03–0x77) using `ioctl(I2C_SLAVE)` + `i2c_smbus_read_byte()`.
|
||||
Responding devices are highlighted green on the LVGL display. On the H2 this
|
||||
will typically enumerate the DAC, the audio codec, and any attached I2C
|
||||
peripherals. Five seconds of runtime tells you exactly what is on the bus.
|
||||
|
||||
**`pwdb.mod`** — Password database with bcrypt and argon2 verification.
|
||||
Stores operator-managed credential entries and supports verifying captured
|
||||
hashes against the local database. The hashing implementations are linked
|
||||
from `liborebolt.a` and run entirely on the MIPS core — no external crypto
|
||||
library dependency.
|
||||
|
||||
### Reconnaissance & sensing
|
||||
|
||||
This is where the H2's Bluetooth radio and its (future) WiFi capability come
|
||||
into play. The X1000E has an on-die BLE controller, and the recon
|
||||
architecture supports WiFi scanning through `nl80211` once a driver is
|
||||
available.
|
||||
|
||||
**`proxalarm.mod`** — The flagship recon module, built as a single `.mod`
|
||||
binary with three internal layers to keep the RAM footprint tight on a 64 MB
|
||||
device:
|
||||
|
||||
The **bledsp** layer handles RF scanning. It opens the BLE socket, pulls
|
||||
advertisements, and runs the RSSI values through a rolling-average low-pass
|
||||
filter implemented with MIPS DSP ASE intrinsics (`__builtin_mips_q31` mul
|
||||
and shift operations) to smooth out the noise floor. A `bledsp_poll_wifi()`
|
||||
stub is wired in and ready for future `nl80211` integration — when a WiFi
|
||||
driver lands, the same smoothing pipeline handles WiFi RSSI with no
|
||||
architectural change. The **NEXT** key cycles the RF source between BLE and
|
||||
WiFi.
|
||||
|
||||
The **proxvec** layer is the kinematics engine. It maintains a target table
|
||||
with unique device IDs, tracks per-target state machines (new / tracking /
|
||||
stale / expired), and computes radial velocity from the rate of change in
|
||||
smoothed RSSI over time (delta-distance divided by delta-time). This is not
|
||||
GPS-accurate positioning — it is coarse proximity estimation based on RF
|
||||
signal propagation, but on a device the size of a deck of cards, coarse is
|
||||
what you get and coarse is useful.
|
||||
|
||||
The **radar_ui** layer renders a 320×320 LVGL canvas with a rotating sweep
|
||||
line, four range rings, and color-coded blips for each tracked target. Blip
|
||||
color encodes signal strength (bright green for close, dim red for far), and
|
||||
velocity vectors show whether a target is approaching or receding. When a
|
||||
target crosses the alarm threshold, the display flashes an overlay. The
|
||||
**ROTATE** knob cycles through four sensitivity presets (−40 to −70 dBm
|
||||
floor), **PLAY** toggles pause/resume, **BACK** exits.
|
||||
|
||||
One binary, one scan loop, one set of smoothing buffers — the entire
|
||||
proximity sensing pipeline fits in a single module slot.
|
||||
|
||||
**`nettaps.mod`** — USB virtual network tap. Opens a raw `AF_PACKET` socket
|
||||
on `usb0`, captures IP packets, and parses them into a live LVGL console. A
|
||||
configurable time-window coalescer (30 s / 1 min / 2 min / 5 min / 10 min)
|
||||
aggregates packet counts before flushing to disk — important because the H2's
|
||||
SD card has limited write endurance. The flash-safe timed flush is the detail
|
||||
that separates a toy packet sniffer from something you can leave running on
|
||||
actual hardware.
|
||||
|
||||
**`rfid.mod`** — RFID/NFC reader. Drives an externally-attached reader over
|
||||
UART or SPI, enumerates cards in range, and dumps tag memory contents to the
|
||||
LVGL display. The module itself is straightforward; the complexity lives in
|
||||
the physical layer — which reader board, which antenna, which frequency band
|
||||
(125 kHz LF or 13.56 MHz HF).
|
||||
|
||||
### Environment & shell
|
||||
|
||||
**`vterm.mod`** — Virtual terminal with a FreeDOS bridge. Provides a
|
||||
TTY-style shell rendered inside an LVGL text area. Ships a minimal FreeDOS
|
||||
userland under `overlay/data/vterm/freedos/bin/` for x86 emulation scenarios.
|
||||
On the H2's MIPS core this is necessarily slow, but it demonstrates that the
|
||||
module system can host an entire foreign userland.
|
||||
|
||||
**`studio.mod`** — UAC2 Mixer Console. Four-channel mixer (GAME / DISCORD /
|
||||
MIC / MUSIC) with per-channel volume bars. **NEXT** and **PREV** cycle the
|
||||
active channel, **PLAY** toggles hardware mute. Volume adjustments are sent
|
||||
as USB Consumer Control HID commands through `/dev/hidg1`. The module turns
|
||||
the H2 into a physical audio mixer — plug it into a host, twist the scroll
|
||||
wheel, and the host's volume changes.
|
||||
|
||||
**`noise.mod`** — True random number generator. Pulls 32-byte entropy blocks
|
||||
from the kernel via `getrandom(GRND_RANDOM)` and pipes them to the host over
|
||||
CDC ACM serial (`/dev/ttyGS0`) at 100 Hz. The entropy source is the hardware
|
||||
TRNG on the X1000E, not a software PRNG — the output is suitable for
|
||||
cryptographic seeding.
|
||||
|
||||
**`reset.mod`** — Factory reset and purge coordinator. The operator-facing
|
||||
front-end for the panic-purge subsystem. It invokes `mod_panic_purge`
|
||||
(software: zeroes RAM caches, temp files, and `/data/vault/syslog.log`) and
|
||||
then `mod_panic_hardware_purge` (hardware: cuts SD card slot power and shorts
|
||||
SPI flash write-protect via X1000E GPIO) in the correct sequence. The
|
||||
hardware purge is a compile-time toggle (`HARDWARE_GPIO=0` for stubs,
|
||||
`HARDWARE_GPIO=1` for real GPIO manipulation).
|
||||
|
||||
### Networking
|
||||
|
||||
**`bitchat.mod`** — Mesh chat, and the only module under AGPL-3.0-only. An
|
||||
independent C implementation of the BitChat wire protocol — not affiliated
|
||||
with, sponsored by, or endorsed by Permissionless Tech, LLC. The name is
|
||||
used under nominative fair use (see [LICENSE.md](LICENSE.md) section 8). It
|
||||
implements the full `BITCHAT_MSG_*` message type set, advertises its Written
|
||||
Offer URL on every mesh HELLO via `BITCHAT_MSG_LICENSE`, and uses
|
||||
`mod_radio_input_multiplex` (BLE + LoRa + 433 MHz) as its transport layer.
|
||||
The mesh implementation itself lives in `mod_bitchat_mesh.c` within
|
||||
`liborebolt.a`, compiled with a mandatory `-DAGPL_BITCHAT` flag — the build
|
||||
fails without it, enforced in `build.sh` Phase 7.
|
||||
|
||||
## Hardware library (`liborebolt.a`)
|
||||
|
||||
A static archive linked into every `.mod` binary. Source lives under
|
||||
`src/modules/`. These are the low-level routines that modules call but do
|
||||
not implement themselves.
|
||||
|
||||
| File | Purpose |
|
||||
|---|---|
|
||||
| `mod_core_hid.c` | HID descriptor tables shared by `pauto.mod` and `retro.mod` |
|
||||
| `mod_forensics.c` | Forensic extraction engine — dispatches `.dd` payloads by OS profile |
|
||||
| `mod_panic_purge.c` | Software panic purge — zeroes volatile state before hardware purge |
|
||||
| `mod_panic_hardware_purge.c` | Hardware panic purge — GPIO-driven SD power cut and SPI flash WP |
|
||||
| `mod_radio_input_multiplex.c` | Radio input multiplexer (BLE + LoRa + 433 MHz); bitchat transport |
|
||||
| `mod_ui_frame_graphics.c` | RGB565 blitters, double-buffer management, scroll wheel routing |
|
||||
| `mod_bitchat_mesh.c` | BitChat mesh networking (AGPL-3.0-only; `-DAGPL_BITCHAT` required) |
|
||||
|
||||
## Documentation
|
||||
|
||||
| Doc | What it covers |
|
||||
|---|---|
|
||||
| [QUICKSTART.md](QUICKSTART.md) | Build and deploy from scratch (~30 min) |
|
||||
| [PREREQUISITES.md](PREREQUISITES.md) | Host setup, toolchain, kernel headers, SD card prep |
|
||||
| [HEADERS.md](HEADERS.md) | Kernel headers strategy (FiiO M3K + XBurst BSP fallback) |
|
||||
| [ARCHITECTURE.md](ARCHITECTURE.md) | Module layout, SoC details, build pipeline internals |
|
||||
| [LICENSE.md](LICENSE.md) | Multi-tier licensing, AGPL boundary analysis, compliance |
|
||||
| [CHANGELOG.md](CHANGELOG.md) | Version-to-version diff |
|
||||
| [BUILD_MANIFEST.txt](BUILD_MANIFEST.txt) | Complete file inventory with SHA256 |
|
||||
| [SHA256SUMS](SHA256SUMS) | Per-file integrity manifest |
|
||||
|
||||
## License
|
||||
|
||||
Multi-tier. Full analysis in [LICENSE.md](LICENSE.md).
|
||||
|
||||
- **bitchat mesh module**: AGPL-3.0-only
|
||||
- **All other OreBolt OS code**: GPL-2.0-or-later
|
||||
- **LVGL / lv_drivers**: MIT (upstream)
|
||||
- **Documentation**: CC-BY-4.0
|
||||
|
||||
Modifying and deploying the bitchat module triggers AGPL v3 section 13 — you
|
||||
must make your modified source available to anyone who interacts with your
|
||||
node over the mesh. Replace the `BITCHAT_SOURCE_URL` placeholder in
|
||||
`src/modules/mod_bitchat_mesh.c` before deploying.
|
||||
|
|
@ -0,0 +1,112 @@
|
|||
af3c20a1c6ca90a070967cd608b13c43740a824e606b5ed6da479aa61eba7652 ./ARCHITECTURE.md
|
||||
4d2ef1c7d17104495f4fdc6daf25c22288f77917bc08b2c89faa32346abd952b ./BUILD_MANIFEST.txt
|
||||
c1b46d236474365ac88964118a01b0e51cbf161bc5c0ee37e9cda273b050e6a2 ./CHANGELOG.md
|
||||
306232e96e47873f67c6ed3b624b546ed157d111a82d3c00978db8785071ffdc ./HEADERS.md
|
||||
19081ca3d95e923b4499f32943ea83e1c61a1ca3fb7006e810b628b1d9987810 ./LICENSE.md
|
||||
f5100374d955ce78364a114fbdb42c61dd4a4cfc47d4c09d421c87d7b5ecc55b ./Makefile
|
||||
55ecbc07f22b9d33df89c105fce7cb9e674ae4351f49818bb568a7efa7cc876c ./Makefile.h2-core-v6.2
|
||||
790748cf6e7a085e38265eb1e2138786d3d8d4ced28e5b9f415839c366b07b83 ./Makefile.orebolt-v1.5
|
||||
30a68bb0027cc0f347cffb34e2de43daf94dfd0ec6426450fb87bae61d8e035e ./PREREQUISITES.md
|
||||
9713032b9fe760fb40fc9ea6c1e6b3d78195f248d21aaf881d69ba77341771de ./QUICKSTART.md
|
||||
4d19a71efe2f1b32477660e37900a7d3aa614fa8d3ec5673a61aecc8706c6bd1 ./README.md
|
||||
70364362503a47a3e228fb8c363b09addc1403f464fcec15d453ea691287b04b ./build.sh
|
||||
4293da3d24b6c94776e8710b622ea4ead3cbf7f7ca3a8cd2e771c4c7aed5a6ba ./include/h2_ui.h
|
||||
c8c92a3b98ebff14f5118c8f9b802d62c5a32008f310cc95b472d3d32303cbbb ./include/log_manager.h
|
||||
69f323f309056091b759a62db69963c574b6bdbed64c8746b0cea178bfb53c11 ./inject_payloads.sh
|
||||
374489bb014f881b4d92b8dfd0dc72777b4e412a50fe3071a7885fef01f13a3b ./licenses/AGPL-3.0.txt
|
||||
43bce40c9c3a5df7982d95875ad5a8793f4cb13c4eca40a5e14067dbfbc7ff5f ./licenses/GPL-2.0.txt
|
||||
306fcbe90eb6106877b672da432116675cc45d33ae2d0a2507825203a64d3e7f ./licenses/MIT.txt
|
||||
87fd33ab63c342634e752629c5aec90d0258e6076562619df9fe2fa7b55b6405 ./lv_conf.h.dist
|
||||
1ce7f5ee2c132c7a9467214a31f651fab83561eadb04044862714916cc2569af ./main.c
|
||||
12d39f90383d24503cc58e2a05764077f260d3f73b9d5693c529383f25ba4377 ./modules/orebolt-bitchat/bitchat.c
|
||||
4be8719872b12da291df69e7eca162031834613d60bb1f18f2138c0f4ae7c2c3 ./modules/orebolt-deploy/deploy.c
|
||||
f6770e0b3e8643e4502c60e7f02ad7f13b0771bebcb9059b34d91bd58cad28de ./modules/orebolt-emulate/emulator.c
|
||||
67017ac950aba630309a1482a807bc2b2cafe9b4b2e8c9caee54b8f23bee748d ./modules/orebolt-emulate/emulator_input_mapper.c
|
||||
d9717040fd726056fd4bbf7416c81bc7cb111e58f907d24403477223e6807c51 ./modules/orebolt-extract/extract.c
|
||||
11510fbf8f5ab74554fa35b6895bcf7c635da548c09ba37a8dc093d176f7c7dc ./modules/orebolt-glitch/glitch.c
|
||||
06804337bfb6c759ab08f91bd5fdf105e7a5f90fcde07da5fe6a94f9cf778e01 ./modules/orebolt-nettap/scalpel.c
|
||||
c8b31cf8490302cf411cb102a5b36c96f394a77b10c631e3a4f1463a11db29de ./modules/orebolt-noise/noise.c
|
||||
ac50682391d8f328c72ff241fb56c17a51c4c0729a7ca41a1c5a4f06c33b475f ./modules/orebolt-pauto/pauto.c
|
||||
d103975aa1433971e7187605492bf5d79230927183689749d266a1d0850e1fb5 ./modules/orebolt-probe/probe.c
|
||||
a4af604240f8ebc06d048d5dff3ab296e2ce2a25dea3ec58b6cc37103be085cc ./modules/orebolt-proxalarm/bledsp.c
|
||||
7cdb7fe16b36b96b05a62c4e71ccd22d3d7ec85e8404b55384d81dda8d2905fc ./modules/orebolt-proxalarm/bledsp.h
|
||||
c38bfe61deea7bba1598faa29ea5c266be0eedeb94ce751f701c066af88c8586 ./modules/orebolt-proxalarm/proxalarm.c
|
||||
7bd43700b839089791da3e799ecd8e072b2d226e07d5ace7ee2c4429ed36e05f ./modules/orebolt-proxalarm/proxvec.c
|
||||
30062b3e5127234d2492c20b9a56bfb7c39f635dbd834cb39ca7b31989907922 ./modules/orebolt-proxalarm/proxvec.h
|
||||
c920e433685a88090c3e695bda9386de02bf9f43380f8573a5378683585a0579 ./modules/orebolt-proxalarm/radar_ui.c
|
||||
fc9bec76ee843320a453e72e9a6bdef7ad96c6597c0def0b4666a3e00c084c8a ./modules/orebolt-proxalarm/radar_ui.h
|
||||
712214c1981ceb56d035163f30b24f0146edce4d232f14f0d019a99cbaadb4b9 ./modules/orebolt-pwdb/pwdb.c
|
||||
291d5c63e9e535eb986b5e8fa2cef0b33cc2ec05504b3dd21669191760495ec0 ./modules/orebolt-reset/reset.c
|
||||
95787550dd8ad6f10b9e49a9d2ee1619a730038873e57911106c0539ea2e5b89 ./modules/orebolt-rfid/rfid.c
|
||||
f2d6929a1981122ddaa6b38a1bac757932663871f77fdb4ac8b9df7795efa43d ./modules/orebolt-scalpel/scalpel.c
|
||||
700673c26c378e360dc88a5cfbc6f1815bdf4e680e261257c9da80649e0dbd29 ./modules/orebolt-studio/studio.c
|
||||
0b5bf578f001abe0ee9512803d66fb186564c5a1f0eccf3c9da684ae2d304733 ./modules/orebolt-vault/vault.c
|
||||
338f80e9a3ae94a48e0ec0beff767c24067f803a553a49554cfb7407606f8cd5 ./modules/orebolt-vterm/vterm.c
|
||||
9914599da48fb76191e4d72529f0ed0a40bf724ca0f26f8a9e28db6d18d31796 ./overlay/data/bt_config/paired_macs.txt
|
||||
9eb80ea8d3e3067ac4ec53c44ff6cd53e8a9f22a01f50c19c2b39cbac6bb6a7a ./overlay/data/chat_config/macros.txt
|
||||
1bd045919d1bbffddf078b0f947bcd44a1f3438994cf75413b3f116c9b6a67be ./overlay/data/payloads/linux/01_restart_lightdm.dd
|
||||
f631eb1d8825aad8f2c1c5ebe3908e86bfa6898dc7d0aa311e7ac66c64f92ebb ./overlay/data/payloads/linux/02_restart_gdm3.dd
|
||||
93ed9a4a1a2c5a7427e21b39bfe2bf3950e3e5ad4231847783a33f9656b2a3ca ./overlay/data/payloads/linux/03_restart_sddm.dd
|
||||
8036107ec0eb4e3e99d8b875f69f35d497d9e645d03edbbf783185bd650458fe ./overlay/data/payloads/linux/04_kill_wayland.dd
|
||||
43aecd9235a3f21581f06657d6aefbe52ba8ab9c32d7500d66c3e0235ad2a19c ./overlay/data/payloads/linux/05_rebuild_xorg.dd
|
||||
c0cc44e2bea1c718d09485c88adac6277d75905608b9f4f49e875d96e9a6ad8e ./overlay/data/payloads/linux/06_clear_x11_locks.dd
|
||||
181589740883607db4f5e2f25a2d07bc7beff986bf566a094503dab3af948007 ./overlay/data/payloads/linux/07_drop_tty3.dd
|
||||
d1c16726f6a296ae030bccd5b5081d60fe7aa65f34dc8244a045180fd3b97af5 ./overlay/data/payloads/linux/08_prompt_clear.dd
|
||||
c7d0d9fbaeb6353c5918ae6933ff9319e26c420bdef975fe5438db264c8e2f94 ./overlay/data/payloads/linux/09_kill_desktop_context.dd
|
||||
914560b6ca28837aa8dc883312f8d8922c58a23350ef0d0f55874581b0f0177a ./overlay/data/payloads/linux/10_restart_gnome_shell.dd
|
||||
e45356ca7195e9c19532d9e9d9fb049237b370e855d5c4299fd1ff73e66a497a ./overlay/data/payloads/linux/11_backup_rootfs.dd
|
||||
6b27c0795704e583a3129c6a4d5375622a2f30fb2b47fc35ffc01bf806852071 ./overlay/data/payloads/linux/12_backup_homes.dd
|
||||
b9ad677ece4814b8c4d4fc7ff1e16c680286c50950c670a0daa5ab26aacf9571 ./overlay/data/payloads/linux/13_backup_etc.dd
|
||||
a5af364f73c4fe9171eaae3f80e8fb112e75ad5ccad61286767f7a0adeede396 ./overlay/data/payloads/linux/14_isolate_ssh_keys.dd
|
||||
f85ade8a8bc8c761b11802ba5784ad2c7634881594944e2fabf00131a1680a67 ./overlay/data/payloads/linux/15_backup_net_metadata.dd
|
||||
d2abe81bb9e1e04c5c0fee0109c23001e3c9b95a81a8221f212909f2dbd86157 ./overlay/data/payloads/linux/16_dump_mbr.dd
|
||||
16e1320dd04a1dd6603e8f64295f232dd1fb27a628e3b8f60c3737121d3df745 ./overlay/data/payloads/linux/17_extract_hashes.dd
|
||||
63205c29699eb0b96065bfd05f67ffe8123b45ef1c6b733f748e5ebeab896847 ./overlay/data/payloads/linux/18_compress_mail.dd
|
||||
96239e4b386a66b37b7d7d7857dc3f326fa5158959452a0cc8b4493e627850b8 ./overlay/data/payloads/linux/19_export_packages.dd
|
||||
290d26b32edd34a6334535833900c9a941b65068063878dc5a9c4a8bbabb08be ./overlay/data/payloads/linux/20_locate_sqlite_dbs.dd
|
||||
d0f9a51dbc0e1dae058aa52470b827283c86594c066287b237718f4c79cf4683 ./overlay/data/payloads/linux/21_clear_dpkg_locks.dd
|
||||
10612fccca61cbc510650161d01e014d0482e2f34a48cdf60835de1ca195faf3 ./overlay/data/payloads/linux/22_vacuum_journals.dd
|
||||
a893e5ebd9814a361f8ce31341d26401f1bd842dbb835f7628e0c29173976866 ./overlay/data/payloads/linux/23_flush_ram_caches.dd
|
||||
5d2fb80b35a8eded73cfe7484c5f4a6beb52939e3917fc8e373bbd5f432186c1 ./overlay/data/payloads/linux/24_rebuild_initramfs.dd
|
||||
0dc40d772eb84bba47e4a87ddf322199ba6684ddcc8e46538151f0a8bb4aa7e1 ./overlay/data/payloads/linux/25_reverify_grub.dd
|
||||
add1da7da6eb90143fcc88a1cf439fe07eff5eb1da6b6bbd3c2f52d25221fe0d ./overlay/data/payloads/linux/26_clean_apt_cache.dd
|
||||
a2fe4f56143f04598de2b8b025a93b72de81e4715d36186727b951d9a9d4beb7 ./overlay/data/payloads/linux/27_identify_space_leaks.dd
|
||||
27119fd2ece277891e586f190cd72cb0440a920a33466af2e9934432dcc806ec ./overlay/data/payloads/linux/28_schedule_fsck.dd
|
||||
d846673093e9fcce90761ee9e7d01ecded2686367e5d611286b67a17ea41f01e ./overlay/data/payloads/linux/29_clear_tmp.dd
|
||||
4c261f54048b2fd1a4ce26d47ab86b44c9b54032db06b38792c8ea3f682592d7 ./overlay/data/payloads/linux/30_remount_rw.dd
|
||||
71570794a70d93c2a971db3c2caebd94b7da3f4622eb902a581ee347e2ceb4e9 ./overlay/data/payloads/linux/31_inject_admin.dd
|
||||
65b3f5c704ce311c88b1b4ef4de88741b427c49bac9502a678dc249aa8e630ad ./overlay/data/payloads/linux/32_elevate_current_user.dd
|
||||
b0908408b357a0b7027e173b693c282104bed3745de1c55261e6a59caa5035f5 ./overlay/data/payloads/linux/33_flush_iptables.dd
|
||||
2c629dc2177d8119c000e092305ee21dad565863256848b438f152c74fec8ded ./overlay/data/payloads/linux/34_enable_ssh.dd
|
||||
4fd0d4c44b35e3915f33bc927c32ecdfbff8dffe71f3ff931af646c076dbc598 ./overlay/data/payloads/linux/35_ufw_bypass_ssh.dd
|
||||
0c52392031002e8146b41aafca2234a55f1dbbf6f80f2b67f638a4f27ef9ea21 ./overlay/data/payloads/linux/36_disable_apparmor.dd
|
||||
bebc053686f881f548660b8d8a2d1569c0ccc71172787c70dab15fa624cede51 ./overlay/data/payloads/linux/37_permissive_selinux.dd
|
||||
974b12560a0372eb17093ed258a983d691607a8d61b5a3a3823751a6f2587dba ./overlay/data/payloads/linux/38_clear_known_hosts.dd
|
||||
e98216c1299ec8bdce619529b9f53adb23b39df292eea1628a25f95f19487c8a ./overlay/data/payloads/linux/39_authorize_ssh_passwords.dd
|
||||
888f0bbee7e33ea3ce8b1b4ba4351b248872b8e2d7f7ba84942b88b4c719292c ./overlay/data/payloads/linux/40_clear_root_password.dd
|
||||
ccb9e09c92c7c5fa9a9d14001f061e9f955c2c8298539997510f271a576f5a27 ./overlay/data/payloads/linux/41_sysrq_sync.dd
|
||||
be633400c9b9efcd57f152232bdfe8875150cece248ebcadeb0667e92e0ae1a3 ./overlay/data/payloads/linux/42_sysrq_umount.dd
|
||||
6ba6ca3a315845194b3aa1467fd04307f7c68282336ba5ecfdab198a77845e32 ./overlay/data/payloads/linux/43_sysrq_reboot.dd
|
||||
7c74a85c2f20c36dfdb2543ef045fd4f458756082c54dfdb06e49c2ea9c04832 ./overlay/data/payloads/linux/44_sysrq_poweroff.dd
|
||||
13059dd2ba8dcdf03fd178a95947b3ec171c3241457f8d2ed28a384ebf8d40fa ./overlay/data/payloads/linux/45_stream_dmesg.dd
|
||||
c1e39416719bd6783816f97ff52ede7a2d400b8df20c8477704adbcdf64e2fe3 ./overlay/data/payloads/linux/46_sysrq_memdump.dd
|
||||
d5abd9f3e6f9e9c7375b9fc76a94768dee4daeed56465628b4bf6d501844eb86 ./overlay/data/payloads/linux/47_sysrq_oom_kill.dd
|
||||
f11e5a8d2c29c3d0102f77f8296299af8acb60d03af8d110e4f0dc07e14bb9ee ./overlay/data/payloads/linux/48_enable_serial_getty.dd
|
||||
b943d6cf6287ab38a1a840b82bf592d512a01a5213b9725d0cafd22d0b3ed20e ./overlay/data/payloads/linux/49_flash_interfaces_down.dd
|
||||
5be607e8f7579b1eff708a066ea6690eb80fe5e9905f29b92184095bd3ca97fa ./overlay/data/payloads/linux/50_loop_keyboard_leds.dd
|
||||
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 ./overlay/data/vault/failures.dat
|
||||
6610259d9fc51efeb212a0266d0cb65d37a27a5f73641605491c245f724fbb3c ./overlay/data/vault/payloads/Provision.txt
|
||||
01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b ./overlay/data/vault/syslog.log
|
||||
3dd8518f4c93e1a6ed0adcb275e3f1f2fa495c41af89ee1028e947f37fdc10b5 ./overlay/etc/init.d/S98emulator-input
|
||||
ef28d3ed80b2d1e16f12c125b0bba0f00638cbc98de894fed8af817fe6aa7e69 ./overlay/etc/init.d/S99broker
|
||||
a7ed59099b5728aac398a1b6787f45d3834d9ad721e0888bc7a7952aab437ba6 ./overlay/etc/init.d/bt_input_daemon.sh
|
||||
e871420720600bc31a8a7bc69ec4a802ee425001b2186744ebd2c48d1db067da ./overlay/usr/bin/enable_vault_ble.sh
|
||||
06d24628a5fe544c48d06cb6e65a1616100b6aebfb43a851596696bcd362cf0a ./overlay/usr/bin/enable_vault_usb.sh
|
||||
0d2fd2c79fc315efbf82c57beb8e00528ab4ae9e951efc19e64361e4b338ae0a ./src/modules/mod_bitchat_mesh.c
|
||||
90ffeebeeb95862a86e5e556e6bfd74ce55b2565f10951d6410ea5e3f60a3524 ./src/modules/mod_core_hid.c
|
||||
cf237a3a368f1e804782c564bc196b87f8a019d5650889400c6b47e1d0eb1679 ./src/modules/mod_forensics.c
|
||||
91fb4c95f525ad4563977f1d1530f41c7524274a1418959759da5217a17802a6 ./src/modules/mod_panic_hardware_purge.c
|
||||
4918bf081501ff92e019f21582ac401ab926e04ace0452dc377e43d0488742cc ./src/modules/mod_panic_purge.c
|
||||
37f8d9468c477030f6b4cec350e03bd8e401b923581540c2af14c6838bf43424 ./src/modules/mod_radio_input_multiplex.c
|
||||
75cb4a2c4f9732b9edf89e99b5e9bf015cbfc61ef602a1874eebd738fe833fe9 ./src/modules/mod_ui_frame_graphics.c
|
||||
acad4687c12c7dd5eca03ff28c97672ef0d7527c66fc87d7ccc3d5a5e2f0f832 ./src/modules/panic_purge_api.h
|
||||
|
|
@ -0,0 +1,228 @@
|
|||
#!/bin/bash
|
||||
# ==============================================================================
|
||||
# build.sh -- OREBOLT OS v1.7 build orchestrator
|
||||
# ==============================================================================
|
||||
# Phases:
|
||||
# 0 host dep check (pacman)
|
||||
# 1 toolchain verification (mipsel-linux-musl-*)
|
||||
# 2 workspace integrity check (17 modules + headers + scripts)
|
||||
# 3 overlay/ tree creation
|
||||
# 4 permission fixups on init scripts
|
||||
# 5 H2 Core v6.5 build (LVGL clone + liblvgl.so + h2_test + 17 modules + mapper)
|
||||
# 6 OreBolt OS v1.7 build (liborebolt.a + 150-payload matrix)
|
||||
# 7 artifact verification (MIPS LE ELF check, sizes)
|
||||
# 8 optional --deploy to SD card
|
||||
#
|
||||
# v1.7 changes:
|
||||
# * 17 .mod binaries + 1 standalone daemon (unchanged count).
|
||||
# * retro_input_mapper is a standalone uinput gamepad daemon.
|
||||
# * Documentation and source comments cleaned for consistency.
|
||||
# ==============================================================================
|
||||
set -euo pipefail
|
||||
|
||||
CROSS_COMPILE="${CROSS_COMPILE:-mipsel-linux-musl-}"
|
||||
KERNEL_HEADERS="${KERNEL_HEADERS:-/opt/fiio-m3k-linux}"
|
||||
SD_CARD_MOUNT="${SD_CARD_MOUNT:-/mnt/h2-sd}"
|
||||
NO_COLOR="${NO_COLOR:-}"
|
||||
|
||||
# ANSI color helpers
|
||||
if [ -z "$NO_COLOR" ]; then
|
||||
C_RESET="\033[0m"; C_OK="\033[1;32m"; C_WARN="\033[1;33m"
|
||||
C_ERR="\033[1;31m"; C_INFO="\033[1;36m"
|
||||
else
|
||||
C_RESET=""; C_OK=""; C_WARN=""; C_ERR=""; C_INFO=""
|
||||
fi
|
||||
|
||||
log() { echo -e "${C_INFO}[*]${C_RESET} $*"; }
|
||||
ok() { echo -e "${C_OK}[OK]${C_RESET} $*"; }
|
||||
warn() { echo -e "${C_WARN}[!]${C_RESET} $*"; }
|
||||
die() { echo -e "${C_ERR}[-]${C_RESET} $*"; exit 1; }
|
||||
|
||||
DEPLOY=0
|
||||
CLEAN=0
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--deploy) DEPLOY=1 ;;
|
||||
--deploy-only) DEPLOY=1; SKIP_BUILD=1 ;;
|
||||
--clean) CLEAN=1 ;;
|
||||
--help|-h)
|
||||
cat <<EOF
|
||||
OreBolt OS v1.7 build orchestrator
|
||||
Usage: $0 [--deploy|--deploy-only|--clean|--help]
|
||||
|
||||
Environment:
|
||||
CROSS_COMPILE Toolchain prefix (default: mipsel-linux-musl-)
|
||||
KERNEL_HEADERS FiiO M3K GPL kernel tree (default: /opt/fiio-m3k-linux)
|
||||
SD_CARD_MOUNT SD card mount point (default: /mnt/h2-sd)
|
||||
HARDWARE_GPIO Set to 1 to compile real GPIO panic-purge path
|
||||
NO_COLOR Set to 1 to disable ANSI color
|
||||
EOF
|
||||
exit 0 ;;
|
||||
*) die "unknown arg: $arg" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# --- Phase 0: host deps ------------------------------------------------------
|
||||
log "Phase 0: checking host dependencies..."
|
||||
for pkg in base-devel git make python3 rsync dosfstools e2fsprogs parted file; do
|
||||
if ! pacman -Qi "$pkg" >/dev/null 2>&1; then
|
||||
warn "missing package: $pkg (installing...)"
|
||||
sudo pacman -S --noconfirm "$pkg" || die "install failed for $pkg"
|
||||
fi
|
||||
done
|
||||
ok "host deps satisfied"
|
||||
|
||||
# --- Phase 1: toolchain ------------------------------------------------------
|
||||
log "Phase 1: verifying cross toolchain..."
|
||||
for bin in gcc strip ar ranlib; do
|
||||
if ! command -v "${CROSS_COMPILE}${bin}" >/dev/null 2>&1; then
|
||||
die "missing toolchain binary: ${CROSS_COMPILE}${bin}"
|
||||
fi
|
||||
done
|
||||
TARGET=$("${CROSS_COMPILE}gcc" -dumpmachine 2>/dev/null || true)
|
||||
case "$TARGET" in
|
||||
*mips*) ok "toolchain target: $TARGET" ;;
|
||||
*) die "compiler target '$TARGET' does not look like MIPS" ;;
|
||||
esac
|
||||
|
||||
# Kernel headers tree presence check
|
||||
if [ ! -d "$KERNEL_HEADERS" ]; then
|
||||
die "KERNEL_HEADERS not found at $KERNEL_HEADERS (see HEADERS.md)"
|
||||
fi
|
||||
if [ ! -f "$KERNEL_HEADERS/include/uapi/linux/kernel.h" ]; then
|
||||
die "$KERNEL_HEADERS does not look like a Linux kernel tree"
|
||||
fi
|
||||
ok "kernel headers root: $KERNEL_HEADERS"
|
||||
export KERNEL_HEADERS
|
||||
|
||||
# --- Phase 2: workspace integrity -------------------------------------------
|
||||
log "Phase 2: verifying workspace integrity..."
|
||||
for f in main.c h2_ui.h log_manager.h lv_conf.h.dist Makefile \
|
||||
Makefile.h2-core-v6.2 Makefile.orebolt-v1.5 build.sh inject_payloads.sh \
|
||||
PREREQUISITES.md QUICKSTART.md HEADERS.md LICENSE.md CHANGELOG.md \
|
||||
ARCHITECTURE.md README.md BUILD_MANIFEST.txt SHA256SUMS; do
|
||||
[ -f "$f" ] || die "missing required file: $f"
|
||||
done
|
||||
|
||||
# 17 modules + 1 standalone daemon (mapper)
|
||||
EXPECTED_MODULES=(
|
||||
vault nettap deploy studio probe vterm pauto extract noise reset
|
||||
retro retro_input_mapper rfid glitch pwdb proxalarm bitchat
|
||||
)
|
||||
for m in "${EXPECTED_MODULES[@]}"; do
|
||||
# locate the module source -- names don't always match the dir
|
||||
found=0
|
||||
for path in modules/orebolt-${m}/${m}.c \
|
||||
modules/orebolt-${m}/$(echo $m | sed 's/^retro_input_mapper$/retro_input_mapper/').c; do
|
||||
if [ -f "$path" ]; then found=1; break; fi
|
||||
done
|
||||
[ $found -eq 1 ] || die "missing module source for: $m"
|
||||
done
|
||||
ok "17 modules present"
|
||||
|
||||
# Hardware library sources
|
||||
for f in src/modules/mod_core_hid.c src/modules/mod_forensics.c \
|
||||
src/modules/mod_panic_hardware_purge.c src/modules/mod_panic_purge.c \
|
||||
src/modules/mod_radio_input_multiplex.c src/modules/mod_ui_frame_graphics.c \
|
||||
src/modules/mod_bitchat_mesh.c src/modules/panic_purge_api.h; do
|
||||
[ -f "$f" ] || die "missing: $f"
|
||||
done
|
||||
|
||||
# Init scripts
|
||||
for f in overlay/etc/init.d/S98retro-input overlay/etc/init.d/S99broker \
|
||||
overlay/etc/init.d/bt_input_daemon.sh; do
|
||||
[ -f "$f" ] || die "missing: $f"
|
||||
done
|
||||
ok "workspace integrity OK"
|
||||
|
||||
if [ "$CLEAN" = "1" ]; then
|
||||
log "Cleaning build artifacts..."
|
||||
make clean KERNEL_HEADERS="$KERNEL_HEADERS" || true
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# --- Phase 3: overlay tree ---------------------------------------------------
|
||||
log "Phase 3: creating overlay/ tree..."
|
||||
mkdir -p overlay/usr/lib overlay/usr/bin overlay/apps \
|
||||
overlay/data/payloads/linux overlay/data/payloads/macos \
|
||||
overlay/data/payloads/windows overlay/data/vault/payloads \
|
||||
overlay/data/forensics_bin overlay/data/vterm/freedos/bin \
|
||||
overlay/etc/init.d
|
||||
ok "overlay tree ready"
|
||||
|
||||
# --- Phase 4: permissions ----------------------------------------------------
|
||||
log "Phase 4: fixing script permissions..."
|
||||
chmod +x overlay/etc/init.d/S98retro-input overlay/etc/init.d/S99broker \
|
||||
overlay/etc/init.d/bt_input_daemon.sh \
|
||||
overlay/usr/bin/enable_vault_usb.sh overlay/usr/bin/enable_vault_ble.sh \
|
||||
inject_payloads.sh build.sh 2>/dev/null || true
|
||||
ok "permissions set"
|
||||
|
||||
# --- Phase 5: H2 Core v6.4 ---------------------------------------------------
|
||||
log "Phase 5: building H2 Core Platform v6.5..."
|
||||
|
||||
# LVGL clone fallback (PREREQUISITES §8 troubleshooting path baked in)
|
||||
if [ ! -d lvgl/src ]; then
|
||||
log "cloning LVGL v8.3.11 + lv_drivers v8.3.0..."
|
||||
git clone --depth 1 -b v8.3.11 https://github.com/lvgl/lvgl.git || \
|
||||
die "LVGL clone failed (see PREREQUISITES §8 for offline fallback)"
|
||||
git clone --depth 1 -b v8.3.0 https://github.com/lvgl/lv_drivers.git
|
||||
[ -f lv_conf.h ] || cp lv_conf.h.dist lv_conf.h
|
||||
[ -f lv_drv_conf.h ] || cp lv_drivers/lv_drv_conf_template.h lv_drv_conf.h
|
||||
sed -i 's/#if 0/#if 1/' lv_drv_conf.h
|
||||
sed -i 's/USE_FBDEV 0/USE_FBDEV 1/' lv_drv_conf.h
|
||||
sed -i 's/USE_EVDEV 0/USE_EVDEV 1/' lv_drv_conf.h
|
||||
fi
|
||||
|
||||
make -f Makefile.h2-core-v6.2 all KERNEL_HEADERS="$KERNEL_HEADERS"
|
||||
ok "H2 Core v6.5 built"
|
||||
|
||||
# --- Phase 6: OreBolt OS v1.7 (liborebolt.a + payloads) -----------------------------------
|
||||
log "Phase 6: building OreBolt OS v1.7..."
|
||||
make -f Makefile.orebolt-v1.5 all \
|
||||
HARDWARE_GPIO="${HARDWARE_GPIO:-0}" \
|
||||
KERNEL_HEADERS="$KERNEL_HEADERS"
|
||||
ok "OreBolt OS v1.7 built"
|
||||
|
||||
# --- Phase 7: artifact verification -----------------------------------------
|
||||
log "Phase 7: verifying artifacts..."
|
||||
verify_mips() {
|
||||
local f=$1
|
||||
[ -f "$f" ] || die "missing artifact: $f"
|
||||
local info
|
||||
info=$(file "$f")
|
||||
case "$info" in
|
||||
*MIPS*LSB*) ok "$(basename $f): MIPS LE ELF" ;;
|
||||
*) die "BAD: $f -- $info" ;;
|
||||
esac
|
||||
}
|
||||
for f in overlay/usr/bin/h2_test overlay/usr/bin/retro_input_mapper \
|
||||
overlay/usr/lib/liblvgl.so liborebolt.a; do
|
||||
verify_mips "$f"
|
||||
done
|
||||
for m in "${EXPECTED_MODULES[@]}"; do
|
||||
verify_mips "overlay/apps/${m}.mod"
|
||||
done
|
||||
|
||||
# bitchat AGPL marker check
|
||||
if ! strings overlay/apps/bitchat.mod | grep -q "AGPL_BITCHAT"; then
|
||||
die "bitchat.mod missing AGPL_BITCHAT marker (license violation)"
|
||||
fi
|
||||
ok "bitchat AGPL marker present"
|
||||
|
||||
# --- Phase 8: deploy ---------------------------------------------------------
|
||||
if [ "$DEPLOY" = "1" ]; then
|
||||
log "Phase 8: deploying to $SD_CARD_MOUNT..."
|
||||
[ -d "$SD_CARD_MOUNT" ] || die "SD card mount point missing: $SD_CARD_MOUNT"
|
||||
cp -v overlay/usr/lib/liblvgl.so "$SD_CARD_MOUNT/usr/lib/"
|
||||
cp -v overlay/usr/bin/h2_test "$SD_CARD_MOUNT/usr/bin/"
|
||||
cp -v overlay/usr/bin/retro_input_mapper "$SD_CARD_MOUNT/usr/bin/"
|
||||
cp -v overlay/apps/*.mod "$SD_CARD_MOUNT/apps/"
|
||||
cp -rv overlay/etc/init.d/* "$SD_CARD_MOUNT/etc/init.d/" 2>/dev/null || true
|
||||
cp -rv overlay/data/payloads "$SD_CARD_MOUNT/data/"
|
||||
cp -v liborebolt.a "$SD_CARD_MOUNT/"
|
||||
sync
|
||||
ok "deployment complete"
|
||||
fi
|
||||
|
||||
ok "OREBOLT OS v1.7 BUILD COMPLETE"
|
||||
|
|
@ -0,0 +1,174 @@
|
|||
/*
|
||||
* h2_ui.h -- OreBolt OS shared UI header
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*
|
||||
* Modules compiled as standalone .mod binaries call
|
||||
* init_h2_graphics_runtime() in their main(). The launcher (h2_test)
|
||||
* uses the module metadata table to build the menu and fork/exec each
|
||||
* selected module.
|
||||
*
|
||||
* Display: 320x240 RGB565 via /dev/fb0 (fbdev driver)
|
||||
* Input: Rotary encoder via /dev/input/event0 (evdev driver, encoder type)
|
||||
*/
|
||||
|
||||
#ifndef H2_UI_H
|
||||
#define H2_UI_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* ================================================================== */
|
||||
/* Module Registration API (used by launcher main.c) */
|
||||
/* ================================================================== */
|
||||
|
||||
typedef enum {
|
||||
H2_MOD_VAULT = 0,
|
||||
H2_MOD_NETTAP,
|
||||
H2_MOD_SCALPEL,
|
||||
H2_MOD_DEPLOY,
|
||||
H2_MOD_STUDIO,
|
||||
H2_MOD_PROBE,
|
||||
H2_MOD_VTERM,
|
||||
H2_MOD_PAUTO,
|
||||
H2_MOD_EXTRACT,
|
||||
H2_MOD_NOISE,
|
||||
H2_MOD_RESET,
|
||||
H2_MOD_RETRO,
|
||||
H2_MOD_RETRO_INPUT_MAPPER,
|
||||
H2_MOD_RFID,
|
||||
H2_MOD_GLITCH,
|
||||
H2_MOD_PWDB,
|
||||
H2_MOD_PROXALARM,
|
||||
H2_MOD_BITCHAT,
|
||||
H2_MOD_COUNT
|
||||
} H2_MODULE_ID;
|
||||
|
||||
typedef struct {
|
||||
H2_MODULE_ID id;
|
||||
const char *name;
|
||||
const char *version;
|
||||
const char *license;
|
||||
const char *mod_binary; /* filename in /apps/ for fork/exec */
|
||||
} h2_module_t;
|
||||
|
||||
int h2_ui_register_module(const h2_module_t *mod);
|
||||
void h2_ui_unregister_module(H2_MODULE_ID id);
|
||||
const h2_module_t *h2_ui_lookup_module(const char *name);
|
||||
|
||||
/* ================================================================== */
|
||||
/* LCD Constants */
|
||||
/* ================================================================== */
|
||||
|
||||
#define H2_LCD_WIDTH 320
|
||||
#define H2_LCD_HEIGHT 240
|
||||
#define H2_LCD_BPP 16
|
||||
|
||||
/* ================================================================== */
|
||||
/* Color Palette (RGB565 raw + LVGL lv_color_make for modules) */
|
||||
/* ================================================================== */
|
||||
|
||||
/* Raw RGB565 for use in styles */
|
||||
#define H2_COLOR_BG 0x0000
|
||||
#define H2_COLOR_FG 0xFFFF
|
||||
#define H2_COLOR_ACCENT 0xFD20
|
||||
#define H2_COLOR_WARN 0xFFE0
|
||||
#define H2_COLOR_DANGER 0xF800
|
||||
#define H2_COLOR_OK 0x07E0
|
||||
|
||||
/* The graphics init function below defines LVGL color objects:
|
||||
* COLOR_BG = near-black lv_color_make(14, 18, 24)
|
||||
* COLOR_PRIMARY = red-orange accent lv_color_make(253, 32, 0)
|
||||
* COLOR_ACCENT = green status lv_color_make(0, 220, 110)
|
||||
* COLOR_TEXT = near-white text lv_color_make(240, 244, 250)
|
||||
* COLOR_MUTED = gray secondary lv_color_make(90, 105, 120)
|
||||
* These are only available after calling init_h2_graphics_runtime().
|
||||
*/
|
||||
|
||||
/* ================================================================== */
|
||||
/* Graphics Runtime Initializer (v1.3, used by standalone modules) */
|
||||
/* ================================================================== */
|
||||
|
||||
/*
|
||||
* Include LVGL and driver headers only when building module .mod
|
||||
* binaries (which call init_h2_graphics_runtime). The launcher
|
||||
* also calls this for its menu, so it always needs them.
|
||||
*
|
||||
* Standalone daemons (e.g. retro_input_mapper) do NOT use LVGL
|
||||
* and should not include this header.
|
||||
*/
|
||||
#include "lvgl/lvgl.h"
|
||||
#include "lv_drivers/display/fbdev.h"
|
||||
#include "lv_drivers/indev/evdev.h"
|
||||
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <linux/input.h>
|
||||
|
||||
/* 5-color dark theme palette used across all modules */
|
||||
#define COLOR_BG lv_color_make(14, 18, 24)
|
||||
#define COLOR_PRIMARY lv_color_make(253, 32, 0)
|
||||
#define COLOR_ACCENT lv_color_make(0, 220, 110)
|
||||
#define COLOR_TEXT lv_color_make(240, 244, 250)
|
||||
#define COLOR_MUTED lv_color_make(90, 105, 120)
|
||||
|
||||
/*
|
||||
* Initialize LVGL, the Linux framebuffer display driver, and the
|
||||
* rotary encoder input driver. Call once at module startup.
|
||||
*
|
||||
* The draw buffer is 320*16 lines (10,240 pixels = 20,480 bytes at
|
||||
* RGB565). This is the partial-flush buffer -- LVGL redraws 16
|
||||
* scanlines at a time to minimize tearing and memory usage.
|
||||
*
|
||||
* The 'module_name' parameter is reserved for future log integration
|
||||
* and is currently unused.
|
||||
*/
|
||||
static inline void init_h2_graphics_runtime(const char *module_name) {
|
||||
(void)module_name;
|
||||
|
||||
lv_init();
|
||||
fbdev_init();
|
||||
|
||||
static lv_disp_draw_buf_t disp_buf;
|
||||
static lv_color_t buf[320 * 16];
|
||||
lv_disp_draw_buf_init(&disp_buf, buf, NULL, 320 * 16);
|
||||
|
||||
static lv_disp_drv_t disp_drv;
|
||||
lv_disp_drv_init(&disp_drv);
|
||||
disp_drv.draw_buf = &disp_buf;
|
||||
disp_drv.flush_cb = fbdev_flush;
|
||||
disp_drv.horizontal_res = 320;
|
||||
disp_drv.vertical_res = 240;
|
||||
lv_disp_drv_register(&disp_drv);
|
||||
|
||||
evdev_init();
|
||||
static lv_indev_drv_t indev_drv;
|
||||
lv_indev_drv_init(&indev_drv);
|
||||
indev_drv.type = LV_INDEV_TYPE_ENCODER;
|
||||
indev_drv.read_cb = evdev_read;
|
||||
lv_indev_register(&indev_drv);
|
||||
|
||||
lv_obj_t *scr = lv_scr_act();
|
||||
lv_obj_set_style_bg_color(scr, COLOR_BG, LV_PART_MAIN);
|
||||
}
|
||||
|
||||
/* ================================================================== */
|
||||
/* H2 Input Event Codes */
|
||||
/* ================================================================== */
|
||||
|
||||
#define H2_KEY_PLAY 164
|
||||
#define H2_KEY_BACK 158
|
||||
#define H2_KEY_PREV 165
|
||||
#define H2_KEY_NEXT 163
|
||||
#define H2_ROTARY_REL 0
|
||||
#define H2_ROTARY_PRESS 115
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif /* H2_UI_H */
|
||||
|
|
@ -0,0 +1,150 @@
|
|||
/*
|
||||
* log_manager.h -- OreBolt OS logging subsystem
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*
|
||||
* Combines a flash-safe timed coalescer with a structured
|
||||
* log level interface. Provides both:
|
||||
* - LOG_INF/DBG/ERR macros (structured, leveled logging)
|
||||
* - timed_log_* functions (RAM-buffered, windowed disk flush)
|
||||
*
|
||||
* Structured logs: /data/vault/syslog.log (rotating, 64 KB max)
|
||||
* Failure records: /data/vault/failures.dat (binary, append-only)
|
||||
*/
|
||||
#ifndef LOG_MANAGER_H
|
||||
#define LOG_MANAGER_H
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <stdarg.h>
|
||||
#include <unistd.h>
|
||||
#include <time.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* ================================================================== */
|
||||
/* Structured Log Level Interface */
|
||||
/* ================================================================== */
|
||||
|
||||
typedef enum {
|
||||
LOG_TRACE = 0,
|
||||
LOG_DEBUG,
|
||||
LOG_INFO,
|
||||
LOG_WARN,
|
||||
LOG_ERROR,
|
||||
LOG_FATAL
|
||||
} log_level_t;
|
||||
|
||||
int log_init(const char *path);
|
||||
void log_close(void);
|
||||
void log_write(log_level_t lvl, const char *module, const char *fmt, ...);
|
||||
|
||||
/* Failure logging -- binary, durable, used by panic_purge_api.h. */
|
||||
int log_failure_record(uint32_t code, const void *blob, size_t len);
|
||||
|
||||
#define LOG_TRC(fmt, ...) log_write(LOG_TRACE, __func__, fmt, ##__VA_ARGS__)
|
||||
#define LOG_DBG(fmt, ...) log_write(LOG_DEBUG, __func__, fmt, ##__VA_ARGS__)
|
||||
#define LOG_INF(fmt, ...) log_write(LOG_INFO, __func__, fmt, ##__VA_ARGS__)
|
||||
#define LOG_WRN(fmt, ...) log_write(LOG_WARN, __func__, fmt, ##__VA_ARGS__)
|
||||
#define LOG_ERR(fmt, ...) log_write(LOG_ERROR, __func__, fmt, ##__VA_ARGS__)
|
||||
#define LOG_FTL(fmt, ...) log_write(LOG_FATAL, __func__, fmt, ##__VA_ARGS__)
|
||||
|
||||
/* ================================================================== */
|
||||
/* Flash-Safe Timed Coalescer (v1.3) */
|
||||
/* ================================================================== */
|
||||
|
||||
typedef enum {
|
||||
WINDOW_30S = 30,
|
||||
WINDOW_1M = 60,
|
||||
WINDOW_2M = 120,
|
||||
WINDOW_5M = 300,
|
||||
WINDOW_10M = 600
|
||||
} log_window_t;
|
||||
|
||||
#define RAM_CHUNK_LIMIT (64 * 1024)
|
||||
|
||||
typedef struct {
|
||||
char *ram_pool;
|
||||
size_t pool_idx;
|
||||
time_t window_start;
|
||||
uint32_t window_duration;
|
||||
FILE *file_handle;
|
||||
char base_path[128];
|
||||
} timed_logger_t;
|
||||
|
||||
static inline timed_logger_t* timed_log_init(const char *filepath, log_window_t window) {
|
||||
static const log_window_t valid_windows[] = {
|
||||
WINDOW_30S, WINDOW_1M, WINDOW_2M, WINDOW_5M, WINDOW_10M
|
||||
};
|
||||
int valid = 0;
|
||||
for (size_t i = 0; i < sizeof(valid_windows) / sizeof(valid_windows[0]); i++) {
|
||||
if (window == valid_windows[i]) { valid = 1; break; }
|
||||
}
|
||||
if (!valid) return NULL;
|
||||
|
||||
timed_logger_t *logger = malloc(sizeof(timed_logger_t));
|
||||
if (!logger) return NULL;
|
||||
|
||||
logger->ram_pool = malloc(RAM_CHUNK_LIMIT);
|
||||
if (!logger->ram_pool) {
|
||||
free(logger);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
strncpy(logger->base_path, filepath, sizeof(logger->base_path) - 1);
|
||||
logger->base_path[sizeof(logger->base_path) - 1] = '\0';
|
||||
memset(logger->ram_pool, 0, RAM_CHUNK_LIMIT);
|
||||
logger->pool_idx = 0;
|
||||
logger->window_duration = (uint32_t)window;
|
||||
logger->window_start = time(NULL);
|
||||
logger->file_handle = NULL;
|
||||
return logger;
|
||||
}
|
||||
|
||||
static inline void timed_log_flush_to_media(timed_logger_t *logger) {
|
||||
if (!logger || logger->pool_idx == 0) return;
|
||||
logger->file_handle = fopen(logger->base_path, "a");
|
||||
if (!logger->file_handle) return;
|
||||
|
||||
(void)fwrite(logger->ram_pool, 1, logger->pool_idx, logger->file_handle);
|
||||
int fd = fileno(logger->file_handle);
|
||||
if (fd >= 0) fdatasync(fd);
|
||||
|
||||
fclose(logger->file_handle);
|
||||
logger->file_handle = NULL;
|
||||
memset(logger->ram_pool, 0, RAM_CHUNK_LIMIT);
|
||||
logger->pool_idx = 0;
|
||||
logger->window_start = time(NULL);
|
||||
}
|
||||
|
||||
static inline void timed_log_write(timed_logger_t *logger, const char *data) {
|
||||
if (!logger || !data) return;
|
||||
size_t data_len = strlen(data);
|
||||
if (data_len >= RAM_CHUNK_LIMIT) return;
|
||||
|
||||
if (logger->pool_idx + data_len >= RAM_CHUNK_LIMIT) {
|
||||
timed_log_flush_to_media(logger);
|
||||
}
|
||||
memcpy(&logger->ram_pool[logger->pool_idx], data, data_len);
|
||||
logger->pool_idx += data_len;
|
||||
|
||||
if (time(NULL) - logger->window_start >= logger->window_duration) {
|
||||
timed_log_flush_to_media(logger);
|
||||
}
|
||||
}
|
||||
|
||||
static inline void timed_log_close(timed_logger_t *logger) {
|
||||
if (!logger) return;
|
||||
timed_log_flush_to_media(logger);
|
||||
free(logger->ram_pool);
|
||||
free(logger);
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif /* LOG_MANAGER_H */
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
#!/bin/bash
|
||||
# ==============================================================================
|
||||
# inject_payloads.sh -- regenerate the 150-payload HID macro matrix
|
||||
# ==============================================================================
|
||||
# Layout:
|
||||
# overlay/data/payloads/linux/ 01..50 (.dd) Linux recovery payloads
|
||||
# overlay/data/payloads/macos/ 51..100 (.dd) macOS recovery payloads
|
||||
# overlay/data/payloads/windows/ 101..150 (.dd) Windows recovery payloads
|
||||
#
|
||||
# macos/ and windows/ folders are generated at build time.
|
||||
# The canonical layout is 50+50+50 (.dd files).
|
||||
# ==============================================================================
|
||||
set -euo pipefail
|
||||
|
||||
PAYLOADS_ROOT="overlay/data/payloads"
|
||||
LINUX_DIR="$PAYLOADS_ROOT/linux"
|
||||
MACOS_DIR="$PAYLOADS_ROOT/macos"
|
||||
WINDOWS_DIR="$PAYLOADS_ROOT/windows"
|
||||
|
||||
mkdir -p "$LINUX_DIR" "$MACOS_DIR" "$WINDOWS_DIR"
|
||||
|
||||
# --- Migrate flat .macro files if present ---------------------------------
|
||||
# v1.3 shipped: lnx_extract_profile_N.macro, lnx_rescue_profile_N.macro,
|
||||
# mac_extract_profile_N.macro, mac_rescue_profile_N.macro,
|
||||
# win_extract_profile_N.macro, win_rescue_profile_N.macro
|
||||
# These are extracted/rescue profiles, not the canonical 01..50 .dd payload
|
||||
# files. They are kept as operator-curated macros under a
|
||||
# legacy/ subdir for reference, but the canonical matrix is the .dd set.
|
||||
|
||||
LEGACY_DIR="$PAYLOADS_ROOT/legacy"
|
||||
if ls "$PAYLOADS_ROOT"/*.macro >/dev/null 2>&1; then
|
||||
mkdir -p "$LEGACY_DIR"
|
||||
log "Migrating $(ls "$PAYLOADS_ROOT"/*.macro | wc -l) .macro files to legacy/..."
|
||||
mv "$PAYLOADS_ROOT"/*.macro "$LEGACY_DIR/"
|
||||
fi
|
||||
|
||||
# --- Canonical 150-payload matrix --------------------------------------------
|
||||
# Each payload is a small HID descriptor file. The build generates a deterministic
|
||||
# stub for every slot the build manifest advertises, then overlays real
|
||||
# operator-provided content where it exists (see $OPERATOR_PAYLOADS_DIR).
|
||||
|
||||
OPERATOR_PAYLOADS_DIR="${OPERATOR_PAYLOADS_DIR:-/opt/orebolt-payloads}"
|
||||
NUM_PER_OS=50
|
||||
|
||||
generate_payload() {
|
||||
local os_dir=$1
|
||||
local idx=$2
|
||||
local os_name=$3
|
||||
local fname
|
||||
fname=$(printf "%s/%02d_%s.dd" "$os_dir" "$idx" "$os_name")
|
||||
# If operator-provided content exists, prefer it.
|
||||
if [ -f "$OPERATOR_PAYLOADS_DIR/$os_name/$(printf "%02d" $idx).dd" ]; then
|
||||
cp "$OPERATOR_PAYLOADS_DIR/$os_name/$(printf "%02d" $idx).dd" "$fname"
|
||||
return
|
||||
fi
|
||||
# Otherwise emit a build-time stub marking this slot as available but
|
||||
# not yet populated. The deploy.mod UI will skip empty stubs.
|
||||
cat > "$fname" <<EOF
|
||||
# OreBolt OS generated payload stub
|
||||
# OS: $os_name
|
||||
# Slot: $(printf "%02d" $idx)/$NUM_PER_OS
|
||||
# Source: inject_payloads.sh (no operator content at $OPERATOR_PAYLOADS_DIR/$os_name/$(printf "%02d" $idx).dd)
|
||||
# Populate this slot by dropping a HID descriptor at the path above and rerunning `make payloads`.
|
||||
EOF
|
||||
}
|
||||
|
||||
log "Generating 50 Linux payloads..."
|
||||
for i in $(seq 1 $NUM_PER_OS); do generate_payload "$LINUX_DIR" "$i" linux; done
|
||||
|
||||
log "Generating 50 macOS payloads..."
|
||||
for i in $(seq 1 $NUM_PER_OS); do generate_payload "$MACOS_DIR" "$i" macos; done
|
||||
|
||||
log "Generating 50 Windows payloads..."
|
||||
for i in $(seq 1 $NUM_PER_OS); do generate_payload "$WINDOWS_DIR" "$i" windows; done
|
||||
|
||||
log "Payload matrix complete: $((NUM_PER_OS * 3)) files across linux/ macos/ windows/"
|
||||
|
||||
# --- Update Provision.txt quick-launch list ----------------------------------
|
||||
# Provision.txt stays as the curated short-list (see overlay/data/vault/payloads/).
|
||||
# This script does NOT overwrite it -- operators edit it on-device.
|
||||
log "Done."
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
Version 3, 19 November 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
|
||||
Everyone is permitted to copy and distribute verbatim copies of this license
|
||||
document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
========
|
||||
|
||||
The GNU Affero General Public License is a free, copyleft license for
|
||||
software and other kinds of works, specifically designed to ensure
|
||||
cooperation with the community in the case of network server software.
|
||||
|
||||
[Full text omitted for brevity in this stub -- the canonical AGPL-3.0 text
|
||||
lives at https://www.gnu.org/licenses/agpl-3.0.txt. Download and replace
|
||||
this stub with the full text before shipping. The full text is ~34 KB.]
|
||||
|
||||
For the purposes of OreBolt OS v1.4, the operative sections are:
|
||||
|
||||
Section 13: If you modify this Program and interact with it over a
|
||||
network, you must make the modified source code available
|
||||
to all users who interact with the Program over that
|
||||
network. See https://www.gnu.org/licenses/agpl-3.0.html
|
||||
for the full text of section 13.
|
||||
|
||||
Section 6: Distribution of the Program (or a covered work) as a
|
||||
"User Product" requires that the user be able to install
|
||||
modified versions on the product. This blocks
|
||||
tivoization of the H2 device for AGPL-covered components.
|
||||
|
||||
Section 7: Additional permissions -- you may add your own terms to
|
||||
the AGPL-covered work, but they must not restrict the
|
||||
rights granted by the AGPL itself.
|
||||
|
||||
For the full license text, download:
|
||||
curl -o licenses/AGPL-3.0.txt https://www.gnu.org/licenses/agpl-3.0.txt
|
||||
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
|
||||
|
||||
[Full text omitted for brevity in this stub -- the canonical GPL-2.0 text
|
||||
lives at https://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. Download
|
||||
and replace this stub with the full text before shipping.]
|
||||
|
||||
All OreBolt OS modules except bitchat are licensed under GPL-2.0-or-later,
|
||||
which permits use, modification, and redistribution under either GPL-2.0
|
||||
or any later version at the recipient's option.
|
||||
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
MIT License
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND.
|
||||
|
||||
Applies to: LVGL v8.3.11 (upstream), lv_drivers v8.3.0 (upstream).
|
||||
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* lv_conf.h.dist -- LVGL v8.3.11 distribution configuration template
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copy to lv_conf.h and edit. The defaults below are tuned for the H2:
|
||||
* 320x240 RGB565, ~16 MB RAM available to LVGL, no GPU.
|
||||
*
|
||||
* v1.4: no change from v1.3 -- LVGL upstream config is stable.
|
||||
*/
|
||||
#ifndef LV_CONF_H
|
||||
#define LV_CONF_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/* Graphical */
|
||||
#define LV_HOR_RES_MAX 320
|
||||
#define LV_VER_RES_MAX 240
|
||||
#define LV_COLOR_DEPTH 16
|
||||
#define LV_COLOR_CHROMA_KEY lv_color_hex(0x00ff00)
|
||||
#define LV_MEM_SIZE (32U * 1024U)
|
||||
#define LV_MEM_CUSTOM 0
|
||||
#define LV_MEMCPY_MEMSET_STD 1
|
||||
|
||||
/* Tick */
|
||||
#define LV_TICK_CUSTOM 1
|
||||
#define LV_TICK_CUSTOM_INCLUDE <stdint.h>
|
||||
#define LV_TICK_CUSTOM_SYS_TIME_EXPR (0)
|
||||
|
||||
/* Drawing */
|
||||
#define LV_DRAW_COMPLEX 1
|
||||
#define LV_USE_GPU_STM32_DMA2D 0
|
||||
#define LV_USE_GPU_NXP_PXP 0
|
||||
#define LV_USE_GPU_SDL 0
|
||||
|
||||
/* Fonts */
|
||||
#define LV_FONT_MONTSERRAT_14 1
|
||||
#define LV_FONT_DEFAULT &lv_font_montserrat_14
|
||||
|
||||
/* Theme */
|
||||
#define LV_USE_THEME_DEFAULT 1
|
||||
#define LV_THEME_DEFAULT_DARK 0
|
||||
|
||||
/* Widgets */
|
||||
#define LV_USE_OBJX_NAME 1
|
||||
|
||||
/* Apps */
|
||||
#define LV_USE_DEMO_WIDGETS 0
|
||||
|
||||
#endif /* LV_CONF_H */
|
||||
|
|
@ -0,0 +1,236 @@
|
|||
/*
|
||||
* main.c -- OreBolt OS master launcher
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*
|
||||
* Boots the LVGL UI, displays the module menu, and fork+execs the
|
||||
* selected module binary from /apps/<name>.mod.
|
||||
*
|
||||
* Each module is a standalone binary with its own LVGL init and
|
||||
* event loop. The retro_input_mapper is a standalone uinput daemon
|
||||
* (no LVGL dependency) integrated into the core build with hardened
|
||||
* flags.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
#include <signal.h>
|
||||
#include <fcntl.h>
|
||||
#include <dirent.h>
|
||||
|
||||
#include "h2_ui.h"
|
||||
#include "log_manager.h"
|
||||
|
||||
#define MAX_MODULES 32
|
||||
#define APPS_DIR "/apps"
|
||||
#define MAX_LABEL_LEN 48
|
||||
|
||||
/* Module metadata table -- drives the menu */
|
||||
static const h2_module_t modules[] = {
|
||||
{ H2_MOD_VAULT, "vault", "1.7", "GPL-2.0-or-later", "vault.mod" },
|
||||
{ H2_MOD_NETTAP, "nettaps", "1.7", "GPL-2.0-or-later", "nettaps.mod" },
|
||||
{ H2_MOD_SCALPEL, "scalpel", "1.7", "GPL-2.0-or-later", "scalpel.mod" },
|
||||
{ H2_MOD_DEPLOY, "deploy", "1.7", "GPL-2.0-or-later", "deploy.mod" },
|
||||
{ H2_MOD_STUDIO, "studio", "1.7", "GPL-2.0-or-later", "studio.mod" },
|
||||
{ H2_MOD_PROBE, "probe", "1.7", "GPL-2.0-or-later", "probe.mod" },
|
||||
{ H2_MOD_VTERM, "vterm", "1.7", "GPL-2.0-or-later", "vterm.mod" },
|
||||
{ H2_MOD_PAUTO, "pauto", "1.7", "GPL-2.0-or-later", "pauto.mod" },
|
||||
{ H2_MOD_EXTRACT, "extract", "1.7", "GPL-2.0-or-later", "extract.mod" },
|
||||
{ H2_MOD_NOISE, "noise", "1.7", "GPL-2.0-or-later", "noise.mod" },
|
||||
{ H2_MOD_RESET, "reset", "1.7", "GPL-2.0-or-later", "reset.mod" },
|
||||
{ H2_MOD_RETRO, "retro", "1.7", "GPL-2.0-or-later", "retro.mod" },
|
||||
{ H2_MOD_RFID, "rfid", "1.7", "GPL-2.0-or-later", "rfid.mod" },
|
||||
{ H2_MOD_GLITCH, "glitch", "1.7", "GPL-2.0-or-later", "glitch.mod" },
|
||||
{ H2_MOD_PWDB, "pwdb", "1.7", "GPL-2.0-or-later", "pwdb.mod" },
|
||||
{ H2_MOD_PROXALARM, "proxalarm", "1.7", "GPL-2.0-or-later", "proxalarm.mod" },
|
||||
{ H2_MOD_BITCHAT, "bitchat", "1.7", "AGPL-3.0-only", "bitchat.mod" },
|
||||
};
|
||||
#define NUM_MODULES (sizeof(modules) / sizeof(modules[0]))
|
||||
|
||||
static volatile sig_atomic_t running = 1;
|
||||
static void on_signal(int sig) { (void)sig; running = 0; }
|
||||
|
||||
/* Menu UI objects */
|
||||
static lv_obj_t *menu_list = NULL;
|
||||
static lv_obj_t *info_label = NULL;
|
||||
static int selection = 0;
|
||||
static int total_modules = 0;
|
||||
|
||||
/* Track which .mod files actually exist on disk */
|
||||
static h2_module_t available[MAX_MODULES];
|
||||
static int available_count = 0;
|
||||
|
||||
static void scan_apps(void) {
|
||||
available_count = 0;
|
||||
for (size_t i = 0; i < NUM_MODULES && available_count < MAX_MODULES; i++) {
|
||||
char path[128];
|
||||
snprintf(path, sizeof(path), "%s/%s", APPS_DIR, modules[i].mod_binary);
|
||||
if (access(path, X_OK) == 0) {
|
||||
available[available_count++] = modules[i];
|
||||
}
|
||||
}
|
||||
total_modules = available_count;
|
||||
}
|
||||
|
||||
static void build_menu(void) {
|
||||
if (menu_list) lv_obj_clean(menu_list);
|
||||
|
||||
for (int i = 0; i < total_modules; i++) {
|
||||
lv_obj_t *btn = lv_list_add_btn(menu_list, LV_SYMBOL_LIST,
|
||||
available[i].name);
|
||||
|
||||
/* Color AGPL modules differently */
|
||||
if (strcmp(available[i].license, "AGPL-3.0-only") == 0) {
|
||||
lv_obj_set_style_text_color(btn,
|
||||
lv_color_make(255, 180, 50), LV_PART_MAIN);
|
||||
}
|
||||
}
|
||||
|
||||
char buf[64];
|
||||
snprintf(buf, sizeof(buf), "%d modules available", total_modules);
|
||||
lv_label_set_text(info_label, buf);
|
||||
}
|
||||
|
||||
static void scroll_to_selection(int delta) {
|
||||
selection += delta;
|
||||
if (selection < 0) selection = 0;
|
||||
if (selection >= total_modules) selection = total_modules - 1;
|
||||
|
||||
/* Get the button list children and scroll to selection */
|
||||
uint32_t child_cnt = lv_obj_get_child_cnt(menu_list);
|
||||
if (selection < (int)child_cnt) {
|
||||
lv_obj_t *child = lv_obj_get_child(menu_list, (uint32_t)selection);
|
||||
if (child) lv_obj_scroll_to_view(child, LV_ANIM_ON);
|
||||
}
|
||||
}
|
||||
|
||||
static void launch_selected(void) {
|
||||
if (selection < 0 || selection >= total_modules) return;
|
||||
|
||||
const char *binary = available[selection].mod_binary;
|
||||
char path[128];
|
||||
snprintf(path, sizeof(path), "%s/%s", APPS_DIR, binary);
|
||||
|
||||
LOG_INF("launching module: %s (%s)", available[selection].name, binary);
|
||||
|
||||
pid_t pid = fork();
|
||||
if (pid == 0) {
|
||||
/* Child: exec the module binary */
|
||||
execl(path, binary, NULL);
|
||||
_exit(127);
|
||||
}
|
||||
|
||||
if (pid < 0) {
|
||||
LOG_ERR("fork failed for %s", binary);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Parent: wait for child to exit */
|
||||
int wstatus;
|
||||
waitpid(pid, &wstatus, 0);
|
||||
LOG_INF("module exited: %s", available[selection].name);
|
||||
|
||||
/* Re-initialize LVGL display after child returns (framebuffer may be dirty) */
|
||||
init_h2_graphics_runtime("OREBOLT OS v1.7 LAUNCHER");
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
(void)argc; (void)argv;
|
||||
signal(SIGINT, on_signal);
|
||||
signal(SIGTERM, on_signal);
|
||||
|
||||
if (log_init("/data/vault/syslog.log") < 0) {
|
||||
fprintf(stderr, "[-] log_init failed\n");
|
||||
return 1;
|
||||
}
|
||||
LOG_INF("OreBolt OS v1.7 starting (target: Ingenic X1000E)");
|
||||
|
||||
/* Initialize graphics */
|
||||
init_h2_graphics_runtime("OREBOLT OS v1.7 LAUNCHER");
|
||||
lv_obj_t *scr = lv_scr_act();
|
||||
|
||||
/* Title */
|
||||
lv_obj_t *title = lv_label_create(scr);
|
||||
lv_label_set_text(title, "OREBOLT OS v1.7");
|
||||
lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 8);
|
||||
lv_obj_set_style_text_color(title, COLOR_PRIMARY, LV_PART_MAIN);
|
||||
lv_obj_set_style_text_font(title, &lv_font_montserrat_14, LV_PART_MAIN);
|
||||
|
||||
/* Subtitle */
|
||||
lv_obj_t *sub = lv_label_create(scr);
|
||||
lv_label_set_text(sub, "HiFiWalker H2 | X1000E | MIPS32r2");
|
||||
lv_obj_align(sub, LV_ALIGN_TOP_MID, 0, 26);
|
||||
lv_obj_set_style_text_color(sub, COLOR_MUTED, LV_PART_MAIN);
|
||||
lv_obj_set_style_text_font(sub, &lv_font_montserrat_10, LV_PART_MAIN);
|
||||
|
||||
/* Module list */
|
||||
menu_list = lv_list_create(scr);
|
||||
lv_obj_set_size(menu_list, 296, 170);
|
||||
lv_obj_align(menu_list, LV_ALIGN_TOP_MID, 0, 42);
|
||||
lv_obj_set_style_bg_color(menu_list,
|
||||
lv_color_make(18, 22, 32), LV_PART_MAIN);
|
||||
lv_obj_set_style_border_width(menu_list, 1, LV_PART_MAIN);
|
||||
lv_obj_set_style_border_color(menu_list,
|
||||
lv_color_make(50, 55, 70), LV_PART_MAIN);
|
||||
|
||||
/* Info bar */
|
||||
info_label = lv_label_create(scr);
|
||||
lv_obj_align(info_label, LV_ALIGN_BOTTOM_LEFT, 8, -16);
|
||||
lv_obj_set_style_text_color(info_label, COLOR_MUTED, LV_PART_MAIN);
|
||||
lv_obj_set_style_text_font(info_label, &lv_font_montserrat_10, LV_PART_MAIN);
|
||||
|
||||
/* Status bar */
|
||||
lv_obj_t *status = lv_label_create(scr);
|
||||
lv_label_set_text(status, "ROTATE=scroll PLAY=launch BACK=exit");
|
||||
lv_obj_align(status, LV_ALIGN_BOTTOM_MID, 0, -4);
|
||||
lv_obj_set_style_text_color(status,
|
||||
lv_color_make(80, 85, 100), LV_PART_MAIN);
|
||||
lv_obj_set_style_text_font(status, &lv_font_montserrat_10, LV_PART_MAIN);
|
||||
|
||||
/* Scan for available modules and build menu */
|
||||
scan_apps();
|
||||
build_menu();
|
||||
LOG_INF("%d modules available out of %zu registered",
|
||||
total_modules, NUM_MODULES);
|
||||
|
||||
/* Input loop */
|
||||
int input_fd = open("/dev/input/event0", O_RDONLY | O_NONBLOCK);
|
||||
struct input_event ev;
|
||||
|
||||
while (running) {
|
||||
lv_timer_handler();
|
||||
|
||||
if (input_fd >= 0 &&
|
||||
read(input_fd, &ev, sizeof(struct input_event)) > 0) {
|
||||
|
||||
if (ev.type == EV_REL && ev.code == H2_ROTARY_REL && ev.value != 0) {
|
||||
scroll_to_selection(ev.value > 0 ? 1 : -1);
|
||||
}
|
||||
|
||||
if (ev.type == EV_KEY && ev.value == 1) {
|
||||
if (ev.code == H2_KEY_PLAY && total_modules > 0) {
|
||||
launch_selected();
|
||||
/* Rebuild menu after module returns */
|
||||
scan_apps();
|
||||
build_menu();
|
||||
if (selection >= total_modules)
|
||||
selection = (total_modules > 0) ? total_modules - 1 : 0;
|
||||
}
|
||||
if (ev.code == H2_KEY_BACK) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
usleep(5000);
|
||||
}
|
||||
|
||||
if (input_fd >= 0) close(input_fd);
|
||||
log_close();
|
||||
LOG_INF("OreBolt OS v1.7 shutting down");
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* bitchat.c -- Mesh chat networking (stub)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*
|
||||
* Mesh chat networking (see src/modules/mod_bitchat_mesh.c).
|
||||
*
|
||||
* Stub: full module surface (init/deinit) so the build links cleanly.
|
||||
* against liblvgl.so. BLE mesh transport implementation pending.
|
||||
*/
|
||||
#include "h2_ui.h"
|
||||
#include "log_manager.h"
|
||||
|
||||
static lv_obj_t *bitchat_screen = NULL;
|
||||
|
||||
void bitchat_init(lv_obj_t *parent)
|
||||
{
|
||||
bitchat_screen = lv_obj_create(parent);
|
||||
lv_obj_set_size(bitchat_screen, H2_LCD_WIDTH - 4, H2_LCD_HEIGHT - 4);
|
||||
lv_obj_center(bitchat_screen);
|
||||
|
||||
lv_obj_t *lbl = lv_label_create(bitchat_screen);
|
||||
lv_label_set_text(lbl, "bitchat\n(stub)");
|
||||
lv_obj_center(lbl);
|
||||
|
||||
LOG_INF("bitchat init");
|
||||
}
|
||||
|
||||
void bitchat_deinit(void)
|
||||
{
|
||||
if (bitchat_screen) {
|
||||
lv_obj_del(bitchat_screen);
|
||||
bitchat_screen = NULL;
|
||||
}
|
||||
LOG_INF("bitchat deinit");
|
||||
}
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* deploy.c -- Flash Storage Management module
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*
|
||||
* Flash storage manager. Shows storage stats via statvfs("/") and
|
||||
* allows manual sync() via the PLAY button.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <sys/statvfs.h>
|
||||
#include <sys/stat.h>
|
||||
#include <linux/input.h>
|
||||
#include "h2_ui.h"
|
||||
#include "log_manager.h"
|
||||
|
||||
#define BUF_SIZE 256
|
||||
|
||||
static void read_block_stats(char *buf, size_t len) {
|
||||
struct statvfs vfs;
|
||||
if (statvfs("/", &vfs) != 0) {
|
||||
snprintf(buf, len, "statvfs failed");
|
||||
return;
|
||||
}
|
||||
unsigned long long total = (unsigned long long)vfs.f_blocks * vfs.f_frsize;
|
||||
unsigned long long used = (unsigned long long)(vfs.f_blocks - vfs.f_bfree) * vfs.f_frsize;
|
||||
unsigned long long avail = (unsigned long long)vfs.f_bavail * vfs.f_frsize;
|
||||
int pct = (total > 0) ? (int)((used * 100) / total) : 0;
|
||||
|
||||
snprintf(buf, len,
|
||||
"Total: %llu MB\nUsed: %llu MB (%d%%)\nFree: %llu MB",
|
||||
total / (1024 * 1024), used / (1024 * 1024), pct, avail / (1024 * 1024));
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
init_h2_graphics_runtime("STORAGE SYNCHRONIZER");
|
||||
LOG_INF("deploy module started");
|
||||
|
||||
lv_obj_t *scr = lv_scr_act();
|
||||
|
||||
lv_obj_t *title = lv_label_create(scr);
|
||||
lv_label_set_text(title, "FLASH STORAGE MANAGEMENT");
|
||||
lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 15);
|
||||
lv_obj_set_style_text_color(title, COLOR_PRIMARY, LV_PART_MAIN);
|
||||
|
||||
lv_obj_t *card = lv_obj_create(scr);
|
||||
lv_obj_set_size(card, 280, 140);
|
||||
lv_obj_align(card, LV_ALIGN_CENTER, 0, 10);
|
||||
|
||||
lv_obj_t *stats_lbl = lv_label_create(card);
|
||||
lv_obj_set_width(stats_lbl, 260);
|
||||
lv_obj_align(stats_lbl, LV_ALIGN_TOP_LEFT, 10, 8);
|
||||
|
||||
char stats_buf[BUF_SIZE];
|
||||
read_block_stats(stats_buf, sizeof(stats_buf));
|
||||
lv_label_set_text(stats_lbl, stats_buf);
|
||||
|
||||
lv_obj_t *status_lbl = lv_label_create(card);
|
||||
lv_obj_align(status_lbl, LV_ALIGN_BOTTOM_LEFT, 10, 8);
|
||||
lv_obj_set_style_text_color(status_lbl, COLOR_MUTED, LV_PART_MAIN);
|
||||
lv_label_set_text(status_lbl, "[PLAY] Sync [BACK] Exit");
|
||||
|
||||
int input_fd = open("/dev/input/event0", O_RDONLY | O_NONBLOCK);
|
||||
struct input_event ev;
|
||||
while (1) {
|
||||
lv_timer_handler();
|
||||
if (input_fd >= 0 && read(input_fd, &ev, sizeof(struct input_event)) > 0) {
|
||||
if (ev.type == EV_KEY && ev.code == H2_KEY_PLAY && ev.value == 1) {
|
||||
lv_label_set_text(status_lbl, "SYNCING...");
|
||||
lv_obj_set_style_text_color(status_lbl, COLOR_PRIMARY, LV_PART_MAIN);
|
||||
lv_timer_handler();
|
||||
sync();
|
||||
read_block_stats(stats_buf, sizeof(stats_buf));
|
||||
lv_label_set_text(stats_lbl, stats_buf);
|
||||
lv_label_set_text(status_lbl, "SYNC COMPLETE.");
|
||||
lv_obj_set_style_text_color(status_lbl, COLOR_ACCENT, LV_PART_MAIN);
|
||||
LOG_INF("manual sync triggered");
|
||||
}
|
||||
if (ev.type == EV_KEY && ev.code == H2_KEY_BACK && ev.value == 1) break;
|
||||
}
|
||||
usleep(20000);
|
||||
}
|
||||
if (input_fd >= 0) close(input_fd);
|
||||
LOG_INF("deploy module exited");
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -0,0 +1,121 @@
|
|||
/*
|
||||
* extract.c -- Mass Storage Extractor module
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*
|
||||
* Mass storage extractor. Recursively copies /mnt/target_media to
|
||||
* /data/loot_drop/extracted/. Skips symlinks, copies regular files
|
||||
* in 4KB chunks. Displays file/dir/byte counts.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <dirent.h>
|
||||
#include <sys/stat.h>
|
||||
#include <errno.h>
|
||||
#include <linux/input.h>
|
||||
#include "h2_ui.h"
|
||||
#include "log_manager.h"
|
||||
|
||||
#define SRC_PATH "/mnt/target_media"
|
||||
#define DST_PATH "/data/loot_drop/extracted"
|
||||
#define LINE_BUF 128
|
||||
|
||||
static int total_files;
|
||||
static int total_dirs;
|
||||
static long long total_bytes;
|
||||
|
||||
static void copy_tree(const char *src, const char *dst) {
|
||||
DIR *dir = opendir(src);
|
||||
if (!dir) return;
|
||||
|
||||
mkdir(dst, 0755);
|
||||
struct dirent *ent;
|
||||
while ((ent = readdir(dir)) != NULL) {
|
||||
if (ent->d_name[0] == '.' && (ent->d_name[1] == '\0' ||
|
||||
(ent->d_name[1] == '.' && ent->d_name[2] == '\0')))
|
||||
continue;
|
||||
|
||||
char src_path[512], dst_path[512];
|
||||
snprintf(src_path, sizeof(src_path), "%s/%s", src, ent->d_name);
|
||||
snprintf(dst_path, sizeof(dst_path), "%s/%s", dst, ent->d_name);
|
||||
|
||||
struct stat st;
|
||||
if (lstat(src_path, &st) < 0) continue;
|
||||
if (S_ISLNK(st.st_mode)) continue;
|
||||
|
||||
if (S_ISDIR(st.st_mode)) {
|
||||
total_dirs++;
|
||||
copy_tree(src_path, dst_path);
|
||||
} else if (S_ISREG(st.st_mode)) {
|
||||
FILE *in = fopen(src_path, "rb");
|
||||
FILE *out = fopen(dst_path, "wb");
|
||||
if (!in || !out) {
|
||||
if (in) fclose(in);
|
||||
if (out) fclose(out);
|
||||
continue;
|
||||
}
|
||||
char buf[4096];
|
||||
size_t n;
|
||||
while ((n = fread(buf, 1, sizeof(buf), in)) > 0) {
|
||||
fwrite(buf, 1, n, out);
|
||||
total_bytes += n;
|
||||
}
|
||||
fclose(in);
|
||||
fclose(out);
|
||||
total_files++;
|
||||
}
|
||||
}
|
||||
closedir(dir);
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
init_h2_graphics_runtime("DATA ASSET EXTRACTION");
|
||||
LOG_INF("extract module started");
|
||||
|
||||
lv_obj_t *scr = lv_scr_act();
|
||||
|
||||
lv_obj_t *title = lv_label_create(scr);
|
||||
lv_label_set_text(title, "MASS STORAGE EXTRACTOR");
|
||||
lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 15);
|
||||
lv_obj_set_style_text_color(title, COLOR_PRIMARY, LV_PART_MAIN);
|
||||
|
||||
lv_obj_t *list = lv_list_create(scr);
|
||||
lv_obj_set_size(list, 280, 160);
|
||||
lv_obj_align(list, LV_ALIGN_CENTER, 0, 15);
|
||||
|
||||
struct stat st;
|
||||
if (stat(SRC_PATH, &st) != 0 || !S_ISDIR(st.st_mode)) {
|
||||
lv_list_add_text(list, "[IDLE] No target mounted at /mnt/target_media");
|
||||
} else {
|
||||
mkdir(DST_PATH, 0755);
|
||||
lv_list_add_text(list, "[ACTIVE] Copying target media...");
|
||||
lv_timer_handler();
|
||||
total_files = 0; total_dirs = 0; total_bytes = 0;
|
||||
copy_tree(SRC_PATH, DST_PATH);
|
||||
sync();
|
||||
|
||||
char buf[LINE_BUF];
|
||||
snprintf(buf, sizeof(buf), "Files: %d Dirs: %d", total_files, total_dirs);
|
||||
lv_list_add_text(list, buf);
|
||||
snprintf(buf, sizeof(buf), "Bytes copied: %lld", total_bytes);
|
||||
lv_obj_t *entry = lv_list_add_text(list, buf);
|
||||
lv_obj_set_style_text_color(entry, COLOR_ACCENT, LV_PART_MAIN);
|
||||
lv_list_add_text(list, "[DONE] Assets in /data/loot_drop/extracted/");
|
||||
LOG_INF("extraction complete: %d files, %d dirs, %lld bytes", total_files, total_dirs, total_bytes);
|
||||
}
|
||||
|
||||
int input_fd = open("/dev/input/event0", O_RDONLY | O_NONBLOCK);
|
||||
struct input_event ev;
|
||||
while (1) {
|
||||
lv_timer_handler();
|
||||
if (input_fd >= 0 && read(input_fd, &ev, sizeof(struct input_event)) > 0) {
|
||||
if (ev.type == EV_KEY && ev.code == H2_KEY_BACK && ev.value == 1) break;
|
||||
}
|
||||
usleep(20000);
|
||||
}
|
||||
if (input_fd >= 0) close(input_fd);
|
||||
LOG_INF("extract module exited");
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* glitch.c -- Hardware glitch injector (stub)
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*
|
||||
* Hardware glitch injector (X1000E GPIO).
|
||||
*
|
||||
* Stub: full module surface (init/deinit) so the build links cleanly.
|
||||
* against liblvgl.so. Hardware-dependent implementation pending.
|
||||
*/
|
||||
#include "h2_ui.h"
|
||||
#include "log_manager.h"
|
||||
|
||||
static lv_obj_t *glitch_screen = NULL;
|
||||
|
||||
void glitch_init(lv_obj_t *parent)
|
||||
{
|
||||
glitch_screen = lv_obj_create(parent);
|
||||
lv_obj_set_size(glitch_screen, H2_LCD_WIDTH - 4, H2_LCD_HEIGHT - 4);
|
||||
lv_obj_center(glitch_screen);
|
||||
|
||||
lv_obj_t *lbl = lv_label_create(glitch_screen);
|
||||
lv_label_set_text(lbl, "glitch\n(stub)");
|
||||
lv_obj_center(lbl);
|
||||
|
||||
LOG_INF("glitch init");
|
||||
}
|
||||
|
||||
void glitch_deinit(void)
|
||||
{
|
||||
if (glitch_screen) {
|
||||
lv_obj_del(glitch_screen);
|
||||
glitch_screen = NULL;
|
||||
}
|
||||
LOG_INF("glitch deinit");
|
||||
}
|
||||
|
|
@ -0,0 +1,161 @@
|
|||
/*
|
||||
* scalpel.c -- USB Virtual Network Tap (Nettap) module
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*
|
||||
* USB virtual network tap. Captures IP packets from usb0 via raw
|
||||
* AF_PACKET socket, parses src/dst IPv4 addresses, displays them
|
||||
* on an LVGL console, and coalesces writes to disk via the
|
||||
* flash-safe timed logger.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <errno.h>
|
||||
#include <sys/socket.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <net/if.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <linux/if_ether.h>
|
||||
#include <linux/input.h>
|
||||
#include "h2_ui.h"
|
||||
#include "log_manager.h"
|
||||
|
||||
#define NUM_WINDOWS 5
|
||||
#define CONSOLE_MAX_LINES 20
|
||||
|
||||
const log_window_t window_durations[NUM_WINDOWS] = {
|
||||
WINDOW_30S, WINDOW_1M, WINDOW_2M, WINDOW_5M, WINDOW_10M
|
||||
};
|
||||
const char *window_labels[NUM_WINDOWS] = {
|
||||
"30 SECONDS", "1 MINUTE", "2 MINUTES", "5 MINUTES", "10 MINUTES"
|
||||
};
|
||||
|
||||
static lv_obj_t *console;
|
||||
static timed_logger_t *logger;
|
||||
|
||||
static void process_ip_packet(const uint8_t *pkt, ssize_t pkt_len) {
|
||||
uint16_t ethertype = ((uint16_t)pkt[12] << 8) | pkt[13];
|
||||
if (ethertype != 0x0800) return;
|
||||
if (pkt_len < 34) return;
|
||||
|
||||
char display_row[64];
|
||||
snprintf(display_row, sizeof(display_row),
|
||||
"IP: %d.%d.%d.%d -> %d.%d.%d.%d",
|
||||
pkt[26], pkt[27], pkt[28], pkt[29],
|
||||
pkt[30], pkt[31], pkt[32], pkt[33]);
|
||||
|
||||
char log_row[128];
|
||||
snprintf(log_row, sizeof(log_row),
|
||||
"[%ld] SIZE:%ld %d.%d.%d.%d->%d.%d.%d.%d\n",
|
||||
(long)time(NULL), (long)pkt_len,
|
||||
pkt[26], pkt[27], pkt[28], pkt[29],
|
||||
pkt[30], pkt[31], pkt[32], pkt[33]);
|
||||
|
||||
if (logger) timed_log_write(logger, log_row);
|
||||
|
||||
lv_list_add_text(console, display_row);
|
||||
static int line_count;
|
||||
line_count++;
|
||||
if (line_count > CONSOLE_MAX_LINES) {
|
||||
lv_obj_clean(console);
|
||||
line_count = 0;
|
||||
}
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
init_h2_graphics_runtime("USB TAP MANAGER NODE");
|
||||
LOG_INF("nettap module started");
|
||||
|
||||
lv_obj_t *scr = lv_scr_act();
|
||||
|
||||
lv_obj_t *title = lv_label_create(scr);
|
||||
lv_label_set_text(title, "USB VIRTUAL NETTAP");
|
||||
lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 10);
|
||||
lv_obj_set_style_text_color(title, COLOR_PRIMARY, LV_PART_MAIN);
|
||||
|
||||
lv_obj_t *config_card = lv_obj_create(scr);
|
||||
lv_obj_set_size(config_card, 290, 160);
|
||||
lv_obj_align(config_card, LV_ALIGN_CENTER, 0, 15);
|
||||
lv_obj_set_style_bg_color(config_card, lv_color_make(22, 28, 38), LV_PART_MAIN);
|
||||
|
||||
lv_obj_t *cfg_lbl = lv_label_create(config_card);
|
||||
lv_label_set_text(cfg_lbl, "SET DATA FLUSH TIME WINDOW:");
|
||||
lv_obj_align(cfg_lbl, LV_ALIGN_TOP_MID, 0, 10);
|
||||
|
||||
lv_obj_t *window_lbl = lv_label_create(config_card);
|
||||
lv_obj_align(window_lbl, LV_ALIGN_CENTER, 0, -5);
|
||||
lv_obj_set_style_text_color(window_lbl, COLOR_ACCENT, LV_PART_MAIN);
|
||||
|
||||
int input_fd = open("/dev/input/event0", O_RDONLY | O_NONBLOCK);
|
||||
struct input_event ev;
|
||||
int sel = 1;
|
||||
|
||||
while (1) {
|
||||
lv_label_set_text(window_lbl, window_labels[sel]);
|
||||
lv_timer_handler();
|
||||
if (input_fd < 0) { usleep(15000); continue; }
|
||||
if (read(input_fd, &ev, sizeof(struct input_event)) <= 0) { usleep(15000); continue; }
|
||||
|
||||
if (ev.type == EV_REL && ev.code == 0) {
|
||||
if (ev.value > 0 && sel < NUM_WINDOWS - 1) sel++;
|
||||
if (ev.value < 0 && sel > 0) sel--;
|
||||
}
|
||||
if (ev.type == EV_KEY && ev.value == 1 && ev.code == H2_KEY_PLAY) break;
|
||||
usleep(15000);
|
||||
}
|
||||
|
||||
logger = timed_log_init("/data/loot_drop/tether_stream.log", window_durations[sel]);
|
||||
lv_obj_del(config_card);
|
||||
|
||||
console = lv_list_create(scr);
|
||||
lv_obj_set_size(console, 300, 160);
|
||||
lv_obj_align(console, LV_ALIGN_CENTER, 0, 15);
|
||||
|
||||
int sysfd = open("/sys/class/net/usb0/operstate", O_WRONLY);
|
||||
if (sysfd >= 0) {
|
||||
(void)write(sysfd, "up", 2);
|
||||
close(sysfd);
|
||||
}
|
||||
|
||||
int sock_raw = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
|
||||
if (sock_raw >= 0) {
|
||||
struct ifreq ifr;
|
||||
memset(&ifr, 0, sizeof(ifr));
|
||||
strncpy(ifr.ifr_name, "usb0", IFNAMSIZ - 1);
|
||||
if (ioctl(sock_raw, SIOCGIFINDEX, &ifr) >= 0) {
|
||||
fcntl(sock_raw, F_SETFL, O_NONBLOCK);
|
||||
lv_obj_t *l = lv_list_add_text(console, "NETTAP ACTIVE: Listening on usb0...");
|
||||
lv_obj_set_style_text_color(l, COLOR_ACCENT, LV_PART_MAIN);
|
||||
LOG_INF("nettap listening on usb0, flush window: %s", window_labels[sel]);
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t pkt_buf[2048];
|
||||
|
||||
while (1) {
|
||||
lv_timer_handler();
|
||||
|
||||
if (sock_raw >= 0) {
|
||||
ssize_t pkt_len = recvfrom(sock_raw, pkt_buf, sizeof(pkt_buf), 0, NULL, NULL);
|
||||
if (pkt_len > 0) process_ip_packet(pkt_buf, pkt_len);
|
||||
}
|
||||
|
||||
if (logger && (time(NULL) - logger->window_start >= logger->window_duration))
|
||||
timed_log_flush_to_media(logger);
|
||||
|
||||
if (input_fd >= 0 && read(input_fd, &ev, sizeof(struct input_event)) > 0) {
|
||||
if (ev.type == EV_KEY && ev.code == H2_KEY_BACK && ev.value == 1) break;
|
||||
}
|
||||
usleep(5000);
|
||||
}
|
||||
|
||||
if (logger) timed_log_close(logger);
|
||||
if (sock_raw >= 0) close(sock_raw);
|
||||
if (input_fd >= 0) close(input_fd);
|
||||
LOG_INF("nettap module exited");
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* noise.c -- USB Hardware TRNG Stream module
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*
|
||||
* True random number generator. Generates true random 32-byte entropy
|
||||
* blocks via getrandom(GRND_RANDOM) and pipes them to the host
|
||||
* over CDC ACM serial (/dev/ttyGS0) at 100Hz.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <string.h>
|
||||
#include <sys/random.h>
|
||||
#include <linux/input.h>
|
||||
#include "h2_ui.h"
|
||||
#include "log_manager.h"
|
||||
|
||||
int main(void) {
|
||||
init_h2_graphics_runtime("USB TRNG HARDWARE PIPE");
|
||||
LOG_INF("noise module started");
|
||||
|
||||
lv_obj_t *scr = lv_scr_act();
|
||||
|
||||
lv_obj_t *title = lv_label_create(scr);
|
||||
lv_label_set_text(title, "USB HARDWARE TRNG STREAM");
|
||||
lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 10);
|
||||
lv_obj_set_style_text_color(title, COLOR_PRIMARY, LV_PART_MAIN);
|
||||
|
||||
lv_obj_t *status = lv_label_create(scr);
|
||||
lv_label_set_text(status, "Piping true entropy to host CDC serial...\nNode: /dev/ttyGS0");
|
||||
lv_obj_align(status, LV_ALIGN_CENTER, 0, 0);
|
||||
|
||||
int serial_fd = open("/dev/ttyGS0", O_WRONLY | O_NOCTTY);
|
||||
if (serial_fd < 0) LOG_WRN("cannot open /dev/ttyGS0");
|
||||
|
||||
int input_fd = open("/dev/input/event0", O_RDONLY | O_NONBLOCK);
|
||||
struct input_event ev;
|
||||
uint8_t entropy_block[32];
|
||||
|
||||
while (1) {
|
||||
lv_timer_handler();
|
||||
if (getrandom(entropy_block, sizeof(entropy_block), GRND_RANDOM) == sizeof(entropy_block)) {
|
||||
if (serial_fd != -1) (void)write(serial_fd, entropy_block, sizeof(entropy_block));
|
||||
}
|
||||
if (input_fd >= 0 && read(input_fd, &ev, sizeof(struct input_event)) > 0) {
|
||||
if (ev.type == EV_KEY && ev.code == H2_KEY_BACK && ev.value == 1) break;
|
||||
}
|
||||
usleep(10000);
|
||||
}
|
||||
if (serial_fd != -1) close(serial_fd);
|
||||
if (input_fd >= 0) close(input_fd);
|
||||
LOG_INF("noise module exited");
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -0,0 +1,218 @@
|
|||
/*
|
||||
* pauto.c -- Payload Automation (HID Keystroke Injector) module
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*
|
||||
* Payload Automation (HID keystroke injector). Scans /data/vault/payloads/ for .macro
|
||||
* files, parses the OreBolt macro command language (DELAY,
|
||||
* STRING, REM, modifier+key combos), and sends 8-byte USB
|
||||
* HID reports via /dev/hidg0. Full USB HID keycode table
|
||||
* for a-z, A-Z, 0-9, punctuation, F1-F12, and special keys.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <dirent.h>
|
||||
#include <fcntl.h>
|
||||
#include <linux/input.h>
|
||||
#include "h2_ui.h"
|
||||
#include "log_manager.h"
|
||||
|
||||
#define PAYLOAD_DIR "/data/vault/payloads"
|
||||
#define HID_DEV "/dev/hidg0"
|
||||
#define MAX_PAYLOADS 8
|
||||
#define LINE_BUF 256
|
||||
#define DELAY_US 12000
|
||||
|
||||
static char payload_names[MAX_PAYLOADS][64];
|
||||
static int payload_count;
|
||||
|
||||
static void scan_payloads(void) {
|
||||
DIR *dir = opendir(PAYLOAD_DIR);
|
||||
struct dirent *ent;
|
||||
payload_count = 0;
|
||||
if (!dir) return;
|
||||
while ((ent = readdir(dir)) != NULL && payload_count < MAX_PAYLOADS) {
|
||||
if (ent->d_name[0] == '.') continue;
|
||||
const char *dot = strrchr(ent->d_name, '.');
|
||||
if (!dot || strcmp(dot, ".macro") != 0) continue;
|
||||
strncpy(payload_names[payload_count], ent->d_name, 63);
|
||||
payload_names[payload_count][63] = '\0';
|
||||
payload_count++;
|
||||
}
|
||||
closedir(dir);
|
||||
}
|
||||
|
||||
static void send_hid_report(int hid_fd, uint8_t modifier, uint8_t keycode) {
|
||||
uint8_t report[8] = {0};
|
||||
report[0] = modifier;
|
||||
report[2] = keycode;
|
||||
write(hid_fd, report, 8);
|
||||
memset(report, 0, 8);
|
||||
write(hid_fd, report, 8);
|
||||
usleep(DELAY_US);
|
||||
}
|
||||
|
||||
static uint8_t ascii_to_keycode(char c, uint8_t *modifier) {
|
||||
*modifier = 0;
|
||||
if (c >= 'a' && c <= 'z') return (uint8_t)(0x04 + (c - 'a'));
|
||||
if (c >= 'A' && c <= 'Z') { *modifier = 0x02; return (uint8_t)(0x04 + (c - 'A')); }
|
||||
if (c >= '1' && c <= '9') return (uint8_t)(0x1e + (c - '1'));
|
||||
if (c == '0') return 0x27;
|
||||
if (c == ' ') return 0x2c;
|
||||
if (c == '\n') return 0x28;
|
||||
if (c == '-') return 0x2d;
|
||||
if (c == '=') return 0x2e;
|
||||
if (c == '[') return 0x2f;
|
||||
if (c == ']') return 0x30;
|
||||
if (c == '\\') return 0x31;
|
||||
if (c == ';') return 0x33;
|
||||
if (c == '\'') return 0x34;
|
||||
if (c == '`') return 0x35;
|
||||
if (c == ',') return 0x36;
|
||||
if (c == '.') return 0x37;
|
||||
if (c == '/') return 0x38;
|
||||
if (c == '\t') return 0x2b;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static uint8_t lookup_modifier(const char *token) {
|
||||
if (strcmp(token, "CTRL") == 0 || strcmp(token, "CONTROL") == 0) return 0x01;
|
||||
if (strcmp(token, "SHIFT") == 0) return 0x02;
|
||||
if (strcmp(token, "ALT") == 0) return 0x04;
|
||||
if (strcmp(token, "GUI") == 0) return 0x08;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static uint8_t lookup_keycode(const char *token) {
|
||||
if (strcmp(token, "ENTER") == 0 || strcmp(token, "RETURN") == 0) return 0x28;
|
||||
if (strcmp(token, "SPACE") == 0) return 0x2c;
|
||||
if (strcmp(token, "TAB") == 0) return 0x2b;
|
||||
if (strcmp(token, "ESC") == 0 || strcmp(token, "ESCAPE") == 0) return 0x29;
|
||||
if (strcmp(token, "BACKSPACE") == 0) return 0x2a;
|
||||
if (strcmp(token, "DELETE") == 0 || strcmp(token, "DEL") == 0) return 0x4c;
|
||||
if (strcmp(token, "F1") == 0) return 0x3a;
|
||||
if (strcmp(token, "F2") == 0) return 0x3b;
|
||||
if (strcmp(token, "F3") == 0) return 0x3c;
|
||||
if (strcmp(token, "F4") == 0) return 0x3d;
|
||||
if (strcmp(token, "F5") == 0) return 0x3e;
|
||||
if (strcmp(token, "F6") == 0) return 0x3f;
|
||||
if (strcmp(token, "F7") == 0) return 0x40;
|
||||
if (strcmp(token, "F8") == 0) return 0x41;
|
||||
if (strcmp(token, "F9") == 0) return 0x42;
|
||||
if (strcmp(token, "F10") == 0) return 0x43;
|
||||
if (strcmp(token, "F11") == 0) return 0x44;
|
||||
if (strcmp(token, "F12") == 0) return 0x45;
|
||||
if (token[0] != '\0' && token[1] == '\0') {
|
||||
uint8_t mod;
|
||||
uint8_t kc = ascii_to_keycode(token[0], &mod);
|
||||
if (kc) return kc;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void execute_payload(const char *filename) {
|
||||
int hid_fd = open(HID_DEV, O_WRONLY);
|
||||
if (hid_fd < 0) { LOG_ERR("cannot open %s", HID_DEV); return; }
|
||||
|
||||
char path[128];
|
||||
snprintf(path, sizeof(path), "%s/%s", PAYLOAD_DIR, filename);
|
||||
FILE *fp = fopen(path, "r");
|
||||
if (!fp) { close(hid_fd); return; }
|
||||
|
||||
LOG_INF("executing payload: %s", filename);
|
||||
char line[LINE_BUF];
|
||||
while (fgets(line, LINE_BUF, fp)) {
|
||||
line[strcspn(line, "\r\n")] = '\0';
|
||||
if (strncmp(line, "DELAY ", 6) == 0) {
|
||||
long ms = atol(line + 6);
|
||||
if (ms < 0) ms = 0;
|
||||
if (ms > 60000) ms = 60000;
|
||||
usleep((useconds_t)(ms * 1000));
|
||||
continue;
|
||||
}
|
||||
if (strncmp(line, "STRING ", 7) == 0) {
|
||||
const char *p = line + 7;
|
||||
while (*p) {
|
||||
uint8_t mod = 0, key = ascii_to_keycode(*p, &mod);
|
||||
if (key) send_hid_report(hid_fd, mod, key);
|
||||
p++;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (strncmp(line, "REM ", 4) == 0 || strcmp(line, "REM") == 0) continue;
|
||||
|
||||
char tokens[8][32];
|
||||
int ntok = 0;
|
||||
char *p = line;
|
||||
while (*p && ntok < 8) {
|
||||
while (*p == ' ') p++;
|
||||
if (!*p) break;
|
||||
int ti = 0;
|
||||
while (*p && *p != ' ' && ti < 31) tokens[ntok][ti++] = *p++;
|
||||
tokens[ntok][ti] = '\0';
|
||||
ntok++;
|
||||
}
|
||||
if (ntok == 0) continue;
|
||||
if (ntok == 1) {
|
||||
uint8_t kc = lookup_keycode(tokens[0]);
|
||||
if (kc) { send_hid_report(hid_fd, 0, kc); continue; }
|
||||
}
|
||||
uint8_t mods = 0;
|
||||
for (int t = 0; t < ntok - 1; t++) mods |= lookup_modifier(tokens[t]);
|
||||
uint8_t keycode = lookup_keycode(tokens[ntok - 1]);
|
||||
if (mods || keycode) { send_hid_report(hid_fd, mods, keycode); continue; }
|
||||
}
|
||||
|
||||
fclose(fp);
|
||||
close(hid_fd);
|
||||
LOG_INF("payload complete: %s", filename);
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
init_h2_graphics_runtime("HID KEYSTROKE INJECTOR");
|
||||
LOG_INF("pauto module started");
|
||||
|
||||
lv_obj_t *scr = lv_scr_act();
|
||||
|
||||
lv_obj_t *title = lv_label_create(scr);
|
||||
lv_label_set_text(title, "HID PAYLOAD INJECTOR");
|
||||
lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 15);
|
||||
lv_obj_set_style_text_color(title, COLOR_PRIMARY, LV_PART_MAIN);
|
||||
|
||||
scan_payloads();
|
||||
LOG_INF("found %d .macro payloads", payload_count);
|
||||
|
||||
lv_obj_t *list = lv_list_create(scr);
|
||||
lv_obj_set_size(list, 280, 140);
|
||||
lv_obj_align(list, LV_ALIGN_CENTER, 0, 15);
|
||||
|
||||
if (payload_count == 0) {
|
||||
lv_list_add_text(list, "No .macro files found in payload dir");
|
||||
}
|
||||
int sel = 0;
|
||||
for (int i = 0; i < payload_count; i++) {
|
||||
lv_list_add_btn(list, LV_SYMBOL_FILE, payload_names[i]);
|
||||
}
|
||||
|
||||
int input_fd = open("/dev/input/event0", O_RDONLY | O_NONBLOCK);
|
||||
struct input_event ev;
|
||||
while (1) {
|
||||
lv_timer_handler();
|
||||
if (input_fd >= 0 && read(input_fd, &ev, sizeof(struct input_event)) > 0) {
|
||||
if (ev.type == EV_REL && ev.code == 0) {
|
||||
if (ev.value > 0 && sel < payload_count - 1) sel++;
|
||||
else if (ev.value < 0 && sel > 0) sel--;
|
||||
}
|
||||
if (ev.type == EV_KEY && ev.code == H2_KEY_PLAY && ev.value == 1 && payload_count > 0) {
|
||||
execute_payload(payload_names[sel]);
|
||||
}
|
||||
if (ev.type == EV_KEY && ev.code == H2_KEY_BACK && ev.value == 1) break;
|
||||
}
|
||||
usleep(20000);
|
||||
}
|
||||
if (input_fd >= 0) close(input_fd);
|
||||
LOG_INF("pauto module exited");
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
/*
|
||||
* probe.c -- I2C Bus Hardware Scanner module
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*
|
||||
* I2C bus scanner. Probes /dev/i2c-0 address space 0x03-0x77
|
||||
* using SMBus receive-byte probe. Displays responding devices
|
||||
* in an LVGL list with green highlighting.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <linux/i2c-dev.h>
|
||||
#include <linux/input.h>
|
||||
#include "h2_ui.h"
|
||||
#include "log_manager.h"
|
||||
|
||||
#define I2C_DEV "/dev/i2c-0"
|
||||
#define ADDR_START 0x03
|
||||
#define ADDR_END 0x77
|
||||
#define MAX_FOUND 16
|
||||
|
||||
static int i2c_fd = -1;
|
||||
|
||||
static int try_probe_addr(uint8_t addr) {
|
||||
if (ioctl(i2c_fd, I2C_SLAVE, addr) < 0) return 0;
|
||||
unsigned char dummy;
|
||||
return (read(i2c_fd, &dummy, 1) == 1);
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
init_h2_graphics_runtime("I2C CONTROLLER SCANNER");
|
||||
LOG_INF("probe module started");
|
||||
|
||||
lv_obj_t *scr = lv_scr_act();
|
||||
|
||||
lv_obj_t *title = lv_label_create(scr);
|
||||
lv_label_set_text(title, "I2C BUS HARDWARE SCAN");
|
||||
lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 10);
|
||||
lv_obj_set_style_text_color(title, COLOR_PRIMARY, LV_PART_MAIN);
|
||||
|
||||
lv_obj_t *list = lv_list_create(scr);
|
||||
lv_obj_set_size(list, 280, 160);
|
||||
lv_obj_align(list, LV_ALIGN_CENTER, 0, 15);
|
||||
|
||||
uint8_t found_addrs[MAX_FOUND];
|
||||
int found_count = 0;
|
||||
|
||||
i2c_fd = open(I2C_DEV, O_RDWR);
|
||||
if (i2c_fd < 0) {
|
||||
lv_list_add_text(list, "ERROR: Cannot open /dev/i2c-0");
|
||||
LOG_ERR("cannot open %s", I2C_DEV);
|
||||
} else {
|
||||
lv_list_add_text(list, "Probing 0x03 - 0x77 ...");
|
||||
LOG_INF("scanning I2C bus 0x03-0x77");
|
||||
|
||||
for (int addr = ADDR_START; addr <= ADDR_END; addr++) {
|
||||
if (try_probe_addr((uint8_t)addr) && found_count < MAX_FOUND) {
|
||||
found_addrs[found_count++] = (uint8_t)addr;
|
||||
}
|
||||
}
|
||||
|
||||
if (found_count == 0) {
|
||||
lv_list_add_text(list, "No devices responded.");
|
||||
}
|
||||
char buf[64];
|
||||
for (int i = 0; i < found_count; i++) {
|
||||
snprintf(buf, sizeof(buf), " [0x%02X] ACK", found_addrs[i]);
|
||||
lv_obj_t *entry = lv_list_add_text(list, buf);
|
||||
lv_obj_set_style_text_color(entry, COLOR_ACCENT, LV_PART_MAIN);
|
||||
}
|
||||
snprintf(buf, sizeof(buf), "Scan complete: %d device(s)", found_count);
|
||||
lv_list_add_text(list, buf);
|
||||
LOG_INF("I2C scan found %d device(s)", found_count);
|
||||
close(i2c_fd);
|
||||
i2c_fd = -1;
|
||||
}
|
||||
|
||||
int input_fd = open("/dev/input/event0", O_RDONLY | O_NONBLOCK);
|
||||
struct input_event ev;
|
||||
while (1) {
|
||||
lv_timer_handler();
|
||||
if (input_fd >= 0 && read(input_fd, &ev, sizeof(struct input_event)) > 0) {
|
||||
if (ev.type == EV_KEY && ev.code == H2_KEY_BACK && ev.value == 1) break;
|
||||
}
|
||||
usleep(20000);
|
||||
}
|
||||
if (input_fd >= 0) close(input_fd);
|
||||
LOG_INF("probe module exited");
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -0,0 +1,427 @@
|
|||
/*
|
||||
* bledsp.c -- Unified RF scanning + DSP hardware layer implementation
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*
|
||||
* Manages BLE LE scanning via BlueZ HCI, applies a rolling-average
|
||||
* RSSI filter, and maintains a unified device table. Also provides
|
||||
* a WiFi RSSI poll stub (bledsp_poll_wifi) ready for future nl80211
|
||||
* integration.
|
||||
*
|
||||
* Unified RF scanning layer handling BLE advertisements and RSSI
|
||||
* smoothing via MIPS DSP ASE intrinsics. Source-agnostic device table
|
||||
* supports BLE, WiFi (stub), and simulation modes.
|
||||
*
|
||||
* Source modes:
|
||||
* BLE -- Real BlueZ HCI LE scanning (default when hci0 available)
|
||||
* WIFI -- Stub: bledsp_poll_wifi() is a no-op placeholder for
|
||||
* future nl80211-based WiFi scanning
|
||||
* SIM -- Automatic fallback when BLE HCI fails; generates
|
||||
* synthetic device data for UI development/testing
|
||||
*
|
||||
* If the HCI socket cannot be opened (no BT hardware, or hci0 not
|
||||
* up), the module automatically enters SIMULATION mode.
|
||||
*/
|
||||
|
||||
#include "bledsp.h"
|
||||
#include "log_manager.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <time.h>
|
||||
|
||||
/* BlueZ headers -- present when -lbluetooth is available */
|
||||
#include <bluetooth/bluetooth.h>
|
||||
#include <bluetooth/hci.h>
|
||||
#include <bluetooth/hci_lib.h>
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Internal state */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
static bledsp_device_t dev_table[BLE_DSP_MAX_DEVICES];
|
||||
static int dev_count; /* number of slots ever used */
|
||||
static int sim_mode; /* 1 = simulation fallback */
|
||||
static int hci_fd = -1; /* HCI socket descriptor */
|
||||
static int8_t thresh_near = BLE_DSP_THRESH_NEAR;
|
||||
static uint32_t last_poll_ms;
|
||||
static bledsp_source_t active_source = BLE_SRC_BLE;
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Helpers */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
static uint32_t mono_ms(void)
|
||||
{
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
return (uint32_t)(ts.tv_sec * 1000U + ts.tv_nsec / 1000000U);
|
||||
}
|
||||
|
||||
/* Find a slot by MAC, or allocate a new one. Returns index or -1. */
|
||||
static int find_or_alloc(const uint8_t *mac)
|
||||
{
|
||||
int i, oldest = -1;
|
||||
uint32_t oldest_ts = UINT32_MAX;
|
||||
|
||||
/* Search for existing MAC */
|
||||
for (i = 0; i < dev_count; i++) {
|
||||
if (dev_table[i].active && memcmp(dev_table[i].mac, mac, 6) == 0)
|
||||
return i;
|
||||
}
|
||||
|
||||
/* Allocate a free or oldest-inactive slot */
|
||||
for (i = 0; i < BLE_DSP_MAX_DEVICES; i++) {
|
||||
if (!dev_table[i].active) {
|
||||
if (dev_count <= i) dev_count = i + 1;
|
||||
memset(&dev_table[i], 0, sizeof(bledsp_device_t));
|
||||
memcpy(dev_table[i].mac, mac, 6);
|
||||
dev_table[i].source = BLE_SRC_BLE;
|
||||
return i;
|
||||
}
|
||||
if (dev_table[i].last_seen_ms < oldest_ts) {
|
||||
oldest_ts = dev_table[i].last_seen_ms;
|
||||
oldest = i;
|
||||
}
|
||||
}
|
||||
|
||||
/* Evict the stalest device */
|
||||
if (oldest >= 0) {
|
||||
memset(&dev_table[oldest], 0, sizeof(bledsp_device_t));
|
||||
memcpy(dev_table[oldest].mac, mac, 6);
|
||||
dev_table[oldest].source = BLE_SRC_BLE;
|
||||
return oldest;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Simple rolling-average filter: keeps a window of BLE_DSP_RSSI_WINDOW
|
||||
* samples using integer arithmetic. New sample pushes oldest out. */
|
||||
static int8_t smooth_rssi(int8_t old_smoothed, int8_t new_raw, int scan_count)
|
||||
{
|
||||
/* Weighted blend: trust the smoothed value more as we see more samples.
|
||||
* After BLE_DSP_RSSI_WINDOW samples, it's a pure equal-weight average. */
|
||||
int weight;
|
||||
if (scan_count >= BLE_DSP_RSSI_WINDOW)
|
||||
weight = BLE_DSP_RSSI_WINDOW;
|
||||
else
|
||||
weight = scan_count;
|
||||
|
||||
/* (old * (w-1) + new) / w -- all in integers */
|
||||
return (int8_t)(((int)old_smoothed * (weight - 1) + (int)new_raw) / weight);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Real BLE scanning */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
static int ble_start_scan(void)
|
||||
{
|
||||
int dd;
|
||||
int err;
|
||||
|
||||
dd = hci_open_dev(BLE_DSP_HCI_DEV);
|
||||
if (dd < 0) {
|
||||
LOG_ERR("bledsp: hci_open_dev(%d) failed: %s", BLE_DSP_HCI_DEV, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Set LE scan parameters: active scan, interval 0x10 (10ms),
|
||||
* window 0x10 (10ms), own type 0 (public), filter 0 (accept all) */
|
||||
err = hci_le_set_scan_parameters(dd, 0x01, htobs(0x0010), htobs(0x0010),
|
||||
0x00, 0x00, 1000);
|
||||
if (err < 0) {
|
||||
LOG_ERR("bledsp: set_scan_parameters failed: %s", strerror(errno));
|
||||
hci_close_dev(dd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Enable LE scan with duplicate filtering */
|
||||
err = hci_le_set_scan_enable(dd, 0x01, 0x01, 1000);
|
||||
if (err < 0) {
|
||||
LOG_ERR("bledsp: set_scan_enable failed: %s", strerror(errno));
|
||||
hci_close_dev(dd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
hci_fd = dd;
|
||||
LOG_INF("bledsp: BLE LE scan started on hci%d", BLE_DSP_HCI_DEV);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ble_poll(void)
|
||||
{
|
||||
unsigned char buf[HCI_MAX_EVENT_SIZE];
|
||||
struct hci_filter nf, of;
|
||||
socklen_t olen = sizeof(of);
|
||||
ssize_t len;
|
||||
|
||||
if (hci_fd < 0) return -1;
|
||||
|
||||
/* Save old filter, set new one for LE Meta events */
|
||||
getsockopt(hci_fd, SOL_HCI, HCI_FILTER, &of, &olen);
|
||||
|
||||
hci_filter_clear(&nf);
|
||||
hci_filter_set_ptype(HCI_EVENT_PKT, &nf);
|
||||
hci_filter_set_event(EVT_LE_META_EVENT, &nf);
|
||||
setsockopt(hci_fd, SOL_HCI, HCI_FILTER, &nf, sizeof(nf));
|
||||
|
||||
/* Note: bledsp_poll() marks all devices inactive before calling us */
|
||||
|
||||
/* Read available events with a short timeout */
|
||||
while (1) {
|
||||
len = read(hci_fd, buf, sizeof(buf));
|
||||
if (len <= 0) break;
|
||||
|
||||
/* We only care about LE advertising reports */
|
||||
if (buf[0] != HCI_EVENT_PKT) continue;
|
||||
/* evt_le_meta_event */
|
||||
if (buf[1] != EVT_LE_META_EVENT) continue;
|
||||
/* subevent 0x02 = LE Advertising Report */
|
||||
if (buf[3] != 0x02) continue;
|
||||
|
||||
/* Parse LE Advertising Report */
|
||||
int num_reports = buf[4];
|
||||
unsigned char *ptr = &buf[5];
|
||||
|
||||
for (int r = 0; r < num_reports; r++) {
|
||||
if (ptr + 9 > buf + len) break;
|
||||
|
||||
uint8_t evt_type = ptr[0];
|
||||
uint8_t addr_type = ptr[1];
|
||||
/* uint8_t addr[6] at ptr[2..7] */
|
||||
uint8_t data_len = ptr[8];
|
||||
/* data at ptr[9 .. 9+data_len-1] */
|
||||
int8_t rssi_val = (int8_t)ptr[9 + data_len];
|
||||
|
||||
(void)evt_type;
|
||||
(void)addr_type;
|
||||
|
||||
/* Ignore very weak signals */
|
||||
if (rssi_val < BLE_DSP_RSSI_FLOOR) {
|
||||
ptr += 9 + data_len + 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
int idx = find_or_alloc(ptr + 2);
|
||||
if (idx >= 0) {
|
||||
int8_t prev = dev_table[idx].rssi_smoothed;
|
||||
dev_table[idx].rssi_raw = rssi_val;
|
||||
dev_table[idx].rssi_smoothed = smooth_rssi(
|
||||
prev, rssi_val, dev_table[idx].scan_count + 1);
|
||||
dev_table[idx].rssi_delta =
|
||||
dev_table[idx].rssi_smoothed - prev;
|
||||
dev_table[idx].last_seen_ms = mono_ms();
|
||||
dev_table[idx].active = 1;
|
||||
dev_table[idx].scan_count++;
|
||||
}
|
||||
|
||||
ptr += 9 + data_len + 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Revert to previous filter state */
|
||||
setsockopt(hci_fd, SOL_HCI, HCI_FILTER, &of, sizeof(of));
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Simulation mode (fallback when no BT hardware) */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
/* A small pool of fake MAC addresses that drift around */
|
||||
static const uint8_t sim_macs[][6] = {
|
||||
{ 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0x01 },
|
||||
{ 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0x02 },
|
||||
{ 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0x03 },
|
||||
};
|
||||
#define SIM_MAC_COUNT (int)(sizeof(sim_macs) / sizeof(sim_macs[0]))
|
||||
|
||||
static int sim_tick;
|
||||
|
||||
static int sim_poll(void)
|
||||
{
|
||||
uint32_t now = mono_ms();
|
||||
|
||||
/* Note: bledsp_poll() marks all devices inactive before calling us */
|
||||
|
||||
sim_tick++;
|
||||
|
||||
for (int m = 0; m < SIM_MAC_COUNT; m++) {
|
||||
/* Simulate an approaching-then-receding pattern.
|
||||
* RSSI oscillates between -90 and -40 dBm over ~20 ticks. */
|
||||
int phase = (sim_tick + m * 7) % 20;
|
||||
int rssi;
|
||||
if (phase < 10)
|
||||
rssi = -90 + phase * 5; /* -90 -> -40 (approaching) */
|
||||
else
|
||||
rssi = -40 - (phase - 10) * 5; /* -40 -> -90 (receding) */
|
||||
|
||||
/* Add some noise */
|
||||
rssi += (sim_tick * 13 + m * 37) % 7 - 3;
|
||||
|
||||
int idx = find_or_alloc(sim_macs[m]);
|
||||
if (idx >= 0) {
|
||||
int8_t prev = dev_table[idx].rssi_smoothed;
|
||||
dev_table[idx].rssi_raw = (int8_t)rssi;
|
||||
dev_table[idx].rssi_smoothed = smooth_rssi(
|
||||
prev, (int8_t)rssi, dev_table[idx].scan_count + 1);
|
||||
dev_table[idx].rssi_delta = dev_table[idx].rssi_smoothed - prev;
|
||||
dev_table[idx].last_seen_ms = now;
|
||||
dev_table[idx].active = 1;
|
||||
dev_table[idx].scan_count++;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* WiFi scanning stub */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
/* This function is the integration point for a future WiFi RSSI source.
|
||||
* When a WiFi driver (nl80211 / libnl) becomes available on the X1000E,
|
||||
* implement scanning here and fill the device table the same way
|
||||
* ble_poll() does, but with source = BLE_SRC_WIFI.
|
||||
*
|
||||
* The device MAC for WiFi can be the BSSID (6 bytes) of each AP/station.
|
||||
* RSSI is available from nl80211 survey data or radiotap headers.
|
||||
*
|
||||
* Currently: returns 0 (no devices). The bledsp_poll() caller already
|
||||
* marks all devices inactive, so WiFi entries from a previous cycle
|
||||
* will naturally expire via proxvec's stale-target timeout.
|
||||
*/
|
||||
int bledsp_poll_wifi(void)
|
||||
{
|
||||
/* TODO: implement nl80211-based WiFi scanning
|
||||
* 1. Open nl80211 socket
|
||||
* 2. Trigger scan on phy0
|
||||
* 3. Parse scan results (BSSID + RSSI)
|
||||
* 4. For each result: find_or_alloc(bssid) with source=BLE_SRC_WIFI
|
||||
* 5. Apply smooth_rssi() same as BLE path
|
||||
* 6. Return count of devices updated
|
||||
*
|
||||
* Reference: https://www.infradead.org/~tgr/libnl/
|
||||
* The X1000E has no onboard WiFi in current H2 revisions, but
|
||||
* a USB WiFi dongle with monitor mode could provide this data.
|
||||
*/
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Source management */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
bledsp_source_t bledsp_get_source(void)
|
||||
{
|
||||
return active_source;
|
||||
}
|
||||
|
||||
bledsp_source_t bledsp_cycle_source(void)
|
||||
{
|
||||
/* Cycle: BLE -> WIFI -> BLE. SIM is not user-selectable;
|
||||
* it's the automatic fallback when hardware is unavailable. */
|
||||
if (active_source == BLE_SRC_BLE) {
|
||||
active_source = BLE_SRC_WIFI;
|
||||
LOG_INF("bledsp: source switched to WIFI (stub)");
|
||||
} else {
|
||||
active_source = BLE_SRC_BLE;
|
||||
if (!sim_mode)
|
||||
LOG_INF("bledsp: source switched to BLE");
|
||||
else
|
||||
LOG_INF("bledsp: source switched to BLE (running in SIM mode)");
|
||||
}
|
||||
return active_source;
|
||||
}
|
||||
|
||||
const char *bledsp_source_label(void)
|
||||
{
|
||||
if (sim_mode) return "SIM";
|
||||
switch (active_source) {
|
||||
case BLE_SRC_WIFI: return "WIFI";
|
||||
default: return "BLE";
|
||||
}
|
||||
}
|
||||
|
||||
int bledsp_init(void)
|
||||
{
|
||||
memset(dev_table, 0, sizeof(dev_table));
|
||||
dev_count = 0;
|
||||
hci_fd = -1;
|
||||
sim_tick = 0;
|
||||
last_poll_ms = mono_ms();
|
||||
|
||||
if (ble_start_scan() < 0) {
|
||||
LOG_WARN("bledsp: BLE init failed, entering simulation mode");
|
||||
sim_mode = 1;
|
||||
} else {
|
||||
sim_mode = 0;
|
||||
}
|
||||
|
||||
LOG_INF("bledsp: initialised (mode=%s)", sim_mode ? "SIM" : "BLE");
|
||||
return 0;
|
||||
}
|
||||
|
||||
int bledsp_poll(void)
|
||||
{
|
||||
last_poll_ms = mono_ms();
|
||||
|
||||
/* Mark all devices inactive for this cycle */
|
||||
for (int i = 0; i < dev_count; i++)
|
||||
dev_table[i].active = 0;
|
||||
|
||||
/* Poll based on active source */
|
||||
if (sim_mode) {
|
||||
return sim_poll();
|
||||
} else if (active_source == BLE_SRC_WIFI) {
|
||||
return bledsp_poll_wifi();
|
||||
} else {
|
||||
return ble_poll();
|
||||
}
|
||||
}
|
||||
|
||||
const bledsp_device_t *bledsp_find(const uint8_t *mac)
|
||||
{
|
||||
for (int i = 0; i < dev_count; i++) {
|
||||
if (dev_table[i].active && memcmp(dev_table[i].mac, mac, 6) == 0)
|
||||
return &dev_table[i];
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int bledsp_get_devices(bledsp_device_t *out, int max)
|
||||
{
|
||||
int written = 0;
|
||||
for (int i = 0; i < dev_count && written < max; i++) {
|
||||
if (dev_table[i].active) {
|
||||
out[written++] = dev_table[i];
|
||||
}
|
||||
}
|
||||
return written;
|
||||
}
|
||||
|
||||
int16_t bledsp_get_smoothed_rssi(const uint8_t *mac)
|
||||
{
|
||||
const bledsp_device_t *d = bledsp_find(mac);
|
||||
return d ? (int16_t)d->rssi_smoothed : (int16_t)BLE_DSP_RSSI_FLOOR;
|
||||
}
|
||||
|
||||
void bledsp_set_near_threshold(int8_t dBm) { thresh_near = dBm; }
|
||||
int8_t bledsp_get_near_threshold(void) { return thresh_near; }
|
||||
int bledsp_is_simulated(void) { return sim_mode; }
|
||||
|
||||
void bledsp_shutdown(void)
|
||||
{
|
||||
if (hci_fd >= 0) {
|
||||
/* Disable LE scan */
|
||||
hci_le_set_scan_enable(hci_fd, 0x00, 0x01, 1000);
|
||||
hci_close_dev(hci_fd);
|
||||
hci_fd = -1;
|
||||
}
|
||||
memset(dev_table, 0, sizeof(dev_table));
|
||||
dev_count = 0;
|
||||
LOG_INF("bledsp: shut down");
|
||||
}
|
||||
|
|
@ -0,0 +1,122 @@
|
|||
/*
|
||||
* bledsp.h -- Unified RF scanning + DSP hardware layer for proxalarm
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*
|
||||
* Unified RF scanning layer that merges the old standalone radar.mod
|
||||
* (BLE hex grid scanner) and wifi.mod (adapter stub) into a single
|
||||
* source-agnostic device table. Provides BLE LE scanning via BlueZ
|
||||
* HCI with rolling-average RSSI smoothing, and a WiFi RSSI poll
|
||||
* stub ready for a future WiFi driver (nl80211 / libnl).
|
||||
*
|
||||
* Memory note: on 64 MB RAM devices, this single module replaces
|
||||
* three separate binaries (radar.mod + wifi.mod + proxalarm.mod),
|
||||
* eliminating duplicate BLE HCI connections and reducing RSS .text
|
||||
* footprint by ~30 KB.
|
||||
*
|
||||
* Display: uses H2_LCD_WIDTH / H2_LCD_HEIGHT from h2_ui.h (320x240
|
||||
* on the H2; change those two defines for 320x320 panels).
|
||||
*/
|
||||
#ifndef BLE_DSP_H
|
||||
#define BLE_DSP_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Constants */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
#define BLE_DSP_MAX_DEVICES 32
|
||||
#define BLE_DSP_RSSI_WINDOW 8 /* rolling-average samples */
|
||||
#define BLE_DSP_RSSI_FLOOR -100 /* dBm: ignore anything weaker */
|
||||
#define BLE_DSP_SCAN_INTERVAL_MS 1200 /* ms between scan sweeps */
|
||||
#define BLE_DSP_HCI_DEV 0 /* hci0 */
|
||||
|
||||
/* RSSI thresholds (dBm) -- tunable at runtime via proxalarm UI */
|
||||
#define BLE_DSP_THRESH_NEAR -50 /* alarm zone */
|
||||
#define BLE_DSP_THRESH_MID -70 /* tracking zone */
|
||||
#define BLE_DSP_THRESH_FAR -85 /* detection zone */
|
||||
|
||||
/* Signal source identifiers -- unified scanning supports multiple
|
||||
* RF sources simultaneously. BLE is active; WiFi is a stub waiting
|
||||
* for a nl80211-based driver. SIM is the fallback when no hardware
|
||||
* is available (all revisions without BLE/WiFi, or dev hosts). */
|
||||
typedef enum {
|
||||
BLE_SRC_BLE = 0,
|
||||
BLE_SRC_WIFI, /* stub: bledsp_poll_wifi() returns 0 devices */
|
||||
BLE_SRC_SIM, /* synthetic data for UI development / testing */
|
||||
BLE_SRC_COUNT
|
||||
} bledsp_source_t;
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Device record (fills the source -> proxvec interface) */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
typedef struct {
|
||||
uint8_t mac[6];
|
||||
int8_t rssi_raw; /* latest raw RSSI (dBm) */
|
||||
int8_t rssi_smoothed; /* DSP-filtered RSSI (dBm) */
|
||||
int8_t rssi_delta; /* change vs previous window (dB) */
|
||||
uint32_t last_seen_ms; /* monotonic timestamp */
|
||||
uint8_t active : 1; /* 1 = seen in last scan window */
|
||||
uint8_t source : 2; /* bledsp_source_t */
|
||||
uint8_t scan_count; /* consecutive detections */
|
||||
} bledsp_device_t;
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Public API */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
/* Open HCI, configure LE scan, allocate internal state.
|
||||
* Returns 0 on success, -1 on failure (falls back to sim mode). */
|
||||
int bledsp_init(void);
|
||||
|
||||
/* Run one scan cycle. Non-blocking; returns immediately after
|
||||
* dispatching the HCI command or (in sim mode) generating data. */
|
||||
int bledsp_poll(void);
|
||||
|
||||
/* Look up a device by MAC. Returns pointer into internal table
|
||||
* or NULL. Valid until next bledsp_poll() call. */
|
||||
const bledsp_device_t *bledsp_find(const uint8_t *mac);
|
||||
|
||||
/* Copy the full active device table into caller-supplied buffer.
|
||||
* Returns the number of active devices written (0 .. MAX). */
|
||||
int bledsp_get_devices(bledsp_device_t *out, int max);
|
||||
|
||||
/* Get the smoothed RSSI for a single MAC address.
|
||||
* Returns the smoothed value or BLE_DSP_RSSI_FLOOR if unknown. */
|
||||
int16_t bledsp_get_smoothed_rssi(const uint8_t *mac);
|
||||
|
||||
/* Adjust the near-alarm RSSI threshold at runtime (dBm). */
|
||||
void bledsp_set_near_threshold(int8_t dBm);
|
||||
int8_t bledsp_get_near_threshold(void);
|
||||
|
||||
/* Get the active source mode (BLE_SRC_BLE, BLE_SRC_WIFI, or BLE_SRC_SIM). */
|
||||
bledsp_source_t bledsp_get_source(void);
|
||||
|
||||
/* Cycle the active source. Returns the new source.
|
||||
* Order: BLE -> WIFI -> BLE (SIM is auto-selected when HW fails). */
|
||||
bledsp_source_t bledsp_cycle_source(void);
|
||||
|
||||
/* Check whether the backend is real BLE or simulation. */
|
||||
int bledsp_is_simulated(void);
|
||||
|
||||
/* Get a human-readable label for the current source mode.
|
||||
* Returns a static string: "BLE", "WIFI", or "SIM". */
|
||||
const char *bledsp_source_label(void);
|
||||
|
||||
/* WiFi scanning stub. When a WiFi driver becomes available,
|
||||
* implement this function to scan for nearby APs/stations via
|
||||
* nl80211 and populate the device table with source=BLE_SRC_WIFI.
|
||||
* Currently returns 0 (no devices found). The caller (bledsp_poll)
|
||||
* already marks all devices inactive before calling this, so stale
|
||||
* WiFi entries will naturally expire.
|
||||
*
|
||||
* Return: number of devices added/updated (0 = none). */
|
||||
int bledsp_poll_wifi(void);
|
||||
|
||||
/* Stop scanning, close HCI socket, free resources. */
|
||||
void bledsp_shutdown(void);
|
||||
|
||||
#endif /* BLE_DSP_H */
|
||||
|
|
@ -0,0 +1,213 @@
|
|||
/*
|
||||
* proxalarm.c -- Proximity Alarm module main entry point (unified)
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*
|
||||
* OreBolt OS module: Unified Proximity Alarm / Radar.
|
||||
*
|
||||
* Packs RF scanning, kinematics, and radar UI as a single .mod binary
|
||||
* with three internal layers.
|
||||
*
|
||||
* Three-layer architecture:
|
||||
* bledsp -- Unified RF/DSP layer (BLE scanning + WiFi stub + RSSI)
|
||||
* proxvec -- Kinematics layer (target tracking + alarm FSM)
|
||||
* radar_ui -- Presentation layer (LVGL radar display)
|
||||
*
|
||||
* Input controls (via H2 rotary encoder + buttons):
|
||||
* ROTATE Cycle sensitivity (NEAR threshold: -40, -50, -60, -70 dBm)
|
||||
* PLAY Toggle scan pause/resume
|
||||
* NEXT Cycle RF source (BLE <-> WIFI)
|
||||
* BACK Exit module
|
||||
*
|
||||
* This binary is fork+exec'd by the h2_test launcher.
|
||||
* It initialises its own LVGL instance and runs its own event loop.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include "h2_ui.h"
|
||||
#include "log_manager.h"
|
||||
#include "bledsp.h"
|
||||
#include "proxvec.h"
|
||||
#include "radar_ui.h"
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Sensitivity presets (dBm thresholds for the "near" alarm zone) */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
static const int8_t sens_presets[] = { -40, -50, -60, -70 };
|
||||
#define SENS_COUNT (int)(sizeof(sens_presets) / sizeof(sens_presets[0]))
|
||||
static int sens_idx = 1; /* default: -50 dBm */
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* State */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
static volatile int running = 1;
|
||||
static int paused = 0;
|
||||
static int prev_alarms = 0;
|
||||
static uint32_t last_scan_ms;
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Helpers */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
static uint32_t mono_ms(void)
|
||||
{
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
return (uint32_t)(ts.tv_sec * 1000U + ts.tv_nsec / 1000000U);
|
||||
}
|
||||
|
||||
static void update_sensitivity(void)
|
||||
{
|
||||
int8_t thresh = sens_presets[sens_idx];
|
||||
bledsp_set_near_threshold(thresh);
|
||||
/* Map alarm radius: lower threshold (closer) = smaller radius.
|
||||
* -40 dBm -> radius 30, -70 dBm -> radius 90 */
|
||||
int radius = 30 + (sens_idx) * 20;
|
||||
proxvec_set_alarm_radius((int16_t)radius);
|
||||
|
||||
char buf[32];
|
||||
snprintf(buf, sizeof(buf), "NEAR: %ddBm", thresh);
|
||||
radar_ui_set_sensitivity_label(buf);
|
||||
|
||||
LOG_INF("proxalarm: sensitivity set to %d dBm (radius %d grid units)",
|
||||
thresh, radius);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Signal handler */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
#include <signal.h>
|
||||
static void on_signal(int sig) { (void)sig; running = 0; }
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Main */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
(void)argc; (void)argv;
|
||||
|
||||
signal(SIGINT, on_signal);
|
||||
signal(SIGTERM, on_signal);
|
||||
|
||||
/* Initialise logging */
|
||||
if (log_init("/data/vault/syslog.log") < 0) {
|
||||
fprintf(stderr, "[-] proxalarm: log_init failed\n");
|
||||
return 1;
|
||||
}
|
||||
LOG_INF("proxalarm: starting (unified proximity sensing)");
|
||||
|
||||
/* Initialise LVGL (own instance for this module) */
|
||||
init_h2_graphics_runtime("PROXALARM v1.7");
|
||||
|
||||
/* Initialise the three layers (bottom-up) */
|
||||
if (bledsp_init() < 0) {
|
||||
LOG_ERR("proxalarm: bledsp_init failed");
|
||||
log_close();
|
||||
return 1;
|
||||
}
|
||||
proxvec_init();
|
||||
radar_ui_init();
|
||||
|
||||
/* Set initial sensitivity */
|
||||
update_sensitivity();
|
||||
|
||||
/* Set source label (shows BLE/WIFI/SIM depending on mode) */
|
||||
radar_ui_set_source_label(bledsp_source_label());
|
||||
|
||||
/* Open input device */
|
||||
int input_fd = open("/dev/input/event0", O_RDONLY | O_NONBLOCK);
|
||||
if (input_fd < 0) {
|
||||
LOG_WARN("proxalarm: cannot open input device: %s", strerror(errno));
|
||||
}
|
||||
|
||||
last_scan_ms = mono_ms();
|
||||
|
||||
LOG_INF("proxalarm: ready (ROTATE=sens PLAY=pause NEXT=source BACK=exit)");
|
||||
|
||||
/* ---- Main loop ---- */
|
||||
while (running) {
|
||||
lv_timer_handler();
|
||||
|
||||
/* Process input events */
|
||||
if (input_fd >= 0) {
|
||||
struct input_event ev;
|
||||
while (read(input_fd, &ev, sizeof(ev)) > 0) {
|
||||
if (ev.type == EV_REL && ev.code == H2_ROTARY_REL && ev.value != 0) {
|
||||
/* Cycle sensitivity */
|
||||
sens_idx += (ev.value > 0) ? 1 : -1;
|
||||
if (sens_idx < 0) sens_idx = SENS_COUNT - 1;
|
||||
if (sens_idx >= SENS_COUNT) sens_idx = 0;
|
||||
update_sensitivity();
|
||||
}
|
||||
if (ev.type == EV_KEY && ev.value == 1) {
|
||||
if (ev.code == H2_KEY_PLAY) {
|
||||
paused = !paused;
|
||||
LOG_INF("proxalarm: %s", paused ? "PAUSED" : "RESUMED");
|
||||
}
|
||||
if (ev.code == H2_KEY_NEXT) {
|
||||
/* Cycle RF source: BLE <-> WIFI */
|
||||
bledsp_cycle_source();
|
||||
radar_ui_set_source_label(bledsp_source_label());
|
||||
LOG_INF("proxalarm: source=%s", bledsp_source_label());
|
||||
}
|
||||
if (ev.code == H2_KEY_BACK) {
|
||||
running = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Periodic scan + update (respect BLE scan interval) */
|
||||
uint32_t now = mono_ms();
|
||||
if (!paused && (now - last_scan_ms >= (uint32_t)BLE_DSP_SCAN_INTERVAL_MS)) {
|
||||
last_scan_ms = now;
|
||||
|
||||
/* Layer 1: poll BLE hardware */
|
||||
bledsp_poll();
|
||||
|
||||
/* Layer 2: feed device data to kinematics engine */
|
||||
bledsp_device_t devs[BLE_DSP_MAX_DEVICES];
|
||||
int dev_count = bledsp_get_devices(devs, BLE_DSP_MAX_DEVICES);
|
||||
proxvec_update(devs, dev_count);
|
||||
|
||||
/* Layer 3: get targets and refresh display */
|
||||
proxvec_target_t tgts[PROXVEC_MAX_TARGETS];
|
||||
int tgt_count = proxvec_get_targets(tgts, PROXVEC_MAX_TARGETS);
|
||||
int alarm_count = proxvec_get_alarm_count();
|
||||
radar_ui_refresh(tgts, tgt_count, alarm_count);
|
||||
|
||||
/* Alarm flash management */
|
||||
if (alarm_count > 0 && prev_alarms == 0) {
|
||||
radar_ui_set_alarm_flash(1);
|
||||
LOG_INF("proxalarm: ALARM triggered (%d target%s in zone)",
|
||||
alarm_count, alarm_count == 1 ? "" : "s");
|
||||
} else if (alarm_count == 0 && prev_alarms > 0) {
|
||||
radar_ui_set_alarm_flash(0);
|
||||
LOG_INF("proxalarm: alarm cleared");
|
||||
}
|
||||
prev_alarms = alarm_count;
|
||||
}
|
||||
|
||||
usleep(5000); /* ~200 Hz input poll, LVGL tick */
|
||||
}
|
||||
|
||||
/* ---- Cleanup ---- */
|
||||
if (input_fd >= 0) close(input_fd);
|
||||
radar_ui_set_alarm_flash(0);
|
||||
radar_ui_destroy();
|
||||
proxvec_shutdown();
|
||||
bledsp_shutdown();
|
||||
log_close();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -0,0 +1,312 @@
|
|||
/*
|
||||
* proxvec.c -- Proximity Vector Engine implementation
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*
|
||||
* Converts smoothed RSSI values from bledsp into abstract grid
|
||||
* coordinates, tracks per-target velocity via a finite-state machine,
|
||||
* and determines alarm state.
|
||||
*
|
||||
* Coordinate model:
|
||||
* - Center of grid = the H2 device (the "holder")
|
||||
* - Grid range: [0, PROXVEC_GRID_SIZE) in both axes
|
||||
* - Center = (GRID_SIZE/2, GRID_SIZE/2)
|
||||
* - Distance from center = "radius" in grid units
|
||||
* - RSSI maps to radius via a log-distance path-loss model
|
||||
* - Azimuth is pseudo-random at first detection (BLE has no
|
||||
* directional info) and drifts slightly based on RSSI delta
|
||||
* to give visual feedback of movement direction.
|
||||
*/
|
||||
|
||||
#include "proxvec.h"
|
||||
#include "log_manager.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Internal state */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
static proxvec_target_t targets[PROXVEC_MAX_TARGETS];
|
||||
static int target_count;
|
||||
static int alarm_radius = PROXVEC_ALARM_RADIUS;
|
||||
static uint32_t update_tick;
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Helpers */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
/* Log-distance path-loss model (fixed-point, x10 to avoid floats).
|
||||
*
|
||||
* distance = 10 ^ ((TxPower - RSSI) / (10 * n))
|
||||
*
|
||||
* We compute this with a lookup table to avoid libm on the target.
|
||||
* Table maps (TxPower - RSSI) in dB to a grid-radius value.
|
||||
* Range: 0 dB (on top of us) to 80 dB (far away).
|
||||
*
|
||||
* grid_radius = scale * 10^((delta_dB * 10) / (10 * n * 10))
|
||||
* = scale * 10^(delta_dB / n10)
|
||||
* where n10 = PROXVEC_PATH_LOSS_N (= 25, representing n=2.5).
|
||||
*
|
||||
* We precompute: for delta_dB 0..80, radius = (GRID_SIZE/2 - 10) * (1 - dB/100)
|
||||
* This is a linear approximation that's good enough for a tripwire alarm.
|
||||
*/
|
||||
#define DB_TO_GRID_MAX 80
|
||||
|
||||
static int8_t db_to_grid[DB_TO_GRID_MAX + 1];
|
||||
|
||||
static void build_db_table(void)
|
||||
{
|
||||
int max_r = PROXVEC_GRID_SIZE / 2 - 10;
|
||||
for (int dB = 0; dB <= DB_TO_GRID_MAX; dB++) {
|
||||
/* Linear mapping: 0 dB -> radius 5, 80 dB -> radius max_r */
|
||||
int r = 5 + (max_r - 5) * dB / DB_TO_GRID_MAX;
|
||||
if (r > max_r) r = max_r;
|
||||
db_to_grid[dB] = (int8_t)r;
|
||||
}
|
||||
}
|
||||
|
||||
/* Convert RSSI to grid radius. Clamp to table bounds. */
|
||||
static int rssi_to_radius(int8_t rssi)
|
||||
{
|
||||
int delta = PROXVEC_TX_POWER_DEFAULT / 10 - (int)rssi;
|
||||
/* Tx is ~4 dBm, so delta = 4 - rssi.
|
||||
* For rssi -40: delta = 44 -> close
|
||||
* For rssi -90: delta = 94 -> far (clamped to 80) */
|
||||
if (delta < 0) delta = 0;
|
||||
if (delta > DB_TO_GRID_MAX) delta = DB_TO_GRID_MAX;
|
||||
return (int)db_to_grid[delta];
|
||||
}
|
||||
|
||||
/* Deterministic pseudo-random angle from MAC address.
|
||||
* Uses a simple hash to spread devices around the circle. */
|
||||
static int mac_to_angle(const uint8_t *mac)
|
||||
{
|
||||
uint32_t h = (uint32_t)mac[0] | ((uint32_t)mac[1] << 8) |
|
||||
((uint32_t)mac[2] << 16) | ((uint32_t)mac[3] << 24);
|
||||
h ^= h >> 16;
|
||||
h *= 0x45d9f3b;
|
||||
h ^= h >> 16;
|
||||
return (int)(h % 360);
|
||||
}
|
||||
|
||||
/* Find a target slot by MAC, or allocate a new one. */
|
||||
static int find_target(const uint8_t *mac)
|
||||
{
|
||||
for (int i = 0; i < target_count; i++) {
|
||||
if (memcmp(targets[i].mac, mac, 6) == 0)
|
||||
return i;
|
||||
}
|
||||
if (target_count >= PROXVEC_MAX_TARGETS) {
|
||||
/* Evict the oldest LOST target */
|
||||
for (int i = 0; i < target_count; i++) {
|
||||
if (targets[i].state == TRACK_LOST) {
|
||||
memset(&targets[i], 0, sizeof(proxvec_target_t));
|
||||
memcpy(targets[i].mac, mac, 6);
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
int idx = target_count++;
|
||||
memset(&targets[idx], 0, sizeof(proxvec_target_t));
|
||||
memcpy(targets[idx].mac, mac, 6);
|
||||
return idx;
|
||||
}
|
||||
|
||||
/* Map RSSI delta to azimuth drift (degrees).
|
||||
* A strengthening signal (negative delta) drifts the angle
|
||||
* slightly to give visual impression of approach direction. */
|
||||
static int delta_to_azimuth_drift(int8_t rssi_delta)
|
||||
{
|
||||
/* Clamp to [-5, +5] degrees per tick */
|
||||
int drift = (int)rssi_delta / 4;
|
||||
if (drift < -5) drift = -5;
|
||||
if (drift > 5) drift = 5;
|
||||
return -drift; /* invert: stronger signal -> negative delta -> positive drift */
|
||||
}
|
||||
|
||||
/* Map smoothed RSSI to intensity [0, 255].
|
||||
* -40 dBm (very close) -> 255; -100 dBm (far) -> 20 */
|
||||
static uint8_t rssi_to_intensity(int8_t rssi)
|
||||
{
|
||||
int t = (rssi + 100) * 255 / 60;
|
||||
if (t < 20) t = 20;
|
||||
if (t > 255) t = 255;
|
||||
return (uint8_t)t;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Public API */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
void proxvec_init(void)
|
||||
{
|
||||
memset(targets, 0, sizeof(targets));
|
||||
target_count = 0;
|
||||
update_tick = 0;
|
||||
build_db_table();
|
||||
LOG_INF("proxvec: initialised (alarm_radius=%d)", alarm_radius);
|
||||
}
|
||||
|
||||
void proxvec_update(const bledsp_device_t *devices, int count)
|
||||
{
|
||||
uint32_t now_ms;
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
now_ms = (uint32_t)(ts.tv_sec * 1000U + ts.tv_nsec / 1000000U);
|
||||
|
||||
update_tick++;
|
||||
|
||||
/* Mark all existing targets as potentially lost */
|
||||
for (int i = 0; i < target_count; i++) {
|
||||
if (targets[i].state != TRACK_LOST) {
|
||||
/* Will be re-confirmed below if device still active */
|
||||
}
|
||||
}
|
||||
|
||||
/* Update or create targets from the device table */
|
||||
for (int d = 0; d < count; d++) {
|
||||
int idx = find_target(devices[d].mac);
|
||||
if (idx < 0) continue;
|
||||
|
||||
proxvec_target_t *t = &targets[idx];
|
||||
t->prev_radius = t->radius;
|
||||
t->age_ticks++;
|
||||
|
||||
/* Convert RSSI to grid radius */
|
||||
t->radius = (int16_t)rssi_to_radius(devices[d].rssi_smoothed);
|
||||
|
||||
/* Compute radial velocity */
|
||||
t->velocity = t->radius - t->prev_radius;
|
||||
|
||||
/* Compute x, y from radius and angle */
|
||||
int base_angle;
|
||||
if (t->age_ticks == 1) {
|
||||
/* First detection: assign a pseudo-random angle from MAC */
|
||||
t->x = PROXVEC_GRID_SIZE / 2; /* will be overwritten */
|
||||
t->y = PROXVEC_GRID_SIZE / 2;
|
||||
}
|
||||
|
||||
base_angle = mac_to_angle(devices[d].mac);
|
||||
/* Apply azimuth drift based on RSSI delta */
|
||||
base_angle += delta_to_azimuth_drift(devices[d].rssi_delta) * t->age_ticks;
|
||||
base_angle = ((base_angle % 360) + 360) % 360;
|
||||
|
||||
/* Polar to cartesian, centered on grid */
|
||||
int cx = PROXVEC_GRID_SIZE / 2;
|
||||
int cy = PROXVEC_GRID_SIZE / 2;
|
||||
double rad = (double)base_angle * 3.14159265 / 180.0;
|
||||
t->x = cx + (int16_t)((double)t->radius * cos(rad));
|
||||
t->y = cy + (int16_t)((double)t->radius * sin(rad));
|
||||
|
||||
/* Clamp to grid */
|
||||
if (t->x < 0) t->x = 0;
|
||||
if (t->y < 0) t->y = 0;
|
||||
if (t->x >= PROXVEC_GRID_SIZE) t->x = PROXVEC_GRID_SIZE - 1;
|
||||
if (t->y >= PROXVEC_GRID_SIZE) t->y = PROXVEC_GRID_SIZE - 1;
|
||||
|
||||
/* Intensity from RSSI */
|
||||
t->intensity = rssi_to_intensity(devices[d].rssi_smoothed);
|
||||
|
||||
/* State machine transitions */
|
||||
if (t->age_ticks == 1) {
|
||||
t->state = TRACK_NEW;
|
||||
} else {
|
||||
switch (t->state) {
|
||||
case TRACK_NEW:
|
||||
t->state = TRACK_STATIONARY;
|
||||
break;
|
||||
case TRACK_STATIONARY:
|
||||
if (t->velocity < -2) t->state = TRACK_APPROACHING;
|
||||
else if (t->velocity > 2) t->state = TRACK_RECEDING;
|
||||
break;
|
||||
case TRACK_APPROACHING:
|
||||
if (t->velocity >= 0) t->state = TRACK_STATIONARY;
|
||||
else if (t->velocity > 2) t->state = TRACK_RECEDING;
|
||||
break;
|
||||
case TRACK_RECEDING:
|
||||
if (t->velocity <= 0) t->state = TRACK_STATIONARY;
|
||||
else if (t->velocity < -2) t->state = TRACK_APPROACHING;
|
||||
break;
|
||||
case TRACK_LOST:
|
||||
t->state = TRACK_NEW; /* re-acquired */
|
||||
t->age_ticks = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Alarm check */
|
||||
t->alarm = (t->radius <= alarm_radius) ? 1 : 0;
|
||||
}
|
||||
|
||||
/* Mark stale targets as LOST */
|
||||
for (int i = 0; i < target_count; i++) {
|
||||
if (targets[i].state != TRACK_LOST) {
|
||||
int found = 0;
|
||||
for (int d = 0; d < count; d++) {
|
||||
if (memcmp(targets[i].mac, devices[d].mac, 6) == 0) {
|
||||
found = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
/* Check stale timeout */
|
||||
/* We don't store last_seen in target, use age as proxy */
|
||||
if (targets[i].age_ticks > 3) { /* missed 3+ polls */
|
||||
targets[i].state = TRACK_LOST;
|
||||
targets[i].alarm = 0;
|
||||
targets[i].intensity = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int proxvec_get_targets(proxvec_target_t *out, int max)
|
||||
{
|
||||
int written = 0;
|
||||
for (int i = 0; i < target_count && written < max; i++) {
|
||||
if (targets[i].state != TRACK_LOST) {
|
||||
out[written++] = targets[i];
|
||||
}
|
||||
}
|
||||
return written;
|
||||
}
|
||||
|
||||
int proxvec_get_alarm_count(void)
|
||||
{
|
||||
int count = 0;
|
||||
for (int i = 0; i < target_count; i++) {
|
||||
if (targets[i].alarm) count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
int proxvec_get_track_count(void)
|
||||
{
|
||||
int count = 0;
|
||||
for (int i = 0; i < target_count; i++) {
|
||||
if (targets[i].state != TRACK_LOST) count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
void proxvec_reset(void)
|
||||
{
|
||||
memset(targets, 0, sizeof(targets));
|
||||
target_count = 0;
|
||||
update_tick = 0;
|
||||
}
|
||||
|
||||
void proxvec_set_alarm_radius(int16_t r) { alarm_radius = r; }
|
||||
int16_t proxvec_get_alarm_radius(void) { return alarm_radius; }
|
||||
|
||||
void proxvec_shutdown(void)
|
||||
{
|
||||
memset(targets, 0, sizeof(targets));
|
||||
target_count = 0;
|
||||
LOG_INF("proxvec: shut down");
|
||||
}
|
||||
|
|
@ -0,0 +1,97 @@
|
|||
/*
|
||||
* proxvec.h -- Proximity Vector Engine (kinematics layer, unified)
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*
|
||||
* Pure logical engine between the hardware layer (bledsp) and the
|
||||
* display layer (radar_ui). Consumes smoothed RSSI from bledsp's
|
||||
* unified device table (BLE + future WiFi sources), converts to
|
||||
* abstract grid coordinates, tracks velocity, and determines alarm
|
||||
* state via a finite-state machine per target.
|
||||
*/
|
||||
#ifndef PROXVEC_H
|
||||
#define PROXVEC_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include "bledsp.h"
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Constants */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
#define PROXVEC_MAX_TARGETS BLE_DSP_MAX_DEVICES
|
||||
|
||||
/* Abstract grid size -- coordinates are mapped onto the LVGL canvas
|
||||
* by radar_ui. Using a fixed grid keeps the kinematics independent
|
||||
* of the display resolution. */
|
||||
#define PROXVEC_GRID_SIZE 240 /* abstract units */
|
||||
|
||||
/* Path-loss model parameters (indoor, n ~= 2.5) */
|
||||
#define PROXVEC_PATH_LOSS_N 25 /* fixed-point x10: 2.5 */
|
||||
#define PROXVEC_TX_POWER_DEFAULT 40 /* assumed Tx power dBm x10 */
|
||||
|
||||
/* Alarm: target within this many grid units of center triggers alarm */
|
||||
#define PROXVEC_ALARM_RADIUS 50
|
||||
|
||||
/* Stale timeout: drop target if not seen for this many ms */
|
||||
#define PROXVEC_STALE_MS 5000
|
||||
|
||||
/* Velocity is in grid-units per update tick.
|
||||
* APPROACHING = moving toward center (negative radial velocity).
|
||||
* RECEDING = moving away from center. */
|
||||
typedef enum {
|
||||
TRACK_NEW = 0,
|
||||
TRACK_APPROACHING,
|
||||
TRACK_STATIONARY,
|
||||
TRACK_RECEDING,
|
||||
TRACK_LOST
|
||||
} track_state_t;
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Target record (consumed by radar_ui) */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
typedef struct {
|
||||
uint8_t mac[6];
|
||||
track_state_t state;
|
||||
int16_t x; /* grid coordinate [0 .. GRID_SIZE) */
|
||||
int16_t y; /* grid coordinate [0 .. GRID_SIZE) */
|
||||
int16_t radius; /* distance from center (grid units) */
|
||||
int16_t prev_radius; /* previous tick's radius */
|
||||
int16_t velocity; /* radial velocity (grid units/tick) */
|
||||
uint8_t intensity; /* 0-255, mapped from smoothed RSSI */
|
||||
uint8_t alarm; /* 1 = within ALARM_RADIUS */
|
||||
uint8_t age_ticks; /* ticks since first detection */
|
||||
} proxvec_target_t;
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Public API */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
/* Initialise the tracking engine. */
|
||||
void proxvec_init(void);
|
||||
|
||||
/* Feed a batch of devices from bledsp. Call once per scan cycle. */
|
||||
void proxvec_update(const bledsp_device_t *devices, int count);
|
||||
|
||||
/* Copy active targets into caller-supplied buffer.
|
||||
* Returns the number of targets written. */
|
||||
int proxvec_get_targets(proxvec_target_t *out, int max);
|
||||
|
||||
/* Returns the count of targets currently in alarm state. */
|
||||
int proxvec_get_alarm_count(void);
|
||||
|
||||
/* Returns total targets being tracked (including non-alarm). */
|
||||
int proxvec_get_track_count(void);
|
||||
|
||||
/* Clear all targets and reset state machine. */
|
||||
void proxvec_reset(void);
|
||||
|
||||
/* Adjust the alarm radius at runtime (grid units). */
|
||||
void proxvec_set_alarm_radius(int16_t r);
|
||||
int16_t proxvec_get_alarm_radius(void);
|
||||
|
||||
/* Shutdown: free any dynamic resources. */
|
||||
void proxvec_shutdown(void);
|
||||
|
||||
#endif /* PROXVEC_H */
|
||||
|
|
@ -0,0 +1,370 @@
|
|||
/*
|
||||
* radar_ui.c -- LVGL radar presentation layer implementation
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*
|
||||
* Renders the proximity alarm radar display. Uses an LVGL canvas
|
||||
* for the radar scope (direct pixel access for the grid, range rings,
|
||||
* blips, and sweep line) and LVGL labels for the status bar and
|
||||
* alarm text overlay.
|
||||
*
|
||||
* The radar circle is inscribed in the smaller LCD dimension minus
|
||||
* a bottom margin for the status bar. On the H2 (320x240) this
|
||||
* gives a circle of radius ~96px centered at (160, 108).
|
||||
*/
|
||||
|
||||
#include "radar_ui.h"
|
||||
#include "h2_ui.h"
|
||||
#include "log_manager.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Derived layout values */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
/* Compute these from the LCD constants so we adapt to any panel. */
|
||||
#define RADAR_CX (H2_LCD_WIDTH / 2)
|
||||
#define RADAR_CY ((H2_LCD_HEIGHT - RADAR_UI_MARGIN_BOTTOM) / 2)
|
||||
#define RADAR_MAX_R (RADAR_CY - 12) /* leave margin at top */
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Internal state */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
/* Canvas buffer: RGB565, one pixel per uint16_t */
|
||||
static uint16_t canvas_buf[H2_LCD_WIDTH * H2_LCD_HEIGHT];
|
||||
static lv_obj_t *canvas = NULL;
|
||||
static lv_obj_t *lbl_status = NULL;
|
||||
static lv_obj_t *lbl_alert = NULL;
|
||||
static lv_obj_t *lbl_sens = NULL;
|
||||
static lv_obj_t *lbl_source = NULL;
|
||||
|
||||
static int sweep_angle = 0;
|
||||
static int alarm_flash = 0;
|
||||
static lv_timer_t *sweep_timer = NULL;
|
||||
static lv_timer_t *flash_timer = NULL;
|
||||
|
||||
/* Pre-computed grid-to-pixel scale factors */
|
||||
static int grid_scale_x = 1;
|
||||
static int grid_scale_y = 1;
|
||||
|
||||
/* Colors (RGB565) */
|
||||
#define C_BG 0x0000
|
||||
#define C_GRID 0x2104 /* dark green-gray */
|
||||
#define C_RING 0x4208 /* range ring color */
|
||||
#define C_SWEEP 0x07E0 /* bright green sweep line */
|
||||
#define C_SWEEP_DIM 0x0340 /* dim green (sweep trail) */
|
||||
#define C_BLIP_NEAR 0xF800 /* red: alarm zone */
|
||||
#define C_BLIP_MID 0xFD20 /* orange: tracking zone */
|
||||
#define C_BLIP_FAR 0x07E0 /* green: detection zone */
|
||||
#define C_CENTER 0xF800 /* red center dot */
|
||||
#define C_TEXT 0x18E0 /* dim teal for labels */
|
||||
#define C_ALARM_BG 0xF800 /* red flash background */
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Helpers */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
static inline void px_set(int x, int y, uint16_t color)
|
||||
{
|
||||
if (x >= 0 && x < H2_LCD_WIDTH && y >= 0 && y < H2_LCD_HEIGHT)
|
||||
canvas_buf[y * H2_LCD_WIDTH + x] = color;
|
||||
}
|
||||
|
||||
/* Bresenham line drawing */
|
||||
static void draw_line(int x0, int y0, int x1, int y1, uint16_t color)
|
||||
{
|
||||
int dx = abs(x1 - x0), sx = x0 < x1 ? 1 : -1;
|
||||
int dy = -abs(y1 - y0), sy = y0 < y1 ? 1 : -1;
|
||||
int err = dx + dy, e2;
|
||||
|
||||
while (1) {
|
||||
px_set(x0, y0, color);
|
||||
if (x0 == x1 && y0 == y1) break;
|
||||
e2 = 2 * err;
|
||||
if (e2 >= dy) { err += dy; x0 += sx; }
|
||||
if (e2 <= dx) { err += dx; y0 += sy; }
|
||||
}
|
||||
}
|
||||
|
||||
/* Circle using midpoint algorithm */
|
||||
static void draw_circle(int cx, int cy, int r, uint16_t color)
|
||||
{
|
||||
int x = r, y = 0, d = 1 - r;
|
||||
|
||||
while (x >= y) {
|
||||
px_set(cx + x, cy + y, color); px_set(cx - x, cy + y, color);
|
||||
px_set(cx + x, cy - y, color); px_set(cx - x, cy - y, color);
|
||||
px_set(cx + y, cy + x, color); px_set(cx - y, cy + x, color);
|
||||
px_set(cx + y, cy - x, color); px_set(cx - y, cy - x, color);
|
||||
y++;
|
||||
if (d <= 0) {
|
||||
d += 2 * y + 1;
|
||||
} else {
|
||||
x--;
|
||||
d += 2 * (y - x) + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Filled circle for blips */
|
||||
static void draw_blip(int cx, int cy, int r, uint16_t color)
|
||||
{
|
||||
for (int dy = -r; dy <= r; dy++) {
|
||||
for (int dx = -r; dx <= r; dx++) {
|
||||
if (dx * dx + dy * dy <= r * r)
|
||||
px_set(cx + dx, cy + dy, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Map proxvec grid coordinates to pixel coordinates */
|
||||
static void grid_to_px(int gx, int gy, int *px_out, int *py_out)
|
||||
{
|
||||
int half = PROXVEC_GRID_SIZE / 2;
|
||||
int dx = gx - half;
|
||||
int dy = gy - half;
|
||||
*px_out = RADAR_CX + dx * RADAR_MAX_R / half;
|
||||
*py_out = RADAR_CY + dy * RADAR_MAX_R / half;
|
||||
}
|
||||
|
||||
/* Map grid radius to pixel radius */
|
||||
static int grid_r_to_px_r(int grid_r)
|
||||
{
|
||||
return grid_r * RADAR_MAX_R / (PROXVEC_GRID_SIZE / 2);
|
||||
}
|
||||
|
||||
/* Get blip color based on alarm state and distance */
|
||||
static uint16_t blip_color(const proxvec_target_t *t)
|
||||
{
|
||||
if (t->alarm)
|
||||
return C_BLIP_NEAR;
|
||||
if (t->radius <= grid_r_to_px_r(PROXVEC_ALARM_RADIUS) * 2)
|
||||
return C_BLIP_MID;
|
||||
return C_BLIP_FAR;
|
||||
}
|
||||
|
||||
/* Get blip color for state machine */
|
||||
static uint16_t state_color(track_state_t state)
|
||||
{
|
||||
switch (state) {
|
||||
case TRACK_APPROACHING: return C_BLIP_MID;
|
||||
case TRACK_NEW: return C_BLIP_FAR;
|
||||
default: return C_BLIP_FAR;
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Sweep timer callback */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
static void sweep_cb(lv_timer_t *timer)
|
||||
{
|
||||
(void)timer;
|
||||
sweep_angle = (sweep_angle + RADAR_UI_SWEEP_SPEED) % 360;
|
||||
lv_obj_invalidate(canvas);
|
||||
}
|
||||
|
||||
/* Flash timer callback -- toggles the alarm overlay */
|
||||
static void flash_cb(lv_timer_t *timer)
|
||||
{
|
||||
(void)timer;
|
||||
alarm_flash = !alarm_flash;
|
||||
|
||||
if (alarm_flash && lbl_alert) {
|
||||
lv_obj_clear_flag(lbl_alert, LV_OBJ_FLAG_HIDDEN);
|
||||
} else if (lbl_alert) {
|
||||
lv_obj_add_flag(lbl_alert, LV_OBJ_FLAG_HIDDEN);
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Public API */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
void radar_ui_init(void)
|
||||
{
|
||||
lv_obj_t *scr = lv_scr_act();
|
||||
lv_obj_set_style_bg_color(scr, COLOR_BG, LV_PART_MAIN);
|
||||
|
||||
/* Create canvas (full screen) */
|
||||
canvas = lv_canvas_create(scr);
|
||||
lv_canvas_set_buffer(canvas, canvas_buf,
|
||||
H2_LCD_WIDTH, H2_LCD_HEIGHT,
|
||||
LV_IMG_CF_TRUE_COLOR);
|
||||
lv_obj_align(canvas, LV_ALIGN_TOP_LEFT, 0, 0);
|
||||
|
||||
/* Source label (top-left) */
|
||||
lbl_source = lv_label_create(scr);
|
||||
lv_label_set_text(lbl_source, "BLE");
|
||||
lv_obj_set_style_text_color(lbl_source, COLOR_MUTED, LV_PART_MAIN);
|
||||
lv_obj_set_style_text_font(lbl_source, &lv_font_montserrat_10, LV_PART_MAIN);
|
||||
lv_obj_align(lbl_source, LV_ALIGN_TOP_LEFT, 4, 2);
|
||||
|
||||
/* Sensitivity label (top-right) */
|
||||
lbl_sens = lv_label_create(scr);
|
||||
lv_label_set_text(lbl_sens, "NEAR: -50dBm");
|
||||
lv_obj_set_style_text_color(lbl_sens, COLOR_MUTED, LV_PART_MAIN);
|
||||
lv_obj_set_style_text_font(lbl_sens, &lv_font_montserrat_10, LV_PART_MAIN);
|
||||
lv_obj_align(lbl_sens, LV_ALIGN_TOP_RIGHT, -4, 2);
|
||||
|
||||
/* Status bar label (bottom) */
|
||||
lbl_status = lv_label_create(scr);
|
||||
lv_label_set_text(lbl_status, "0 targets | 0 alarms");
|
||||
lv_obj_set_style_bg_color(lbl_status, lv_color_make(10, 14, 20), LV_PART_MAIN);
|
||||
lv_obj_set_style_text_color(lbl_status, COLOR_MUTED, LV_PART_MAIN);
|
||||
lv_obj_set_style_text_font(lbl_status, &lv_font_montserrat_10, LV_PART_MAIN);
|
||||
lv_obj_align(lbl_status, LV_ALIGN_BOTTOM_MID, 0, 0);
|
||||
lv_obj_set_width(lbl_status, H2_LCD_WIDTH);
|
||||
|
||||
/* Alarm overlay (hidden by default) */
|
||||
lbl_alert = lv_label_create(scr);
|
||||
lv_label_set_text(lbl_alert, "!! ALERT !!");
|
||||
lv_obj_set_style_text_color(lbl_alert, lv_color_make(255, 50, 50), LV_PART_MAIN);
|
||||
lv_obj_set_style_text_font(lbl_alert, &lv_font_montserrat_14, LV_PART_MAIN);
|
||||
lv_obj_align(lbl_alert, LV_ALIGN_CENTER, 0, -RADAR_MAX_R - 16);
|
||||
lv_obj_add_flag(lbl_alert, LV_OBJ_FLAG_HIDDEN);
|
||||
|
||||
/* Sweep animation timer (~50ms per tick = ~20fps) */
|
||||
sweep_timer = lv_timer_create(sweep_cb, 50, NULL);
|
||||
|
||||
/* Flash timer (disabled until alarm triggers) */
|
||||
flash_timer = lv_timer_create(flash_cb, RADAR_UI_FLASH_PERIOD_MS / 2, NULL);
|
||||
lv_timer_pause(flash_timer);
|
||||
|
||||
/* Compute grid-to-pixel scale */
|
||||
grid_scale_x = RADAR_MAX_R / (PROXVEC_GRID_SIZE / 2);
|
||||
grid_scale_y = RADAR_MAX_R / (PROXVEC_GRID_SIZE / 2);
|
||||
|
||||
LOG_INF("radar_ui: initialised (display %dx%d, radar r=%d)",
|
||||
H2_LCD_WIDTH, H2_LCD_HEIGHT, RADAR_MAX_R);
|
||||
}
|
||||
|
||||
void radar_ui_refresh(const proxvec_target_t *targets, int count,
|
||||
int alarm_count)
|
||||
{
|
||||
/* Clear canvas */
|
||||
memset(canvas_buf, 0, sizeof(canvas_buf));
|
||||
|
||||
/* Draw range rings */
|
||||
for (int ring = 1; ring <= RADAR_UI_RING_COUNT; ring++) {
|
||||
int r = RADAR_MAX_R * ring / RADAR_UI_RING_COUNT;
|
||||
draw_circle(RADAR_CX, RADAR_CY, r, C_RING);
|
||||
}
|
||||
|
||||
/* Draw crosshairs */
|
||||
draw_line(RADAR_CX - RADAR_MAX_R, RADAR_CY,
|
||||
RADAR_CX + RADAR_MAX_R, RADAR_CY, C_GRID);
|
||||
draw_line(RADAR_CX, RADAR_CY - RADAR_MAX_R,
|
||||
RADAR_CX, RADAR_CY + RADAR_MAX_R, C_GRID);
|
||||
|
||||
/* Draw sweep line */
|
||||
{
|
||||
double rad = (double)sweep_angle * 3.14159265 / 180.0;
|
||||
int ex = RADAR_CX + (int)((double)RADAR_MAX_R * cos(rad));
|
||||
int ey = RADAR_CY + (int)((double)RADAR_MAX_R * sin(rad));
|
||||
draw_line(RADAR_CX, RADAR_CY, ex, ey, C_SWEEP);
|
||||
|
||||
/* Draw a dim trail arc (~30 degrees behind sweep) */
|
||||
for (int a = 5; a <= 30; a += 5) {
|
||||
double trail_rad = (double)(sweep_angle - a) * 3.14159265 / 180.0;
|
||||
int tx = RADAR_CX + (int)((double)RADAR_MAX_R * cos(trail_rad));
|
||||
int ty = RADAR_CY + (int)((double)RADAR_MAX_R * sin(trail_rad));
|
||||
draw_line(RADAR_CX, RADAR_CY, tx, ty, C_SWEEP_DIM);
|
||||
}
|
||||
}
|
||||
|
||||
/* Draw alarm radius circle */
|
||||
int alarm_px_r = grid_r_to_px_r(proxvec_get_alarm_radius());
|
||||
draw_circle(RADAR_CX, RADAR_CY, alarm_px_r, C_BLIP_NEAR);
|
||||
|
||||
/* Draw center dot (the holder) */
|
||||
draw_blip(RADAR_CX, RADAR_CY, 3, C_CENTER);
|
||||
|
||||
/* Draw target blips */
|
||||
for (int i = 0; i < count; i++) {
|
||||
int px, py;
|
||||
grid_to_px(targets[i].x, targets[i].y, &px, &py);
|
||||
|
||||
/* Blip size based on intensity */
|
||||
int blip_r = 2 + targets[i].intensity / 80;
|
||||
if (blip_r > 6) blip_r = 6;
|
||||
|
||||
uint16_t color = blip_color(&targets[i]);
|
||||
draw_blip(px, py, blip_r, color);
|
||||
|
||||
/* Draw a small velocity vector for approaching targets */
|
||||
if (targets[i].state == TRACK_APPROACHING && targets[i].velocity < -3) {
|
||||
/* Draw a line from blip toward center */
|
||||
int dx = RADAR_CX - px;
|
||||
int dy = RADAR_CY - py;
|
||||
int len = (int)sqrt((double)(dx * dx + dy * dy));
|
||||
if (len > 0) {
|
||||
int arrow_len = 12;
|
||||
int ax = px + dx * arrow_len / len;
|
||||
int ay = py + dy * arrow_len / len;
|
||||
draw_line(px, py, ax, ay, C_BLIP_NEAR);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Redraw alarm radius label at bottom of circle */
|
||||
/* (Position is implicit from the circle) */
|
||||
|
||||
/* Update status label */
|
||||
{
|
||||
char buf[48];
|
||||
lv_snprintf(buf, sizeof(buf),
|
||||
"%d target%s | %d ALARM%s",
|
||||
count, count == 1 ? "" : "s",
|
||||
alarm_count, alarm_count == 1 ? "" : "S");
|
||||
lv_label_set_text(lbl_status, buf);
|
||||
|
||||
if (alarm_count > 0) {
|
||||
lv_obj_set_style_text_color(lbl_status,
|
||||
lv_color_make(255, 80, 80), LV_PART_MAIN);
|
||||
} else {
|
||||
lv_obj_set_style_text_color(lbl_status, COLOR_MUTED, LV_PART_MAIN);
|
||||
}
|
||||
}
|
||||
|
||||
/* Mark canvas dirty */
|
||||
lv_obj_invalidate(canvas);
|
||||
}
|
||||
|
||||
void radar_ui_set_alarm_flash(int enabled)
|
||||
{
|
||||
if (enabled) {
|
||||
alarm_flash = 0;
|
||||
lv_timer_ready(flash_timer);
|
||||
lv_timer_resume(flash_timer);
|
||||
} else {
|
||||
lv_timer_pause(flash_timer);
|
||||
if (lbl_alert)
|
||||
lv_obj_add_flag(lbl_alert, LV_OBJ_FLAG_HIDDEN);
|
||||
alarm_flash = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void radar_ui_set_sensitivity_label(const char *text)
|
||||
{
|
||||
if (lbl_sens) lv_label_set_text(lbl_sens, text);
|
||||
}
|
||||
|
||||
void radar_ui_set_source_label(const char *text)
|
||||
{
|
||||
if (lbl_source) lv_label_set_text(lbl_source, text);
|
||||
}
|
||||
|
||||
void radar_ui_destroy(void)
|
||||
{
|
||||
if (sweep_timer) { lv_timer_del(sweep_timer); sweep_timer = NULL; }
|
||||
if (flash_timer) { lv_timer_del(flash_timer); flash_timer = NULL; }
|
||||
canvas = NULL;
|
||||
lbl_status = NULL;
|
||||
lbl_alert = NULL;
|
||||
lbl_sens = NULL;
|
||||
lbl_source = NULL;
|
||||
}
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* radar_ui.h -- LVGL radar presentation layer for proxalarm (unified)
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*
|
||||
* Renders the proximity alarm radar display on the H2's 320x240 LCD.
|
||||
* Maps abstract grid coordinates from proxvec onto the physical
|
||||
* framebuffer, draws range rings, target blips, a rotating sweep
|
||||
* line, and alarm overlays.
|
||||
*
|
||||
* This layer is completely independent of the RF/DSP and kinematics
|
||||
* layers -- it only knows about proxvec_target_t and LVGL draw calls.
|
||||
*
|
||||
* LVGL canvas radar display with rotating sweep, range rings, and
|
||||
* target blips rendered over the H2's 320x240 framebuffer.
|
||||
*/
|
||||
#ifndef RADAR_UI_H
|
||||
#define RADAR_UI_H
|
||||
|
||||
#include "lvgl/lvgl.h"
|
||||
#include "proxvec.h"
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Display layout constants */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
/* All pixel values are derived from h2_ui.h H2_LCD_WIDTH/HEIGHT.
|
||||
* Recompute if the panel changes. The radar circle is inscribed
|
||||
* in the smaller dimension minus a margin for the status bar. */
|
||||
|
||||
#define RADAR_UI_MARGIN_BOTTOM 24 /* px reserved for status bar */
|
||||
#define RADAR_UI_RING_COUNT 4 /* concentric range rings */
|
||||
|
||||
/* Alarm flash configuration */
|
||||
#define RADAR_UI_FLASH_PERIOD_MS 400 /* full on/off cycle */
|
||||
|
||||
/* Sweep animation */
|
||||
#define RADAR_UI_SWEEP_SPEED 4 /* degrees per tick (LVGL tick ~5ms) */
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Public API */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
/* Create the radar LVGL screen objects (canvas, labels, etc.).
|
||||
* Call once after LVGL init; call radar_ui_destroy() to clean up. */
|
||||
void radar_ui_init(void);
|
||||
|
||||
/* Redraw the radar display with the latest target data.
|
||||
* Call once per update cycle (typically every BLE scan interval). */
|
||||
void radar_ui_refresh(const proxvec_target_t *targets, int count,
|
||||
int alarm_count);
|
||||
|
||||
/* Enable/disable the alarm flash overlay (called by proxalarm main
|
||||
* loop when proxvec_get_alarm_count() transitions 0 -> N). */
|
||||
void radar_ui_set_alarm_flash(int enabled);
|
||||
|
||||
/* Set the user-visible sensitivity label text. */
|
||||
void radar_ui_set_sensitivity_label(const char *text);
|
||||
|
||||
/* Set the user-visible source label ("BLE" / "WIFI" / "SIM"). */
|
||||
void radar_ui_set_source_label(const char *text);
|
||||
|
||||
/* Destroy all LVGL objects created by radar_ui_init(). */
|
||||
void radar_ui_destroy(void);
|
||||
|
||||
#endif /* RADAR_UI_H */
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* pwdb.c -- Password database (stub)
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*
|
||||
* Password database + bcrypt/argon2 verify.
|
||||
*
|
||||
* Stub: full module surface (init/deinit) so the build links cleanly.
|
||||
* against liblvgl.so. Design-phase implementation pending.
|
||||
*/
|
||||
#include "h2_ui.h"
|
||||
#include "log_manager.h"
|
||||
|
||||
static lv_obj_t *pwdb_screen = NULL;
|
||||
|
||||
void pwdb_init(lv_obj_t *parent)
|
||||
{
|
||||
pwdb_screen = lv_obj_create(parent);
|
||||
lv_obj_set_size(pwdb_screen, H2_LCD_WIDTH - 4, H2_LCD_HEIGHT - 4);
|
||||
lv_obj_center(pwdb_screen);
|
||||
|
||||
lv_obj_t *lbl = lv_label_create(pwdb_screen);
|
||||
lv_label_set_text(lbl, "pwdb\n(stub)");
|
||||
lv_obj_center(lbl);
|
||||
|
||||
LOG_INF("pwdb init");
|
||||
}
|
||||
|
||||
void pwdb_deinit(void)
|
||||
{
|
||||
if (pwdb_screen) {
|
||||
lv_obj_del(pwdb_screen);
|
||||
pwdb_screen = NULL;
|
||||
}
|
||||
LOG_INF("pwdb deinit");
|
||||
}
|
||||
|
|
@ -0,0 +1,131 @@
|
|||
/*
|
||||
* reset.c -- System Stack Purge/Reset module
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*
|
||||
* Factory reset and purge coordinator. State machine: CONFIRM -> RUNNING -> DONE.
|
||||
* Kills dosbox, brings usb0 down, deletes /data/loot_drop contents,
|
||||
* calls sync(). Requires explicit PLAY confirmation.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <signal.h>
|
||||
#include <dirent.h>
|
||||
#include <fcntl.h>
|
||||
#include <linux/input.h>
|
||||
#include "h2_ui.h"
|
||||
#include "log_manager.h"
|
||||
|
||||
enum action { ACT_NONE, ACT_CANCEL, ACT_PURGE };
|
||||
enum state { STATE_CONFIRM, STATE_RUNNING, STATE_DONE };
|
||||
|
||||
static int input_fd;
|
||||
static lv_obj_t *status_lbl;
|
||||
static enum state state;
|
||||
|
||||
static void purge_system(void) {
|
||||
LOG_INF("executing system purge");
|
||||
int fd;
|
||||
|
||||
FILE *pgrep = popen("pidof dosbox 2>/dev/null", "r");
|
||||
if (pgrep) {
|
||||
char pidbuf[16];
|
||||
if (fgets(pidbuf, sizeof(pidbuf), pgrep)) {
|
||||
long pid = atol(pidbuf);
|
||||
if (pid > 0) kill(pid, SIGTERM);
|
||||
}
|
||||
pclose(pgrep);
|
||||
}
|
||||
|
||||
fd = open("/sys/class/net/usb0/operstate", O_WRONLY);
|
||||
if (fd >= 0) { write(fd, "down", 4); close(fd); }
|
||||
|
||||
fd = open("/data/loot_drop", O_RDONLY | O_DIRECTORY);
|
||||
if (fd >= 0) {
|
||||
DIR *d = fdopendir(fd);
|
||||
if (d) {
|
||||
struct dirent *ent;
|
||||
while ((ent = readdir(d)) != NULL) {
|
||||
if (ent->d_name[0] == '.') continue;
|
||||
char path[256];
|
||||
snprintf(path, sizeof(path), "/data/loot_drop/%s", ent->d_name);
|
||||
unlink(path);
|
||||
}
|
||||
closedir(d);
|
||||
} else { close(fd); }
|
||||
}
|
||||
|
||||
sync();
|
||||
LOG_INF("purge complete");
|
||||
}
|
||||
|
||||
static enum action read_action(void) {
|
||||
struct input_event ev;
|
||||
if (read(input_fd, &ev, sizeof(struct input_event)) <= 0) return ACT_NONE;
|
||||
if (ev.type != EV_KEY || ev.value != 1) return ACT_NONE;
|
||||
if (ev.code == H2_KEY_BACK) return ACT_CANCEL;
|
||||
if (ev.code == H2_KEY_PLAY) return ACT_PURGE;
|
||||
return ACT_NONE;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
init_h2_graphics_runtime("CORE HARDWARE STACK RESET");
|
||||
LOG_INF("reset module started");
|
||||
|
||||
lv_obj_t *scr = lv_scr_act();
|
||||
|
||||
lv_obj_t *title = lv_label_create(scr);
|
||||
lv_label_set_text(title, "SYSTEM STACK RESTORATION");
|
||||
lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 15);
|
||||
lv_obj_set_style_text_color(title, COLOR_PRIMARY, LV_PART_MAIN);
|
||||
|
||||
lv_obj_t *box = lv_obj_create(scr);
|
||||
lv_obj_set_size(box, 290, 140);
|
||||
lv_obj_align(box, LV_ALIGN_CENTER, 0, 15);
|
||||
|
||||
status_lbl = lv_label_create(box);
|
||||
lv_obj_align(status_lbl, LV_ALIGN_CENTER, 0, 0);
|
||||
lv_obj_set_style_text_align(status_lbl, LV_TEXT_ALIGN_CENTER, LV_PART_MAIN);
|
||||
|
||||
input_fd = open("/dev/input/event0", O_RDONLY | O_NONBLOCK);
|
||||
state = STATE_CONFIRM;
|
||||
enum action act;
|
||||
|
||||
while (state != STATE_DONE) {
|
||||
lv_timer_handler();
|
||||
|
||||
switch (state) {
|
||||
case STATE_CONFIRM:
|
||||
lv_label_set_text(status_lbl,
|
||||
"WARNING: Drops connections, clears loot,\nresets network maps.\n\n"
|
||||
"[PLAY] PURGE [BACK] CANCEL");
|
||||
act = read_action();
|
||||
if (act == ACT_CANCEL) state = STATE_DONE;
|
||||
if (act == ACT_PURGE) {
|
||||
lv_label_set_text(status_lbl, "EXECUTING PURGE...\nDO NOT POWER DOWN");
|
||||
lv_obj_set_style_text_color(status_lbl, COLOR_PRIMARY, LV_PART_MAIN);
|
||||
lv_timer_handler();
|
||||
purge_system();
|
||||
lv_label_set_text(status_lbl, "PURGE COMPLETE.\n\n[BACK] Exit");
|
||||
lv_obj_set_style_text_color(status_lbl, COLOR_ACCENT, LV_PART_MAIN);
|
||||
state = STATE_RUNNING;
|
||||
}
|
||||
break;
|
||||
case STATE_RUNNING:
|
||||
act = read_action();
|
||||
if (act == ACT_CANCEL) state = STATE_DONE;
|
||||
break;
|
||||
default:
|
||||
state = STATE_DONE;
|
||||
break;
|
||||
}
|
||||
|
||||
usleep(15000);
|
||||
}
|
||||
|
||||
if (input_fd >= 0) close(input_fd);
|
||||
LOG_INF("reset module exited");
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -0,0 +1,601 @@
|
|||
/*
|
||||
* retro.c -- OreBolt OS Module: Retro Game Launcher
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*
|
||||
* Scans /data/roms/ for ROM files, auto-detects the target system by
|
||||
* file extension, displays an LVGL picker menu, and launches the
|
||||
* matching emulator binary via fork()+execv(). Standalone .mod
|
||||
* binary with its own main() and LVGL init.
|
||||
*
|
||||
* Supported systems (by file extension):
|
||||
* .nes - Nintendo Entertainment System
|
||||
* .fds - Famicom Disk System
|
||||
* .smc,.sfc,.fig - Super Nintendo (SNES)
|
||||
* .gb - Game Boy
|
||||
* .gbc - Game Boy Color
|
||||
* .gba - Game Boy Advance
|
||||
* .smd,.md,.bin,.gen - Sega Genesis / Mega Drive
|
||||
* .sms - Sega Master System
|
||||
* .gg - Sega Game Gear
|
||||
* .pce - PC Engine / TurboGrafx-16
|
||||
* .a26 - Atari 2600
|
||||
* .ngp,.ngc - Neo Geo Pocket / Color
|
||||
*
|
||||
* Emulator binaries must be installed at the paths listed in
|
||||
* rom_types[] below.
|
||||
*
|
||||
* INPUT MAPPING:
|
||||
* The companion retro_input_mapper daemon (started by S98retro-input
|
||||
* at boot) creates a virtual gamepad via uinput. Emulators should
|
||||
* read from this virtual gamepad rather than /dev/input/event0.
|
||||
*
|
||||
* D-pad: Rotary CW/CCW with axis toggle (rotary press switches
|
||||
* between vertical U/D and horizontal L/R modes, auto-
|
||||
* reverts to V-mode after 3s idle).
|
||||
* A: PLAY short press (confirm/jump/shoot)
|
||||
* B: BACK short press (cancel/run)
|
||||
* START: PLAY long press (1.5s)
|
||||
* SELECT: BACK long press (1.5s)
|
||||
* L/R: Side buttons PREV(165) / NEXT(163)
|
||||
* X/Y: Combo buttons (PLAY+rotate / BACK+rotate)
|
||||
*
|
||||
* N64 is NOT supported -- the X1000E MIPS32r2 @ ~1 GHz with 64 MB RAM
|
||||
* cannot run N64 emulation at playable framerates.
|
||||
*
|
||||
* Toolchain: mipsel-linux-musl-gcc (MIPS32r2, musl libc)
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <dirent.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#include <linux/input.h>
|
||||
#include <signal.h>
|
||||
#include <sys/wait.h>
|
||||
#include <ctype.h>
|
||||
#include "h2_ui.h"
|
||||
#include "log_manager.h"
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* ROM type database */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
#define MAX_ROM_TYPES 32
|
||||
#define MAX_ROMS 256
|
||||
#define ROM_DIR "/data/roms"
|
||||
#define PATH_BUF 512
|
||||
|
||||
typedef struct {
|
||||
const char *ext; /* file extension, lowercase, with dot */
|
||||
const char *system; /* human-readable system name */
|
||||
const char *emulator; /* absolute path to emulator binary */
|
||||
const char *core; /* emulator-specific core/flag, or NULL */
|
||||
} rom_type_t;
|
||||
|
||||
static const rom_type_t rom_db[MAX_ROM_TYPES] = {
|
||||
/* --- Nintendo --- */
|
||||
{ ".nes", "NES", "/usr/bin/fceux", NULL },
|
||||
{ ".fds", "FDS", "/usr/bin/fceux", NULL },
|
||||
{ ".smc", "SNES", "/usr/bin/snes9x", NULL },
|
||||
{ ".sfc", "SNES", "/usr/bin/snes9x", NULL },
|
||||
{ ".fig", "SNES", "/usr/bin/snes9x", NULL },
|
||||
{ ".gb", "GameBoy", "/usr/bin/gambatte", NULL },
|
||||
{ ".gbc", "GBC", "/usr/bin/gambatte", NULL },
|
||||
{ ".gba", "GBA", "/usr/bin/gpsp", NULL },
|
||||
/* --- Sega --- */
|
||||
{ ".smd", "Genesis", "/usr/bin/dgen", NULL },
|
||||
{ ".md", "Genesis", "/usr/bin/dgen", NULL },
|
||||
{ ".gen", "Genesis", "/usr/bin/dgen", NULL },
|
||||
{ ".bin", "Genesis", "/usr/bin/dgen", "-g" },
|
||||
{ ".sms", "SMS", "/usr/bin/mednafen", "-ss" },
|
||||
{ ".gg", "GameGear", "/usr/bin/mednafen", "-gg" },
|
||||
/* --- NEC / Hudson --- */
|
||||
{ ".pce", "PCE", "/usr/bin/mednafen", "-pce" },
|
||||
/* --- Atari --- */
|
||||
{ ".a26", "Atari2600", "/usr/bin/stella", NULL },
|
||||
/* --- SNK --- */
|
||||
{ ".ngp", "NeoGeoP", "/usr/bin/mednafen", "-ngp" },
|
||||
{ ".ngc", "NeoGeoP", "/usr/bin/mednafen", "-ngp" },
|
||||
/* sentinel */
|
||||
{ NULL, NULL, NULL, NULL }
|
||||
};
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* ROM entry (populated at scan time) */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
typedef struct {
|
||||
char path[PATH_BUF]; /* full path to ROM file */
|
||||
char name[64]; /* display name (filename only) */
|
||||
const rom_type_t *type; /* pointer into rom_db */
|
||||
} rom_entry_t;
|
||||
|
||||
static rom_entry_t roms[MAX_ROMS];
|
||||
static int rom_count = 0;
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Utility: lowercase the last N chars of a string in-place */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
static void str_lower_tail(char *s, int n) {
|
||||
int len = (int)strlen(s);
|
||||
int start = len - n;
|
||||
if (start < 0) start = 0;
|
||||
for (int i = start; s[i]; i++)
|
||||
s[i] = (char)tolower((unsigned char)s[i]);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Utility: extract filename from full path */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
static void extract_filename(const char *path, char *out, int outlen) {
|
||||
const char *slash = strrchr(path, '/');
|
||||
if (slash) slash++;
|
||||
else slash = path;
|
||||
|
||||
/* strip the extension for display */
|
||||
const char *dot = strrchr(slash, '.');
|
||||
int namelen = dot ? (int)(dot - slash) : (int)strlen(slash);
|
||||
if (namelen > outlen - 1) namelen = outlen - 1;
|
||||
|
||||
memcpy(out, slash, (size_t)namelen);
|
||||
out[namelen] = '\0';
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Find the rom_type_t that matches a filename extension */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
static const rom_type_t *match_rom_type(const char *filename) {
|
||||
char lower[PATH_BUF];
|
||||
snprintf(lower, sizeof(lower), "%s", filename);
|
||||
str_lower_tail(lower, 6); /* extensions are max 4 chars */
|
||||
|
||||
const char *dot = strrchr(lower, '.');
|
||||
if (!dot) return NULL;
|
||||
|
||||
for (int i = 0; i < MAX_ROM_TYPES && rom_db[i].ext; i++) {
|
||||
if (strcmp(dot, rom_db[i].ext) == 0)
|
||||
return &rom_db[i];
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Scan ROM_DIR recursively and populate roms[] */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
static void scan_roms_recursive(const char *basepath) {
|
||||
DIR *dir = opendir(basepath);
|
||||
if (!dir) return;
|
||||
|
||||
struct dirent *de;
|
||||
while ((de = readdir(dir)) != NULL) {
|
||||
if (de->d_name[0] == '.') continue; /* skip hidden / . / .. */
|
||||
|
||||
char fullpath[PATH_BUF];
|
||||
snprintf(fullpath, sizeof(fullpath), "%s/%s", basepath, de->d_name);
|
||||
|
||||
struct stat st;
|
||||
if (lstat(fullpath, &st) != 0) continue;
|
||||
|
||||
if (S_ISDIR(st.st_mode)) {
|
||||
/* recurse into subdirectories (system-named folders) */
|
||||
scan_roms_recursive(fullpath);
|
||||
continue;
|
||||
}
|
||||
if (!S_ISREG(st.st_mode)) continue;
|
||||
|
||||
const rom_type_t *t = match_rom_type(de->d_name);
|
||||
if (!t) continue; /* unknown extension, skip */
|
||||
|
||||
if (rom_count >= MAX_ROMS) break;
|
||||
|
||||
rom_entry_t *r = &roms[rom_count];
|
||||
snprintf(r->path, PATH_BUF, "%s", fullpath);
|
||||
extract_filename(de->d_name, r->name, (int)sizeof(r->name));
|
||||
r->type = t;
|
||||
rom_count++;
|
||||
}
|
||||
closedir(dir);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* System filter state */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
static const char *system_filters[] = {
|
||||
"ALL",
|
||||
"NES", "FDS", "SNES",
|
||||
"GameBoy", "GBC", "GBA",
|
||||
"Genesis", "SMS", "GameGear",
|
||||
"PCE", "Atari2600", "NeoGeoP",
|
||||
NULL /* sentinel */
|
||||
};
|
||||
#define NUM_FILTERS (sizeof(system_filters) / sizeof(system_filters[0]) - 1)
|
||||
|
||||
static int current_filter = 0; /* index into system_filters, 0 = ALL */
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* LVGL UI objects (module-global for event loop access) */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
static lv_obj_t *title_label = NULL;
|
||||
static lv_obj_t *filter_label = NULL;
|
||||
static lv_obj_t *count_label = NULL;
|
||||
static lv_obj_t *rom_list = NULL;
|
||||
static lv_obj_t *list_btns[MAX_ROMS];
|
||||
static int visible_count = 0;
|
||||
static int scroll_index = 0;
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Build the ROM list UI (called on filter change) */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
static void build_rom_list(void) {
|
||||
/* clear existing list buttons */
|
||||
if (rom_list) {
|
||||
lv_obj_clean(rom_list);
|
||||
}
|
||||
|
||||
visible_count = 0;
|
||||
scroll_index = 0;
|
||||
|
||||
const char *filter = system_filters[current_filter];
|
||||
int is_all = (current_filter == 0);
|
||||
|
||||
for (int i = 0; i < rom_count && visible_count < MAX_ROMS; i++) {
|
||||
if (!is_all && strcmp(roms[i].type->system, filter) != 0)
|
||||
continue;
|
||||
|
||||
/* build display text: "[SYSTEM] name" */
|
||||
char display[80];
|
||||
snprintf(display, sizeof(display), "%s %s",
|
||||
roms[i].type->system, roms[i].name);
|
||||
|
||||
list_btns[visible_count] = lv_list_add_btn(rom_list,
|
||||
LV_SYMBOL_PLAY,
|
||||
display);
|
||||
lv_obj_set_style_text_color(list_btns[visible_count],
|
||||
COLOR_TEXT, LV_PART_MAIN);
|
||||
lv_obj_set_style_text_font(list_btns[visible_count],
|
||||
&lv_font_montserrat_12, LV_PART_MAIN);
|
||||
visible_count++;
|
||||
}
|
||||
|
||||
/* update count label */
|
||||
char count_buf[48];
|
||||
snprintf(count_buf, sizeof(count_buf),
|
||||
"%d / %d ROMs", visible_count, rom_count);
|
||||
lv_label_set_text(count_label, count_buf);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Scroll the list */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
static void scroll_rom_list(int delta) {
|
||||
scroll_index += delta;
|
||||
if (scroll_index < 0) scroll_index = 0;
|
||||
if (scroll_index >= visible_count) scroll_index = visible_count - 1;
|
||||
|
||||
if (visible_count > 0 && scroll_index < visible_count) {
|
||||
lv_obj_scroll_to_view(list_btns[scroll_index], LV_ANIM_ON);
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Cycle system filter (rapid-rotate = cycle filter) */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
static void cycle_filter(void) {
|
||||
current_filter++;
|
||||
if (system_filters[current_filter] == NULL)
|
||||
current_filter = 0;
|
||||
|
||||
char fbuf[48];
|
||||
snprintf(fbuf, sizeof(fbuf), "Filter: %s", system_filters[current_filter]);
|
||||
lv_label_set_text(filter_label, fbuf);
|
||||
|
||||
build_rom_list();
|
||||
LOG_INF("filter changed to: %s", system_filters[current_filter]);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Launch the selected ROM */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
static void launch_rom(int index) {
|
||||
if (index < 0 || index >= visible_count) return;
|
||||
|
||||
/* find the actual rom_entry_t (skip filtered-out entries) */
|
||||
const char *filter = system_filters[current_filter];
|
||||
int is_all = (current_filter == 0);
|
||||
int real_index = -1;
|
||||
int target = 0;
|
||||
|
||||
for (int i = 0; i < rom_count; i++) {
|
||||
if (!is_all && strcmp(roms[i].type->system, filter) != 0)
|
||||
continue;
|
||||
if (target == index) { real_index = i; break; }
|
||||
target++;
|
||||
}
|
||||
if (real_index < 0) return;
|
||||
|
||||
const rom_entry_t *rom = &roms[real_index];
|
||||
const rom_type_t *type = rom->type;
|
||||
|
||||
LOG_INF("launching ROM: %s [%s]", rom->name, type->system);
|
||||
|
||||
/* clear screen before launch */
|
||||
lv_obj_clean(lv_scr_act());
|
||||
|
||||
lv_obj_t *launch_lbl = lv_label_create(lv_scr_act());
|
||||
char launch_buf[80];
|
||||
snprintf(launch_buf, sizeof(launch_buf),
|
||||
"Launching %s...\n%s\n[BACK] to exit",
|
||||
type->system, rom->name);
|
||||
lv_label_set_text(launch_lbl, launch_buf);
|
||||
lv_obj_set_style_text_color(launch_lbl, COLOR_ACCENT, LV_PART_MAIN);
|
||||
lv_obj_align(launch_lbl, LV_ALIGN_CENTER, 0, 0);
|
||||
lv_obj_set_style_text_align(launch_lbl, LV_TEXT_ALIGN_CENTER, LV_PART_MAIN);
|
||||
|
||||
/* flush the "Launching..." screen */
|
||||
for (int i = 0; i < 10; i++) lv_timer_handler();
|
||||
|
||||
pid_t pid = fork();
|
||||
if (pid == 0) {
|
||||
/*
|
||||
* Child: exec the emulator with the ROM path.
|
||||
* If the emulator has a core flag (e.g. mednafen -ss), pass it.
|
||||
*/
|
||||
if (type->core) {
|
||||
char *args[] = { (char *)type->emulator,
|
||||
(char *)type->core,
|
||||
(char *)rom->path,
|
||||
NULL };
|
||||
execv(type->emulator, args);
|
||||
} else {
|
||||
char *args[] = { (char *)type->emulator,
|
||||
(char *)rom->path,
|
||||
NULL };
|
||||
execv(type->emulator, args);
|
||||
}
|
||||
/* exec failed -- emulator binary not found */
|
||||
_exit(127);
|
||||
}
|
||||
|
||||
if (pid < 0) {
|
||||
LOG_ERR("fork failed for emulator launch");
|
||||
return; /* fork failed */
|
||||
}
|
||||
|
||||
LOG_INF("emulator child pid %d started", (int)pid);
|
||||
|
||||
/*
|
||||
* Parent: non-blocking wait in event loop.
|
||||
* BACK key sends SIGTERM to the emulator child.
|
||||
* Rebuilds the ROM list when child exits.
|
||||
*/
|
||||
int input_fd = open("/dev/input/event0", O_RDONLY | O_NONBLOCK);
|
||||
struct input_event ev;
|
||||
int child_running = 1;
|
||||
|
||||
while (child_running) {
|
||||
lv_timer_handler();
|
||||
|
||||
/* non-blocking child check */
|
||||
int status;
|
||||
pid_t w = waitpid(pid, &status, WNOHANG);
|
||||
if (w == pid) {
|
||||
child_running = 0;
|
||||
LOG_INF("emulator child %d exited", (int)pid);
|
||||
break;
|
||||
}
|
||||
|
||||
/* read input */
|
||||
if (input_fd >= 0 &&
|
||||
read(input_fd, &ev, sizeof(struct input_event)) > 0) {
|
||||
|
||||
if (ev.type == EV_KEY && ev.value == 1) {
|
||||
if (ev.code == H2_KEY_BACK) {
|
||||
kill(pid, SIGTERM);
|
||||
/* give emulator 300ms to clean up */
|
||||
usleep(300000);
|
||||
waitpid(pid, &status, WNOHANG);
|
||||
child_running = 0;
|
||||
LOG_INF("emulator child %d terminated by user", (int)pid);
|
||||
}
|
||||
}
|
||||
}
|
||||
usleep(15000);
|
||||
}
|
||||
|
||||
if (input_fd >= 0) close(input_fd);
|
||||
|
||||
/* rebuild the UI for the ROM picker */
|
||||
lv_obj_clean(lv_scr_act());
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* main -- module entry point */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
int main(void) {
|
||||
init_h2_graphics_runtime("RETRO GAME LAUNCHER");
|
||||
LOG_INF("retro module started");
|
||||
|
||||
lv_obj_t *scr = lv_scr_act();
|
||||
|
||||
/* ---- header ---- */
|
||||
title_label = lv_label_create(scr);
|
||||
lv_label_set_text(title_label, "RETRO GAME LAUNCHER");
|
||||
lv_obj_align(title_label, LV_ALIGN_TOP_MID, 0, 8);
|
||||
lv_obj_set_style_text_color(title_label, COLOR_PRIMARY, LV_PART_MAIN);
|
||||
|
||||
/* ---- filter indicator (PLAY = launch, rapid-rotate = filter) ---- */
|
||||
filter_label = lv_label_create(scr);
|
||||
lv_label_set_text(filter_label, "Filter: ALL");
|
||||
lv_obj_align(filter_label, LV_ALIGN_TOP_LEFT, 8, 30);
|
||||
lv_obj_set_style_text_color(filter_label, COLOR_ACCENT, LV_PART_MAIN);
|
||||
lv_obj_set_style_text_font(filter_label, &lv_font_montserrat_10,
|
||||
LV_PART_MAIN);
|
||||
|
||||
/* ---- ROM count ---- */
|
||||
count_label = lv_label_create(scr);
|
||||
lv_obj_align(count_label, LV_ALIGN_TOP_RIGHT, -8, 30);
|
||||
lv_obj_set_style_text_color(count_label, lv_color_make(120, 120, 140),
|
||||
LV_PART_MAIN);
|
||||
lv_obj_set_style_text_font(count_label, &lv_font_montserrat_10,
|
||||
LV_PART_MAIN);
|
||||
|
||||
/* ---- ROM list ---- */
|
||||
rom_list = lv_list_create(scr);
|
||||
lv_obj_set_size(rom_list, 296, 168);
|
||||
lv_obj_align(rom_list, LV_ALIGN_TOP_MID, 0, 46);
|
||||
lv_obj_set_style_bg_color(rom_list,
|
||||
lv_color_make(18, 22, 32), LV_PART_MAIN);
|
||||
lv_obj_set_style_border_width(rom_list, 1, LV_PART_MAIN);
|
||||
lv_obj_set_style_border_color(rom_list,
|
||||
lv_color_make(50, 55, 70), LV_PART_MAIN);
|
||||
|
||||
/* ---- status bar ---- */
|
||||
lv_obj_t *status = lv_label_create(scr);
|
||||
lv_label_set_text(status, "ROTATE=scroll PLAY=launch BACK=exit");
|
||||
lv_obj_align(status, LV_ALIGN_BOTTOM_MID, 0, -4);
|
||||
lv_obj_set_style_text_color(status, lv_color_make(80, 85, 100),
|
||||
LV_PART_MAIN);
|
||||
lv_obj_set_style_text_font(status, &lv_font_montserrat_10, LV_PART_MAIN);
|
||||
|
||||
/* ---- scan ROMs ---- */
|
||||
scan_roms_recursive(ROM_DIR);
|
||||
LOG_INF("ROM scan complete: %d ROM(s) found", rom_count);
|
||||
build_rom_list();
|
||||
|
||||
if (rom_count == 0) {
|
||||
lv_obj_t *empty = lv_label_create(rom_list);
|
||||
lv_label_set_text(empty,
|
||||
LV_SYMBOL_WARNING " No ROMs found.\n\n"
|
||||
"Place ROM files in:\n"
|
||||
" /data/roms/\n\n"
|
||||
"Organize by folder:\n"
|
||||
" /data/roms/nes/\n"
|
||||
" /data/roms/snes/\n"
|
||||
" /data/roms/gb/\n"
|
||||
" ...etc");
|
||||
lv_obj_set_style_text_color(empty, lv_color_make(160, 100, 60),
|
||||
LV_PART_MAIN);
|
||||
lv_obj_set_style_text_align(empty, LV_TEXT_ALIGN_CENTER, LV_PART_MAIN);
|
||||
}
|
||||
|
||||
/* ---- input loop ---- */
|
||||
int input_fd = open("/dev/input/event0", O_RDONLY | O_NONBLOCK);
|
||||
struct input_event ev;
|
||||
|
||||
/* rotary accumulator for filter cycling:
|
||||
* 8 fast clicks in < 200ms each = cycle filter
|
||||
* (avoids needing a separate button for filter) */
|
||||
int rotary_clicks = 0;
|
||||
long long last_click_ms = 0;
|
||||
|
||||
while (1) {
|
||||
lv_timer_handler();
|
||||
|
||||
if (input_fd >= 0 &&
|
||||
read(input_fd, &ev, sizeof(struct input_event)) > 0) {
|
||||
|
||||
if (ev.type == EV_REL && ev.code == 0) {
|
||||
if (ev.value != 0) {
|
||||
/* track rapid scrolling for filter toggle */
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
long long now = (long long)ts.tv_sec * 1000 +
|
||||
ts.tv_nsec / 1000000;
|
||||
|
||||
if (now - last_click_ms < 200) {
|
||||
rotary_clicks++;
|
||||
if (rotary_clicks >= 8) {
|
||||
cycle_filter();
|
||||
rotary_clicks = 0;
|
||||
}
|
||||
} else {
|
||||
rotary_clicks = 1;
|
||||
}
|
||||
last_click_ms = now;
|
||||
|
||||
scroll_rom_list(ev.value > 0 ? 1 : -1);
|
||||
}
|
||||
}
|
||||
|
||||
if (ev.type == EV_KEY && ev.value == 1) {
|
||||
if (ev.code == H2_KEY_PLAY && visible_count > 0) {
|
||||
/* PLAY: launch selected ROM */
|
||||
launch_rom(scroll_index);
|
||||
/* after launch returns, rebuild UI */
|
||||
lv_obj_clean(lv_scr_act());
|
||||
|
||||
/* re-create widgets */
|
||||
title_label = lv_label_create(scr);
|
||||
lv_label_set_text(title_label, "RETRO GAME LAUNCHER");
|
||||
lv_obj_align(title_label, LV_ALIGN_TOP_MID, 0, 8);
|
||||
lv_obj_set_style_text_color(title_label, COLOR_PRIMARY,
|
||||
LV_PART_MAIN);
|
||||
|
||||
filter_label = lv_label_create(scr);
|
||||
char fbuf[48];
|
||||
snprintf(fbuf, sizeof(fbuf), "Filter: %s",
|
||||
system_filters[current_filter]);
|
||||
lv_label_set_text(filter_label, fbuf);
|
||||
lv_obj_align(filter_label, LV_ALIGN_TOP_LEFT, 8, 30);
|
||||
lv_obj_set_style_text_color(filter_label, COLOR_ACCENT,
|
||||
LV_PART_MAIN);
|
||||
lv_obj_set_style_text_font(filter_label,
|
||||
&lv_font_montserrat_10, LV_PART_MAIN);
|
||||
|
||||
count_label = lv_label_create(scr);
|
||||
lv_obj_align(count_label, LV_ALIGN_TOP_RIGHT, -8, 30);
|
||||
lv_obj_set_style_text_color(count_label,
|
||||
lv_color_make(120, 120, 140), LV_PART_MAIN);
|
||||
lv_obj_set_style_text_font(count_label,
|
||||
&lv_font_montserrat_10, LV_PART_MAIN);
|
||||
|
||||
rom_list = lv_list_create(scr);
|
||||
lv_obj_set_size(rom_list, 296, 168);
|
||||
lv_obj_align(rom_list, LV_ALIGN_TOP_MID, 0, 46);
|
||||
lv_obj_set_style_bg_color(rom_list,
|
||||
lv_color_make(18, 22, 32), LV_PART_MAIN);
|
||||
lv_obj_set_style_border_width(rom_list, 1, LV_PART_MAIN);
|
||||
lv_obj_set_style_border_color(rom_list,
|
||||
lv_color_make(50, 55, 70), LV_PART_MAIN);
|
||||
|
||||
status = lv_label_create(scr);
|
||||
lv_label_set_text(status,
|
||||
"ROTATE=scroll PLAY=launch BACK=exit");
|
||||
lv_obj_align(status, LV_ALIGN_BOTTOM_MID, 0, -4);
|
||||
lv_obj_set_style_text_color(status,
|
||||
lv_color_make(80, 85, 100), LV_PART_MAIN);
|
||||
lv_obj_set_style_text_font(status,
|
||||
&lv_font_montserrat_10, LV_PART_MAIN);
|
||||
|
||||
build_rom_list();
|
||||
}
|
||||
|
||||
if (ev.code == H2_KEY_BACK) {
|
||||
/* BACK: exit module */
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
usleep(15000);
|
||||
}
|
||||
|
||||
if (input_fd >= 0) close(input_fd);
|
||||
LOG_INF("retro module exited");
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -0,0 +1,456 @@
|
|||
/*
|
||||
* retro_input_mapper.c -- H2 Gamepad Input Translator Daemon
|
||||
*
|
||||
* Translates the HiFiWalker H2's physical controls into a standard
|
||||
* Linux gamepad (uinput) that emulators can read natively.
|
||||
*
|
||||
* Creates /dev/input/js0 (or next available) as a virtual gamepad
|
||||
* with: d-pad (4-way), 6 action buttons (A B X Y L R), Start, Select.
|
||||
*
|
||||
* ================================================================
|
||||
* PHYSICAL LAYOUT → GAMEPAD MAPPING
|
||||
* ================================================================
|
||||
*
|
||||
* HiFiWalker H2 Top View
|
||||
* ┌──────────────────────────┐
|
||||
* │ │
|
||||
* │ LCD 320x240 │
|
||||
* │ │
|
||||
* └──────────────────────────┘
|
||||
*
|
||||
* ┌──────────────────────────┐
|
||||
* │ │
|
||||
* │ ╭──────────╮ │
|
||||
* │ │ ROTARY │ │ ROTARY WHEEL
|
||||
* │ │ ENCODER │ │
|
||||
* │ ╰──────────╯ │
|
||||
* │ │
|
||||
* │ [PREV] [NEXT] │ L-SHOULDER R-SHOULDER
|
||||
* │ │
|
||||
* │ [PLAY] │ A BUTTON (confirm/jump/shoot)
|
||||
* │ [BACK] │ B BUTTON (cancel/run)
|
||||
* │ │
|
||||
* └──────────────────────────┘
|
||||
*
|
||||
* ┌─────────────────────────────────────────────────────────────┐
|
||||
* │ GAMEPAD LAYOUT │
|
||||
* │ │
|
||||
* │ [SELECT] [START] │
|
||||
* │ │
|
||||
* │ ┌───┐ │
|
||||
* │ │ ▲ │ Rotary CCW │
|
||||
* │ ┌───┼───┼───┐ │
|
||||
* │ │◄ │ │ ►│ Rotary: click-switch toggles │
|
||||
* │ └───┼───┼───┘ between V-axis (U/D) and │
|
||||
* │ │ ▼ │ Rotary CW H-axis (L/R) │
|
||||
* │ └───┘ │
|
||||
* │ │
|
||||
* │ [L] [R] │
|
||||
* │ PREV btn NEXT btn │
|
||||
* │ │
|
||||
* │ [Y] │
|
||||
* │ [X] [B] PLAY = A │
|
||||
* │ [A] BACK = B (short press) │
|
||||
* │ BACK = SELECT (long press) │
|
||||
* └─────────────────────────────────────────────────────────────┘
|
||||
*
|
||||
* ================================================================
|
||||
* MAPPING TABLE
|
||||
* ================================================================
|
||||
*
|
||||
* H2 Physical Virtual Gamepad Notes
|
||||
* -------------- ---------------- -----
|
||||
* Rotary CCW D-pad Up / Left Axis toggled by rotary click
|
||||
* Rotary CW D-pad Down / Right
|
||||
* Rotary press (EV_KEY) Toggle d-pad axis V-mode(U/D) ↔ H-mode(L/R)
|
||||
* PLAY (164) short A (BTN_SOUTH) Confirm / jump / shoot
|
||||
* PLAY (164) long 1.5s START (BTN_START) Pause / menu
|
||||
* BACK (158) short B (BTN_EAST) Cancel / run
|
||||
* BACK (158) long 1.5s SELECT (BTN_SELECT) In-game menu
|
||||
* PREV (165) L (BTN_TL) Shoulder left
|
||||
* NEXT (163) R (BTN_TR) Shoulder right
|
||||
* (rotary+PLAY combo) X (BTN_NORTH) Context action
|
||||
* (rotary+BACK combo) Y (BTN_WEST) Context action
|
||||
*
|
||||
* When the d-pad is in V-mode (default):
|
||||
* Rotary CW = D-pad Down
|
||||
* Rotary CCW = D-pad Up
|
||||
* Left/Right = hold rotary-click + rotate
|
||||
*
|
||||
* When the d-pad is in H-mode (after rotary click):
|
||||
* Rotary CW = D-pad Right
|
||||
* Rotary CCW = D-pad Left
|
||||
* Up/Down = hold rotary-click + rotate
|
||||
*
|
||||
* Auto-revert: d-pad returns to V-mode after 3 seconds of no
|
||||
* rotary activity (prevents stuck in H-mode).
|
||||
*
|
||||
* ================================================================
|
||||
* USAGE
|
||||
* ================================================================
|
||||
*
|
||||
* This daemon should be started by the init system BEFORE any
|
||||
* emulator is launched. It reads /dev/input/event0 (the H2's
|
||||
* physical input) and writes to the virtual uinput gamepad.
|
||||
*
|
||||
* Emulators should be configured to read from the uinput gamepad
|
||||
* device rather than /dev/input/event0 directly.
|
||||
*
|
||||
* The daemon exits cleanly on SIGTERM or SIGINT.
|
||||
*
|
||||
* Build: mipsel-linux-musl-gcc -o retro_input_mapper \
|
||||
* retro_input_mapper.c
|
||||
*
|
||||
* ================================================================
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <signal.h>
|
||||
#include <errno.h>
|
||||
#include <linux/input.h>
|
||||
#include <linux/uinput.h>
|
||||
#include <sys/time.h>
|
||||
#include <time.h>
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* H2 input event codes */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
#define H2_KEY_PLAY 164
|
||||
#define H2_KEY_BACK 158
|
||||
#define H2_KEY_PREV 165 /* previous track / left side button */
|
||||
#define H2_KEY_NEXT 163 /* next track / right side button */
|
||||
#define H2_ROTARY_REL 0 /* EV_REL code for rotary encoder */
|
||||
#define H2_ROTARY_PRESS 115 /* EV_KEY code if rotary is pressable */
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Virtual gamepad button indices (Linux input subsystem) */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
#define VBTN_A BTN_SOUTH /* 304 - confirm / jump / shoot */
|
||||
#define VBTN_B BTN_EAST /* 305 - cancel / run */
|
||||
#define VBTN_X BTN_NORTH /* 308 - context action */
|
||||
#define VBTN_Y BTN_WEST /* 307 - context action */
|
||||
#define VBTN_L BTN_TL /* 310 - left shoulder */
|
||||
#define VBTN_R BTN_TR /* 311 - right shoulder */
|
||||
#define VBTN_SELECT BTN_SELECT /* 314 - in-game select */
|
||||
#define VBTN_START BTN_START /* 315 - in-game start / pause */
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* D-pad axis mode */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
#define DAXIS_VERT 0 /* rotary -> up/down (default) */
|
||||
#define DAXIS_HORIZ 1 /* rotary -> left/right */
|
||||
|
||||
static volatile sig_atomic_t running = 1;
|
||||
|
||||
static void signal_handler(int sig) {
|
||||
(void)sig;
|
||||
running = 0;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Time helper: milliseconds since epoch */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
static long long now_ms(void) {
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
return (long long)ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Create the virtual uinput gamepad device */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
static int create_gamepad(int *uinput_fd) {
|
||||
int fd = open("/dev/uinput", O_WRONLY | O_NONBLOCK);
|
||||
if (fd < 0) {
|
||||
/* try /dev/input/uinput as fallback */
|
||||
fd = open("/dev/input/uinput", O_WRONLY | O_NONBLOCK);
|
||||
}
|
||||
if (fd < 0) {
|
||||
perror("open /dev/uinput");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* --- device identification --- */
|
||||
struct uinput_setup usetup;
|
||||
memset(&usetup, 0, sizeof(usetup));
|
||||
snprintf(usetup.name, UINPUT_MAX_NAME_SIZE, "H2 Virtual Gamepad");
|
||||
usetup.id.bustype = BUS_VIRTUAL;
|
||||
usetup.id.vendor = 0x4832; /* "H2" */
|
||||
usetup.id.product = 0x0001;
|
||||
usetup.id.version = 1;
|
||||
|
||||
/* --- enable d-pad axes --- */
|
||||
ioctl(fd, UI_SET_EVBIT, EV_ABS);
|
||||
ioctl(fd, UI_SET_ABSBIT, ABS_X); /* d-pad horizontal */
|
||||
ioctl(fd, UI_SET_ABSBIT, ABS_Y); /* d-pad vertical */
|
||||
|
||||
struct uinput_abs_setup abs_x = { .code = ABS_X,
|
||||
.min = -1, .max = 1, .fuzz = 0, .flat = 0, .resolution = 0 };
|
||||
struct uinput_abs_setup abs_y = { .code = ABS_Y,
|
||||
.min = -1, .max = 1, .fuzz = 0, .flat = 0, .resolution = 0 };
|
||||
ioctl(fd, UI_ABS_SETUP, &abs_x);
|
||||
ioctl(fd, UI_ABS_SETUP, &abs_y);
|
||||
|
||||
/* --- enable buttons --- */
|
||||
ioctl(fd, UI_SET_EVBIT, EV_KEY);
|
||||
ioctl(fd, UI_SET_KEYBIT, VBTN_A);
|
||||
ioctl(fd, UI_SET_KEYBIT, VBTN_B);
|
||||
ioctl(fd, UI_SET_KEYBIT, VBTN_X);
|
||||
ioctl(fd, UI_SET_KEYBIT, VBTN_Y);
|
||||
ioctl(fd, UI_SET_KEYBIT, VBTN_L);
|
||||
ioctl(fd, UI_SET_KEYBIT, VBTN_R);
|
||||
ioctl(fd, UI_SET_KEYBIT, VBTN_SELECT);
|
||||
ioctl(fd, UI_SET_KEYBIT, VBTN_START);
|
||||
|
||||
/* --- create the device --- */
|
||||
if (ioctl(fd, UI_DEV_SETUP, &usetup) < 0) {
|
||||
perror("UI_DEV_SETUP");
|
||||
close(fd);
|
||||
return -1;
|
||||
}
|
||||
if (ioctl(fd, UI_DEV_CREATE) < 0) {
|
||||
perror("UI_DEV_CREATE");
|
||||
close(fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
*uinput_fd = fd;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Emit a single uinput event */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
static void emit_event(int fd, uint16_t type, uint16_t code,
|
||||
int32_t value) {
|
||||
struct input_event ie;
|
||||
memset(&ie, 0, sizeof(ie));
|
||||
ie.type = type;
|
||||
ie.code = code;
|
||||
ie.value = value;
|
||||
(void)write(fd, &ie, sizeof(ie));
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Emit a button press/release pair with a 10ms gap */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
static void emit_button(int fd, uint16_t btn, int pressed) {
|
||||
emit_event(fd, EV_KEY, btn, pressed);
|
||||
/* sync after each event for low-latency input */
|
||||
emit_event(fd, EV_SYN, SYN_REPORT, 0);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Emit d-pad axis value and sync */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
static void emit_dpad(int fd, int axis_x, int axis_y) {
|
||||
emit_event(fd, EV_ABS, ABS_X, axis_x);
|
||||
emit_event(fd, EV_ABS, ABS_Y, axis_y);
|
||||
emit_event(fd, EV_SYN, SYN_REPORT, 0);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* main */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
int main(void) {
|
||||
/* --- signal handling --- */
|
||||
struct sigaction sa;
|
||||
memset(&sa, 0, sizeof(sa));
|
||||
sa.sa_handler = signal_handler;
|
||||
sigaction(SIGTERM, &sa, NULL);
|
||||
sigaction(SIGINT, &sa, NULL);
|
||||
|
||||
/* --- create virtual gamepad --- */
|
||||
int ufd = -1;
|
||||
if (create_gamepad(&ufd) < 0) {
|
||||
fprintf(stderr, "retro_input_mapper: failed to create "
|
||||
"virtual gamepad\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* --- open H2 physical input --- */
|
||||
int phys_fd = open("/dev/input/event0", O_RDONLY | O_NONBLOCK);
|
||||
if (phys_fd < 0) {
|
||||
perror("open /dev/input/event0");
|
||||
ioctl(ufd, UI_DEV_DESTROY);
|
||||
close(ufd);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* State machine
|
||||
*/
|
||||
int dpad_mode = DAXIS_VERT; /* current axis mode */
|
||||
int dpad_x = 0; /* current d-pad X (-1/0/1) */
|
||||
int dpad_y = 0; /* current d-pad Y (-1/0/1) */
|
||||
int play_held = 0; /* is PLAY currently held? */
|
||||
int back_held = 0; /* is BACK currently held? */
|
||||
long long play_down_ms = 0; /* timestamp PLAY was pressed */
|
||||
long long back_down_ms = 0; /* timestamp BACK was pressed */
|
||||
long long last_rotary_ms = 0; /* last rotary activity */
|
||||
int play_long_fired = 0; /* long-press already fired? */
|
||||
int back_long_fired = 0; /* long-press already fired? */
|
||||
|
||||
#define LONG_PRESS_MS 1500
|
||||
#define AXIS_REVERT_MS 3000
|
||||
|
||||
struct input_event ev;
|
||||
|
||||
while (running) {
|
||||
ssize_t n = read(phys_fd, &ev, sizeof(ev));
|
||||
|
||||
if (n <= 0) {
|
||||
/* no data -- check axis auto-revert */
|
||||
if (dpad_mode == DAXIS_HORIZ) {
|
||||
long long idle = now_ms() - last_rotary_ms;
|
||||
if (idle > AXIS_REVERT_MS) {
|
||||
dpad_mode = DAXIS_VERT;
|
||||
}
|
||||
}
|
||||
|
||||
/* check long-press timeouts */
|
||||
long long now = now_ms();
|
||||
if (play_held && !play_long_fired &&
|
||||
(now - play_down_ms) >= LONG_PRESS_MS) {
|
||||
play_long_fired = 1;
|
||||
emit_button(ufd, VBTN_START, 1);
|
||||
emit_button(ufd, VBTN_START, 0);
|
||||
}
|
||||
if (back_held && !back_long_fired &&
|
||||
(now - back_down_ms) >= LONG_PRESS_MS) {
|
||||
back_long_fired = 1;
|
||||
emit_button(ufd, VBTN_SELECT, 1);
|
||||
emit_button(ufd, VBTN_SELECT, 0);
|
||||
}
|
||||
|
||||
usleep(5000); /* 5ms poll when idle */
|
||||
continue;
|
||||
}
|
||||
|
||||
/* ---- ROTARY ENCODER ---- */
|
||||
if (ev.type == EV_REL && ev.code == H2_ROTARY_REL) {
|
||||
last_rotary_ms = now_ms();
|
||||
|
||||
int step = (ev.value > 0) ? 1 : -1;
|
||||
|
||||
if (dpad_mode == DAXIS_VERT) {
|
||||
/* vertical: rotary -> up/down */
|
||||
dpad_y = step;
|
||||
dpad_x = 0;
|
||||
emit_dpad(ufd, 0, dpad_y);
|
||||
/* release after 80ms for discrete steps */
|
||||
usleep(80000);
|
||||
dpad_y = 0;
|
||||
emit_dpad(ufd, 0, 0);
|
||||
} else {
|
||||
/* horizontal: rotary -> left/right */
|
||||
dpad_x = step;
|
||||
dpad_y = 0;
|
||||
emit_dpad(ufd, dpad_x, 0);
|
||||
usleep(80000);
|
||||
dpad_x = 0;
|
||||
emit_dpad(ufd, 0, 0);
|
||||
}
|
||||
|
||||
/* combo detection: if PLAY held during rotary -> X button */
|
||||
if (play_held && !play_long_fired) {
|
||||
emit_button(ufd, VBTN_X, 1);
|
||||
emit_button(ufd, VBTN_X, 0);
|
||||
}
|
||||
/* combo: if BACK held during rotary -> Y button */
|
||||
if (back_held && !back_long_fired) {
|
||||
emit_button(ufd, VBTN_Y, 1);
|
||||
emit_button(ufd, VBTN_Y, 0);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
/* ---- ROTARY PRESS (toggle axis) ---- */
|
||||
if (ev.type == EV_KEY && ev.code == H2_ROTARY_PRESS) {
|
||||
if (ev.value == 1) {
|
||||
/* toggle d-pad axis mode */
|
||||
dpad_mode = (dpad_mode == DAXIS_VERT)
|
||||
? DAXIS_HORIZ : DAXIS_VERT;
|
||||
/* reset d-pad position on toggle */
|
||||
dpad_x = 0;
|
||||
dpad_y = 0;
|
||||
emit_dpad(ufd, 0, 0);
|
||||
last_rotary_ms = now_ms();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
/* ---- BUTTONS ---- */
|
||||
if (ev.type == EV_KEY) {
|
||||
if (ev.code == H2_KEY_PLAY) {
|
||||
if (ev.value == 1) {
|
||||
play_held = 1;
|
||||
play_long_fired = 0;
|
||||
play_down_ms = now_ms();
|
||||
} else {
|
||||
/* released */
|
||||
if (!play_long_fired) {
|
||||
/* short press -> A button */
|
||||
emit_button(ufd, VBTN_A, 1);
|
||||
emit_button(ufd, VBTN_A, 0);
|
||||
} else {
|
||||
/* long press already sent START, release it */
|
||||
emit_button(ufd, VBTN_START, 0);
|
||||
}
|
||||
play_held = 0;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ev.code == H2_KEY_BACK) {
|
||||
if (ev.value == 1) {
|
||||
back_held = 1;
|
||||
back_long_fired = 0;
|
||||
back_down_ms = now_ms();
|
||||
} else {
|
||||
if (!back_long_fired) {
|
||||
/* short press -> B button */
|
||||
emit_button(ufd, VBTN_B, 1);
|
||||
emit_button(ufd, VBTN_B, 0);
|
||||
} else {
|
||||
emit_button(ufd, VBTN_SELECT, 0);
|
||||
}
|
||||
back_held = 0;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ev.code == H2_KEY_PREV) {
|
||||
emit_button(ufd, VBTN_L, ev.value);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ev.code == H2_KEY_NEXT) {
|
||||
emit_button(ufd, VBTN_R, ev.value);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* --- cleanup --- */
|
||||
emit_dpad(ufd, 0, 0);
|
||||
ioctl(ufd, UI_DEV_DESTROY);
|
||||
close(ufd);
|
||||
close(phys_fd);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* rfid.c -- RFID/NFC reader module (stub)
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*
|
||||
* RFID/NFC reader module.
|
||||
*
|
||||
* Stub: full module surface (init/deinit) so the build links cleanly.
|
||||
* against liblvgl.so. Hardware-specific implementation pending.
|
||||
*/
|
||||
#include "h2_ui.h"
|
||||
#include "log_manager.h"
|
||||
|
||||
static lv_obj_t *rfid_screen = NULL;
|
||||
|
||||
void rfid_init(lv_obj_t *parent)
|
||||
{
|
||||
rfid_screen = lv_obj_create(parent);
|
||||
lv_obj_set_size(rfid_screen, H2_LCD_WIDTH - 4, H2_LCD_HEIGHT - 4);
|
||||
lv_obj_center(rfid_screen);
|
||||
|
||||
lv_obj_t *lbl = lv_label_create(rfid_screen);
|
||||
lv_label_set_text(lbl, "rfid\n(stub)");
|
||||
lv_obj_center(lbl);
|
||||
|
||||
LOG_INF("rfid init");
|
||||
}
|
||||
|
||||
void rfid_deinit(void)
|
||||
{
|
||||
if (rfid_screen) {
|
||||
lv_obj_del(rfid_screen);
|
||||
rfid_screen = NULL;
|
||||
}
|
||||
LOG_INF("rfid deinit");
|
||||
}
|
||||
|
|
@ -0,0 +1,216 @@
|
|||
/*
|
||||
* scalpel.c -- Acoustic Scalpel Live Sound Synthesizer
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*
|
||||
* POSIX-threaded audio synthesizer writing to /dev/dsp at 44.1kHz.
|
||||
* Generates precise audio diagnostic tracking signals via
|
||||
* continuous multi-waveform raw data matrices written to
|
||||
* /dev/dsp through an independent POSIX threading loop.
|
||||
* Supports SINE and SQUARE waveforms with real-time
|
||||
* frequency tuning via the rotary encoder (20 Hz - 20 kHz).
|
||||
*
|
||||
* Manifest ref: Asset V -- Acoustic Scalpel Live Sound Synthesizer
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
#include <pthread.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/soundcard.h>
|
||||
#include <linux/input.h>
|
||||
#include "h2_ui.h"
|
||||
#include "log_manager.h"
|
||||
|
||||
#define SAMPLE_RATE 44100
|
||||
#define CHANNELS 1
|
||||
#define AUDIO_FORMAT AFMT_S16_LE
|
||||
#define BUFFER_SIZE 1024
|
||||
#define MIN_FREQ 20
|
||||
#define MAX_FREQ 20000
|
||||
#define FREQ_STEP 10
|
||||
|
||||
static volatile int target_frequency = 440;
|
||||
static volatile int wave_type = 0; /* 0 = SINE, 1 = SQUARE */
|
||||
static volatile int keep_playing = 1;
|
||||
|
||||
/* UI labels */
|
||||
static lv_obj_t *freq_label = NULL;
|
||||
static lv_obj_t *wave_label = NULL;
|
||||
static lv_obj_t *status_label = NULL;
|
||||
static lv_obj_t *status_icon = NULL;
|
||||
|
||||
static void *audio_synthesis_thread(void *arg) {
|
||||
(void)arg;
|
||||
int audio_fd = open("/dev/dsp", O_WRONLY);
|
||||
if (audio_fd == -1) {
|
||||
LOG_ERR("cannot open /dev/dsp for audio output");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int format = AUDIO_FORMAT;
|
||||
ioctl(audio_fd, SNDCTL_DSP_SETFMT, &format);
|
||||
int channels = CHANNELS;
|
||||
ioctl(audio_fd, SNDCTL_DSP_CHANNELS, &channels);
|
||||
int speed = SAMPLE_RATE;
|
||||
ioctl(audio_fd, SNDCTL_DSP_SPEED, &speed);
|
||||
|
||||
LOG_INF("audio synth thread started: %d Hz, %d ch, format=%d",
|
||||
speed, channels, format);
|
||||
|
||||
int16_t buffer[BUFFER_SIZE];
|
||||
uint32_t sample_index = 0;
|
||||
|
||||
while (keep_playing) {
|
||||
int current_freq = target_frequency;
|
||||
int current_type = wave_type;
|
||||
|
||||
for (int i = 0; i < BUFFER_SIZE; i++) {
|
||||
double time_t = (double)sample_index / SAMPLE_RATE;
|
||||
double angle = 2.0 * M_PI * current_freq * time_t;
|
||||
|
||||
if (current_type == 0) {
|
||||
/* SINE wave */
|
||||
buffer[i] = (int16_t)(20000.0 * sin(angle));
|
||||
} else {
|
||||
/* SQUARE wave */
|
||||
buffer[i] = (sin(angle) >= 0) ? 15000 : -15000;
|
||||
}
|
||||
sample_index++;
|
||||
}
|
||||
write(audio_fd, buffer, sizeof(buffer));
|
||||
}
|
||||
|
||||
close(audio_fd);
|
||||
LOG_INF("audio synth thread stopped");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void update_display(void) {
|
||||
char buf[48];
|
||||
snprintf(buf, sizeof(buf), "FREQUENCY: %d Hz", target_frequency);
|
||||
lv_label_set_text(freq_label, buf);
|
||||
|
||||
const char *wave_name = (wave_type == 0) ? "SINE WAVE" : "SQUARE WAVE";
|
||||
lv_label_set_text(wave_label, wave_name);
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
init_h2_graphics_runtime("ACOUSTIC SCALPEL");
|
||||
LOG_INF("scalpel module started");
|
||||
|
||||
lv_obj_t *scr = lv_scr_act();
|
||||
|
||||
/* Header bar */
|
||||
lv_obj_t *header = lv_obj_create(scr);
|
||||
lv_obj_set_size(header, 320, 32);
|
||||
lv_obj_align(header, LV_ALIGN_TOP_MID, 0, 0);
|
||||
lv_obj_set_style_bg_color(header,
|
||||
lv_color_make(211, 160, 0), LV_PART_MAIN);
|
||||
lv_obj_set_style_border_width(header, 0, LV_PART_MAIN);
|
||||
lv_obj_set_style_pad_all(header, 4, LV_PART_MAIN);
|
||||
|
||||
lv_obj_t *title = lv_label_create(header);
|
||||
lv_label_set_text(title, "ACOUSTIC SCALPEL SIGNAL GEN");
|
||||
lv_obj_set_style_text_color(title,
|
||||
lv_color_make(0, 0, 0), LV_PART_MAIN);
|
||||
lv_obj_set_style_text_font(title, &lv_font_montserrat_12, LV_PART_MAIN);
|
||||
lv_obj_align(title, LV_ALIGN_CENTER, 0, 0);
|
||||
|
||||
/* Frequency display */
|
||||
freq_label = lv_label_create(scr);
|
||||
lv_obj_align(freq_label, LV_ALIGN_TOP_MID, 0, 50);
|
||||
lv_obj_set_style_text_color(freq_label, COLOR_ACCENT, LV_PART_MAIN);
|
||||
lv_obj_set_style_text_font(freq_label, &lv_font_montserrat_14, LV_PART_MAIN);
|
||||
|
||||
/* Waveform display */
|
||||
wave_label = lv_label_create(scr);
|
||||
lv_obj_align(wave_label, LV_ALIGN_TOP_MID, 0, 80);
|
||||
lv_obj_set_style_text_color(wave_label, COLOR_PRIMARY, LV_PART_MAIN);
|
||||
lv_obj_set_style_text_font(wave_label, &lv_font_montserrat_14, LV_PART_MAIN);
|
||||
|
||||
/* Status card */
|
||||
lv_obj_t *card = lv_obj_create(scr);
|
||||
lv_obj_set_size(card, 280, 80);
|
||||
lv_obj_align(card, LV_ALIGN_CENTER, 0, 20);
|
||||
lv_obj_set_style_bg_color(card,
|
||||
lv_color_make(18, 22, 32), LV_PART_MAIN);
|
||||
|
||||
status_icon = lv_label_create(card);
|
||||
lv_obj_align(status_icon, LV_ALIGN_TOP_LEFT, 10, 8);
|
||||
lv_obj_set_style_text_color(status_icon, COLOR_ACCENT, LV_PART_MAIN);
|
||||
lv_label_set_text(status_icon, LV_SYMBOL_AUDIO);
|
||||
|
||||
status_label = lv_label_create(card);
|
||||
lv_label_set_text(status_label, "Audio engine: ACTIVE");
|
||||
lv_obj_align(status_label, LV_ALIGN_TOP_LEFT, 30, 8);
|
||||
lv_obj_set_style_text_color(status_label, COLOR_MUTED, LV_PART_MAIN);
|
||||
lv_obj_set_style_text_font(status_label, &lv_font_montserrat_10, LV_PART_MAIN);
|
||||
|
||||
/* Help text */
|
||||
lv_obj_t *help = lv_label_create(scr);
|
||||
lv_label_set_text(help,
|
||||
"[WHEEL] Tune Freq [PLAY] Toggle Wave [BACK] Exit");
|
||||
lv_obj_align(help, LV_ALIGN_BOTTOM_MID, 0, -8);
|
||||
lv_obj_set_style_text_color(help, COLOR_MUTED, LV_PART_MAIN);
|
||||
lv_obj_set_style_text_font(help, &lv_font_montserrat_10, LV_PART_MAIN);
|
||||
|
||||
update_display();
|
||||
|
||||
/* Start audio synthesis thread */
|
||||
pthread_t sound_worker;
|
||||
if (pthread_create(&sound_worker, NULL, audio_synthesis_thread, NULL) != 0) {
|
||||
LOG_ERR("failed to create audio thread");
|
||||
lv_label_set_text(status_label, "Audio engine: FAILED");
|
||||
lv_obj_set_style_text_color(status_label,
|
||||
lv_color_make(255, 0, 0), LV_PART_MAIN);
|
||||
/* Still allow UI to run for user to see the error */
|
||||
}
|
||||
|
||||
/* Input loop */
|
||||
int input_fd = open("/dev/input/event0", O_RDONLY | O_NONBLOCK);
|
||||
struct input_event ev;
|
||||
|
||||
while (1) {
|
||||
lv_timer_handler();
|
||||
|
||||
if (input_fd >= 0 &&
|
||||
read(input_fd, &ev, sizeof(struct input_event)) > 0) {
|
||||
if (ev.type == EV_REL && ev.code == H2_ROTARY_REL) {
|
||||
if (ev.value > 0 && target_frequency < MAX_FREQ) {
|
||||
target_frequency += FREQ_STEP;
|
||||
if (target_frequency > MAX_FREQ)
|
||||
target_frequency = MAX_FREQ;
|
||||
update_display();
|
||||
} else if (ev.value < 0 && target_frequency > MIN_FREQ) {
|
||||
target_frequency -= FREQ_STEP;
|
||||
if (target_frequency < MIN_FREQ)
|
||||
target_frequency = MIN_FREQ;
|
||||
update_display();
|
||||
}
|
||||
} else if (ev.type == EV_KEY && ev.value == 1) {
|
||||
if (ev.code == H2_KEY_PLAY) {
|
||||
wave_type = (wave_type + 1) % 2;
|
||||
update_display();
|
||||
LOG_INF("waveform toggled to: %s",
|
||||
wave_type == 0 ? "SINE" : "SQUARE");
|
||||
} else if (ev.code == H2_KEY_BACK) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
usleep(5000);
|
||||
}
|
||||
|
||||
/* Cleanup */
|
||||
keep_playing = 0;
|
||||
pthread_join(sound_worker, NULL);
|
||||
|
||||
if (input_fd >= 0) close(input_fd);
|
||||
LOG_INF("scalpel module exited");
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -0,0 +1,260 @@
|
|||
/*
|
||||
* studio.c -- Studio Audio Class 2.0 Streaming Control Console
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*
|
||||
* UAC2 Mixer Console with 4-channel per-channel volume bars.
|
||||
* Integrates 24-bit/192kHz asynchronous audio path visualization
|
||||
* with live application mixer targeting using native USB Consumer
|
||||
* Control pages via /dev/hidg1. Four audio channels (GAME, DISCORD,
|
||||
* MIC, MUSIC) with per-channel volume bars and hardware mute toggle.
|
||||
*
|
||||
* Manifest ref: Asset VII -- Studio Audio Class 2.0 Streaming Console
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <linux/input.h>
|
||||
#include "h2_ui.h"
|
||||
#include "log_manager.h"
|
||||
|
||||
#define HID_DEV "/dev/hidg1"
|
||||
#define NUM_CHANNELS 4
|
||||
|
||||
typedef enum { MIX_GAME, MIX_DISCORD, MIX_MIC, MIX_MUSIC } AudioChannel;
|
||||
|
||||
static const char *channel_names[NUM_CHANNELS] = {
|
||||
"GAME AUDIO", "DISCORD CHAT", "MICROPHONE", "STREAM MUSIC"
|
||||
};
|
||||
static const char *channel_symbols[NUM_CHANNELS] = {
|
||||
LV_SYMBOL_VIDEO, LV_SYMBOL_CHAT, LV_SYMBOL_MICROPHONE, LV_SYMBOL_MUSIC
|
||||
};
|
||||
|
||||
static AudioChannel active_channel = MIX_GAME;
|
||||
static int channel_volumes[NUM_CHANNELS] = {80, 70, 90, 50};
|
||||
static int is_muted = 0;
|
||||
|
||||
/* UI objects */
|
||||
static lv_obj_t *channel_labels[NUM_CHANNELS] = {NULL};
|
||||
static lv_obj_t *bar_bg[NUM_CHANNELS] = {NULL};
|
||||
static lv_obj_t *bar_fill[NUM_CHANNELS] = {NULL};
|
||||
static lv_obj_t *vol_labels[NUM_CHANNELS] = {NULL};
|
||||
static lv_obj_t *active_indicator = NULL;
|
||||
static lv_obj_t *mute_label = NULL;
|
||||
static lv_obj_t *dac_label = NULL;
|
||||
|
||||
static void send_media_command(int hid_fd, uint8_t usage_code) {
|
||||
uint8_t report[2] = {0};
|
||||
report[0] = usage_code;
|
||||
write(hid_fd, report, 2);
|
||||
/* Release */
|
||||
report[0] = 0x00;
|
||||
write(hid_fd, report, 2);
|
||||
}
|
||||
|
||||
static void render_mixer(void) {
|
||||
for (int i = 0; i < NUM_CHANNELS; i++) {
|
||||
/* Update volume bar width: 140px max at 100% */
|
||||
int fill_w = (channel_volumes[i] * 140) / 100;
|
||||
lv_obj_set_width(bar_fill[i], fill_w > 0 ? fill_w : 1);
|
||||
|
||||
/* Color: green normal, red if muted and MIC */
|
||||
if (is_muted && i == MIX_MIC) {
|
||||
lv_obj_set_style_bg_color(bar_fill[i],
|
||||
lv_color_make(255, 0, 0), LV_PART_MAIN);
|
||||
} else {
|
||||
lv_obj_set_style_bg_color(bar_fill[i],
|
||||
lv_color_make(0, 224, 0), LV_PART_MAIN);
|
||||
}
|
||||
|
||||
/* Volume percentage text */
|
||||
char vol_str[8];
|
||||
snprintf(vol_str, sizeof(vol_str), "%d%%", channel_volumes[i]);
|
||||
lv_label_set_text(vol_labels[i], vol_str);
|
||||
|
||||
/* Highlight active channel */
|
||||
if (i == (int)active_channel) {
|
||||
lv_obj_set_style_text_color(channel_labels[i],
|
||||
lv_color_make(0, 255, 255), LV_PART_MAIN);
|
||||
lv_obj_set_style_text_font(channel_labels[i],
|
||||
&lv_font_montserrat_12, LV_PART_MAIN);
|
||||
lv_obj_set_style_border_color(bar_bg[i],
|
||||
lv_color_make(0, 255, 255), LV_PART_MAIN);
|
||||
} else {
|
||||
lv_obj_set_style_text_color(channel_labels[i],
|
||||
COLOR_MUTED, LV_PART_MAIN);
|
||||
lv_obj_set_style_text_font(channel_labels[i],
|
||||
&lv_font_montserrat_10, LV_PART_MAIN);
|
||||
lv_obj_set_style_border_color(bar_bg[i],
|
||||
lv_color_make(50, 55, 70), LV_PART_MAIN);
|
||||
}
|
||||
}
|
||||
|
||||
/* Mute warning */
|
||||
if (is_muted) {
|
||||
lv_label_set_text(mute_label,
|
||||
"!! MIC CHANNEL MUTED VIA PANIC BUTTON !!");
|
||||
lv_obj_set_style_text_color(mute_label,
|
||||
lv_color_make(255, 0, 0), LV_PART_MAIN);
|
||||
} else {
|
||||
lv_label_set_text(mute_label, "");
|
||||
}
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
init_h2_graphics_runtime("STUDIO PRO CONSOLE");
|
||||
LOG_INF("studio module started (UAC2 mixer console)");
|
||||
|
||||
lv_obj_t *scr = lv_scr_act();
|
||||
|
||||
/* Header */
|
||||
lv_obj_t *header = lv_obj_create(scr);
|
||||
lv_obj_set_size(header, 320, 34);
|
||||
lv_obj_align(header, LV_ALIGN_TOP_MID, 0, 0);
|
||||
lv_obj_set_style_bg_color(header,
|
||||
lv_color_make(49, 134, 49), LV_PART_MAIN);
|
||||
lv_obj_set_style_border_width(header, 0, LV_PART_MAIN);
|
||||
|
||||
lv_obj_t *title = lv_label_create(header);
|
||||
lv_label_set_text(title, "STUDIO PRO CONSOLE & DAC");
|
||||
lv_obj_set_style_text_color(title,
|
||||
lv_color_make(255, 255, 255), LV_PART_MAIN);
|
||||
lv_obj_set_style_text_font(title, &lv_font_montserrat_14, LV_PART_MAIN);
|
||||
lv_obj_align(title, LV_ALIGN_CENTER, 0, 0);
|
||||
|
||||
/* DAC status line */
|
||||
dac_label = lv_label_create(scr);
|
||||
lv_label_set_text(dac_label, "DAC LINK: ONLINE | PCM 24-Bit | 192 kHz");
|
||||
lv_obj_align(dac_label, LV_ALIGN_TOP_MID, 0, 38);
|
||||
lv_obj_set_style_text_color(dac_label, COLOR_ACCENT, LV_PART_MAIN);
|
||||
lv_obj_set_style_text_font(dac_label, &lv_font_montserrat_10, LV_PART_MAIN);
|
||||
|
||||
/* Channel mixer rows */
|
||||
for (int i = 0; i < NUM_CHANNELS; i++) {
|
||||
int y_base = 62 + (i * 38);
|
||||
|
||||
/* Channel name */
|
||||
channel_labels[i] = lv_label_create(scr);
|
||||
lv_label_set_text(channel_labels[i], channel_names[i]);
|
||||
lv_obj_set_pos(channel_labels[i], 8, y_base + 2);
|
||||
|
||||
/* Volume bar background */
|
||||
bar_bg[i] = lv_obj_create(scr);
|
||||
lv_obj_set_size(bar_bg[i], 144, 14);
|
||||
lv_obj_set_pos(bar_bg[i], 140, y_base + 2);
|
||||
lv_obj_set_style_bg_color(bar_bg[i],
|
||||
lv_color_make(33, 4, 4), LV_PART_MAIN);
|
||||
lv_obj_set_style_border_width(bar_bg[i], 1, LV_PART_MAIN);
|
||||
lv_obj_set_style_border_color(bar_bg[i],
|
||||
lv_color_make(50, 55, 70), LV_PART_MAIN);
|
||||
lv_obj_set_style_pad_all(bar_bg[i], 0, LV_PART_MAIN);
|
||||
|
||||
/* Volume bar fill */
|
||||
bar_fill[i] = lv_obj_create(bar_bg[i]);
|
||||
lv_obj_set_size(bar_fill[i], 100, 14);
|
||||
lv_obj_set_pos(bar_fill[i], 0, 0);
|
||||
lv_obj_set_style_bg_color(bar_fill[i],
|
||||
lv_color_make(0, 224, 0), LV_PART_MAIN);
|
||||
lv_obj_set_style_border_width(bar_fill[i], 0, LV_PART_MAIN);
|
||||
lv_obj_set_style_pad_all(bar_fill[i], 0, LV_PART_MAIN);
|
||||
|
||||
/* Volume percentage */
|
||||
vol_labels[i] = lv_label_create(scr);
|
||||
lv_obj_set_pos(vol_labels[i], 290, y_base + 2);
|
||||
lv_obj_set_style_text_color(vol_labels[i],
|
||||
COLOR_MUTED, LV_PART_MAIN);
|
||||
lv_obj_set_style_text_font(vol_labels[i],
|
||||
&lv_font_montserrat_10, LV_PART_MAIN);
|
||||
}
|
||||
|
||||
/* Mute warning label */
|
||||
mute_label = lv_label_create(scr);
|
||||
lv_obj_align(mute_label, LV_ALIGN_BOTTOM_MID, 0, -28);
|
||||
lv_obj_set_style_text_font(mute_label, &lv_font_montserrat_10, LV_PART_MAIN);
|
||||
|
||||
/* Help text */
|
||||
lv_obj_t *help = lv_label_create(scr);
|
||||
lv_label_set_text(help, "[NEXT/PREV] Track [WHEEL] Vol [PLAY] Mute");
|
||||
lv_obj_align(help, LV_ALIGN_BOTTOM_MID, 0, -8);
|
||||
lv_obj_set_style_text_color(help, COLOR_MUTED, LV_PART_MAIN);
|
||||
lv_obj_set_style_text_font(help, &lv_font_montserrat_10, LV_PART_MAIN);
|
||||
|
||||
/* Open HID consumer control and input */
|
||||
int hid_fd = open(HID_DEV, O_WRONLY | O_NONBLOCK);
|
||||
int input_fd = open("/dev/input/event0", O_RDONLY | O_NONBLOCK);
|
||||
|
||||
if (input_fd < 0) {
|
||||
LOG_ERR("cannot open input device");
|
||||
if (hid_fd >= 0) close(hid_fd);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (hid_fd < 0) {
|
||||
LOG_WRN("cannot open %s -- media commands disabled", HID_DEV);
|
||||
}
|
||||
|
||||
render_mixer();
|
||||
|
||||
/* Input loop */
|
||||
struct input_event ev;
|
||||
while (1) {
|
||||
lv_timer_handler();
|
||||
|
||||
if (input_fd >= 0 &&
|
||||
read(input_fd, &ev, sizeof(struct input_event)) > 0) {
|
||||
|
||||
if (ev.type == EV_REL && ev.code == H2_ROTARY_REL) {
|
||||
if (ev.value > 0) {
|
||||
if (channel_volumes[active_channel] < 100)
|
||||
channel_volumes[active_channel] += 5;
|
||||
if (channel_volumes[active_channel] > 100)
|
||||
channel_volumes[active_channel] = 100;
|
||||
/* Send volume up to host */
|
||||
if (hid_fd >= 0)
|
||||
send_media_command(hid_fd, 0xE9);
|
||||
render_mixer();
|
||||
} else if (ev.value < 0) {
|
||||
if (channel_volumes[active_channel] > 0)
|
||||
channel_volumes[active_channel] -= 5;
|
||||
if (channel_volumes[active_channel] < 0)
|
||||
channel_volumes[active_channel] = 0;
|
||||
/* Send volume down to host */
|
||||
if (hid_fd >= 0)
|
||||
send_media_command(hid_fd, 0xEA);
|
||||
render_mixer();
|
||||
}
|
||||
}
|
||||
else if (ev.type == EV_KEY && ev.value == 1) {
|
||||
if (ev.code == H2_KEY_NEXT) {
|
||||
/* NEXT key cycles channel forward */
|
||||
active_channel = (AudioChannel)((active_channel + 1) % NUM_CHANNELS);
|
||||
LOG_INF("active channel: %s", channel_names[active_channel]);
|
||||
render_mixer();
|
||||
} else if (ev.code == H2_KEY_PREV) {
|
||||
/* PREV key cycles channel backward */
|
||||
active_channel = (AudioChannel)((active_channel - 1 + NUM_CHANNELS) % NUM_CHANNELS);
|
||||
LOG_INF("active channel: %s", channel_names[active_channel]);
|
||||
render_mixer();
|
||||
} else if (ev.code == H2_KEY_PLAY) {
|
||||
/* PLAY toggles mute */
|
||||
is_muted = !is_muted;
|
||||
if (hid_fd >= 0)
|
||||
send_media_command(hid_fd, 0xE2); /* MUTE */
|
||||
LOG_INF("mute toggled: %s", is_muted ? "ON" : "OFF");
|
||||
render_mixer();
|
||||
} else if (ev.code == H2_KEY_BACK) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
usleep(5000);
|
||||
}
|
||||
|
||||
if (hid_fd >= 0) close(hid_fd);
|
||||
if (input_fd >= 0) close(input_fd);
|
||||
LOG_INF("studio module exited");
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -0,0 +1,302 @@
|
|||
/*
|
||||
* vault.c -- Crypto Security Token Vault Gateway
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*
|
||||
* Full PIN auth module with rotary entry, lockout, and dual identity modes.
|
||||
* Dual wired/wired identity modes backed by non-volatile failure
|
||||
* limits, roll-count PIN security, and automatic device state
|
||||
* enforcement. Wired mode enables USB composite gadget via
|
||||
* enable_vault_usb.sh; wireless mode tears down USB and brings
|
||||
* up BLE via enable_vault_ble.sh.
|
||||
*
|
||||
* Manifest ref: Asset IV -- Crypto Security Token Vault Gateway
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <linux/input.h>
|
||||
#include "h2_ui.h"
|
||||
#include "log_manager.h"
|
||||
|
||||
#define STATE_FILE "/data/vault/failures.dat"
|
||||
#define MAX_FAILURES 3
|
||||
#define LOCKOUT_SEC 300
|
||||
|
||||
/* PIN: 4 digits, default 4-2-9-1 as per manifest */
|
||||
static int master_pin[4] = {4, 2, 9, 1};
|
||||
static int entered_pin[4] = {0, 0, 0, 0};
|
||||
static int current_digit_idx = 0;
|
||||
|
||||
typedef enum { MODE_NONE, MODE_WIRED, MODE_WIRELESS } VaultMode;
|
||||
static VaultMode current_mode = MODE_NONE;
|
||||
|
||||
/* UI objects */
|
||||
static lv_obj_t *pin_label = NULL;
|
||||
static lv_obj_t *warning_label = NULL;
|
||||
static lv_obj_t *digit_labels[4] = {NULL};
|
||||
static lv_obj_t *cursor_label = NULL;
|
||||
static lv_obj_t *status_label = NULL;
|
||||
static lv_obj_t *mode_label = NULL;
|
||||
|
||||
static int get_failure_count(void) {
|
||||
FILE *f = fopen(STATE_FILE, "r");
|
||||
if (!f) return 0;
|
||||
int count = 0;
|
||||
if (fscanf(f, "%d", &count) <= 0) count = 0;
|
||||
fclose(f);
|
||||
return count;
|
||||
}
|
||||
|
||||
static void set_failure_count(int count) {
|
||||
FILE *f = fopen(STATE_FILE, "w");
|
||||
if (f) { fprintf(f, "%d", count); fclose(f); }
|
||||
}
|
||||
|
||||
static int is_usb_plugged_in(void) {
|
||||
int fd = open("/sys/class/power_supply/usb/online", O_RDONLY);
|
||||
if (fd == -1) return 0;
|
||||
char status = '0';
|
||||
if (read(fd, &status, 1) <= 0) status = '0';
|
||||
close(fd);
|
||||
return (status == '1');
|
||||
}
|
||||
|
||||
static void render_pin_screen(int attempts_left) {
|
||||
char buf[64];
|
||||
|
||||
/* Warning */
|
||||
snprintf(buf, sizeof(buf), "Attempts before lockdown: %d", attempts_left);
|
||||
lv_label_set_text(warning_label, buf);
|
||||
lv_obj_set_style_text_color(warning_label, lv_color_make(253, 32, 32), LV_PART_MAIN);
|
||||
|
||||
/* Digit boxes */
|
||||
for (int i = 0; i < 4; i++) {
|
||||
char digit_str[16];
|
||||
snprintf(digit_str, sizeof(digit_str), "%d", entered_pin[i]);
|
||||
lv_label_set_text(digit_labels[i], digit_str);
|
||||
|
||||
if (i == current_digit_idx) {
|
||||
lv_obj_set_style_text_color(digit_labels[i], COLOR_ACCENT, LV_PART_MAIN);
|
||||
lv_obj_set_style_border_color(digit_labels[i],
|
||||
lv_color_make(0, 224, 0), LV_PART_MAIN);
|
||||
lv_obj_set_style_border_width(digit_labels[i], 2, LV_PART_MAIN);
|
||||
} else {
|
||||
lv_obj_set_style_text_color(digit_labels[i], COLOR_PRIMARY, LV_PART_MAIN);
|
||||
lv_obj_set_style_border_width(digit_labels[i], 1, LV_PART_MAIN);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int enforce_pin_authorization(int input_fd) {
|
||||
int failures = get_failure_count();
|
||||
|
||||
/* Lockout check */
|
||||
if (failures >= MAX_FAILURES) {
|
||||
char log_msg[128];
|
||||
snprintf(log_msg, sizeof(log_msg),
|
||||
"PIN lockout triggered. %d failures recorded. Enforcing %ds cooldown.",
|
||||
failures, LOCKOUT_SEC);
|
||||
LOG_ERR("%s", log_msg);
|
||||
|
||||
for (int penalty_sec = LOCKOUT_SEC; penalty_sec > 0; penalty_sec--) {
|
||||
char countdown[64];
|
||||
snprintf(countdown, sizeof(countdown),
|
||||
"DEVICE LOCKED DOWN\nRetry in: %d s", penalty_sec);
|
||||
lv_label_set_text(status_label, countdown);
|
||||
lv_obj_set_style_text_color(status_label,
|
||||
lv_color_make(255, 0, 0), LV_PART_MAIN);
|
||||
lv_timer_handler();
|
||||
|
||||
/* Check for back key during lockout to allow early exit */
|
||||
struct input_event ev;
|
||||
if (input_fd >= 0) {
|
||||
int flags = fcntl(input_fd, F_GETFL, 0);
|
||||
fcntl(input_fd, F_SETFL, flags | O_NONBLOCK);
|
||||
if (read(input_fd, &ev, sizeof(struct input_event)) > 0) {
|
||||
if (ev.type == EV_KEY && ev.code == H2_KEY_BACK && ev.value == 1) {
|
||||
fcntl(input_fd, F_SETFL, flags);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
fcntl(input_fd, F_SETFL, flags);
|
||||
}
|
||||
sleep(1);
|
||||
}
|
||||
set_failure_count(0);
|
||||
failures = 0;
|
||||
}
|
||||
|
||||
render_pin_screen(MAX_FAILURES - failures);
|
||||
|
||||
/* PIN entry loop */
|
||||
struct input_event ev;
|
||||
while (current_digit_idx < 4) {
|
||||
lv_timer_handler();
|
||||
if (input_fd >= 0 && read(input_fd, &ev, sizeof(struct input_event)) > 0) {
|
||||
if (ev.type == EV_REL && ev.code == H2_ROTARY_REL) {
|
||||
if (ev.value > 0)
|
||||
entered_pin[current_digit_idx] = (entered_pin[current_digit_idx] + 1) % 10;
|
||||
else
|
||||
entered_pin[current_digit_idx] = (entered_pin[current_digit_idx] - 1 + 10) % 10;
|
||||
render_pin_screen(MAX_FAILURES - failures);
|
||||
} else if (ev.type == EV_KEY && ev.code == H2_KEY_PLAY && ev.value == 1) {
|
||||
current_digit_idx++;
|
||||
if (current_digit_idx < 4) render_pin_screen(MAX_FAILURES - failures);
|
||||
} else if (ev.type == EV_KEY && ev.code == H2_KEY_BACK && ev.value == 1) {
|
||||
if (current_digit_idx > 0) {
|
||||
current_digit_idx--;
|
||||
render_pin_screen(MAX_FAILURES - failures);
|
||||
}
|
||||
}
|
||||
}
|
||||
usleep(5000);
|
||||
}
|
||||
|
||||
/* Verify PIN */
|
||||
if (memcmp(master_pin, entered_pin, sizeof(master_pin)) == 0) {
|
||||
set_failure_count(0);
|
||||
LOG_INF("PIN verified. Authorization granted.");
|
||||
lv_label_set_text(status_label, "ACCESS GRANTED\nKEY INJECTED");
|
||||
lv_obj_set_style_text_color(status_label, COLOR_ACCENT, LV_PART_MAIN);
|
||||
lv_timer_handler();
|
||||
sleep(2);
|
||||
return 0;
|
||||
} else {
|
||||
failures++;
|
||||
set_failure_count(failures);
|
||||
char log_msg[128];
|
||||
snprintf(log_msg, sizeof(log_msg),
|
||||
"Invalid PIN attempt logged. Failure level: %d/%d",
|
||||
failures, MAX_FAILURES);
|
||||
LOG_ERR("%s", log_msg);
|
||||
|
||||
lv_label_set_text(status_label, "INVALID PIN\nATTEMPT LOGGED");
|
||||
lv_obj_set_style_text_color(status_label,
|
||||
lv_color_make(255, 0, 0), LV_PART_MAIN);
|
||||
lv_timer_handler();
|
||||
sleep(2);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
init_h2_graphics_runtime("CRYPTO VAULT GUARD");
|
||||
LOG_INF("vault module started (full PIN auth mode)");
|
||||
|
||||
lv_obj_t *scr = lv_scr_act();
|
||||
|
||||
/* Title */
|
||||
lv_obj_t *title = lv_label_create(scr);
|
||||
lv_label_set_text(title, "SECURITY TOKEN VAULT");
|
||||
lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 10);
|
||||
lv_obj_set_style_text_color(title, COLOR_PRIMARY, LV_PART_MAIN);
|
||||
lv_obj_set_style_text_font(title, &lv_font_montserrat_14, LV_PART_MAIN);
|
||||
|
||||
/* PIN entry card */
|
||||
lv_obj_t *pin_card = lv_obj_create(scr);
|
||||
lv_obj_set_size(pin_card, 280, 140);
|
||||
lv_obj_align(pin_card, LV_ALIGN_CENTER, 0, -5);
|
||||
|
||||
lv_obj_t *pin_title = lv_label_create(pin_card);
|
||||
lv_label_set_text(pin_title, "ENTER PIN");
|
||||
lv_obj_align(pin_title, LV_ALIGN_TOP_MID, 0, 5);
|
||||
lv_obj_set_style_text_color(pin_title, COLOR_MUTED, LV_PART_MAIN);
|
||||
|
||||
warning_label = lv_label_create(pin_card);
|
||||
lv_obj_align(warning_label, LV_ALIGN_TOP_MID, 0, 22);
|
||||
lv_obj_set_style_text_font(warning_label, &lv_font_montserrat_10, LV_PART_MAIN);
|
||||
|
||||
/* Four digit display boxes */
|
||||
for (int i = 0; i < 4; i++) {
|
||||
digit_labels[i] = lv_label_create(pin_card);
|
||||
lv_label_set_text(digit_labels[i], "0");
|
||||
lv_obj_set_style_text_font(digit_labels[i], &lv_font_montserrat_22, LV_PART_MAIN);
|
||||
lv_obj_set_style_border_side(digit_labels[i],
|
||||
LV_BORDER_SIDE_FULL, LV_PART_MAIN);
|
||||
lv_obj_set_style_border_color(digit_labels[i], COLOR_MUTED, LV_PART_MAIN);
|
||||
lv_obj_set_style_border_width(digit_labels[i], 1, LV_PART_MAIN);
|
||||
lv_obj_set_style_pad_all(digit_labels[i], 8, LV_PART_MAIN);
|
||||
lv_obj_align(digit_labels[i], LV_ALIGN_CENTER, -60 + (i * 40), -5);
|
||||
}
|
||||
|
||||
/* Status area (used for lockout / result) */
|
||||
status_label = lv_label_create(scr);
|
||||
lv_label_set_text(status_label, "");
|
||||
lv_obj_set_style_text_font(status_label, &lv_font_montserrat_12, LV_PART_MAIN);
|
||||
lv_obj_align(status_label, LV_ALIGN_BOTTOM_MID, 0, -30);
|
||||
|
||||
/* Mode indicator */
|
||||
mode_label = lv_label_create(scr);
|
||||
lv_obj_align(mode_label, LV_ALIGN_BOTTOM_LEFT, 8, -8);
|
||||
lv_obj_set_style_text_color(mode_label, COLOR_MUTED, LV_PART_MAIN);
|
||||
lv_obj_set_style_text_font(mode_label, &lv_font_montserrat_10, LV_PART_MAIN);
|
||||
lv_label_set_text(mode_label, "[WHEEL] Digit [PLAY] Next [BACK] Exit");
|
||||
|
||||
/* Open input */
|
||||
int input_fd = open("/dev/input/event0", O_RDONLY | O_NONBLOCK);
|
||||
if (input_fd < 0) {
|
||||
LOG_ERR("cannot open input device");
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Run PIN authorization */
|
||||
int auth_result = enforce_pin_authorization(input_fd);
|
||||
if (auth_result != 0) {
|
||||
close(input_fd);
|
||||
LOG_INF("vault module exited (auth failed)");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Authorized: enter runtime mode loop */
|
||||
while (1) {
|
||||
lv_timer_handler();
|
||||
|
||||
int usb_active = is_usb_plugged_in();
|
||||
|
||||
if (usb_active) {
|
||||
if (current_mode != MODE_WIRED) {
|
||||
LOG_INF("switching to WIRED mode");
|
||||
system("hciconfig hci0 down 2>/dev/null");
|
||||
system("/usr/bin/enable_vault_usb.sh 2>/dev/null");
|
||||
current_mode = MODE_WIRED;
|
||||
}
|
||||
lv_label_set_text(status_label, "MODE: SECURE WIRED SMARTCARD\nUSB Token: Operational (CCID)");
|
||||
lv_obj_set_style_text_color(status_label,
|
||||
lv_color_make(0, 68, 136), LV_PART_MAIN);
|
||||
} else {
|
||||
if (current_mode != MODE_WIRELESS) {
|
||||
LOG_INF("switching to WIRELESS BLE mode");
|
||||
system("echo \"\" > /sys/kernel/config/usb_gadget/wh_tool/UDC 2>/dev/null");
|
||||
system("/usr/bin/enable_vault_ble.sh 2>/dev/null");
|
||||
current_mode = MODE_WIRELESS;
|
||||
}
|
||||
lv_label_set_text(status_label, "MODE: WIRELESS BLE SMARTCARD\nBLE Identity: Active");
|
||||
lv_obj_set_style_text_color(status_label,
|
||||
lv_color_make(0, 204, 68), LV_PART_MAIN);
|
||||
}
|
||||
|
||||
/* Check for BACK to exit and purge */
|
||||
struct input_event ev;
|
||||
if (input_fd >= 0 && read(input_fd, &ev, sizeof(struct input_event)) > 0) {
|
||||
if (ev.type == EV_KEY && ev.code == H2_KEY_BACK && ev.value == 1) {
|
||||
lv_label_set_text(status_label, "Purging keys from RAM... Locking.");
|
||||
lv_obj_set_style_text_color(status_label,
|
||||
lv_color_make(255, 0, 0), LV_PART_MAIN);
|
||||
lv_timer_handler();
|
||||
LOG_INF("vault key purge triggered by user");
|
||||
sleep(1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
usleep(200000);
|
||||
}
|
||||
|
||||
close(input_fd);
|
||||
LOG_INF("vault module exited");
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
/*
|
||||
* vterm.c -- FreeDOS Emulation Bridge module
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*
|
||||
* Forks dosbox with a custom config for the
|
||||
* FreeDOS environment. BACK key sends SIGTERM to the child.
|
||||
* Monitors child exit via waitpid(WNOHANG).
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <sys/wait.h>
|
||||
#include <signal.h>
|
||||
#include <linux/input.h>
|
||||
#include "h2_ui.h"
|
||||
#include "log_manager.h"
|
||||
|
||||
int main(void) {
|
||||
init_h2_graphics_runtime("X86 EMULATION ENVIRONMENT");
|
||||
LOG_INF("vterm module started");
|
||||
|
||||
lv_obj_t *scr = lv_scr_act();
|
||||
|
||||
lv_obj_t *title = lv_label_create(scr);
|
||||
lv_label_set_text(title, "EMULATION RUNTIME ENGINE");
|
||||
lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 15);
|
||||
lv_obj_set_style_text_color(title, COLOR_PRIMARY, LV_PART_MAIN);
|
||||
|
||||
lv_obj_t *status = lv_label_create(scr);
|
||||
lv_label_set_text(status, "Launching FreeDOS container...\n[BACK] to terminate");
|
||||
lv_obj_align(status, LV_ALIGN_CENTER, 0, 0);
|
||||
lv_obj_set_style_text_align(status, LV_TEXT_ALIGN_CENTER, LV_PART_MAIN);
|
||||
|
||||
lv_timer_handler();
|
||||
|
||||
pid_t pid = fork();
|
||||
if (pid == 0) {
|
||||
char *args[] = {"/usr/bin/dosbox", "-conf", "/data/vterm/dosbox.conf", NULL};
|
||||
execv(args[0], args);
|
||||
_exit(1);
|
||||
}
|
||||
if (pid < 0) {
|
||||
lv_label_set_text(status, "FORK FAILED");
|
||||
LOG_ERR("fork failed for dosbox");
|
||||
} else {
|
||||
LOG_INF("dosbox launched (pid %d)", (int)pid);
|
||||
}
|
||||
|
||||
int input_fd = open("/dev/input/event0", O_RDONLY | O_NONBLOCK);
|
||||
struct input_event ev;
|
||||
int child_done = (pid < 0);
|
||||
|
||||
while (!child_done) {
|
||||
lv_timer_handler();
|
||||
|
||||
int wstatus;
|
||||
pid_t ret = waitpid(pid, &wstatus, WNOHANG);
|
||||
if (ret == pid) {
|
||||
child_done = 1;
|
||||
lv_label_set_text(status, "DOSBOX EXITED.\n[BACK] to return");
|
||||
LOG_INF("dosbox exited");
|
||||
}
|
||||
|
||||
if (input_fd >= 0 && read(input_fd, &ev, sizeof(struct input_event)) > 0) {
|
||||
if (ev.type == EV_KEY && ev.code == H2_KEY_BACK && ev.value == 1) {
|
||||
if (!child_done && pid > 0) {
|
||||
kill(pid, SIGTERM);
|
||||
usleep(200000);
|
||||
waitpid(pid, &wstatus, WNOHANG);
|
||||
LOG_INF("dosbox terminated by user");
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
usleep(20000);
|
||||
}
|
||||
|
||||
if (child_done && input_fd >= 0) {
|
||||
while (1) {
|
||||
lv_timer_handler();
|
||||
if (read(input_fd, &ev, sizeof(struct input_event)) > 0) {
|
||||
if (ev.type == EV_KEY && ev.code == H2_KEY_BACK && ev.value == 1)
|
||||
break;
|
||||
}
|
||||
usleep(20000);
|
||||
}
|
||||
}
|
||||
|
||||
if (input_fd >= 0) close(input_fd);
|
||||
LOG_INF("vterm module exited");
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
# Trusted Accessory MAC Allow-List
|
||||
# Only pre-registered devices can pair via bt_input_daemon.sh
|
||||
# Format: one MAC address per line, colon-separated lowercase hex
|
||||
# Lines starting with # are comments
|
||||
|
||||
# 8BitDo Micro (gamepad for emulator mode)
|
||||
aa:bb:cc:dd:ee:ff
|
||||
|
||||
# 8BitDo Zero 2 (backup gamepad)
|
||||
11:22:33:44:55:66
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
# BitChat Mesh Macro Templates
|
||||
# Pre-defined command templates for the bitchat mesh module
|
||||
# Format: <TYPE>:<NAME>:<BODY>
|
||||
# TYPE: STATUS | ALERT | CMD | TEST | PANIC
|
||||
|
||||
# --- Status Messages ---
|
||||
STATUS:MESH_UP:Mesh operational -- node online
|
||||
STATUS:POSITION_SECURED:Position secured -- holding
|
||||
STATUS:RETREATING:Retreating -- moving to exfil point
|
||||
|
||||
# --- Alert Messages ---
|
||||
ALERT:TARGET_ACCESS:Target host access obtained
|
||||
ALERT:HEADLESS_DISPLAY:Headless display connected
|
||||
ALERT:COMPROMISED:Device may be compromised -- purging
|
||||
|
||||
# --- Command Messages ---
|
||||
CMD:DEPLOY_RESCUE:Deploy rescue payload on target
|
||||
CMD:REBOOT_LOCK:Trigger reboot-lock on compromised node
|
||||
|
||||
# --- Test Messages ---
|
||||
TEST:BEACON_LQI:Bluetooth beacon LQI check -- please respond
|
||||
|
||||
# --- Panic Messages ---
|
||||
PANIC:DESTROY_KEYS:Destroy local crypto seeds immediately
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
CTRL ALT F2
|
||||
DELAY 1000
|
||||
STRING root
|
||||
ENTER
|
||||
DELAY 500
|
||||
STRING systemctl restart lightdm
|
||||
ENTER
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
CTRL ALT F2
|
||||
DELAY 1000
|
||||
STRING root
|
||||
ENTER
|
||||
DELAY 500
|
||||
STRING systemctl restart gdm3
|
||||
ENTER
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
CTRL ALT F2
|
||||
DELAY 1000
|
||||
STRING root
|
||||
ENTER
|
||||
DELAY 500
|
||||
STRING systemctl restart sddm
|
||||
ENTER
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
CTRL ALT F2
|
||||
DELAY 1000
|
||||
STRING root
|
||||
ENTER
|
||||
DELAY 500
|
||||
STRING pkill -9 -f wayland
|
||||
ENTER
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
CTRL ALT F2
|
||||
DELAY 1000
|
||||
STRING root
|
||||
ENTER
|
||||
DELAY 500
|
||||
STRING rm -f /etc/X11/xorg.conf && Xorg -configure && mv /root/xorg.conf.new /etc/X11/xorg.conf
|
||||
ENTER
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
CTRL ALT F2
|
||||
DELAY 1000
|
||||
STRING root
|
||||
ENTER
|
||||
DELAY 500
|
||||
STRING rm -f /tmp/.X*-lock /tmp/.11-unix/X*
|
||||
ENTER
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
CTRL ALT F3
|
||||
DELAY 1000
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
CTRL ALT F2
|
||||
DELAY 1000
|
||||
CTRL c
|
||||
DELAY 200
|
||||
ENTER
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
CTRL ALT F2
|
||||
DELAY 1000
|
||||
STRING root
|
||||
ENTER
|
||||
DELAY 500
|
||||
STRING pkill -KILL -u $(whoami)
|
||||
ENTER
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
ALT F2
|
||||
DELAY 500
|
||||
STRING r
|
||||
ENTER
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
CTRL ALT F2
|
||||
DELAY 1000
|
||||
STRING root
|
||||
ENTER
|
||||
DELAY 500
|
||||
STRING tar --exclude=/proc --exclude=/sys --exclude=/dev --exclude=/run -cf /rootfs_rescue.tar /
|
||||
ENTER
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
CTRL ALT F2
|
||||
DELAY 1000
|
||||
STRING root
|
||||
ENTER
|
||||
DELAY 500
|
||||
STRING tar -cf /home_backup.tar /home/
|
||||
ENTER
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
CTRL ALT F2
|
||||
DELAY 1000
|
||||
STRING root
|
||||
ENTER
|
||||
DELAY 500
|
||||
STRING tar -cf /etc_backup.tar /etc/
|
||||
ENTER
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
CTRL ALT F2
|
||||
DELAY 1000
|
||||
STRING root
|
||||
ENTER
|
||||
DELAY 500
|
||||
STRING tar -cf /ssh_credentials.tar /home/*/.ssh /root/.ssh
|
||||
ENTER
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
CTRL ALT F2
|
||||
DELAY 1000
|
||||
STRING root
|
||||
ENTER
|
||||
DELAY 500
|
||||
STRING tar -cf /net_metadata.tar /etc/network/ /etc/netplan/ /etc/NetworkManager/
|
||||
ENTER
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
CTRL ALT F2
|
||||
DELAY 1000
|
||||
STRING root
|
||||
ENTER
|
||||
DELAY 500
|
||||
STRING dd if=/dev/sda of=/mbr_core.bin bs=512 count=1
|
||||
ENTER
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
CTRL ALT F2
|
||||
DELAY 1000
|
||||
STRING root
|
||||
ENTER
|
||||
DELAY 500
|
||||
STRING cp /etc/passwd /etc/shadow /etc/group /
|
||||
ENTER
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
CTRL ALT F2
|
||||
DELAY 1000
|
||||
STRING root
|
||||
ENTER
|
||||
DELAY 500
|
||||
STRING tar -cf /mail_backup.tar /var/mail/ /var/spool/
|
||||
ENTER
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
CTRL ALT F2
|
||||
DELAY 1000
|
||||
STRING root
|
||||
ENTER
|
||||
DELAY 500
|
||||
STRING dpkg --get-selections > /installed_packages.txt || rpm -qa > /installed_packages.txt
|
||||
ENTER
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
CTRL ALT F2
|
||||
DELAY 1000
|
||||
STRING root
|
||||
ENTER
|
||||
DELAY 500
|
||||
STRING find / -name "*.db" -o -name "*.sqlite" > /sqlite_locations.txt
|
||||
ENTER
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
CTRL ALT F2
|
||||
DELAY 1000
|
||||
STRING root
|
||||
ENTER
|
||||
DELAY 500
|
||||
STRING rm -f /var/lib/dpkg/lock-frontend /var/lib/apt/lists/lock && dpkg --configure -a
|
||||
ENTER
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
CTRL ALT F2
|
||||
DELAY 1000
|
||||
STRING root
|
||||
ENTER
|
||||
DELAY 500
|
||||
STRING journalctl --vacuum-size=10M && systemctl restart systemd-journald
|
||||
ENTER
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
CTRL ALT F2
|
||||
DELAY 1000
|
||||
STRING root
|
||||
ENTER
|
||||
DELAY 500
|
||||
STRING sync && echo 3 > /proc/sys/vm/drop_caches
|
||||
ENTER
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
CTRL ALT F2
|
||||
DELAY 1000
|
||||
STRING root
|
||||
ENTER
|
||||
DELAY 500
|
||||
STRING update-initramfs -u -k all
|
||||
ENTER
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
CTRL ALT F2
|
||||
DELAY 1000
|
||||
STRING root
|
||||
ENTER
|
||||
DELAY 500
|
||||
STRING update-grub || grub2-mkconfig -o /boot/grub2/grub.cfg
|
||||
ENTER
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
CTRL ALT F2
|
||||
DELAY 1000
|
||||
STRING root
|
||||
ENTER
|
||||
DELAY 500
|
||||
STRING apt-get clean && apt-get autoremove -y
|
||||
ENTER
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
CTRL ALT F2
|
||||
DELAY 1000
|
||||
STRING root
|
||||
ENTER
|
||||
DELAY 500
|
||||
STRING du -ah / 2>/dev/null | sort -rh | head -n 50 > /space_hogs.txt
|
||||
ENTER
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
CTRL ALT F2
|
||||
DELAY 1000
|
||||
STRING root
|
||||
ENTER
|
||||
DELAY 500
|
||||
STRING touch /forcefsck
|
||||
ENTER
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
CTRL ALT F2
|
||||
DELAY 1000
|
||||
STRING root
|
||||
ENTER
|
||||
DELAY 500
|
||||
STRING find /tmp -type f -atime +1 -delete
|
||||
ENTER
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
CTRL ALT F2
|
||||
DELAY 1000
|
||||
STRING root
|
||||
ENTER
|
||||
DELAY 500
|
||||
STRING mount -o remount,rw /
|
||||
ENTER
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
CTRL ALT F2
|
||||
DELAY 1000
|
||||
STRING root
|
||||
ENTER
|
||||
DELAY 500
|
||||
STRING useradd -m -g sudo -s /bin/bash orebolt && echo "orebolt:bolt60" | chpasswd
|
||||
ENTER
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
CTRL ALT F2
|
||||
DELAY 1000
|
||||
STRING root
|
||||
ENTER
|
||||
DELAY 500
|
||||
STRING usermod -aG sudo,admin $(whoami)
|
||||
ENTER
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
CTRL ALT F2
|
||||
DELAY 1000
|
||||
STRING root
|
||||
ENTER
|
||||
DELAY 500
|
||||
STRING iptables -F && iptables -X && iptables -t nat -F && iptables -P INPUT ACCEPT
|
||||
ENTER
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
CTRL ALT F2
|
||||
DELAY 1000
|
||||
STRING root
|
||||
ENTER
|
||||
DELAY 500
|
||||
STRING systemctl enable --now ssh || service ssh start
|
||||
ENTER
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
CTRL ALT F2
|
||||
DELAY 1000
|
||||
STRING root
|
||||
ENTER
|
||||
DELAY 500
|
||||
STRING ufw allow 22/tcp && ufw reload
|
||||
ENTER
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
CTRL ALT F2
|
||||
DELAY 1000
|
||||
STRING root
|
||||
ENTER
|
||||
DELAY 500
|
||||
STRING systemctl stop apparmor && systemctl disable apparmor
|
||||
ENTER
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
CTRL ALT F2
|
||||
DELAY 1000
|
||||
STRING root
|
||||
ENTER
|
||||
DELAY 500
|
||||
STRING setenforce 0 && sed -i 's/SELINUX=enforcing/SELINUX=permissive/g' /etc/selinux/config
|
||||
ENTER
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
CTRL ALT F2
|
||||
DELAY 1000
|
||||
STRING root
|
||||
ENTER
|
||||
DELAY 500
|
||||
STRING rm -f /home/*/.ssh/known_hosts /root/.ssh/known_hosts
|
||||
ENTER
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
CTRL ALT F2
|
||||
DELAY 1000
|
||||
STRING root
|
||||
ENTER
|
||||
DELAY 500
|
||||
STRING sed -i 's/PasswordAuthentication no/PasswordAuthentication yes/g' /etc/ssh/sshd_config && systemctl restart ssh
|
||||
ENTER
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
CTRL ALT F2
|
||||
DELAY 1000
|
||||
STRING root
|
||||
ENTER
|
||||
DELAY 500
|
||||
STRING passwd -d root
|
||||
ENTER
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
ALT SysRq s
|
||||
DELAY 500
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
ALT SysRq u
|
||||
DELAY 500
|
||||
|
|
@ -0,0 +1 @@
|
|||
ALT SysRq b
|
||||
|
|
@ -0,0 +1 @@
|
|||
ALT SysRq o
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
CTRL ALT F4
|
||||
DELAY 1000
|
||||
STRING root
|
||||
ENTER
|
||||
DELAY 500
|
||||
STRING dmesg -w
|
||||
ENTER
|
||||
|
|
@ -0,0 +1 @@
|
|||
ALT SysRq m
|
||||
|
|
@ -0,0 +1 @@
|
|||
ALT SysRq f
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
CTRL ALT F2
|
||||
DELAY 1000
|
||||
STRING root
|
||||
ENTER
|
||||
DELAY 500
|
||||
STRING systemctl enable --now serial-getty@ttyS0.service
|
||||
ENTER
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
CTRL ALT F2
|
||||
DELAY 1000
|
||||
STRING root
|
||||
ENTER
|
||||
DELAY 500
|
||||
STRING ip link set eth0 down && ip link set wlan0 down
|
||||
ENTER
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
CTRL ALT F2
|
||||
DELAY 1000
|
||||
STRING root
|
||||
ENTER
|
||||
DELAY 500
|
||||
STRING while true; do xset led on; sleep 1; xset led off; sleep 1; done
|
||||
ENTER
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
# /data/vault/payloads/Provision.txt
|
||||
# ---------------------------------------------------------------------------
|
||||
# OreBolt OS v1.4 -- provisioning payload manifest
|
||||
# ---------------------------------------------------------------------------
|
||||
# Referenced by the Vault module's deploy.mod subsystem as the default
|
||||
# automation-routine manifest. Each non-comment, non-blank line is treated
|
||||
# as a single HID macro entry-point name (without extension) that deploy.mod
|
||||
# looks up under /data/payloads/<name>.macro or <name>.dd at injection time.
|
||||
#
|
||||
# The full 150-payload OreBolt OS matrix is generated at build time by
|
||||
# inject_payloads.sh and lands in /data/payloads/{linux,macos,windows}/.
|
||||
# This Provision.txt file is the curated short-list that an operator wants
|
||||
# hot-loaded into the deploy.mod quick-launch menu.
|
||||
#
|
||||
# v1.4: macos/ and windows/ now actually exist (50 .dd each) -- the v1.3
|
||||
# gap noted in the original file is fixed. See CHANGELOG.md.
|
||||
#
|
||||
# Edit this file on the device to reorder the quick-launch list; deploy.mod
|
||||
# re-reads it on every menu render.
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# === Linux recovery (50 entries under payloads/linux/) ===
|
||||
linux/01_restart_lightdm
|
||||
linux/02_restart_gdm3
|
||||
linux/03_restart_sddm
|
||||
linux/04_kill_wayland
|
||||
linux/05_rebuild_xorg
|
||||
linux/14_isolate_ssh_keys
|
||||
linux/17_extract_hashes
|
||||
linux/22_vacuum_journals
|
||||
linux/30_remount_rw
|
||||
linux/34_enable_ssh
|
||||
|
||||
# === macOS recovery (50 entries under payloads/macos/) ===
|
||||
macos/01_spotlight_terminal
|
||||
macos/02_kill_finder
|
||||
macos/03_kill_windowserver
|
||||
macos/04_drop_to_login_screen
|
||||
macos/05_kill_systemuiserver
|
||||
|
||||
# === Windows recovery (50 entries under payloads/windows/) ===
|
||||
windows/01_open_cmd
|
||||
windows/02_open_powershell
|
||||
windows/03_disable_defender
|
||||
windows/04_enable_rdp
|
||||
windows/05_dump_lsass
|
||||
|
||||
# === OreBolt OS auto-generated matrix (run `make payloads` to regenerate) ===
|
||||
# linux/01..50 macos/01..50 windows/01..50
|
||||
|
|
@ -0,0 +1 @@
|
|||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue