A hackable pocket rig for the HiFiWalker H2 digital audio player and its rebadged siblings (Surfans F20, Aigo Eros Q, Phinistec Z6, Agptek H3). All of these devices share the same Ingenic X1000E (JZ4760 family) SoC and run a Linux userspace that OreBolt OS extends with 17 LVGL UI modules, a hardware panic-purge subsystem, a 150-payload HID macro matrix, a unified BLE/WiFi proximity alarm with radar display, and an AGPL-licensed mesh networking layer.
This commit is contained in:
commit
0d3bcabcfc
|
|
@ -0,0 +1,218 @@
|
|||
# OreBolt OS v1.7 -- Architecture
|
||||
|
||||
## 1. Target hardware
|
||||
|
||||
| Component | Spec |
|
||||
|---|---|
|
||||
| Device | **Aigo Eros Q** family: HiFiWalker H2, Surfans F20, 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 | system (reset.mod) | GPL-2.0+ | System Tools: operational purge, Hiby Player restore, reboot. Detects user-installed Rockbox for reboot-to-Rockbox option. |
|
||||
| 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 [--backup-stock] 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. Firmware coexistence (SD-card-only)
|
||||
|
||||
OreBolt OS and the stock Hiby Player both boot from the FAT32
|
||||
MicroSD card. The 8 MB SPI flash holds only the bootloader and is
|
||||
never modified by OreBolt OS.
|
||||
|
||||
| Environment | SD card presence | Boot method |
|
||||
|---|---|---|
|
||||
| **Hiby Player** (stock) | Factory files in /usr/bin/ (hiby_player), /etc/, /res/ | Stock bootloader loads kernel + stock init |
|
||||
| **OreBolt OS** | /apps/*.mod, /usr/bin/h2_test, /usr/lib/liblvgl.so, /etc/init.d/S99broker | Stock bootloader loads kernel, S99broker starts h2_test |
|
||||
|
||||
If a user has separately installed Rockbox on the same SD card (a
|
||||
third-party, user-managed firmware), the System Tools module detects
|
||||
its presence and offers a reboot option. OreBolt OS does not ship,
|
||||
bundle, or install Rockbox. The user installs and manages Rockbox
|
||||
independently.
|
||||
|
||||
Switching between Hiby Player and OreBolt OS is a matter of which
|
||||
files are on the SD card. The System Tools module (reset.mod,
|
||||
displayed as "system" in the menu) handles the stock restore. The
|
||||
stock firmware backup is created at deploy time with `--backup-stock`.
|
||||
|
||||
## 8. Filesystem layout on the device
|
||||
|
||||
```
|
||||
/
|
||||
├── usr/
|
||||
│ ├── bin/
|
||||
│ │ ├── h2_test # OreBolt OS launcher
|
||||
│ │ ├── retro_input_mapper # uinput gamepad 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
|
||||
│ │ ├── 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
|
||||
│ ├── stock_backup/ # Stock firmware snapshot (--backup-stock)
|
||||
│ │ ├── usr/bin/ # Stock binaries (hiby_player)
|
||||
│ │ ├── etc/ # Stock init configuration
|
||||
│ │ ├── res/ # Stock resources
|
||||
│ │ └── setting/ # Stock player settings
|
||||
│ ├── forensics_bin/
|
||||
│ ├── roms/ # Retro game ROM directories
|
||||
│ │ ├── nes/ snes/ gb/ gbc/ gba/
|
||||
│ │ ├── genesis/ sms/ gg/
|
||||
│ │ └── pce/ atari2600/ ngp/
|
||||
│ └── vterm/freedos/bin/
|
||||
```
|
||||
|
||||
## 9. 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,264 @@
|
|||
# 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
|
||||
|
||||
### System Tools Module Rewrite
|
||||
|
||||
The reset module is now a multi-function System Tools menu inspired
|
||||
by Rockbox's tools menu on the Aigo Eros Q platform. The module
|
||||
label in the launcher menu changes from "reset" to "system".
|
||||
|
||||
### Changed
|
||||
|
||||
- **reset.c rewritten as System Tools.** Four-option scrollable
|
||||
menu: PURGE (operational cleanup), HIBY PLAYER (restore stock
|
||||
firmware from /data/stock_backup/ and reboot), ROCKBOX (reboot
|
||||
if .rockbox/ detected), REBOOT. Confirmation overlays for all
|
||||
destructive actions. Status overlays with error/success states.
|
||||
Rockbox detection is passive only -- OreBolt OS does not ship or
|
||||
install Rockbox.
|
||||
- **build.sh gains --backup-stock flag.** Phase 8a snapshots the
|
||||
stock firmware files (usr/bin/, usr/lib/, etc/, res/, setting/)
|
||||
into /data/stock_backup/ before any OreBolt files are written.
|
||||
This enables the "Restore Hiby Player" option in System Tools.
|
||||
- **SD-card-only firmware model.** All documentation now reflects
|
||||
that OreBolt OS and Hiby Player coexist through SD card file
|
||||
management alone. The SPI flash bootloader is never modified.
|
||||
SD card is FAT32 single-partition only.
|
||||
- **Device family identifier set to Aigo Eros Q.** ARCHITECTURE.md
|
||||
and README.md now lead with "Aigo Eros Q family" as the canonical
|
||||
hardware platform name.
|
||||
- **ARCHITECTURE.md adds Section 7: Firmware coexistence.** Table
|
||||
showing which SD card files correspond to which firmware
|
||||
environment, and how switching works.
|
||||
- **ARCHITECTURE.md filesystem layout updated.** Now includes
|
||||
/data/stock_backup/, /data/roms/ paths.
|
||||
- **main.c module table: "reset" label changed to "system".** The
|
||||
.mod binary filename remains reset.mod for build compatibility.
|
||||
- **Source comments cleaned across all .c/.h files.** Version-
|
||||
prefixed comment lines replaced with intentional, self-describing
|
||||
headers. No functional code changed (except reset.c).
|
||||
- **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.
|
||||
- **retro.mod** (retro game launcher) is properly listed across all
|
||||
documentation with its full feature description.
|
||||
- **S99broker: OREBOLT_VERSION exports 1.7**, module count 17.
|
||||
|
||||
## [v1.6] -- 2026-07-15
|
||||
|
||||
### The "Unified Radar" Release
|
||||
|
||||
v1.6 consolidates the standalone `radar.mod` (BLE hex grid scanner) and the
|
||||
`wifi.mod` adapter stub 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 WiFi integration stub.
|
||||
|
||||
### Removed
|
||||
|
||||
- **radar.mod (orebolt-radar/noise_radar.c).** Superseded by
|
||||
proxalarm's radar_ui canvas renderer (rotating sweep, range rings,
|
||||
velocity vectors, alarm overlay). The `modules/orebolt-radar/`
|
||||
directory is removed.
|
||||
- **wifi.mod (orebolt-wifi/wifi.c).** The v1.4 stub had no `main()`
|
||||
function and could not 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 removed.
|
||||
|
||||
### 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" 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
|
||||
provides full implementations for 13 of those 18 modules, 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 uses the fork/exec launcher model.** v1.4's main.c
|
||||
ran a single monolithic LVGL event loop with all modules as stub
|
||||
callbacks. v1.5 uses the 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 provides both module 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 provides both logging APIs.** 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 set 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:**
|
||||
- **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
|
||||
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.
|
||||
- **S98retro-input init script** -- Full PID tracking,
|
||||
daemon existence checks, graceful TERM to 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/{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 First Deploy with Stock Backup
|
||||
|
||||
If this is the first time deploying OreBolt OS to this SD card, use
|
||||
`--backup-stock` to snapshot the stock Hiby Player firmware before
|
||||
any OreBolt files are written. This enables the "Restore Hiby
|
||||
Player" option in System Tools.
|
||||
|
||||
```bash
|
||||
SD_CARD_MOUNT=/mnt/h2-sd ./build.sh --deploy --backup-stock
|
||||
```
|
||||
|
||||
### 6.4 Subsequent Deploys
|
||||
|
||||
```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,196 @@
|
|||
# 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
|
||||
|
||||
# First deploy: back up stock firmware, then deploy
|
||||
SD_CARD_MOUNT=/mnt/h2-sd ./build.sh --deploy --backup-stock
|
||||
|
||||
# Subsequent deploys (stock backup already exists):
|
||||
# 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,294 @@
|
|||
# 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 HiFiWalker H2 is one of five rebadged devices built on the same
|
||||
PCB — the **Aigo Eros Q** family. They are interchangeable from a
|
||||
firmware perspective. OreBolt OS and the stock Hiby Player both boot
|
||||
from the FAT32 MicroSD card; the SPI flash bootloader is never
|
||||
modified.
|
||||
|
||||
| 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 share the same PCB and 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.
|
||||
|
||||
**`system.mod`** (file: reset.mod) — System Tools, inspired by Rockbox's
|
||||
tools menu on the Aigo Eros Q platform. Four-option menu:
|
||||
|
||||
- **Purge** — Operational cleanup. Kills child processes (dosbox,
|
||||
emulators), brings usb0 down, wipes `/data/loot_drop/`, calls
|
||||
`sync()`. Does not touch any firmware files.
|
||||
- **Hiby Player** — Restores the stock firmware from
|
||||
`/data/stock_backup/` (created at deploy time with
|
||||
`--backup-stock`), removes all OreBolt OS files, and reboots
|
||||
into the original music player.
|
||||
- **Rockbox** — If the user has separately installed Rockbox on the
|
||||
SD card, this option reboots the device so Rockbox can load.
|
||||
OreBolt OS does not ship or install Rockbox; the user manages it
|
||||
independently. OreBolt OS files are not removed.
|
||||
- **Reboot** — Standard warm reboot.
|
||||
|
||||
The Hiby Player and OreBolt OS coexist on the same hardware
|
||||
through SD card file management alone. If the user has installed
|
||||
Rockbox separately, it can also share the SD card.
|
||||
|
||||
### 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,266 @@
|
|||
#!/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
|
||||
BACKUP_STOCK=0
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--deploy) DEPLOY=1 ;;
|
||||
--deploy-only) DEPLOY=1; SKIP_BUILD=1 ;;
|
||||
--backup-stock) BACKUP_STOCK=1 ;;
|
||||
--clean) CLEAN=1 ;;
|
||||
--help|-h)
|
||||
cat <<EOF
|
||||
OreBolt OS v1.7 build orchestrator
|
||||
Usage: $0 [--deploy|--deploy-only|--backup-stock|--clean|--help]
|
||||
|
||||
Options:
|
||||
--deploy Build and deploy to SD card
|
||||
--deploy-only Deploy only (skip build)
|
||||
--backup-stock Snapshot stock firmware before first deploy
|
||||
(enables "Restore Hiby Player" in System Tools)
|
||||
--clean Remove build artifacts
|
||||
|
||||
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
|
||||
[ -d "$SD_CARD_MOUNT" ] || die "SD card mount point missing: $SD_CARD_MOUNT"
|
||||
|
||||
# 8a: backup stock firmware (first deploy only)
|
||||
if [ "$BACKUP_STOCK" = "1" ]; then
|
||||
log "Phase 8a: backing up stock firmware..."
|
||||
STOCK_DIR="$SD_CARD_MOUNT/data/stock_backup"
|
||||
if [ -d "$STOCK_DIR" ]; then
|
||||
warn "stock backup already exists at $STOCK_DIR (skipping)"
|
||||
else
|
||||
mkdir -p "$STOCK_DIR/usr/bin" "$STOCK_DIR/usr/lib" \
|
||||
"$STOCK_DIR/etc/init.d"
|
||||
# Snapshot stock binaries (includes Hiby Player)
|
||||
cp -a "$SD_CARD_MOUNT/usr/bin/"* "$STOCK_DIR/usr/bin/" 2>/dev/null || true
|
||||
cp -a "$SD_CARD_MOUNT/usr/lib/"* "$STOCK_DIR/usr/lib/" 2>/dev/null || true
|
||||
# Snapshot stock init configuration
|
||||
cp -a "$SD_CARD_MOUNT/etc/inittab" "$STOCK_DIR/etc/" 2>/dev/null || true
|
||||
cp -a "$SD_CARD_MOUNT/etc/init.d/"* "$STOCK_DIR/etc/init.d/" 2>/dev/null || true
|
||||
# Snapshot any other known stock paths
|
||||
cp -a "$SD_CARD_MOUNT/res" "$STOCK_DIR/res" 2>/dev/null || true
|
||||
cp -a "$SD_CARD_MOUNT/setting" "$STOCK_DIR/setting" 2>/dev/null || true
|
||||
sync
|
||||
ok "stock firmware backed up to $STOCK_DIR"
|
||||
ok "Hiby Player restore is now available in System Tools"
|
||||
fi
|
||||
fi
|
||||
|
||||
# 8b: deploy OreBolt OS
|
||||
log "Phase 8b: deploying OreBolt OS to $SD_CARD_MOUNT..."
|
||||
mkdir -p "$SD_CARD_MOUNT/apps" "$SD_CARD_MOUNT/usr/lib" \
|
||||
"$SD_CARD_MOUNT/usr/bin" "$SD_CARD_MOUNT/etc/init.d" \
|
||||
"$SD_CARD_MOUNT/data/payloads"
|
||||
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/" 2>/dev/null || true
|
||||
cp -v liborebolt.a "$SD_CARD_MOUNT/" 2>/dev/null || true
|
||||
sync
|
||||
ok "OreBolt OS 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, "system", "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,587 @@
|
|||
/*
|
||||
* reset.c -- System Tools (Aigo Eros Q platform)
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*
|
||||
* Multi-function system tools module for the Aigo Eros Q hardware
|
||||
* family (HiFiWalker H2, Surfans F20, Phinistec Z6, Agptek H3).
|
||||
*
|
||||
* OreBolt OS, Hiby Player (stock firmware), and Rockbox all boot
|
||||
* from the FAT32 MicroSD card -- the SPI flash bootloader is never
|
||||
* modified. Switching between them is a matter of which files live
|
||||
* on the SD card.
|
||||
*
|
||||
* Menu options:
|
||||
* 1. PURGE -- Operational cleanup. Kills child processes
|
||||
* (dosbox, emulators), brings usb0 down, wipes
|
||||
* /data/loot_drop/, calls sync(). Does NOT
|
||||
* touch any firmware files.
|
||||
* 2. HIBY -- Restore stock firmware (Hiby Player). Removes
|
||||
* all OreBolt OS files from the SD card and
|
||||
* restores the stock backup from /data/stock_backup/.
|
||||
* Reboots into the original music player.
|
||||
* 3. ROCKBOX -- If the user has separately installed Rockbox on
|
||||
* the SD card (detected by presence of .rockbox/
|
||||
* or rockbox/ directory), reboots the device.
|
||||
* OreBolt OS does NOT ship, bundle, or install
|
||||
* Rockbox. This is detection-only.
|
||||
* 4. REBOOT -- Warm reboot the device.
|
||||
*
|
||||
* STOCK BACKUP CONVENTION:
|
||||
* When OreBolt OS is first deployed (build.sh --deploy), the
|
||||
* deploy script snapshots the stock firmware files into
|
||||
* /data/stock_backup/ before writing any OreBolt files. The
|
||||
* expected layout is:
|
||||
*
|
||||
* /data/stock_backup/
|
||||
* usr/bin/hiby_player (stock music player binary)
|
||||
* etc/inittab (stock init configuration)
|
||||
* etc/init.d/S* (stock init scripts)
|
||||
* ...any other stock files...
|
||||
*
|
||||
* If /data/stock_backup/ does not exist, the HIBY option shows
|
||||
* a warning that no backup is available.
|
||||
*
|
||||
* INPUT:
|
||||
* ROTATE = scroll menu
|
||||
* PLAY = select / confirm
|
||||
* BACK = cancel / exit
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <signal.h>
|
||||
#include <dirent.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
#include <linux/input.h>
|
||||
#include <linux/reboot.h>
|
||||
#include <sys/reboot.h>
|
||||
#include "h2_ui.h"
|
||||
#include "log_manager.h"
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Menu items */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
typedef enum {
|
||||
OPT_PURGE = 0,
|
||||
OPT_HIBY,
|
||||
OPT_ROCKBOX,
|
||||
OPT_REBOOT,
|
||||
OPT_COUNT
|
||||
} menu_opt_t;
|
||||
|
||||
static const char *opt_labels[] = {
|
||||
"PURGE - Clear data & connections",
|
||||
"HIBY PLAYER - Restore stock FW",
|
||||
"ROCKBOX - Reboot to Rockbox",
|
||||
"REBOOT - Restart device",
|
||||
};
|
||||
#define NUM_OPTS ((int)(sizeof(opt_labels) / sizeof(opt_labels[0])))
|
||||
|
||||
static const char *opt_descs[] = {
|
||||
"Kills child procs, wipes loot_drop,\nbrings usb0 down. Safe, non-destructive.",
|
||||
"Removes OreBolt OS files, restores\nstock Hiby Player from backup, reboots.",
|
||||
"Reboots so the Rockbox bootloader\ncan chainload .rockbox/ on the SD card.",
|
||||
"Standard warm reboot via kernel.",
|
||||
};
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* State */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
static int input_fd = -1;
|
||||
static int selection = 0;
|
||||
static lv_obj_t *menu_list = NULL;
|
||||
static lv_obj_t *desc_label = NULL;
|
||||
static lv_obj_t *status_lbl = NULL;
|
||||
static lv_obj_t *list_btns[OPT_COUNT];
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Filesystem helpers */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
/*
|
||||
* Recursively remove all files and directories under `path`.
|
||||
* Does not follow symlinks. Skips `.` and `..`.
|
||||
* Returns 0 on success, -1 on error.
|
||||
*/
|
||||
static int rm_rf(const char *path) {
|
||||
DIR *d = opendir(path);
|
||||
if (!d) return -1;
|
||||
|
||||
struct dirent *ent;
|
||||
int ret = 0;
|
||||
while ((ent = readdir(d)) != NULL) {
|
||||
if (ent->d_name[0] == '.') continue;
|
||||
|
||||
char fullpath[512];
|
||||
snprintf(fullpath, sizeof(fullpath), "%s/%s", path, ent->d_name);
|
||||
|
||||
struct stat st;
|
||||
if (lstat(fullpath, &st) != 0) continue;
|
||||
|
||||
if (S_ISDIR(st.st_mode)) {
|
||||
ret |= rm_rf(fullpath);
|
||||
rmdir(fullpath);
|
||||
} else {
|
||||
ret |= unlink(fullpath);
|
||||
}
|
||||
}
|
||||
closedir(d);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Check if a directory exists and is non-empty.
|
||||
* Returns 1 if directory exists with at least one non-dot entry.
|
||||
*/
|
||||
static int dir_exists(const char *path) {
|
||||
DIR *d = opendir(path);
|
||||
if (!d) return 0;
|
||||
|
||||
struct dirent *ent;
|
||||
int found = 0;
|
||||
while ((ent = readdir(d)) != NULL) {
|
||||
if (ent->d_name[0] == '.') continue;
|
||||
found = 1;
|
||||
break;
|
||||
}
|
||||
closedir(d);
|
||||
return found;
|
||||
}
|
||||
|
||||
/*
|
||||
* Check if a file exists and is executable.
|
||||
*/
|
||||
static int bin_exists(const char *path) {
|
||||
return access(path, X_OK) == 0;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Actions */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
static void action_purge(void) {
|
||||
LOG_INF("executing operational purge");
|
||||
|
||||
/* Kill known child processes */
|
||||
const char *procs[] = { "dosbox", "snes9x", "fceux", "mednafen",
|
||||
"dgen", "gambatte", "gpsp", "stella", NULL };
|
||||
for (int i = 0; procs[i]; i++) {
|
||||
char cmd[128];
|
||||
snprintf(cmd, sizeof(cmd), "pidof %s 2>/dev/null", procs[i]);
|
||||
FILE *pgrep = popen(cmd, "r");
|
||||
if (pgrep) {
|
||||
char pidbuf[16];
|
||||
if (fgets(pidbuf, sizeof(pidbuf), pgrep)) {
|
||||
long pid = atol(pidbuf);
|
||||
if (pid > 0) {
|
||||
kill(pid, SIGTERM);
|
||||
LOG_INF("killed %s (pid %ld)", procs[i], pid);
|
||||
}
|
||||
}
|
||||
pclose(pgrep);
|
||||
}
|
||||
}
|
||||
|
||||
/* Bring usb0 down */
|
||||
int fd = open("/sys/class/net/usb0/operstate", O_WRONLY);
|
||||
if (fd >= 0) { write(fd, "down", 4); close(fd); }
|
||||
|
||||
/* Wipe loot_drop */
|
||||
rm_rf("/data/loot_drop");
|
||||
mkdir("/data/loot_drop", 0755);
|
||||
|
||||
sync();
|
||||
LOG_INF("operational purge complete");
|
||||
}
|
||||
|
||||
static void action_hiby(void) {
|
||||
LOG_INF("initiating stock firmware restore (Hiby Player)");
|
||||
|
||||
if (!dir_exists("/data/stock_backup")) {
|
||||
LOG_ERR("no stock backup found at /data/stock_backup/");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Step 1: Stop OreBolt init scripts */
|
||||
system("/etc/init.d/S98retro-input stop 2>/dev/null");
|
||||
system("/etc/init.d/S99broker stop 2>/dev/null");
|
||||
system("/etc/init.d/bt_input_daemon.sh stop 2>/dev/null");
|
||||
|
||||
/* Step 2: Remove all OreBolt module binaries */
|
||||
rm_rf("/apps");
|
||||
mkdir("/apps", 0755);
|
||||
|
||||
/* Step 3: Remove OreBolt-specific binaries */
|
||||
unlink("/usr/bin/h2_test");
|
||||
unlink("/usr/bin/retro_input_mapper");
|
||||
|
||||
/* Step 4: Remove OreBolt init scripts */
|
||||
unlink("/etc/init.d/S98retro-input");
|
||||
unlink("/etc/init.d/S99broker");
|
||||
unlink("/etc/init.d/bt_input_daemon.sh");
|
||||
|
||||
/* Step 5: Remove OreBolt shared libraries */
|
||||
unlink("/usr/lib/liblvgl.so");
|
||||
|
||||
/* Step 6: Restore stock files from backup */
|
||||
if (dir_exists("/data/stock_backup/usr")) {
|
||||
system("cp -a /data/stock_backup/usr/bin/* /usr/bin/ 2>/dev/null");
|
||||
system("cp -a /data/stock_backup/usr/lib/* /usr/lib/ 2>/dev/null");
|
||||
}
|
||||
if (dir_exists("/data/stock_backup/etc")) {
|
||||
system("cp -a /data/stock_backup/etc/inittab /etc/inittab 2>/dev/null");
|
||||
system("cp -a /data/stock_backup/etc/init.d/* /etc/init.d/ 2>/dev/null");
|
||||
}
|
||||
|
||||
sync();
|
||||
LOG_INF("stock firmware restored, rebooting to Hiby Player");
|
||||
sleep(1);
|
||||
sync();
|
||||
reboot(RB_AUTOBOOT);
|
||||
}
|
||||
|
||||
static void action_rockbox(void) {
|
||||
if (!dir_exists("/.rockbox") && !dir_exists("/rockbox")) {
|
||||
LOG_ERR("no .rockbox/ or rockbox/ directory found on SD card");
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* OreBolt OS does NOT ship or install Rockbox. If the user
|
||||
* has installed Rockbox on their SD card independently, a
|
||||
* reboot is sufficient for the Rockbox bootloader (or the
|
||||
* stock bootloader with Rockbox boot files) to load it.
|
||||
*
|
||||
* We do NOT modify any files -- Rockbox manages its own boot
|
||||
* presence. A reboot is sufficient.
|
||||
*/
|
||||
LOG_INF("Rockbox detected, rebooting to Rockbox");
|
||||
sleep(1);
|
||||
sync();
|
||||
reboot(RB_AUTOBOOT);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* UI */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
static void build_menu(void) {
|
||||
lv_obj_clean(menu_list);
|
||||
|
||||
for (int i = 0; i < NUM_OPTS; i++) {
|
||||
list_btns[i] = lv_list_add_btn(menu_list, LV_SYMBOL_LIST,
|
||||
opt_labels[i]);
|
||||
lv_obj_set_style_text_color(list_btns[i],
|
||||
COLOR_TEXT, LV_PART_MAIN);
|
||||
lv_obj_set_style_text_font(list_btns[i],
|
||||
&lv_font_montserrat_10, LV_PART_MAIN);
|
||||
}
|
||||
}
|
||||
|
||||
static void update_desc(void) {
|
||||
lv_label_set_text(desc_label, opt_descs[selection]);
|
||||
|
||||
/* Color-code destructive options */
|
||||
lv_color_t desc_color = COLOR_TEXT;
|
||||
if (selection == OPT_HIBY)
|
||||
desc_color = COLOR_PRIMARY; /* red-orange: destructive */
|
||||
else if (selection == OPT_ROCKBOX)
|
||||
desc_color = lv_color_make(100, 180, 255); /* blue: external */
|
||||
|
||||
lv_obj_set_style_text_color(desc_label, desc_color, LV_PART_MAIN);
|
||||
}
|
||||
|
||||
static void scroll_menu(int delta) {
|
||||
selection += delta;
|
||||
if (selection < 0) selection = NUM_OPTS - 1;
|
||||
if (selection >= NUM_OPTS) selection = 0;
|
||||
|
||||
if (selection < NUM_OPTS && list_btns[selection]) {
|
||||
lv_obj_scroll_to_view(list_btns[selection], LV_ANIM_ON);
|
||||
}
|
||||
update_desc();
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Confirmation overlay */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
static int show_confirm(const char *warning, const char *action_name) {
|
||||
lv_obj_clean(lv_scr_act());
|
||||
|
||||
lv_obj_t *scr = lv_scr_act();
|
||||
|
||||
lv_obj_t *box = lv_obj_create(scr);
|
||||
lv_obj_set_size(box, 296, 180);
|
||||
lv_obj_align(box, LV_ALIGN_CENTER, 0, 0);
|
||||
lv_obj_set_style_bg_color(box, lv_color_make(24, 18, 18), LV_PART_MAIN);
|
||||
lv_obj_set_style_border_color(box, COLOR_PRIMARY, LV_PART_MAIN);
|
||||
lv_obj_set_style_border_width(box, 2, LV_PART_MAIN);
|
||||
|
||||
lv_obj_t *title = lv_label_create(box);
|
||||
lv_label_set_text(title, action_name);
|
||||
lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 12);
|
||||
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);
|
||||
|
||||
lv_obj_t *warn = lv_label_create(box);
|
||||
lv_label_set_text(warn, warning);
|
||||
lv_obj_align(warn, LV_ALIGN_TOP_MID, 0, 38);
|
||||
lv_obj_set_style_text_color(warn, COLOR_TEXT, LV_PART_MAIN);
|
||||
lv_obj_set_style_text_font(warn, &lv_font_montserrat_10, LV_PART_MAIN);
|
||||
lv_obj_set_style_text_align(warn, LV_TEXT_ALIGN_CENTER, LV_PART_MAIN);
|
||||
|
||||
lv_obj_t *prompt = lv_label_create(box);
|
||||
lv_label_set_text(prompt, "[PLAY] CONFIRM [BACK] CANCEL");
|
||||
lv_obj_align(prompt, LV_ALIGN_BOTTOM_MID, 0, -16);
|
||||
lv_obj_set_style_text_color(prompt, lv_color_make(120, 120, 140),
|
||||
LV_PART_MAIN);
|
||||
lv_obj_set_style_text_font(prompt, &lv_font_montserrat_10, LV_PART_MAIN);
|
||||
|
||||
/* Wait for PLAY or BACK */
|
||||
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.value == 1) {
|
||||
if (ev.code == H2_KEY_PLAY) {
|
||||
lv_label_set_text(prompt, "EXECUTING...");
|
||||
lv_obj_set_style_text_color(prompt, COLOR_ACCENT,
|
||||
LV_PART_MAIN);
|
||||
lv_timer_handler();
|
||||
return 1; /* confirmed */
|
||||
}
|
||||
if (ev.code == H2_KEY_BACK) return 0; /* cancelled */
|
||||
}
|
||||
}
|
||||
usleep(15000);
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Status overlay (result message + BACK to dismiss) */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
static void show_status(const char *msg, int is_error) {
|
||||
lv_obj_clean(lv_scr_act());
|
||||
|
||||
lv_obj_t *scr = lv_scr_act();
|
||||
|
||||
lv_obj_t *box = lv_obj_create(scr);
|
||||
lv_obj_set_size(box, 296, 120);
|
||||
lv_obj_align(box, LV_ALIGN_CENTER, 0, 0);
|
||||
|
||||
lv_obj_t *lbl = lv_label_create(box);
|
||||
lv_label_set_text(lbl, msg);
|
||||
lv_obj_align(lbl, LV_ALIGN_CENTER, 0, 0);
|
||||
lv_obj_set_style_text_align(lbl, LV_TEXT_ALIGN_CENTER, LV_PART_MAIN);
|
||||
lv_obj_set_style_text_font(lbl, &lv_font_montserrat_10, LV_PART_MAIN);
|
||||
|
||||
if (is_error)
|
||||
lv_obj_set_style_text_color(lbl, COLOR_PRIMARY, LV_PART_MAIN);
|
||||
else
|
||||
lv_obj_set_style_text_color(lbl, COLOR_ACCENT, LV_PART_MAIN);
|
||||
|
||||
lv_obj_t *hint = lv_label_create(scr);
|
||||
lv_label_set_text(hint, "[BACK] Return to menu");
|
||||
lv_obj_align(hint, LV_ALIGN_BOTTOM_MID, 0, -4);
|
||||
lv_obj_set_style_text_color(hint, lv_color_make(80, 85, 100),
|
||||
LV_PART_MAIN);
|
||||
lv_obj_set_style_text_font(hint, &lv_font_montserrat_10, LV_PART_MAIN);
|
||||
|
||||
/* Wait for BACK */
|
||||
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.value == 1 &&
|
||||
ev.code == H2_KEY_BACK)
|
||||
break;
|
||||
}
|
||||
usleep(15000);
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Rebuild the main menu after a confirmation/status overlay */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
static void rebuild_main_ui(void) {
|
||||
lv_obj_clean(lv_scr_act());
|
||||
lv_obj_t *scr = lv_scr_act();
|
||||
|
||||
lv_obj_t *title = lv_label_create(scr);
|
||||
lv_label_set_text(title, "SYSTEM TOOLS");
|
||||
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);
|
||||
|
||||
lv_obj_t *sub = lv_label_create(scr);
|
||||
lv_label_set_text(sub, "Aigo Eros Q | X1000E");
|
||||
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);
|
||||
|
||||
menu_list = lv_list_create(scr);
|
||||
lv_obj_set_size(menu_list, 296, 140);
|
||||
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);
|
||||
|
||||
desc_label = lv_label_create(scr);
|
||||
lv_obj_set_size(desc_label, 296, 36);
|
||||
lv_obj_align(desc_label, LV_ALIGN_TOP_MID, 0, 186);
|
||||
lv_obj_set_style_text_color(desc_label, COLOR_TEXT, LV_PART_MAIN);
|
||||
lv_obj_set_style_text_font(desc_label, &lv_font_montserrat_10,
|
||||
LV_PART_MAIN);
|
||||
lv_obj_set_style_text_align(desc_label, LV_TEXT_ALIGN_CENTER,
|
||||
LV_PART_MAIN);
|
||||
|
||||
status_lbl = lv_label_create(scr);
|
||||
lv_label_set_text(status_lbl, "ROTATE=scroll PLAY=select BACK=exit");
|
||||
lv_obj_align(status_lbl, LV_ALIGN_BOTTOM_MID, 0, -4);
|
||||
lv_obj_set_style_text_color(status_lbl, lv_color_make(80, 85, 100),
|
||||
LV_PART_MAIN);
|
||||
lv_obj_set_style_text_font(status_lbl, &lv_font_montserrat_10,
|
||||
LV_PART_MAIN);
|
||||
|
||||
build_menu();
|
||||
update_desc();
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* main */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
int main(void) {
|
||||
init_h2_graphics_runtime("SYSTEM TOOLS");
|
||||
LOG_INF("reset/system-tools module started");
|
||||
|
||||
input_fd = open("/dev/input/event0", O_RDONLY | O_NONBLOCK);
|
||||
|
||||
/* Build initial UI */
|
||||
rebuild_main_ui();
|
||||
|
||||
/* Detect available options */
|
||||
int has_stock_backup = dir_exists("/data/stock_backup");
|
||||
int has_rockbox = dir_exists("/.rockbox") || dir_exists("/rockbox");
|
||||
|
||||
if (has_stock_backup)
|
||||
LOG_INF("stock backup detected: /data/stock_backup/");
|
||||
if (has_rockbox)
|
||||
LOG_INF("Rockbox detected on SD card");
|
||||
|
||||
/* 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 == 0 && ev.value != 0) {
|
||||
scroll_menu(ev.value > 0 ? 1 : -1);
|
||||
}
|
||||
|
||||
if (ev.type == EV_KEY && ev.value == 1) {
|
||||
if (ev.code == H2_KEY_BACK) break;
|
||||
|
||||
if (ev.code == H2_KEY_PLAY) {
|
||||
switch ((menu_opt_t)selection) {
|
||||
case OPT_PURGE:
|
||||
if (show_confirm(
|
||||
"Clears all captured data, kills\n"
|
||||
"child processes, drops network.\n"
|
||||
"OreBolt OS files are NOT removed.",
|
||||
"OPERATIONAL PURGE")) {
|
||||
action_purge();
|
||||
show_status("PURGE COMPLETE.\n\n"
|
||||
"All captured data cleared.", 0);
|
||||
}
|
||||
rebuild_main_ui();
|
||||
break;
|
||||
|
||||
case OPT_HIBY:
|
||||
if (!has_stock_backup) {
|
||||
show_status(
|
||||
"No stock backup found.\n\n"
|
||||
"/data/stock_backup/ does not\n"
|
||||
"exist. Was OreBolt OS deployed\n"
|
||||
"with --backup-stock?", 1);
|
||||
rebuild_main_ui();
|
||||
break;
|
||||
}
|
||||
if (show_confirm(
|
||||
"This will REMOVE OreBolt OS from\n"
|
||||
"the SD card and restore the stock\n"
|
||||
"Hiby Player firmware. All OreBolt\n"
|
||||
"modules, data, and configs will be\n"
|
||||
"permanently deleted.\n\n"
|
||||
"The device will reboot into the\n"
|
||||
"original Hiby music player.",
|
||||
"RESTORE HIBY PLAYER")) {
|
||||
/* This reboots -- does not return */
|
||||
action_hiby();
|
||||
}
|
||||
rebuild_main_ui();
|
||||
break;
|
||||
|
||||
case OPT_ROCKBOX:
|
||||
if (!has_rockbox) {
|
||||
show_status(
|
||||
"No Rockbox installation found.\n\n"
|
||||
"Install .rockbox/ on the SD card\n"
|
||||
"or use a Rockboot-flashed SD card.", 1);
|
||||
rebuild_main_ui();
|
||||
break;
|
||||
}
|
||||
if (show_confirm(
|
||||
"Reboot to Rockbox.\n\n"
|
||||
"The Rockbox bootloader will\n"
|
||||
"chainload from the SD card.\n"
|
||||
"OreBolt OS files are NOT removed.",
|
||||
"BOOT ROCKBOX")) {
|
||||
/* This reboots -- does not return */
|
||||
action_rockbox();
|
||||
}
|
||||
rebuild_main_ui();
|
||||
break;
|
||||
|
||||
case OPT_REBOOT:
|
||||
if (show_confirm(
|
||||
"Standard warm reboot.\n"
|
||||
"Device will restart normally.",
|
||||
"REBOOT DEVICE")) {
|
||||
LOG_INF("user-initiated reboot");
|
||||
sync();
|
||||
reboot(RB_AUTOBOOT);
|
||||
}
|
||||
rebuild_main_ui();
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
usleep(15000);
|
||||
}
|
||||
|
||||
if (input_fd >= 0) close(input_fd);
|
||||
LOG_INF("system-tools 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,55 @@
|
|||
# Stock Firmware Backup Convention
|
||||
|
||||
## Purpose
|
||||
|
||||
OreBolt OS and the stock Hiby Player firmware both boot from the
|
||||
FAT32 MicroSD card. The SPI flash bootloader is never modified.
|
||||
This directory holds a snapshot of the stock firmware so the System
|
||||
Tools module can restore the device to its factory state.
|
||||
|
||||
## How the backup is created
|
||||
|
||||
On the **first deployment only**, run:
|
||||
|
||||
```bash
|
||||
SD_CARD_MOUNT=/mnt/h2-sd ./build.sh --deploy --backup-stock
|
||||
```
|
||||
|
||||
This snapshots the following paths from the stock SD card into this
|
||||
directory, before any OreBolt OS files are written:
|
||||
|
||||
```
|
||||
data/stock_backup/
|
||||
usr/bin/ Stock binaries (including hiby_player)
|
||||
usr/lib/ Stock shared libraries
|
||||
etc/inittab Stock init configuration
|
||||
etc/init.d/ Stock init scripts
|
||||
res/ Stock resources (fonts, themes, assets)
|
||||
setting/ Stock player settings and database
|
||||
```
|
||||
|
||||
## How the restore works
|
||||
|
||||
The System Tools module (reset.mod) reads from this directory when
|
||||
the operator selects "HIBY PLAYER -- Restore stock FW":
|
||||
|
||||
1. Stops all OreBolt OS init scripts (S98retro-input, S99broker, bt_input_daemon)
|
||||
2. Removes /apps/*.mod (all module binaries)
|
||||
3. Removes /usr/bin/h2_test and /usr/bin/retro_input_mapper
|
||||
4. Removes OreBolt init scripts from /etc/init.d/
|
||||
5. Removes /usr/lib/liblvgl.so
|
||||
6. Restores stock files from /data/stock_backup/ back to their
|
||||
original locations (/usr/bin/, /usr/lib/, /etc/, /res/, /setting/)
|
||||
7. Calls sync() and reboots
|
||||
|
||||
After reboot, the device loads the original Hiby Player -- the music
|
||||
player that shipped from the factory.
|
||||
|
||||
## User-installed Rockbox
|
||||
|
||||
OreBolt OS does not ship, bundle, or install Rockbox. If the user
|
||||
has installed Rockbox on the SD card independently, the System Tools
|
||||
module detects its presence (`.rockbox/` or `rockbox/` at the SD card
|
||||
root) and offers a reboot option. No OreBolt OS files are modified
|
||||
when booting to Rockbox -- a reboot is sufficient for the user's
|
||||
Rockbox installation to load.
|
||||
|
|
@ -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
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue