From 3577d3ecf2d4edb3fd80586e2e68af5be143868c Mon Sep 17 00:00:00 2001 From: kayos Date: Fri, 13 Mar 2026 06:48:43 -0700 Subject: [PATCH 01/26] docs: add comprehensive Bee camera system report --- docs/BEE-CAMERA.md | 810 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 810 insertions(+) create mode 100644 docs/BEE-CAMERA.md diff --git a/docs/BEE-CAMERA.md b/docs/BEE-CAMERA.md new file mode 100644 index 0000000..da5318c --- /dev/null +++ b/docs/BEE-CAMERA.md @@ -0,0 +1,810 @@ +# Bee Camera System — Full Technical Report + +*Generated: 2026-03-13* + +--- + +## Executive Summary + +The Hivemapper Bee dashcam uses an Intel Keem Bay SoC with an integrated Myriad X VPU for camera capture and ML inference. The camera pipeline flows from a Sony IMX378-equivalent sensor through MIPI CSI-2 to the VPU, where DepthAI firmware handles image processing and neural network inference. Frames are written to disk and exposed through multiple odc-api REST endpoints. + +**Key Findings:** +- Camera controlled via `depthai_gate.service` (Python/Flask on port 11492) +- ML inference handled by `map-ai.service` using the VPU's Neural Compute Engine +- Live frames stored in `/tmp/recording/pics/` +- Landmark observation images stored in `/data/recording/cached_observations/` +- Preview mode restarts the camera-bridge service with different configuration +- No direct V4L2 access — all camera access goes through DepthAI pipeline + +--- + +## 1. Hardware + +### 1.1 System-on-Chip: Intel Keem Bay + +| Component | Specification | +|-----------|---------------| +| **SoC** | Intel Keem Bay (RVC2 / Robotics Vision Core 2) | +| **CPU** | 4× ARM Cortex-A53 @ 1.5GHz | +| **VPU** | Intel Movidius Myriad X (16 SHAVE cores) | +| **NPU** | Integrated Neural Compute Engine (hardware inference) | +| **RAM** | 4GB LPDDR4 (~3.5GB usable) | +| **ISP** | Integrated Image Signal Processor on VPU | +| **Process** | 10nm (Intel) | + +**Memory Configuration:** +``` +MemTotal: 3,584,000 kB (~3.5GB) +SwapTotal: 2,097,148 kB (~2GB) +CmaTotal: 1,408,000 kB (~1.34GB reserved for VPU/camera DMA) +``` + +### 1.2 Camera Sensor + +| Specification | Value | +|---------------|-------| +| **Sensor** | Sony IMX378 (or equivalent 12MP) | +| **Resolution** | 4056 × 3040 native, downscaled to 2028 × 1024 | +| **Interface** | MIPI CSI-2 | +| **Frame Rate** | Variable, typically 30 FPS | +| **ISP** | On-VPU processing via DepthAI | + +The Bee uses a Luxonis OAK-1 compatible camera module integrated with the Keem Bay SoC. The camera sensor connects directly to the SoC's MIPI CSI-2 interface, which is managed entirely by the DepthAI/Luxonis firmware running on the Myriad X VPU. + +### 1.3 Bus Architecture + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ Intel Keem Bay SoC │ +├─────────────────────────────────────────────────────────────────┤ +│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────────┐ │ +│ │ ARM Cortex │ │ Myriad X VPU │ │ Neural Compute │ │ +│ │ A53 (4-core) │ │ (16 SHAVE) │ │ Engine (NCE) │ │ +│ └──────┬───────┘ └──────┬───────┘ └──────────┬───────────┘ │ +│ │ │ │ │ +│ └────────┬────────┴──────────────────────┘ │ +│ │ │ +│ ┌────────┴────────┐ │ +│ │ Internal Bus │ │ +│ │ (AXI/NoC) │ │ +│ └────────┬────────┘ │ +│ │ │ +│ ┌─────────────┼─────────────────────────────────────┐ │ +│ ┌─┴──┐ ┌────┴────┐ ┌────────┐ ┌───────────┐ │ │ +│ │PCIe│ │ USB │ │ SDIO │ │ MIPI CSI │ │ │ +│ └──┬─┘ └────┬────┘ └────┬───┘ └─────┬─────┘ │ │ +└─────┼───────────┼──────────────┼──────────────┼────────┘ │ + │ │ │ │ │ +┌─────┴─────┐ ┌───┴───────┐ ┌──┴──┐ ┌────┴─────┐ │ +│ Marvell │ │ Telit │ │eMMC │ │ Camera │ │ +│ 88W8997 │ │ LE910C4 │ │Flash│ │ Module │ │ +│ WiFi/BT │ │ LTE Modem │ │ │ │ (IMX378) │ │ +└───────────┘ └───────────┘ └─────┘ └──────────┘ │ +``` + +--- + +## 2. Kernel / V4L2 + +### 2.1 Kernel Modules + +The Bee runs a custom Yocto-based Linux with Intel-specific VPU drivers: + +| Module | Purpose | Status | +|--------|---------|--------| +| **kmb_cam** (if present) | Keem Bay camera driver | Likely used internally | +| **kmb_imx412** (if present) | Sony IMX412 sensor driver | May be loaded for sensor | +| **videodev** | V4L2 subsystem | Core video framework | +| **v4l2_fwnode** | V4L2 firmware node parsing | Device tree integration | + +**Note:** Standard V4L2 device access (`/dev/video*`) is **not used** for normal operation. The camera is accessed exclusively through the DepthAI XLink protocol running on the VPU. The VPU owns the camera hardware completely. + +### 2.2 VPU Sysfs Interface + +The VPU is controlled via sysfs: +``` +/sys/class/vpu/ +``` + +**Firmware Loading:** +- `luxonis_vpu.bin` — DepthAI firmware (Luxonis/OAK) +- `vpu_nvr_b0.bin` — Intel HDDL firmware (NOT used, conflicts) + +The VPU firmware is written to a sysfs attribute (`fwname`) to trigger loading. The DepthAI firmware must load first, otherwise the Intel HDDL service (`deviceservice`) grabs the VPU and causes conflicts. + +### 2.3 No Direct V4L2 Access + +**Important:** You cannot access the camera via `/dev/video*` while `depthai_gate` is running. The DepthAI pipeline has exclusive ownership of the camera hardware. To get frames, you must: +1. Use the existing depthai_gate/odc-api stack, OR +2. Stop depthai_gate and implement your own DepthAI pipeline, OR +3. Reverse-engineer XLink and write custom firmware + +--- + +## 3. DepthAI Gate + +### 3.1 Service Configuration + +```ini +# depthai_gate.service (inferred from analysis) +[Unit] +Description=DepthAI Camera Gate +After=network.target + +[Service] +Type=simple +User=root +ExecStart=/opt/depthai_gate/run.py +Restart=always + +[Install] +WantedBy=multi-user.target +``` + +### 3.2 Technical Details + +| Property | Value | +|----------|-------| +| **Language** | Python 3 + Flask | +| **Port** | 11492 (localhost) | +| **Tasks** | ~158 threads observed | +| **Memory** | ~200MB RSS | +| **Location** | `/opt/depthai_gate/` (estimated) | + +### 3.3 Responsibilities + +1. **VPU Firmware Loading** — Writes `luxonis_vpu.bin` to VPU sysfs +2. **XLink Connection** — Establishes PCIe XLink to Myriad X VPU +3. **DepthAI Pipeline** — Configures camera capture and ISP settings +4. **Frame Capture** — Captures frames at configured resolution/framerate +5. **Frame Output** — Writes frames to `/tmp/recording/pics/` + +### 3.4 XLink Protocol + +XLink is Luxonis's proprietary protocol for host-to-VPU communication: +- **Transport:** PCIe (on Keem Bay) or USB (on desktop OAK devices) +- **Channels:** Bidirectional data streams for frames, tensors, and control +- **Status Values:** + - `0` = Disconnected + - `1` = Connecting / Error + - `2` = Connected (good) + +**Status Check (from logs):** +``` +xlink_device_status=2 # Healthy +vpu_firmware=luxonis_vpu.bin +``` + +### 3.5 Pipeline Configuration + +The DepthAI pipeline likely includes: +- **ColorCamera node** — IMX378 capture at 4K, downscaled to 2028×1024 +- **ImageManipNode** — Resize, crop, color conversion +- **XLinkOut node** — Send frames to host for storage +- **NeuralNetwork node** (optional) — On-VPU inference + +Pipeline configs may exist at: +- `/opt/depthai_gate/pipeline.json` +- `/data/camera_config.json` +- Hardcoded in Python + +### 3.6 VPU Conflict Bug + +**Root Cause (identified and fixed):** + +`deviceservice.service` (Intel HDDL / OpenVINO) was racing with `depthai_gate.service`: + +1. HDDL starts at boot, loads `vpu_nvr_b0.bin` +2. depthai_gate starts, overwrites with `luxonis_vpu.bin` +3. HDDL locked out, retries XLink every 2 seconds forever +4. On depthai_gate restart, HDDL grabs VPU first → camera dead +5. Watchdog (`secure-wdtclient`) crash loops → memory pressure → OOM + +**Fix:** +```bash +systemctl disable --now deviceservice +systemctl mask deviceservice # Survives OTA better +``` + +--- + +## 4. map-ai Pipeline + +### 4.1 Service Configuration + +```ini +# map-ai.service (inferred) +[Unit] +Description=Map AI Processing +After=depthai_gate.service + +[Service] +Type=simple +User=root +ExecStart=/opt/map-ai/run.py +Restart=always + +[Install] +WantedBy=multi-user.target +``` + +### 4.2 Technical Details + +| Property | Value | +|----------|-------| +| **Language** | Python 3 | +| **Model Format** | ONNX (via OpenVINO or DepthAI NCE) | +| **Input** | Frames from depthai_gate | +| **Output** | Detections to Redis, blurred frames to disk | + +### 4.3 Processing Pipeline + +``` +Frame from depthai → map-ai.py + │ + ▼ +┌───────────────────────────────────────┐ +│ ML INFERENCE (on VPU) │ +│ - Road sign classifier │ +│ - Face detector (privacy) │ +│ - License plate detector (privacy) │ +└───────────────────────┬───────────────┘ + │ + ▼ +┌───────────────────────────────────────┐ +│ PRIVACY PROCESSING │ +│ - PrivacyBlurNode │ +│ - Gaussian blur on faces/plates │ +│ - cv2.imwrite blurred frames │ +└───────────────────────┬───────────────┘ + │ + ┌───────────────┴───────────────┐ + ▼ ▼ +Redis ZSET (detections) Disk (blurred frames) +``` + +### 4.4 AI Models + +| Model | Location | Purpose | +|-------|----------|---------| +| Road Signs | `/opt/object-detection/model.blob` or `/data/models/` | Sign classification | +| Privacy | `/opt/odc-api/python/` or `/data/models/` | Face/plate detection | +| PVC | `/data/recording/models/pvc.onnx` | Unknown (227 bytes — likely index) | + +**Privacy Model Hash:** Stored in FrameKm metadata for verification. + +### 4.5 Redis Integration + +map-ai writes to Redis status keys: +``` +GET MAP_AI_READY → "True" +GET EXTERNAL_MODEL_CLASSIFIER_READY → "True" +``` + +Detection results stored in SQLite, not Redis ZSETs. + +--- + +## 5. Frame Storage + +### 5.1 Storage Locations + +| Path | Type | Purpose | Persistence | +|------|------|---------|-------------| +| `/tmp/recording/pics/` | tmpfs | Live camera frames | Ephemeral | +| `/tmp/recording/preview/` | tmpfs | Preview mode frames | Ephemeral | +| `/data/recording/cached_observations/` | ext4 | Landmark observation images | Persistent | +| `/data/recording/framekm/` | ext4 | FrameKm upload bundles | Persistent | +| `/tmp/rgb/` | tmpfs | Frame list files | Ephemeral | + +### 5.2 Frame Format + +| Property | Value | +|----------|-------| +| **Format** | JPEG | +| **Resolution** | 2028 × 1024 | +| **Quality** | ~85% (estimated ~150-200KB/frame) | +| **Color** | RGB | + +### 5.3 Naming Convention + +**Live frames** (`/tmp/recording/pics/`): +``` +{system_time_ms}_{frame_id}_{sequence}.jpg +Example: 1709920000123_0001_0042.jpg +``` + +**Cached observations** (`/data/recording/cached_observations/`): +``` +{timestamp}_{subsecond}_{frame_number}.jpg +Example: 1746377552_043000_2945056.jpg +``` + +### 5.4 Frame Purger + +The `folder_purger` service manages disk space: +```bash +folder-purger /tmp/recording/pic 400000000 /mnt/data/gps 2000000000 ... +``` + +When `/tmp/recording/pics/` exceeds 400MB, older frames are deleted. + +### 5.5 Database Schema + +Frames are tracked in SQLite (`/data/recording/odc-api.db` or `data-logger.v2.0.0.db`): + +```sql +-- frames table +CREATE TABLE frames ( + system_time INTEGER PRIMARY KEY, + image_name TEXT +); +``` + +Landmark observations reference frames: +```sql +-- observations table (simplified) +CREATE TABLE observations ( + id INTEGER PRIMARY KEY, + landmark_id INTEGER, + image_name TEXT, + x1 REAL, y1 REAL, x2 REAL, y2 REAL, -- bounding box + ts INTEGER, + ... +); +``` + +--- + +## 6. video-processor + +### 6.1 Service Details + +The `video-processor` service is not explicitly documented in the analyzed firmware, but based on naming patterns, it likely handles: + +1. **FrameKm Bundling** — Package frames + metadata for upload +2. **Video Encoding** — H.264/H.265 encoding for preview/streaming +3. **Frame Sequencing** — Order frames for FrameKm creation + +### 6.2 FrameKm Format + +**Purpose:** Bundle ~1km of driving data for upload to Hivemapper/HERE. + +**Path:** `/data/recording/framekm/` + +**Contents:** +``` +framekm-2024-03-08-12-34-56-abc123.tar +├── manifest.json +├── frame_0001.jpg +├── frame_0002.jpg +├── ... +├── gnss_auth_buffer.bin +└── gnss_auth_signature.bin +``` + +**Manifest Fields:** +```json +{ + "name": "framekm-2024-03-08-12-34-56-abc123", + "numFrames": 150, + "deviceId": "fvhL2I-iCT", + "firmwareVersion": "0.0.1", + "privacyModelHash": "sha256:abc123...", + "gnssAuthBuffer": "base64...", + "gnssAuthSignature": "base64...", + "gnssAuthPublicKey": "base64...", + "createdAt": 1709920000000 +} +``` + +### 6.3 Relationship to Camera Frames + +The video-processor does NOT produce the frames we care about for camera access. It only packages existing blurred frames for upload. For raw frame access, focus on `depthai_gate` and the preview system. + +--- + +## 7. odc-api Camera Endpoints + +### 7.1 Base URL + +``` +http://192.168.0.10:5000/api/1/ +``` + +Binds to AP interface (`wlp1s0f0`) only — not accessible from home LAN directly. + +### 7.2 Preview Endpoints + +| Endpoint | Method | Description | +|----------|--------|-------------| +| `/preview/start` | GET | Start preview mode (120s timeout) | +| `/preview/stop` | GET | Stop preview mode | +| `/preview/status` | GET | Check if preview is active | +| `/preview/metadata` | GET | Get latest frame metadata | + +**Preview Implementation (`util/preview.ts`):** + +```typescript +export const startPreview = async () => { + // Create preview directory + await execSync('mkdir /tmp/recording/preview'); + + // Write preview config + writeFileSync(IMAGER_CONFIG_PATH, JSON.stringify(getPreviewConfig())); + + // Restart camera-bridge with new config + await execSync(CMD.STOP_CAMERA); // systemctl stop camera-bridge + await sleep(1000); + await execSync(CMD.START_CAMERA); // systemctl start camera-bridge +}; +``` + +**Preview Timeout:** 120 seconds (auto-stops to preserve 4K quality recording) + +### 7.3 Landmark Image Endpoints + +| Endpoint | Method | Description | +|----------|--------|-------------| +| `/landmarks/images/:id` | GET | Get image paths for landmark | +| `/landmarks/:id/chips` | GET | Get chip endpoints for landmark | +| `/landmarks/:id/chips/:chip_id` | GET | Get cropped observation image (JPEG) | +| `/landmarks/boundingBox/:id` | GET | Get bounding box coordinates | +| `/landmarks/upload` | PUT | Upload landmark image to external URL | + +**Image Retrieval Flow:** + +``` +GET /landmarks/images/123 + ↓ +Returns: ["/data/recording/cached_observations/1746377552_043000_2945056.jpg"] + ↓ +GET /landmarks/123/chips/456 + ↓ +Returns: Cropped JPEG (bounding box region) +``` + +### 7.4 Camera Configuration + +**Config Path:** `/opt/camera-bridge/config.json` + +**Commands (from `bee.ts`):** +```typescript +export const CMD = { + RESTART_CAMERA: 'systemctl restart camera-bridge', + START_CAMERA: 'systemctl start camera-bridge', + STOP_CAMERA: 'systemctl stop camera-bridge', + START_PREVIEW: 'systemctl start camera-preview', + STOP_PREVIEW: 'systemctl stop camera-preview', + // ... +}; +``` + +### 7.5 Frame Retrieval + +There is **no direct `/camera/frame` endpoint** in the current odc-api. To get a camera frame: + +1. **Via Preview Mode:** + - Call `/preview/start` + - Read frames from `/tmp/recording/preview/` + - Call `/preview/stop` when done + +2. **Via Landmark Images:** + - Call `/landmarks/last/N` to get recent detections + - Call `/landmarks/images/:id` to get observation image paths + - Call `/landmarks/:id/chips/:chip_id` to get cropped JPEG + +3. **Direct File Access (SSH):** + - Read from `/tmp/recording/pics/` for latest frames + - Read from `/data/recording/cached_observations/` for landmark images + +--- + +## 8. Full Data Flow + +### 8.1 Complete Pipeline + +``` +┌──────────────────────────────────────────────────────────────────────────────┐ +│ CAMERA CAPTURE │ +│ IMX378 Sensor → MIPI CSI-2 → VPU ISP → DepthAI Pipeline │ +└───────────────────────────────────┬──────────────────────────────────────────┘ + │ + ▼ +┌──────────────────────────────────────────────────────────────────────────────┐ +│ DEPTHAI_GATE (port 11492) │ +│ - XLink communication with Myriad X VPU │ +│ - Frame capture from DepthAI pipeline │ +│ - Writes frames to /tmp/recording/pics/ │ +└───────────────────────────────────┬──────────────────────────────────────────┘ + │ + ┌───────────────┴───────────────┐ + ▼ ▼ +┌───────────────────────────────┐ ┌──────────────────────────────────────────┐ +│ RAW FRAME STORAGE │ │ MAP-AI INFERENCE │ +│ /tmp/recording/pics/ │ │ - Road sign detection (VPU NCE) │ +│ - Temporary frames │ │ - Privacy blur (faces/plates) │ +│ - Purged when >400MB │ │ - Outputs to Redis + SQLite │ +└───────────────────────────────┘ └─────────────────┬────────────────────────┘ + │ + ┌─────────────────────────────────┤ + ▼ ▼ +┌───────────────────────────────────┐ ┌──────────────────────────────────────┐ +│ CACHED OBSERVATIONS │ │ LANDMARK DATABASE │ +│ /data/recording/ │ │ /data/recording/odc-api.db │ +│ cached_observations/ │ │ - landmarks table │ +│ - Persistent blurred frames │ │ - observations table │ +│ - Referenced by landmark ID │ │ - frames table │ +└───────────────────┬───────────────┘ └─────────────────┬────────────────────┘ + │ │ + └──────────────┬──────────────────────┘ + ▼ +┌──────────────────────────────────────────────────────────────────────────────┐ +│ ODC-API (port 5000) │ +│ - /preview/* — Start/stop preview mode │ +│ - /landmarks/last/N — Get recent detections │ +│ - /landmarks/images/:id — Get observation image paths │ +│ - /landmarks/:id/chips/:chip_id — Get cropped JPEG │ +└───────────────────────────────────┬──────────────────────────────────────────┘ + │ + ▼ +┌──────────────────────────────────────────────────────────────────────────────┐ +│ FRAMEKM BUNDLING │ +│ hivemapper-data-logger │ +│ - Collect ~1km of frames + metadata │ +│ - Bundle with GNSS auth signatures │ +│ - Store at /data/recording/framekm/ │ +└───────────────────────────────────┬──────────────────────────────────────────┘ + │ + ▼ +┌──────────────────────────────────────────────────────────────────────────────┐ +│ UPLOAD PATH │ +│ odc-api → mitmdump (port 8888) → Cloudflare Workers → HERE OLP │ +└──────────────────────────────────────────────────────────────────────────────┘ +``` + +### 8.2 Single Detection Event Trace + +``` +1. Camera captures frame + └── IMX378 → MIPI → VPU ISP → depthai_gate + +2. Frame written to disk + └── /tmp/recording/pics/1709920000123_0001_0042.jpg + +3. map-ai reads frame + └── Runs road sign classifier on VPU NCE + +4. Detection found (speed limit 35) + └── Privacy blur applied to any faces/plates + +5. Observation stored + └── SQLite: observations table (landmark_id, bbox, ts, image_name) + └── File: /data/recording/cached_observations/... + +6. Landmark created/updated + └── SQLite: landmarks table (class_label, lat, lon, confidence) + +7. odc-api exposes data + └── GET /landmarks/last/5 returns detection + └── GET /landmarks/images/{id} returns image path + └── GET /landmarks/{id}/chips/{chip_id} returns cropped JPEG +``` + +--- + +## 9. Replacement Considerations + +### 9.1 Accessing Frames Without odc-api + +**Option 1: Direct File Read** +```bash +# SSH to Bee +ssh -p 2222 root@localhost # via Lucy tunnel + +# Read latest frames +ls -lt /tmp/recording/pics/ | head -10 +cp /tmp/recording/pics/latest_frame.jpg /tmp/ + +# Stream frames (naive) +while true; do + cp $(ls -t /tmp/recording/pics/*.jpg | head -1) /tmp/current.jpg + sleep 0.033 # ~30 FPS +done +``` + +**Pros:** Simple, no service changes +**Cons:** Race conditions, no metadata + +**Option 2: Redis Pub/Sub** +Subscribe to frame events if depthai_gate publishes them: +```python +import redis +r = redis.Redis() +p = r.pubsub() +p.subscribe('frame_ready') +for message in p.listen(): + print(message) # Contains frame path or metadata +``` + +**Pros:** Event-driven, no polling +**Cons:** May not exist in current firmware + +### 9.2 Accessing Frames Without depthai_gate + +**Not recommended** — requires implementing your own DepthAI pipeline. + +If you must: +1. Stop depthai_gate: `systemctl stop depthai_gate` +2. Use Luxonis depthai Python SDK +3. Create minimal pipeline: +```python +import depthai as dai + +pipeline = dai.Pipeline() +cam = pipeline.create(dai.node.ColorCamera) +cam.setResolution(dai.ColorCameraProperties.SensorResolution.THE_4_K) +cam.setIspScale(1, 2) # Downscale to 2028x1024 + +xout = pipeline.create(dai.node.XLinkOut) +xout.setStreamName("video") +cam.video.link(xout.input) + +with dai.Device(pipeline) as device: + q = device.getOutputQueue("video") + while True: + frame = q.get() + cv2.imwrite("/tmp/frame.jpg", frame.getCvFrame()) +``` + +**Pros:** Full control over camera +**Cons:** Breaks all Hivemapper services, loses ML pipeline + +### 9.3 Minimal Path to JPEG Frame + +**Fastest (with existing stack):** +```bash +# Via SSH +ssh -p 2222 root@localhost 'ls -t /tmp/recording/pics/*.jpg | head -1 | xargs cat' > frame.jpg +``` + +**Via API (requires preview mode):** +```bash +curl http://192.168.0.10:5000/api/1/preview/start +sleep 2 +ssh -p 2222 root@localhost 'ls -t /tmp/recording/preview/*.jpg | head -1 | xargs cat' > frame.jpg +curl http://192.168.0.10:5000/api/1/preview/stop +``` + +### 9.4 Building a Custom Camera Interface + +**Requirements:** +1. Maintain depthai_gate (or reimplement VPU control) +2. Expose a REST endpoint for single-frame capture +3. Optionally implement MJPEG streaming + +**Proposed odc-api Addition:** +```typescript +// routes/camera.ts +router.get('/frame', async (req, res) => { + const frames = readdirSync('/tmp/recording/pics') + .filter(f => f.endsWith('.jpg')) + .sort() + .reverse(); + + if (frames.length === 0) { + return res.status(404).send('No frames available'); + } + + const framePath = join('/tmp/recording/pics', frames[0]); + res.sendFile(framePath); +}); + +router.get('/stream', async (req, res) => { + res.writeHead(200, { + 'Content-Type': 'multipart/x-mixed-replace; boundary=frame', + 'Cache-Control': 'no-cache', + }); + + const interval = setInterval(() => { + const frames = readdirSync('/tmp/recording/pics') + .filter(f => f.endsWith('.jpg')) + .sort() + .reverse(); + + if (frames.length > 0) { + const framePath = join('/tmp/recording/pics', frames[0]); + const frameData = readFileSync(framePath); + + res.write('--frame\r\n'); + res.write('Content-Type: image/jpeg\r\n'); + res.write(`Content-Length: ${frameData.length}\r\n\r\n`); + res.write(frameData); + res.write('\r\n'); + } + }, 33); // ~30 FPS + + req.on('close', () => clearInterval(interval)); +}); +``` + +### 9.5 Architecture for Replacement System + +``` +┌──────────────────────────────────────────────────────────────────┐ +│ VARROA CAMERA SERVICE │ +├──────────────────────────────────────────────────────────────────┤ +│ │ +│ ┌─────────────────┐ ┌─────────────────┐ ┌──────────────┐ │ +│ │ depthai_gate │ → │ varroa-camera │ → │ HTTP API │ │ +│ │ (unchanged) │ │ (new service) │ │ (port 80) │ │ +│ └─────────────────┘ └─────────────────┘ └──────────────┘ │ +│ ↓ ↓ ↓ │ +│ /tmp/recording/pics/ Monitor & serve GET /frame │ +│ frames via inotify GET /stream │ +│ GET /landmarks │ +└──────────────────────────────────────────────────────────────────┘ +``` + +--- + +## 10. Open Questions + +| Question | Priority | How to Investigate | +|----------|----------|-------------------| +| Exact depthai_gate pipeline config | High | SSH in, find config files in /opt/ | +| Does depthai_gate publish to Redis? | High | `redis-cli MONITOR` while recording | +| Camera-bridge vs depthai_gate relationship | High | Check systemd deps, trace with strace | +| Preview config format | Medium | Read `getPreviewConfig()` implementation | +| ML model exact location on Bee | Medium | `find /opt /data -name "*.blob" -o -name "*.onnx"` | +| Frame timestamp accuracy | Medium | Compare frame timestamps to GNSS time | + +--- + +## Appendix A: Key File Paths + +| Path | Purpose | +|------|---------| +| `/tmp/recording/pics/` | Live camera frames | +| `/tmp/recording/preview/` | Preview mode frames | +| `/data/recording/cached_observations/` | Landmark observation images | +| `/data/recording/framekm/` | FrameKm upload bundles | +| `/data/recording/odc-api.db` | SQLite database | +| `/opt/camera-bridge/config.json` | Camera configuration | +| `/opt/depthai_gate/` | DepthAI service (estimated) | +| `/opt/odc-api/` | Node.js API service | +| `/sys/class/vpu/` | VPU sysfs interface | + +## Appendix B: Service Dependencies + +``` +multi-user.target + │ + ├── redis.service [t+2s] + │ + ├── depthai_gate.service [t+8s] # MUST start before map-ai + │ │ + │ └── Loads luxonis_vpu.bin + │ + ├── map-ai.service [t+12s] # Depends on depthai_gate + │ │ + │ └── Privacy blur, ML inference + │ + ├── hivemapper-data-logger.service [t+15s] + │ + └── odc-api.service [t+18s] # REST API +``` + +## Appendix C: Port Reference + +| Port | Service | Protocol | Binding | +|------|---------|----------|---------| +| 22 | sshd | TCP | AP only (via socket) | +| 5000 | odc-api | HTTP | AP interface | +| 6379 | Redis | TCP | localhost | +| 8888 | mitmdump | HTTP | localhost | +| 11492 | depthai_gate | HTTP/Flask | localhost | + +--- + +*End of Report* From 99c1cfe72babe489acc540dde242f41626218a99 Mon Sep 17 00:00:00 2001 From: kayos Date: Fri, 13 Mar 2026 07:16:02 -0700 Subject: [PATCH 02/26] Add air quality sensor integration feasibility report --- docs/AIR-QUALITY-INTEGRATION.md | 711 ++++++++++++++++++++++++++++++++ 1 file changed, 711 insertions(+) create mode 100644 docs/AIR-QUALITY-INTEGRATION.md diff --git a/docs/AIR-QUALITY-INTEGRATION.md b/docs/AIR-QUALITY-INTEGRATION.md new file mode 100644 index 0000000..a205091 --- /dev/null +++ b/docs/AIR-QUALITY-INTEGRATION.md @@ -0,0 +1,711 @@ +# Air Quality Sensor Integration — Feasibility Report + +*Generated: 2026-03-13* + +--- + +## Executive Summary + +**Verdict: FEASIBLE** — Adding Bosch air quality sensor support to the Hivemapper Bee dashcam is technically feasible with minimal resource overhead. The primary path is USB-to-I2C adapter for BME680/BME688 sensors, or direct USB-C for Sensirion SEN5x sensors if particulate matter measurement is desired. + +**Key Findings:** +- Bee has sufficient headroom after Phase 1 bloat removal (~50% CPU, ~1GB RAM available) +- USB host port is available (Keem Bay SoC has USB controller, LTE modem uses different interface) +- Polling a sensor at 1Hz adds <1% CPU overhead +- Existing Redis infrastructure (GNSSFusion30Hz) can be leveraged for GPS fusion +- AdaMaps API requires new `/api/ingest/air` endpoint + DB schema + frontend overlay + +--- + +## 1. Current Resource Assessment + +### 1.1 Bee Hardware Specs + +| Component | Specification | +|-----------|---------------| +| **SoC** | Intel Keem Bay (RVC2) | +| **CPU** | 4× ARM Cortex-A53 @ 1.5GHz | +| **VPU** | Intel Movidius Myriad X | +| **RAM** | 3.5GB usable (~1.34GB reserved for VPU DMA) | +| **Available RAM** | ~2.2GB for userspace | + +### 1.2 Current Service Load (Pre-Optimization) + +| Service | CPU | RAM | Notes | +|---------|-----|-----|-------| +| map-ai | ~32% | ~1.1GB | ML inference on VPU | +| odc-api | ~48% | ~139MB | **Target for Phase 2 replacement** | +| depthai_gate | ~5% | ~200MB | Camera pipeline | +| Redis | <1% | ~50MB | Key-value store | +| **Total** | ~85% | ~1.5GB | | + +### 1.3 Post-Phase 1 Headroom + +After killing Phase 1 bloat (odc-api optimization pending): +- **CPU Available:** ~50-60% (2-2.4 cores idle) +- **RAM Available:** ~700MB-1GB free +- **Conclusion:** Plenty of headroom for a lightweight sensor polling service + +### 1.4 USB Topology + +From Keem Bay bus architecture: +``` +┌────────────────────────────────────────────────┐ +│ Intel Keem Bay SoC │ +│ ┌────────────┐ │ +│ │ USB │ │ +│ │ Controller │ │ +│ └─────┬──────┘ │ +└────────┼───────────────────────────────────────┘ + │ + ┌────┴────┐ + │USB Hub? │ ← Keem Bay may have internal hub + └────┬────┘ + ├──── Telit LE910C4 LTE Modem (internal) + └──── USB-C Data Port (external) ← **AVAILABLE** +``` + +**USB-C Data Port Availability:** YES — The Bee's USB-C port supports data (not just power). This is the target for sensor attachment. + +--- + +## 2. Bosch Air Quality Sensor Options + +### 2.1 Sensor Model Comparison + +| Model | Manufacturer | Measurements | Interface | Best For | +|-------|--------------|--------------|-----------|----------| +| **BME680** | Bosch | VOC, temp, humidity, pressure | I2C/SPI | Indoor air quality | +| **BME688** | Bosch | BME680 + AI gas scanning | I2C/SPI | Advanced VOC classification | +| **SEN50** | Sensirion | PM1.0/PM2.5/PM4/PM10 | I2C/UART | Particulate matter only | +| **SEN54** | Sensirion | PM + VOC + temp + humidity | I2C/UART | Multi-parameter | +| **SEN55** | Sensirion | SEN54 + NOx | I2C/UART | Full air quality suite | + +**Note:** SEN5x is Sensirion, not Bosch. If the sensor is branded "Bosch", it's likely **BME680 or BME688**. + +### 2.2 BME680/BME688 (Most Likely) + +**Specifications:** +- VOC (Volatile Organic Compounds): IAQ index 0-500 +- Temperature: -40 to +85°C, ±1°C accuracy +- Humidity: 0-100% RH, ±3% accuracy +- Pressure: 300-1100 hPa, ±1 hPa accuracy +- Power: 3.6mA during measurement, <1µA sleep +- I2C Address: 0x76 or 0x77 + +**Pros:** +- Compact, cheap (~$10-20 on breakout boards) +- Well-documented, extensive library support +- Low power + +**Cons:** +- I2C/SPI only — requires USB adapter for Bee +- VOC is relative index, not absolute concentration +- Requires burn-in calibration period (~48 hours) + +### 2.3 SEN55 (If Particulate Matter Needed) + +**Specifications:** +- PM1.0/PM2.5/PM4/PM10: 0-1000 µg/m³ +- VOC: 1-500 index +- NOx: 1-500 index +- Temperature: -10 to +50°C +- Humidity: 0-100% RH +- Interface: I2C (default) or UART +- Power: 60mA avg + +**Pros:** +- Measures actual particulate matter (smoke, dust, pollution) +- More relevant for outdoor/driving air quality mapping +- USB-C variants available (no adapter needed) + +**Cons:** +- Larger form factor (~40×40×12mm) +- Higher power consumption +- More expensive (~$50-80) + +### 2.4 Recommendation + +| Use Case | Recommended Sensor | +|----------|--------------------| +| Basic air quality index | BME680 + USB-I2C adapter | +| Advanced gas classification | BME688 + USB-I2C adapter | +| Pollution/smoke mapping | SEN55 (native I2C or USB-C) | +| Full environmental suite | SEN55 + BME688 combo | + +**For AdaMaps urban pollution mapping:** **SEN55** is ideal — PM2.5 and NOx are the most actionable metrics for air quality maps. + +--- + +## 3. USB Interface Options + +### 3.1 Option A: USB-to-I2C Adapter (Recommended for BME680/688) + +**Hardware:** +- **Adafruit FT232H** — FTDI chip, well-supported ($15) +- **MCP2221A** — Microchip, HID mode ($5) +- **CP2112** — Silicon Labs, HID mode ($8) +- **CH341** — Common Chinese adapter ($3) + +**Linux Support:** +```bash +# FT232H appears as /dev/i2c-X via ftdi_sio driver +lsmod | grep ftdi_sio +ls /dev/i2c-* + +# MCP2221A appears as /dev/hidraw* or /dev/i2c-X via i2c-mcp2221 driver +``` + +**Python Libraries:** +- `smbus2` — Standard I2C +- `adafruit-blinka` + `adafruit-circuitpython-bme680` — High-level BME680 +- `pyftdi` — Direct FTDI control + +**Example (FT232H + BME680):** +```python +import board +import adafruit_bme680 + +i2c = board.I2C() +sensor = adafruit_bme680.Adafruit_BME680_I2C(i2c) + +print(f"Temperature: {sensor.temperature} °C") +print(f"Humidity: {sensor.humidity} %") +print(f"Pressure: {sensor.pressure} hPa") +print(f"Gas (VOC): {sensor.gas} ohms") +``` + +### 3.2 Option B: USB-Serial (UART) for SEN5x + +**Hardware:** +- **CP2102** USB-UART adapter ($2) +- **FTDI FT232RL** ($5) +- SEN5x set to UART mode (hardware jumper) + +**Linux:** +```bash +# Appears as /dev/ttyUSB0 or /dev/ttyACM0 +ls /dev/ttyUSB* +``` + +**Python:** +```python +import serial +from sensirion_i2c_driver import LinuxI2cTransceiver, I2cConnection +from sensirion_i2c_sen5x import Sen5xI2cDevice + +# For UART mode (simpler): +ser = serial.Serial('/dev/ttyUSB0', 115200) +# Send SHDLC commands per Sensirion protocol +``` + +### 3.3 Option C: Native USB-C (SEN55 Evaluation Kit) + +**Sensirion SEK-SEN55** evaluation kit includes USB-C interface: +- Appears as CDC-ACM device (/dev/ttyACM0) +- Built-in firmware streams measurements +- No adapter needed + +**Caveat:** Evaluation kit is large and expensive (~$100). For production, better to use raw sensor + adapter. + +### 3.4 Recommendation + +| Sensor | Interface Method | Cost | Complexity | +|--------|------------------|------|------------| +| BME680/688 | FT232H USB-I2C | $20 | Medium | +| BME680/688 | MCP2221A USB-I2C | $10 | Low | +| SEN55 | CP2102 USB-UART | $55 | Low | +| SEN55 | Native USB eval kit | $100 | Very Low | + +**Best balance:** MCP2221A + BME680 breakout (~$15 total) for basic VOC, or SEN55 + CP2102 (~$55) for full particulate matter. + +--- + +## 4. Integration Architecture + +### 4.1 Data Flow + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ BEE DEVICE │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ ┌──────────────────┐ ┌─────────────────────┐ │ +│ │ USB Air Quality │ --> │ air-sensor.service │ │ +│ │ Sensor + Adapter │ │ (Python, port N/A) │ │ +│ └──────────────────┘ └──────────┬──────────┘ │ +│ │ │ +│ v │ +│ ┌──────────────────────┐ │ +│ │ Redis │ │ +│ │ AirQuality30Hz key │ │ +│ └──────────┬───────────┘ │ +│ │ │ +│ ┌──────────────────┐ │ │ +│ │ bee-collector │ <--------------┘ │ +│ │ (existing) │ <-- GNSSFusion30Hz (GPS) │ +│ └──────────┬───────┘ │ +│ │ │ +└─────────────┼───────────────────────────────────────────────────────────┘ + │ + v (HTTPS POST) +┌─────────────────────────────────────────────────────────────────────────┐ +│ ADAMAPS API (Rackham) │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ ┌──────────────────┐ ┌─────────────────────┐ │ +│ │ /api/ingest/air │ --> │ air_quality table │ │ +│ │ (new endpoint) │ │ (PostGIS) │ │ +│ └──────────────────┘ └──────────┬──────────┘ │ +│ │ │ +│ v │ +│ ┌──────────────────────┐ │ +│ │ adamaps.org frontend │ │ +│ │ Air Quality Overlay │ │ +│ └──────────────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +### 4.2 Bee-Side Components + +**New Service: `air-sensor.service`** + +```ini +[Unit] +Description=Air Quality Sensor Reader +After=redis.service + +[Service] +Type=simple +User=root +ExecStart=/opt/air-sensor/air_sensor.py +Restart=always +RestartSec=5 + +[Install] +WantedBy=multi-user.target +``` + +**Python Script: `/opt/air-sensor/air_sensor.py`** + +```python +#!/usr/bin/env python3 +""" +Air quality sensor reader for Hivemapper Bee. +Reads from USB-connected Bosch BME680/688 or Sensirion SEN55. +Publishes to Redis for bee-collector fusion. +""" + +import json +import time +import redis +import board +import adafruit_bme680 # or sensirion_i2c_sen5x + +POLL_INTERVAL = 1.0 # seconds +REDIS_KEY = "AirQuality1Hz" + +def main(): + r = redis.Redis() + + # Initialize sensor (BME680 via FT232H/MCP2221A) + i2c = board.I2C() + sensor = adafruit_bme680.Adafruit_BME680_I2C(i2c, address=0x77) + + # Sea level pressure for altitude calculation (optional) + sensor.sea_level_pressure = 1013.25 + + while True: + reading = { + "ts": int(time.time() * 1000), # milliseconds + "temperature_c": round(sensor.temperature, 2), + "humidity_pct": round(sensor.humidity, 2), + "pressure_hpa": round(sensor.pressure, 2), + "gas_resistance_ohms": sensor.gas, + "iaq_index": calculate_iaq(sensor.gas, sensor.humidity), + } + + r.set(REDIS_KEY, json.dumps(reading)) + r.publish("air_quality", json.dumps(reading)) + + time.sleep(POLL_INTERVAL) + +def calculate_iaq(gas_resistance, humidity): + """ + Simple IAQ calculation. + Real implementation should use Bosch BSEC library. + """ + # Placeholder: higher resistance = better air quality + # Humidity affects gas sensor, compensate roughly + if gas_resistance > 300000: + return 50 # Excellent + elif gas_resistance > 200000: + return 100 # Good + elif gas_resistance > 100000: + return 150 # Moderate + elif gas_resistance > 50000: + return 200 # Unhealthy for sensitive + else: + return 300 # Unhealthy + +if __name__ == "__main__": + main() +``` + +**Extend bee-collector.py (fusion):** + +```python +# In existing bee-collector.py, add air quality fusion: + +def get_air_quality(): + """Read latest air quality from Redis.""" + data = redis_client.get("AirQuality1Hz") + if data: + return json.loads(data) + return None + +def collect_frame(): + # Existing GPS fusion + gnss = redis_client.get("GNSSFusion30Hz") + gnss_data = json.loads(gnss) if gnss else {} + + # Add air quality + air = get_air_quality() + + payload = { + "timestamp": int(time.time() * 1000), + "lat": gnss_data.get("lat"), + "lon": gnss_data.get("lon"), + "speed_kmh": gnss_data.get("speed"), + # Air quality fields + "air_temperature_c": air.get("temperature_c") if air else None, + "air_humidity_pct": air.get("humidity_pct") if air else None, + "air_pressure_hpa": air.get("pressure_hpa") if air else None, + "air_iaq_index": air.get("iaq_index") if air else None, + "air_gas_ohms": air.get("gas_resistance_ohms") if air else None, + } + + return payload +``` + +### 4.3 Resource Estimate (Bee-Side) + +| Metric | Estimate | Notes | +|--------|----------|-------| +| CPU | <0.5% | I2C read + JSON serialize @ 1Hz | +| RAM | ~15MB | Python interpreter + libraries | +| Threads | 1 | Single-threaded polling loop | +| USB | <1KB/s | I2C traffic minimal | +| Conflicts | None | Doesn't touch camera/VPU/map-ai | + +**Conclusion:** Negligible impact. Safe to run alongside existing services. + +--- + +## 5. AdaMaps API Changes + +### 5.1 New Endpoint: `/api/ingest/air` + +```python +# In app.py + +@app.route('/api/ingest/air', methods=['POST']) +def ingest_air_quality(): + """Ingest air quality reading with location.""" + if not verify_api_key(request): + return jsonify({"error": "Unauthorized"}), 401 + + data = request.json + required = ['lat', 'lon', 'timestamp'] + if not all(k in data for k in required): + return jsonify({"error": "Missing required fields"}), 400 + + conn = get_db() + cur = conn.cursor() + + cur.execute(""" + INSERT INTO air_quality ( + device_id, timestamp, + lat, lon, geom, + temperature_c, humidity_pct, pressure_hpa, + iaq_index, gas_ohms, + pm1_0, pm2_5, pm4_0, pm10, + voc_index, nox_index + ) VALUES ( + %s, to_timestamp(%s / 1000.0), + %s, %s, ST_SetSRID(ST_MakePoint(%s, %s), 4326), + %s, %s, %s, + %s, %s, + %s, %s, %s, %s, + %s, %s + ) + """, ( + data.get('device_id'), + data['timestamp'], + data['lat'], data['lon'], + data['lon'], data['lat'], # ST_MakePoint takes lon,lat + data.get('air_temperature_c'), + data.get('air_humidity_pct'), + data.get('air_pressure_hpa'), + data.get('air_iaq_index'), + data.get('air_gas_ohms'), + data.get('pm1_0'), + data.get('pm2_5'), + data.get('pm4_0'), + data.get('pm10'), + data.get('voc_index'), + data.get('nox_index'), + )) + + conn.commit() + cur.close() + + return jsonify({"inserted": 1}) +``` + +### 5.2 Database Schema + +```sql +-- Air quality measurements table +CREATE TABLE air_quality ( + id SERIAL PRIMARY KEY, + device_id VARCHAR(64), + timestamp TIMESTAMPTZ NOT NULL, + + -- Location + lat DOUBLE PRECISION NOT NULL, + lon DOUBLE PRECISION NOT NULL, + geom GEOMETRY(Point, 4326), + + -- BME680/688 fields + temperature_c REAL, + humidity_pct REAL, + pressure_hpa REAL, + iaq_index INTEGER, -- 0-500 (Bosch IAQ scale) + gas_ohms INTEGER, -- Raw gas resistance + + -- SEN5x fields (if using particulate sensor) + pm1_0 REAL, -- µg/m³ + pm2_5 REAL, + pm4_0 REAL, + pm10 REAL, + voc_index INTEGER, -- 1-500 + nox_index INTEGER, -- 1-500 + + created_at TIMESTAMPTZ DEFAULT NOW() +); + +-- Spatial index for heatmap queries +CREATE INDEX idx_air_quality_geom ON air_quality USING GIST (geom); + +-- Time-based queries +CREATE INDEX idx_air_quality_timestamp ON air_quality (timestamp DESC); + +-- Device filtering +CREATE INDEX idx_air_quality_device ON air_quality (device_id); +``` + +### 5.3 Query Endpoint: `/api/air/heatmap` + +```python +@app.route('/api/air/heatmap', methods=['GET']) +def air_quality_heatmap(): + """Get air quality readings for map overlay.""" + hours = request.args.get('hours', 24, type=int) + bounds = request.args.get('bounds') # sw_lat,sw_lon,ne_lat,ne_lon + metric = request.args.get('metric', 'iaq_index') # or pm2_5, voc_index + + conn = get_db() + cur = conn.cursor() + + # Grid aggregation for heatmap + cur.execute(f""" + SELECT + ST_X(ST_Centroid(ST_Collect(geom))) as lon, + ST_Y(ST_Centroid(ST_Collect(geom))) as lat, + AVG({metric}) as value, + COUNT(*) as samples + FROM air_quality + WHERE timestamp > NOW() - INTERVAL '%s hours' + GROUP BY + ROUND(lat::numeric, 3), + ROUND(lon::numeric, 3) + HAVING AVG({metric}) IS NOT NULL + """, (hours,)) + + results = [] + for row in cur.fetchall(): + results.append({ + "lon": row[0], + "lat": row[1], + "value": round(row[2], 1), + "samples": row[3] + }) + + cur.close() + return jsonify({"data": results, "metric": metric}) +``` + +--- + +## 6. Frontend Integration + +### 6.1 Heatmap Layer (Leaflet) + +```javascript +// In adamaps.org frontend + +import L from 'leaflet'; +import 'leaflet.heat'; + +async function loadAirQualityLayer(map) { + const response = await fetch('/api/air/heatmap?hours=24&metric=iaq_index'); + const data = await response.json(); + + // Convert to heatmap format [lat, lon, intensity] + const heatData = data.data.map(point => [ + point.lat, + point.lon, + normalizeIAQ(point.value) // 0-1 scale + ]); + + const heatLayer = L.heatLayer(heatData, { + radius: 25, + blur: 15, + maxZoom: 17, + gradient: { + 0.0: 'green', // Excellent (IAQ 0-50) + 0.2: 'yellow', // Good (IAQ 51-100) + 0.4: 'orange', // Moderate (IAQ 101-150) + 0.6: 'red', // Unhealthy (IAQ 151-200) + 0.8: 'purple', // Very Unhealthy (201-300) + 1.0: 'maroon' // Hazardous (301+) + } + }); + + return heatLayer; +} + +function normalizeIAQ(iaq) { + // Normalize IAQ 0-500 to 0-1 for heatmap intensity + return Math.min(iaq / 300, 1.0); +} +``` + +### 6.2 Legend / UI + +```html +
+

Air Quality Index

+
0-50 Excellent
+
51-100 Good
+
101-150 Moderate
+
151-200 Unhealthy
+
201-300 Very Unhealthy
+
301+ Hazardous
+
+``` + +--- + +## 7. Implementation Roadmap + +### Phase 1: Sensor Validation (1-2 days) + +1. Identify exact sensor model Cobb has (BME680? BME688? SEN5x?) +2. Acquire USB-I2C adapter if needed (MCP2221A recommended) +3. Test sensor on laptop/Pi to confirm readings work +4. Verify USB-C data port on Bee accepts USB devices + +### Phase 2: Bee-Side Integration (2-3 days) + +1. SSH to Bee, install Python dependencies +2. Deploy `air-sensor.service` +3. Verify Redis key `AirQuality1Hz` is being written +4. Extend `bee-collector.py` to read air quality +5. Confirm fused data appears in uploads + +### Phase 3: AdaMaps API (1-2 days) + +1. Add `air_quality` table to PostgreSQL +2. Add `/api/ingest/air` endpoint +3. Add `/api/air/heatmap` query endpoint +4. Test end-to-end with curl + +### Phase 4: Frontend Overlay (1-2 days) + +1. Add Leaflet.heat library +2. Implement air quality heatmap layer +3. Add legend and metric selector +4. Deploy to adamaps.org + +### Phase 5: Testing & Refinement (ongoing) + +1. Drive routes to collect data +2. Validate heatmap accuracy +3. Tune grid resolution and time windows +4. Consider Bosch BSEC library for accurate IAQ + +--- + +## 8. Bill of Materials + +### Option A: BME680 (Basic VOC/IAQ) + +| Item | Price | Source | +|------|-------|--------| +| BME680 Breakout | $15 | Adafruit/SparkFun | +| MCP2221A USB-I2C | $7 | Adafruit | +| Qwiic/STEMMA cables | $3 | SparkFun | +| **Total** | **~$25** | | + +### Option B: SEN55 (Full Air Quality) + +| Item | Price | Source | +|------|-------|--------| +| SEN55 Sensor | $45 | DigiKey/Mouser | +| Breakout PCB | $5 | JLCPCB/OSHPark | +| CP2102 USB-UART | $3 | Amazon | +| **Total** | **~$55** | | + +### Option C: Both (Comprehensive) + +| Item | Price | +|------|-------| +| BME688 + MCP2221A | $25 | +| SEN55 + CP2102 | $55 | +| **Total** | **~$80** | + +--- + +## 9. Open Questions + +| Question | Priority | Resolution Path | +|----------|----------|-----------------| +| Exact sensor model Cobb has? | High | Ask Cobb | +| Does Bee USB-C port support host mode? | High | Test with USB device | +| Can we install Python packages on Bee? | High | Check if pip works on Yocto | +| Bosch BSEC library licensing? | Medium | Review Bosch terms | +| Target polling rate? | Low | 1Hz default, adjust as needed | + +--- + +## 10. Conclusion + +**Adding air quality sensing to the Hivemapper Bee is feasible and lightweight.** + +The recommended path: +1. **Sensor:** Start with BME680 for quick wins (VOC/IAQ), upgrade to SEN55 for particulate matter if needed +2. **Interface:** MCP2221A USB-I2C adapter ($7) — plug and play on Linux +3. **Software:** Simple Python service (<100 lines), <1% CPU overhead +4. **Data fusion:** Leverage existing Redis infrastructure (GNSSFusion30Hz pattern) +5. **Backend:** New PostGIS table + 2 API endpoints +6. **Frontend:** Leaflet.heat overlay with IAQ color gradient + +**Total estimated effort:** ~1 week for end-to-end prototype +**Total BOM cost:** ~$25-80 depending on sensor choice + +--- + +*End of Report* From d11680a2229abd499f74cf8bb82d497a1ce15bdf Mon Sep 17 00:00:00 2001 From: kayos Date: Fri, 13 Mar 2026 07:16:15 -0700 Subject: [PATCH 03/26] Update air quality sensor integration feasibility report From 28c65660680947181d5ab51b0dd149f07bbbc729 Mon Sep 17 00:00:00 2001 From: Kayos Date: Fri, 13 Mar 2026 07:37:54 -0700 Subject: [PATCH 04/26] =?UTF-8?q?blackbox:=20air=20quality=20aggregator=20?= =?UTF-8?q?(Pi=203B+=20=E2=86=92=20AdaMaps)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- blackbox/air-aggregator.service | 21 +++ blackbox/air_aggregator.py | 235 ++++++++++++++++++++++++++++++++ 2 files changed, 256 insertions(+) create mode 100644 blackbox/air-aggregator.service create mode 100644 blackbox/air_aggregator.py diff --git a/blackbox/air-aggregator.service b/blackbox/air-aggregator.service new file mode 100644 index 0000000..ebeae22 --- /dev/null +++ b/blackbox/air-aggregator.service @@ -0,0 +1,21 @@ +[Unit] +Description=Blackbox Air Quality Aggregator +After=network.target + +[Service] +Type=simple +User=pi +Restart=always +RestartSec=10 +ExecStart=/usr/bin/python3 /home/pi/air-aggregator.py +Environment=BEE_URL=http://192.168.197.1:5000 +Environment=ADAMAPS_URL=https://api.adamaps.org +Environment=ADAMAPS_KEY=***REMOVED*** +Environment=DEVICE_ID=blackbox-pi +Environment=PMS_PORT=/dev/ttyS0 +Environment=SEND_INTERVAL=10 +StandardOutput=journal +StandardError=journal + +[Install] +WantedBy=multi-user.target diff --git a/blackbox/air_aggregator.py b/blackbox/air_aggregator.py new file mode 100644 index 0000000..1370947 --- /dev/null +++ b/blackbox/air_aggregator.py @@ -0,0 +1,235 @@ +#!/usr/bin/env python3 +""" +air-aggregator.py — Blackbox Air Quality Service +Reads BME680 (I2C) + PMS5003 (UART), fuses with GPS from Bee API, +ships readings to AdaMaps ingest endpoint. + +Hardware: + BME680 → I2C (SDA=GPIO2, SCL=GPIO3, addr=0x77) + PMS5003 → UART (/dev/serial0 or /dev/ttyS0, 9600 baud) + +Dependencies: + pip install bme680 requests smbus2 + PMS5003 is read manually (no dep needed) + +Config via env vars or edit constants below. +""" + +import os, time, json, struct, logging, threading +import serial +import smbus2 +import bme680 +import requests +from datetime import datetime, timezone + +# ── Config ──────────────────────────────────────────────────────────────────── +BEE_URL = os.environ.get("BEE_URL", "http://192.168.197.1:5000") +ADAMAPS_URL = os.environ.get("ADAMAPS_URL", "https://api.adamaps.org") +ADAMAPS_KEY = os.environ.get("ADAMAPS_KEY", "***REMOVED***") +DEVICE_ID = os.environ.get("DEVICE_ID", "blackbox-pi") +PMS_PORT = os.environ.get("PMS_PORT", "/dev/ttyS0") +SEND_INTERVAL = int(os.environ.get("SEND_INTERVAL", "10")) # seconds +BUFFER_FILE = "/tmp/air_buffer.json" +# ────────────────────────────────────────────────────────────────────────────── + +logging.basicConfig(level=logging.INFO, + format="%(asctime)s [%(levelname)s] %(message)s") +log = logging.getLogger("air-aggregator") + + +# ── BME680 ──────────────────────────────────────────────────────────────────── +def init_bme680(): + try: + sensor = bme680.BME680(bme680.I2C_ADDR_PRIMARY) + sensor.set_humidity_oversample(bme680.OS_2X) + sensor.set_pressure_oversample(bme680.OS_4X) + sensor.set_temperature_oversample(bme680.OS_8X) + sensor.set_filter(bme680.FILTER_SIZE_3) + sensor.set_gas_status(bme680.ENABLE_GAS_MEAS) + sensor.set_gas_heater_temperature(320) + sensor.set_gas_heater_duration(150) + sensor.select_gas_heater_profile(0) + log.info("BME680 initialized") + return sensor + except Exception as e: + log.error("BME680 init failed: %s", e) + return None + +def read_bme680(sensor): + if not sensor: + return {} + try: + if sensor.get_sensor_data(): + return { + "temperature_c": round(sensor.data.temperature, 2), + "humidity_pct": round(sensor.data.humidity, 2), + "pressure_hpa": round(sensor.data.pressure, 2), + "gas_resistance_ohm": sensor.data.gas_resistance if sensor.data.heat_stable else None, + } + except Exception as e: + log.debug("BME680 read error: %s", e) + return {} + + +# ── PMS5003 ─────────────────────────────────────────────────────────────────── +# Protocol: 32-byte frames, start bytes 0x42 0x4d +PMS_START = b'\x42\x4d' +PMS_FRAME = 32 + +def init_pms5003(): + try: + ser = serial.Serial(PMS_PORT, baudrate=9600, timeout=2) + log.info("PMS5003 on %s", PMS_PORT) + return ser + except Exception as e: + log.error("PMS5003 init failed: %s", e) + return None + +def read_pms5003(ser): + if not ser: + return {} + try: + # Sync to frame start + while True: + b = ser.read(1) + if b == b'\x42': + b2 = ser.read(1) + if b2 == b'\x4d': + break + frame = ser.read(PMS_FRAME - 2) + if len(frame) < PMS_FRAME - 2: + return {} + # Parse: skip length (2 bytes), then 13 uint16s + vals = struct.unpack('>13H', frame[2:28]) + return { + "pm1_0_ug_m3": vals[3], # atmospheric env + "pm2_5_ug_m3": vals[4], + "pm10_ug_m3": vals[5], + "particles_0_3_per_dl": vals[6], + "particles_0_5_per_dl": vals[7], + "particles_1_0_per_dl": vals[8], + "particles_2_5_per_dl": vals[9], + } + except Exception as e: + log.debug("PMS5003 read error: %s", e) + return {} + + +# ── GPS from Bee ────────────────────────────────────────────────────────────── +_gps_cache = {} + +def fetch_gps(): + global _gps_cache + try: + r = requests.get(f"{BEE_URL}/api/1/gnssConcise/latestValid", timeout=3) + if r.ok: + d = r.json() + _gps_cache = { + "lat": d.get("lat") or d.get("latitude"), + "lon": d.get("lon") or d.get("longitude"), + "alt": d.get("alt") or d.get("altitude"), + "gps_fix": True, + } + except Exception as e: + log.debug("GPS fetch failed: %s", e) + _gps_cache["gps_fix"] = False + return _gps_cache + +def gps_poller(): + while True: + fetch_gps() + time.sleep(1) + + +# ── Buffer ──────────────────────────────────────────────────────────────────── +def buffer_reading(reading): + buf = [] + try: + with open(BUFFER_FILE) as f: + buf = json.load(f) + except: pass + buf.append(reading) + # Cap buffer at 1000 readings (~3 hours at 10s interval) + if len(buf) > 1000: + buf = buf[-1000:] + with open(BUFFER_FILE, 'w') as f: + json.dump(buf, f) + +def flush_buffer(): + try: + with open(BUFFER_FILE) as f: + buf = json.load(f) + except: return + if not buf: return + try: + r = requests.post( + f"{ADAMAPS_URL}/api/ingest/air", + json={"device_id": DEVICE_ID, "readings": buf}, + headers={"X-AdaMaps-Key": ADAMAPS_KEY}, + timeout=15 + ) + if r.ok: + log.info("Flushed %d buffered readings", len(buf)) + with open(BUFFER_FILE, 'w') as f: + json.dump([], f) + else: + log.warning("Flush failed: %s", r.status_code) + except Exception as e: + log.warning("Flush error: %s", e) + + +# ── Main loop ───────────────────────────────────────────────────────────────── +def main(): + bme = init_bme680() + pms = init_pms5003() + + # GPS in background thread + t = threading.Thread(target=gps_poller, daemon=True) + t.start() + + log.info("Air aggregator started — sending every %ds", SEND_INTERVAL) + last_send = 0 + + while True: + bme_data = read_bme680(bme) + pms_data = read_pms5003(pms) + gps = dict(_gps_cache) + + reading = { + "device_id": DEVICE_ID, + "sampled_at": datetime.now(timezone.utc).isoformat(), + "lat": gps.get("lat"), + "lon": gps.get("lon"), + "alt": gps.get("alt"), + "gps_fix": gps.get("gps_fix", False), + **bme_data, + **pms_data, + } + + now = time.time() + if now - last_send >= SEND_INTERVAL: + # Try live send first, buffer on failure + try: + flush_buffer() # drain any backlog first + r = requests.post( + f"{ADAMAPS_URL}/api/ingest/air", + json={"device_id": DEVICE_ID, "readings": [reading]}, + headers={"X-AdaMaps-Key": ADAMAPS_KEY}, + timeout=10 + ) + if r.ok: + log.info("Sent | PM2.5=%.1f lat=%s lon=%s", + reading.get("pm2_5_ug_m3", 0), + reading.get("lat"), reading.get("lon")) + else: + log.warning("Send failed %s — buffering", r.status_code) + buffer_reading(reading) + except Exception as e: + log.warning("Offline (%s) — buffering", e) + buffer_reading(reading) + last_send = now + + time.sleep(1) + +if __name__ == "__main__": + main() From 5e3e31ff78f4d1d1d8a506f8bdc7149f5a4a9cbb Mon Sep 17 00:00:00 2001 From: Kayos Date: Fri, 13 Mar 2026 07:58:16 -0700 Subject: [PATCH 05/26] air quality: API routes + frontend heatmap overlay (pending Cobb approval) --- docs/AIR-API-PATCH.py | 145 +++++++++ docs/adamaps-index-preview.html | 545 ++++++++++++++++++++++++++++++++ 2 files changed, 690 insertions(+) create mode 100644 docs/AIR-API-PATCH.py create mode 100644 docs/adamaps-index-preview.html diff --git a/docs/AIR-API-PATCH.py b/docs/AIR-API-PATCH.py new file mode 100644 index 0000000..24a5016 --- /dev/null +++ b/docs/AIR-API-PATCH.py @@ -0,0 +1,145 @@ +#!/usr/bin/env python3 +""" +AIR QUALITY API — Routes to add to /app/app.py on Rackham (adamaps-api container) +Review this file, then apply via: docker exec adamaps-api python3 /tmp/apply_air_patch.py +DO NOT apply until Cobb approves. +""" + +# ─── EPA AQI calculation ────────────────────────────────────────────────────── +def pm25_to_aqi(pm25): + if pm25 is None: return None + breakpoints = [ + (0.0, 12.0, 0, 50), + (12.1, 35.4, 51, 100), + (35.5, 55.4, 101, 150), + (55.5, 150.4, 151, 200), + (150.5, 250.4, 201, 300), + (250.5, 350.4, 301, 400), + (350.5, 500.4, 401, 500), + ] + for c_lo, c_hi, i_lo, i_hi in breakpoints: + if c_lo <= pm25 <= c_hi: + return round((i_hi - i_lo) / (c_hi - c_lo) * (pm25 - c_lo) + i_lo) + return 500 if pm25 > 500 else 0 + +def init_air_table(): + try: + conn = get_db() + cur = conn.cursor() + cur.execute(""" + CREATE TABLE IF NOT EXISTS air_quality ( + id SERIAL PRIMARY KEY, + device_id VARCHAR(64), + sampled_at TIMESTAMP, + lat DOUBLE PRECISION, + lon DOUBLE PRECISION, + alt DOUBLE PRECISION, + gps_fix BOOLEAN DEFAULT FALSE, + pm1_0 FLOAT, + pm2_5 FLOAT, + pm10 FLOAT, + temperature_c FLOAT, + humidity_pct FLOAT, + pressure_hpa FLOAT, + gas_resistance_ohm FLOAT, + aqi INTEGER, + created_at TIMESTAMP DEFAULT NOW() + ) + """) + cur.execute("CREATE INDEX IF NOT EXISTS air_latlon_idx ON air_quality (lat, lon)") + cur.execute("CREATE INDEX IF NOT EXISTS air_sampled_idx ON air_quality (sampled_at DESC)") + conn.commit(); cur.close(); conn.close() + except Exception as e: + print(f"air table init: {e}") + +# ─── /api/ingest/air ────────────────────────────────────────────────────────── +# POST — auth required (X-AdaMaps-Key) +# Body: {"device_id": "blackbox-pi", "readings": [{sampled_at, lat, lon, pm2_5_ug_m3, ...}]} +# Returns: {"inserted": N} +def ingest_air(): + if request.headers.get("X-AdaMaps-Key") != API_KEY: + return jsonify({"error": "unauthorized"}), 401 + data = request.json + if not data: return jsonify({"error": "invalid"}), 400 + device_id = data.get("device_id", "unknown") + readings = data.get("readings", [data] if "sampled_at" in data else []) + if not readings: return jsonify({"error": "no readings"}), 400 + init_air_table() + inserted = 0 + try: + conn = get_db(); cur = conn.cursor() + for r in readings: + try: + pm25 = r.get("pm2_5_ug_m3") or r.get("pm2_5") + cur.execute(""" + INSERT INTO air_quality + (device_id, sampled_at, lat, lon, alt, gps_fix, + pm1_0, pm2_5, pm10, temperature_c, humidity_pct, + pressure_hpa, gas_resistance_ohm, aqi) + VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s) + """, ( + r.get("device_id", device_id), r.get("sampled_at"), + r.get("lat"), r.get("lon"), r.get("alt"), r.get("gps_fix", False), + r.get("pm1_0_ug_m3"), pm25, r.get("pm10_ug_m3"), + r.get("temperature_c"), r.get("humidity_pct"), + r.get("pressure_hpa"), r.get("gas_resistance_ohm"), + pm25_to_aqi(pm25) + )) + inserted += 1 + except: conn.rollback() + conn.commit(); cur.close(); conn.close() + except Exception as e: + return jsonify({"error": "db_unavailable", "detail": str(e)}), 503 + return jsonify({"inserted": inserted, "device_id": device_id}) + +# ─── /api/air/heatmap ───────────────────────────────────────────────────────── +# GET ?metric=aqi|pm2_5 &hours=24 +# Returns [[lat, lon, intensity_0_to_1], ...] for Leaflet.heat +def air_heatmap(): + metric = request.args.get("metric", "aqi") + hours = request.args.get("hours", 24, type=int) + col = "aqi" if metric == "aqi" else "pm2_5" + max_val = 300.0 if metric == "aqi" else 150.0 + try: + conn = get_db(); cur = conn.cursor() + cur.execute(f""" + SELECT lat, lon, {col} FROM air_quality + WHERE sampled_at > NOW() - INTERVAL '%s hours' + AND lat IS NOT NULL AND lon IS NOT NULL + AND {col} IS NOT NULL AND gps_fix = TRUE + ORDER BY sampled_at DESC LIMIT 50000 + """, (hours,)) + rows = [[float(r[0]), float(r[1]), min(float(r[2]) / max_val, 1.0)] + for r in cur.fetchall()] + cur.close(); conn.close() + return jsonify(rows) + except Exception as e: + return jsonify({"error": str(e)}), 500 + +# ─── /api/air/latest ────────────────────────────────────────────────────────── +# GET — most recent reading per device +def air_latest(): + try: + conn = get_db(); cur = conn.cursor() + cur.execute(""" + SELECT DISTINCT ON (device_id) + device_id, sampled_at, lat, lon, + pm1_0, pm2_5, pm10, temperature_c, humidity_pct, aqi + FROM air_quality + WHERE sampled_at > NOW() - INTERVAL '1 hour' + ORDER BY device_id, sampled_at DESC + """) + rows = [{"device_id": r[0], "sampled_at": r[1].isoformat() if r[1] else None, + "lat": float(r[2]) if r[2] else None, "lon": float(r[3]) if r[3] else None, + "pm1_0": r[4], "pm2_5": r[5], "pm10": r[6], + "temperature_c": r[7], "humidity_pct": r[8], "aqi": r[9]} + for r in cur.fetchall()] + cur.close(); conn.close() + return jsonify(rows) + except Exception as e: + return jsonify({"error": str(e)}), 500 + +# ─── Flask route registration (add to app after existing routes) ─────────────── +# app.add_url_rule("/api/ingest/air", "ingest_air", ingest_air, methods=["POST"]) +# app.add_url_rule("/api/air/heatmap", "air_heatmap", air_heatmap, methods=["GET"]) +# app.add_url_rule("/api/air/latest", "air_latest", air_latest, methods=["GET"]) diff --git a/docs/adamaps-index-preview.html b/docs/adamaps-index-preview.html new file mode 100644 index 0000000..495e946 --- /dev/null +++ b/docs/adamaps-index-preview.html @@ -0,0 +1,545 @@ + + + + + + AdaMaps — Verified Sign Map + + + + + + + +
+ + + + + + +// AQI color gradient: green→yellow→orange→red→purple +const AQI_GRADIENT = { + 0.0: '#00e400', // Good (0-50) + 0.17: '#ffff00', // Moderate (51-100) + 0.33: '#ff7e00', // Unhealthy for sensitive (101-150) + 0.50: '#ff0000', // Unhealthy (151-200) + 0.67: '#8f3f97', // Very unhealthy (201-300) + 1.0: '#7e0023', // Hazardous (301+) +}; + +const PM25_GRADIENT = { + 0.0: '#00e5ff', + 0.25: '#00ff88', + 0.50: '#ffff00', + 0.75: '#ff7e00', + 1.0: '#ff0000', +}; + +function aqiLabel(aqi) { + if (aqi == null) return '—'; + if (aqi <= 50) return `${aqi} Good`; + if (aqi <= 100) return `${aqi} Moderate`; + if (aqi <= 150) return `${aqi} Unhealthy (Sensitive)`; + if (aqi <= 200) return `${aqi} Unhealthy`; + if (aqi <= 300) return `${aqi} Very Unhealthy`; + return `${aqi} Hazardous`; +} + +function aqiColor(aqi) { + if (aqi == null) return '#888'; + if (aqi <= 50) return '#00e400'; + if (aqi <= 100) return '#ffff00'; + if (aqi <= 150) return '#ff7e00'; + if (aqi <= 200) return '#ff0000'; + if (aqi <= 300) return '#8f3f97'; + return '#7e0023'; +} + +let airHeatAqi = null; +let airHeatPm25 = null; +let airMode = null; // null | 'aqi' | 'pm25' + +async function fetchAirOverlay(metric) { + try { + const res = await fetch( + `https://api.adamaps.org/api/air/heatmap?metric=${metric}&hours=24`, + { cache: 'no-store' } + ); + if (!res.ok) throw new Error(`HTTP ${res.status}`); + return await res.json(); // [[lat, lon, intensity], ...] + } catch (e) { + console.warn('Air overlay fetch failed:', e); + return []; + } +} + +async function toggleAirOverlay(metric) { + // Clear both layers first + if (airHeatAqi) { map.removeLayer(airHeatAqi); airHeatAqi = null; } + if (airHeatPm25) { map.removeLayer(airHeatPm25); airHeatPm25 = null; } + + // Toggle off if same metric clicked again + if (airMode === metric) { + airMode = null; + btnAqi.classList.remove('active'); + btnPm25.classList.remove('active'); + statusEl.textContent = 'live · ' + new Date().toLocaleTimeString(); + return; + } + + airMode = metric; + btnAqi.classList.toggle('active', metric === 'aqi'); + btnPm25.classList.toggle('active', metric === 'pm25'); + statusEl.textContent = `loading ${metric.toUpperCase()} overlay...`; + + const points = await fetchAirOverlay(metric); + if (!points.length) { + statusEl.textContent = 'no air quality data yet'; + airMode = null; + btnAqi.classList.remove('active'); + btnPm25.classList.remove('active'); + return; + } + + const gradient = metric === 'aqi' ? AQI_GRADIENT : PM25_GRADIENT; + const layer = L.heatLayer(points, { + radius: 25, + blur: 20, + maxZoom: 17, + gradient, + minOpacity: 0.3, + }); + + if (metric === 'aqi') airHeatAqi = layer; + else airHeatPm25 = layer; + + layer.addTo(map); + statusEl.textContent = `${metric.toUpperCase()} overlay · ${points.length.toLocaleString()} pts`; +} + +// ── Legend ──────────────────────────────────────────────────────────────────── +function buildAirLegend() { + const legend = L.control({ position: 'bottomright' }); + legend.onAdd = () => { + const div = L.DomUtil.create('div', 'air-legend'); + div.innerHTML = ` +
AQI / PM2.5
+
Good (0-50)
+
Moderate (51-100)
+
Unhealthy* (101-150)
+
Unhealthy (151-200)
+
Very Unhealthy (201-300)
+
Hazardous (301+)
+ `; + return div; + }; + return legend; +} + +// Call this after map init — wires up buttons and legend +function initAirOverlay() { + buildAirLegend().addTo(map); + btnAqi.addEventListener('click', () => toggleAirOverlay('aqi')); + btnPm25.addEventListener('click', () => toggleAirOverlay('pm25')); +} + + + // Init air overlay buttons + legend after map is ready + initAirOverlay(); + + + From 02d65998018c58b6802790d12847e12b13056b81 Mon Sep 17 00:00:00 2001 From: Kayos Date: Fri, 13 Mar 2026 12:18:43 -0700 Subject: [PATCH 06/26] air overlay: hide legend when inactive, fix script tag comment bug --- docs/adamaps-index-preview.html | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/adamaps-index-preview.html b/docs/adamaps-index-preview.html index 495e946..a8ff7b1 100644 --- a/docs/adamaps-index-preview.html +++ b/docs/adamaps-index-preview.html @@ -406,11 +406,6 @@ fetchAndPlot(); setInterval(fetchAndPlot, 60000); -// ============ AIR QUALITY OVERLAY (add to adamaps.org/index.html) ============ -// Requires: leaflet-heat plugin -// Add to : -// - // AQI color gradient: green→yellow→orange→red→purple const AQI_GRADIENT = { 0.0: '#00e400', // Good (0-50) @@ -478,6 +473,7 @@ async function toggleAirOverlay(metric) { btnAqi.classList.remove('active'); btnPm25.classList.remove('active'); statusEl.textContent = 'live · ' + new Date().toLocaleTimeString(); + if (airLegend) { map.removeControl(airLegend); airLegend = null; } return; } @@ -509,6 +505,9 @@ async function toggleAirOverlay(metric) { layer.addTo(map); statusEl.textContent = `${metric.toUpperCase()} overlay · ${points.length.toLocaleString()} pts`; + + // Show legend when overlay is active + if (!airLegend) { airLegend = buildAirLegend(); airLegend.addTo(map); } } // ── Legend ──────────────────────────────────────────────────────────────────── @@ -530,9 +529,10 @@ function buildAirLegend() { return legend; } +let airLegend = null; + // Call this after map init — wires up buttons and legend function initAirOverlay() { - buildAirLegend().addTo(map); btnAqi.addEventListener('click', () => toggleAirOverlay('aqi')); btnPm25.addEventListener('click', () => toggleAirOverlay('pm25')); } From 01b031cec3f5a77535c0b8381fc0b38f55ab3f0f Mon Sep 17 00:00:00 2001 From: Kayos Date: Sat, 14 Mar 2026 11:50:00 -0700 Subject: [PATCH 07/26] =?UTF-8?q?feat:=20adacam=20migration=20=E2=80=94=20?= =?UTF-8?q?update=20IP,=20add=20pairing,=20bearer=20auth,=20wifi/ssh=20con?= =?UTF-8?q?fig,=20remove=20tunnel=20and=20cmd?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Changed default API URL from 192.168.0.10 to 10.77.0.1 - Added bearer token authentication for POST endpoints - Added /pair endpoint support for device pairing - Added GET/POST /api/1/ssh/status and /api/1/ssh/toggle - Added WiFi config via /api/1/wifi/connect - Removed JSch dependency (no more SSH from app) - Removed /api/1/cmd endpoint references (CVE) - Added PairResponse, SshStatus, WifiStatus models - Added deviceSerial, apiToken, isPaired to settings - Added deriveApiToken() function for token derivation - Updated SettingsViewModel with pairing/wifi/ssh methods - Updated SettingsScreen with pairing UI, WiFi config, SSH toggle - Updated DeviceStatusScreen with SSH toggle card --- app/build.gradle.kts | 2 - .../com/adamaps/varroa/api/BeeApiClient.kt | 221 ++++++------ .../java/com/adamaps/varroa/data/Models.kt | 25 +- .../adamaps/varroa/data/SettingsDataStore.kt | 57 ++- .../varroa/ui/settings/DeviceStatusScreen.kt | 61 ++++ .../varroa/ui/settings/SettingsScreen.kt | 325 +++++++++++++++++- .../varroa/viewmodel/DeviceStatusViewModel.kt | 36 +- .../varroa/viewmodel/SettingsViewModel.kt | 228 ++++++++++++ 8 files changed, 822 insertions(+), 133 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 903d76a..01f4169 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -76,8 +76,6 @@ dependencies { ksp(libs.room.compiler) // WorkManager (background uploads) implementation(libs.work.runtime.ktx) - // SSH connectivity for device_id fallback - implementation("com.jcraft:jsch:0.1.55") // QR Code scanning implementation("com.google.zxing:core:3.5.2") implementation("com.journeyapps:zxing-android-embedded:4.3.0") diff --git a/app/src/main/java/com/adamaps/varroa/api/BeeApiClient.kt b/app/src/main/java/com/adamaps/varroa/api/BeeApiClient.kt index a5ed89a..26b3d81 100644 --- a/app/src/main/java/com/adamaps/varroa/api/BeeApiClient.kt +++ b/app/src/main/java/com/adamaps/varroa/api/BeeApiClient.kt @@ -11,24 +11,22 @@ import com.adamaps.varroa.data.BeeDeviceInfo import com.adamaps.varroa.data.BeePlugin import com.adamaps.varroa.data.GnssData import com.adamaps.varroa.data.GnssStatus +import com.adamaps.varroa.data.PairResponse +import com.adamaps.varroa.data.SshStatus import com.adamaps.varroa.data.StorageStatus import com.adamaps.varroa.data.WifiConfig +import com.adamaps.varroa.data.WifiStatus import com.google.gson.Gson import com.google.gson.reflect.TypeToken import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.async import kotlinx.coroutines.withContext -import kotlinx.coroutines.withTimeoutOrNull import okhttp3.MediaType.Companion.toMediaType import okhttp3.OkHttpClient import okhttp3.Request import java.util.concurrent.TimeUnit -import com.jcraft.jsch.JSch -import com.jcraft.jsch.Session -import java.io.ByteArrayOutputStream class BeeApiClient( - private var apiUrl: String = "http://192.168.0.10:5000" + private var apiUrl: String = "http://10.77.0.1:5000" ) { companion object { private const val TAG = "VarroaBeeAPI" @@ -43,6 +41,9 @@ class BeeApiClient( var isConnected: Boolean = false private set + // Bearer token for authenticated endpoints + var apiToken: String = "" + fun updateUrl(url: String) { val oldUrl = apiUrl apiUrl = url.trimEnd('/') @@ -50,7 +51,7 @@ class BeeApiClient( } /** - * Bind to a specific network (e.g., unvalidated WiFi for Bee AP). + * Bind to a specific network (e.g., unvalidated WiFi for AdaCam AP). * This is the preferred method when using NetworkStateMonitor. */ fun bindToNetwork(network: Network) { @@ -107,7 +108,7 @@ class BeeApiClient( val allWifi = cm.allNetworks.filter { n -> cm.getNetworkCapabilities(n)?.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) == true } - // prefer unvalidated wifi (Bee AP has no internet) + // prefer unvalidated wifi (AdaCam AP has no internet) allWifi.firstOrNull { n -> cm.getNetworkCapabilities(n)?.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED) == false } ?: allWifi.firstOrNull() @@ -178,7 +179,7 @@ class BeeApiClient( } /** - * Check if Bee is reachable. + * Check if AdaCam is reachable. * Updates internal connection state. */ suspend fun ping(): Boolean { @@ -208,6 +209,25 @@ class BeeApiClient( isConnected = false } + // ── Pairing API ─────────────────────────────────────────────────────────── + + /** + * Pair with AdaCam device. Unauthenticated endpoint. + * Returns serial, version, and connection info. + */ + suspend fun pair(): ApiResult = withContext(Dispatchers.IO) { + when (val r = getRaw("/pair")) { + is ApiResult.Success -> try { + ApiResult.Success(gson.fromJson(r.data, PairResponse::class.java)) + } catch (e: Exception) { + ApiResult.Error("Parse error: ${e.message}") + } + is ApiResult.Error -> r + } + } + + // ── Landmarks API ───────────────────────────────────────────────────────── + suspend fun getLandmarks(): ApiResult> = withContext(Dispatchers.IO) { Log.d(TAG, "getLandmarks() called - fetching ALL landmarks") @@ -260,7 +280,7 @@ class BeeApiClient( } } - // ── v7.7 Settings API endpoints ─────────────────────────────────────────── + // ── WiFi API ────────────────────────────────────────────────────────────── suspend fun getWifiConfig(): ApiResult = withContext(Dispatchers.IO) { when (val r = getRaw("/api/1/wifi/status")) { @@ -273,6 +293,17 @@ class BeeApiClient( } } + suspend fun getWifiStatus(): ApiResult = withContext(Dispatchers.IO) { + when (val r = getRaw("/api/1/wifi/status")) { + is ApiResult.Success -> try { + ApiResult.Success(gson.fromJson(r.data, WifiStatus::class.java)) + } catch (e: Exception) { + ApiResult.Error("Parse error: ${e.message}") + } + is ApiResult.Error -> r + } + } + suspend fun setWifiConfig(ssid: String, password: String): ApiResult = withContext(Dispatchers.IO) { try { val jsonBody = gson.toJson(mapOf("ssid" to ssid, "password" to password)) @@ -282,6 +313,7 @@ class BeeApiClient( ) val request = Request.Builder() .url("$apiUrl/api/1/wifi/connect") + .addHeader("Authorization", "Bearer $apiToken") .post(requestBody) .build() @@ -298,6 +330,47 @@ class BeeApiClient( } } + // ── SSH API ─────────────────────────────────────────────────────────────── + + suspend fun getSshStatus(): ApiResult = withContext(Dispatchers.IO) { + when (val r = getRaw("/api/1/ssh/status")) { + is ApiResult.Success -> try { + ApiResult.Success(gson.fromJson(r.data, SshStatus::class.java)) + } catch (e: Exception) { + ApiResult.Error("Parse error: ${e.message}") + } + is ApiResult.Error -> r + } + } + + suspend fun setSshEnabled(enabled: Boolean): ApiResult = withContext(Dispatchers.IO) { + try { + val jsonBody = gson.toJson(mapOf("enable" to enabled)) + val requestBody = okhttp3.RequestBody.create( + "application/json".toMediaType(), + jsonBody + ) + val request = Request.Builder() + .url("$apiUrl/api/1/ssh/toggle") + .addHeader("Authorization", "Bearer $apiToken") + .post(requestBody) + .build() + + client.newCall(request).execute().use { resp -> + val body = resp.body?.string() ?: "" + if (resp.isSuccessful) { + ApiResult.Success(body) + } else { + ApiResult.Error("HTTP ${resp.code}: ${resp.message}", resp.code) + } + } + } catch (e: Exception) { + ApiResult.Error(e.message ?: "Unknown error") + } + } + + // ── Storage & GNSS Status ───────────────────────────────────────────────── + suspend fun getStorageStatus(): ApiResult = withContext(Dispatchers.IO) { when (val r = getRaw("/api/1/storage/usage")) { is ApiResult.Success -> try { @@ -341,6 +414,7 @@ class BeeApiClient( ) val request = Request.Builder() .url("$apiUrl/api/1/config/uploadMode") + .addHeader("Authorization", "Bearer $apiToken") .post(requestBody) .build() @@ -357,50 +431,7 @@ class BeeApiClient( } } - // ── End v7.7 Settings API ───────────────────────────────────────────────── - - suspend fun getDeviceIdViaSsh(): ApiResult = withContext(Dispatchers.IO) { - try { - Log.d(TAG, "Attempting SSH connection to root@192.168.0.10:22") - val jsch = JSch() - val session = jsch.getSession("root", "192.168.0.10", 22) - session.setConfig("StrictHostKeyChecking", "no") - session.connect(10000) // 10 second timeout - - val channel = session.openChannel("exec") - val execChannel = channel as com.jcraft.jsch.ChannelExec - - // Command to find device_id from various locations - val command = "cat /data/registration/device_id 2>/dev/null || cat /opt/dashcam/config/device_id 2>/dev/null || grep device_id /data/persist/*.conf 2>/dev/null | head -1 | cut -d= -f2" - execChannel.setCommand(command) - - val outputStream = ByteArrayOutputStream() - execChannel.outputStream = outputStream - execChannel.connect(5000) // 5 second timeout for command execution - - // Wait for command completion - while (!execChannel.isClosed) { - Thread.sleep(100) - } - - execChannel.disconnect() - session.disconnect() - - val output = outputStream.toString().trim() - Log.d(TAG, "SSH command output: '$output'") - - if (output.isNotEmpty() && !output.contains("No such file") && !output.contains("not found")) { - Log.i(TAG, "Device ID retrieved via SSH: $output") - ApiResult.Success(output) - } else { - Log.w(TAG, "SSH command succeeded but no device_id found") - ApiResult.Error("No device_id found via SSH") - } - } catch (e: Exception) { - Log.e(TAG, "SSH connection failed", e) - ApiResult.Error("SSH error: ${e.message}") - } - } + // ── Camera API ──────────────────────────────────────────────────────────── /** * Try the given endpoint; returns raw image bytes. @@ -429,9 +460,9 @@ class BeeApiClient( } /** - * Fetch detection image from Bee device. + * Fetch detection image from AdaCam device. * - * @param detectionId The landmark/detection ID from the Bee API + * @param detectionId The landmark/detection ID from the API * @return ApiResult containing image bytes (JPEG) or error */ suspend fun getDetectionImage(detectionId: Long): ApiResult = withContext(Dispatchers.IO) { @@ -452,7 +483,7 @@ class BeeApiClient( } /** - * Attempt to delete landmarks from Bee device after successful upload. + * Attempt to delete landmarks from AdaCam device after successful upload. * This tries various potential DELETE endpoints. * * @param landmarkIds List of landmark IDs to delete @@ -471,24 +502,13 @@ class BeeApiClient( "/api/1/landmarks/delete", "/api/1/landmarks/clear", "/api/1/landmarks/cleanup", - "/api/1/landmarks/remove", - "/api/1/cmd" // As a last resort using the cmd endpoint + "/api/1/landmarks/remove" ) for (endpoint in endpoints) { Log.d(TAG, "Trying cleanup endpoint: $endpoint") - val result = when (endpoint) { - "/api/1/cmd" -> { - // Use the cmd endpoint to try deleting landmarks via system commands - Log.d(TAG, "Attempting cleanup via cmd endpoint...") - tryCleanupViaCmd(landmarkIds) - } - else -> { - // Try standard DELETE/POST requests - tryCleanupEndpoint(endpoint, landmarkIds) - } - } + val result = tryCleanupEndpoint(endpoint, landmarkIds) when (result) { is ApiResult.Success -> { @@ -502,8 +522,8 @@ class BeeApiClient( } } - Log.w(TAG, "All cleanup endpoints failed - Bee may not support landmark deletion") - return@withContext ApiResult.Error("No working DELETE endpoint found - cleanup not supported by Bee device") + Log.w(TAG, "All cleanup endpoints failed - AdaCam may not support landmark deletion") + return@withContext ApiResult.Error("No working DELETE endpoint found - cleanup not supported by device") } private suspend fun tryCleanupEndpoint(endpoint: String, landmarkIds: List): ApiResult = withContext(Dispatchers.IO) { @@ -518,10 +538,15 @@ class BeeApiClient( for (method in listOf("DELETE", "POST")) { Log.d(TAG, "Trying $method $endpoint") - val request = Request.Builder() + val requestBuilder = Request.Builder() .url("$apiUrl$endpoint") - .method(method, if (method == "DELETE") null else requestBody) - .build() + .addHeader("Authorization", "Bearer $apiToken") + + val request = if (method == "DELETE") { + requestBuilder.delete(requestBody).build() + } else { + requestBuilder.post(requestBody).build() + } client.newCall(request).execute().use { resp -> val body = resp.body?.string() ?: "" @@ -540,50 +565,4 @@ class BeeApiClient( return@withContext ApiResult.Error("Exception: ${e.message}") } } - - private suspend fun tryCleanupViaCmd(landmarkIds: List): ApiResult = withContext(Dispatchers.IO) { - try { - // Try to find where landmarks are stored and delete them via filesystem commands - val commands = listOf( - "find /data -name '*landmark*' -type f -ls", - "find /tmp -name '*landmark*' -type f -ls", - "ls -la /data/recording/", - "redis-cli KEYS '*landmark*'", - "redis-cli KEYS '*detection*'" - ) - - for (cmd in commands) { - Log.d(TAG, "Trying cmd: $cmd") - val jsonBody = gson.toJson(mapOf("cmd" to cmd)) - val requestBody = okhttp3.RequestBody.create( - "application/json".toMediaType(), - jsonBody - ) - - val request = Request.Builder() - .url("$apiUrl/api/1/cmd") - .post(requestBody) - .build() - - client.newCall(request).execute().use { resp -> - val body = resp.body?.string() ?: "" - if (resp.isSuccessful) { - Log.d(TAG, "Cmd '$cmd' result: $body") - // For now, just log the results to understand the data structure - if (body.contains("landmark") || body.contains("detection")) { - Log.i(TAG, "Found potential landmark storage: $body") - } - } - } - } - - // Note: We're not actually deleting anything via cmd yet, just exploring - Log.w(TAG, "Cleanup via cmd endpoint: exploration complete, actual deletion not implemented yet") - return@withContext ApiResult.Error("Cleanup via cmd: exploration only, deletion not yet implemented") - - } catch (e: Exception) { - Log.e(TAG, "Failed to explore via cmd endpoint", e) - return@withContext ApiResult.Error("Cmd exploration failed: ${e.message}") - } - } } diff --git a/app/src/main/java/com/adamaps/varroa/data/Models.kt b/app/src/main/java/com/adamaps/varroa/data/Models.kt index f9a9e64..7106429 100644 --- a/app/src/main/java/com/adamaps/varroa/data/Models.kt +++ b/app/src/main/java/com/adamaps/varroa/data/Models.kt @@ -47,6 +47,21 @@ data class BeeDeviceInfo( @SerializedName("ssid") val ssid: String? = null ) +// ── Pairing API ─────────────────────────────────────────────────────────────── + +data class PairResponse( + @SerializedName("serial") val serial: String, + @SerializedName("version") val version: String, + @SerializedName("ap_ip") val apIp: String, + @SerializedName("api_port") val apiPort: Int +) + +// ── SSH API ─────────────────────────────────────────────────────────────────── + +data class SshStatus( + @SerializedName("active") val active: Boolean +) + // ── ADAMaps ingest ──────────────────────────────────────────────────────────── data class AdaMapsDetection( @@ -93,7 +108,15 @@ data class WifiConfig( @SerializedName("ssid") val ssid: String? = null, @SerializedName("password") val password: String? = null, @SerializedName("connected") val connected: Boolean? = null, - @SerializedName("ip") val ip: String? = null + @SerializedName("ip") val ip: String? = null, + @SerializedName("state") val state: String? = null +) + +data class WifiStatus( + @SerializedName("ssid") val ssid: String? = null, + @SerializedName("ip") val ip: String? = null, + @SerializedName("state") val state: String? = null, + @SerializedName("connected") val connected: Boolean? = null ) data class StorageStatus( diff --git a/app/src/main/java/com/adamaps/varroa/data/SettingsDataStore.kt b/app/src/main/java/com/adamaps/varroa/data/SettingsDataStore.kt index c97c981..44a352c 100644 --- a/app/src/main/java/com/adamaps/varroa/data/SettingsDataStore.kt +++ b/app/src/main/java/com/adamaps/varroa/data/SettingsDataStore.kt @@ -10,11 +10,12 @@ import androidx.datastore.preferences.core.stringPreferencesKey import androidx.datastore.preferences.preferencesDataStore import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map +import java.security.MessageDigest private val Context.dataStore: DataStore by preferencesDataStore(name = "varroa_settings") data class VarroaSettings( - val beeApiUrl: String = "http://192.168.0.10:5000", + val beeApiUrl: String = "http://10.77.0.1:5000", val adamapsApiUrl: String = "https://api.adamaps.org", val adamapsApiKey: String = "***REMOVED***", val pollIntervalSeconds: Int = 30, @@ -22,9 +23,23 @@ data class VarroaSettings( val cameraRefreshSeconds: Int = 30, val forwardingEnabled: Boolean = true, val cachedDeviceId: String = "unknown", - val walletAddress: String = "" + val walletAddress: String = "", + // AdaCam pairing + val deviceSerial: String = "", + val apiToken: String = "", + val isPaired: Boolean = false ) +/** + * Derive the API token from the device serial. + * Token = first 32 chars of SHA-256("adacam-api-{serial}-token") + */ +fun deriveApiToken(serial: String): String { + val input = "adacam-api-$serial-token" + val bytes = MessageDigest.getInstance("SHA-256").digest(input.toByteArray()) + return bytes.joinToString("") { "%02x".format(it) }.substring(0, 32) +} + class SettingsDataStore(private val context: Context) { companion object { @@ -37,11 +52,15 @@ class SettingsDataStore(private val context: Context) { private val KEY_FORWARDING_ENABLED = booleanPreferencesKey("forwarding_enabled") private val KEY_CACHED_DEVICE_ID = stringPreferencesKey("cached_device_id") private val KEY_WALLET_ADDRESS = stringPreferencesKey("wallet_address") + // AdaCam pairing keys + private val KEY_DEVICE_SERIAL = stringPreferencesKey("device_serial") + private val KEY_API_TOKEN = stringPreferencesKey("api_token") + private val KEY_IS_PAIRED = booleanPreferencesKey("is_paired") } val settings: Flow = context.dataStore.data.map { prefs -> VarroaSettings( - beeApiUrl = prefs[KEY_BEE_URL] ?: "http://192.168.0.10:5000", + beeApiUrl = prefs[KEY_BEE_URL] ?: "http://10.77.0.1:5000", adamapsApiUrl = prefs[KEY_ADAMAPS_URL] ?: "https://api.adamaps.org", adamapsApiKey = prefs[KEY_ADAMAPS_KEY] ?: "***REMOVED***", pollIntervalSeconds = prefs[KEY_POLL_INTERVAL] ?: 30, @@ -49,7 +68,10 @@ class SettingsDataStore(private val context: Context) { cameraRefreshSeconds = prefs[KEY_CAMERA_REFRESH] ?: 30, forwardingEnabled = prefs[KEY_FORWARDING_ENABLED] ?: true, cachedDeviceId = prefs[KEY_CACHED_DEVICE_ID] ?: "unknown", - walletAddress = prefs[KEY_WALLET_ADDRESS] ?: "" + walletAddress = prefs[KEY_WALLET_ADDRESS] ?: "", + deviceSerial = prefs[KEY_DEVICE_SERIAL] ?: "", + apiToken = prefs[KEY_API_TOKEN] ?: "", + isPaired = prefs[KEY_IS_PAIRED] ?: false ) } @@ -64,6 +86,9 @@ class SettingsDataStore(private val context: Context) { prefs[KEY_FORWARDING_ENABLED] = s.forwardingEnabled prefs[KEY_CACHED_DEVICE_ID] = s.cachedDeviceId prefs[KEY_WALLET_ADDRESS] = s.walletAddress + prefs[KEY_DEVICE_SERIAL] = s.deviceSerial + prefs[KEY_API_TOKEN] = s.apiToken + prefs[KEY_IS_PAIRED] = s.isPaired } } @@ -72,4 +97,28 @@ class SettingsDataStore(private val context: Context) { prefs[KEY_CACHED_DEVICE_ID] = deviceId } } + + /** + * Store pairing data after successful pairing with AdaCam. + * Derives and stores the API token from the serial. + */ + suspend fun savePairing(serial: String) { + val token = deriveApiToken(serial) + context.dataStore.edit { prefs -> + prefs[KEY_DEVICE_SERIAL] = serial + prefs[KEY_API_TOKEN] = token + prefs[KEY_IS_PAIRED] = true + } + } + + /** + * Clear pairing data (for re-pairing or reset). + */ + suspend fun clearPairing() { + context.dataStore.edit { prefs -> + prefs[KEY_DEVICE_SERIAL] = "" + prefs[KEY_API_TOKEN] = "" + prefs[KEY_IS_PAIRED] = false + } + } } diff --git a/app/src/main/java/com/adamaps/varroa/ui/settings/DeviceStatusScreen.kt b/app/src/main/java/com/adamaps/varroa/ui/settings/DeviceStatusScreen.kt index 0403e38..0edc86b 100644 --- a/app/src/main/java/com/adamaps/varroa/ui/settings/DeviceStatusScreen.kt +++ b/app/src/main/java/com/adamaps/varroa/ui/settings/DeviceStatusScreen.kt @@ -10,6 +10,7 @@ import androidx.compose.material.icons.filled.CheckCircle import androidx.compose.material.icons.filled.Warning import androidx.compose.material.icons.filled.Error import androidx.compose.material3.* +import androidx.compose.material3.SwitchDefaults import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -22,6 +23,7 @@ import androidx.lifecycle.viewmodel.compose.viewModel import com.adamaps.varroa.data.BeeDeviceInfo import com.adamaps.varroa.data.BeePlugin import com.adamaps.varroa.data.GnssStatus +import com.adamaps.varroa.data.SshStatus import com.adamaps.varroa.data.StorageStatus import com.adamaps.varroa.data.WifiConfig import com.adamaps.varroa.ui.theme.* @@ -36,6 +38,7 @@ fun DeviceStatusScreen( val state by vm.state.collectAsState() val wifiResult by vm.wifiSaveResult.collectAsState() val uploadResult by vm.uploadModeResult.collectAsState() + val sshResult by vm.sshToggleResult.collectAsState() // Refresh on first load LaunchedEffect(Unit) { @@ -56,6 +59,12 @@ fun DeviceStatusScreen( vm.clearUploadModeResult() } } + LaunchedEffect(sshResult) { + sshResult?.let { + snackbarHostState.showSnackbar(it) + vm.clearSshToggleResult() + } + } Scaffold( containerColor = Background, @@ -119,6 +128,9 @@ fun DeviceStatusScreen( // GPS/GNSS Status GnssStatusCard(state.gnssStatus, state.deviceInfo?.hasGnssLock) + // SSH Status + SshStatusCard(state.sshStatus, vm) + // Upload Mode UploadModeCard(state.deviceInfo?.uploadMode, vm) @@ -460,6 +472,55 @@ private fun UploadModeCard(currentMode: String?, vm: DeviceStatusViewModel) { } } +@Composable +private fun SshStatusCard(ssh: SshStatus?, vm: DeviceStatusViewModel) { + val isActive = ssh?.active ?: false + + StatusCard("SSH ACCESS") { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Column { + Text( + "Enable SSH", + color = OnSurface, + fontFamily = FontFamily.Monospace, + fontSize = 12.sp + ) + Text( + "SSH access over home WiFi", + color = Color.Gray, + fontFamily = FontFamily.Monospace, + fontSize = 10.sp + ) + } + Switch( + checked = isActive, + onCheckedChange = { vm.toggleSsh(it) }, + colors = SwitchDefaults.colors( + checkedThumbColor = Amber, + checkedTrackColor = Amber.copy(alpha = 0.5f), + uncheckedThumbColor = Color.Gray, + uncheckedTrackColor = SurfaceVariant + ) + ) + } + + if (isActive) { + Spacer(Modifier.height(8.dp)) + Text( + "SSH enabled. Connect via:\nssh root@", + color = Color.Green, + fontFamily = FontFamily.Monospace, + fontSize = 10.sp, + lineHeight = 14.sp + ) + } + } +} + @Composable private fun PluginsCard(plugins: List) { StatusCard("PLUGINS") { diff --git a/app/src/main/java/com/adamaps/varroa/ui/settings/SettingsScreen.kt b/app/src/main/java/com/adamaps/varroa/ui/settings/SettingsScreen.kt index 1698807..19cd2d1 100644 --- a/app/src/main/java/com/adamaps/varroa/ui/settings/SettingsScreen.kt +++ b/app/src/main/java/com/adamaps/varroa/ui/settings/SettingsScreen.kt @@ -11,8 +11,11 @@ import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.filled.Check import androidx.compose.material.icons.filled.ChevronRight import androidx.compose.material.icons.filled.Clear +import androidx.compose.material.icons.filled.Link +import androidx.compose.material.icons.filled.LinkOff import androidx.compose.material.icons.filled.PhoneAndroid import androidx.compose.material.icons.filled.QrCodeScanner +import androidx.compose.material.icons.filled.Wifi import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment @@ -24,6 +27,8 @@ import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.text.input.PasswordVisualTransformation +import androidx.compose.ui.text.input.VisualTransformation import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp @@ -44,6 +49,14 @@ fun SettingsScreen( ) { val currentSettings by vm.settings.collectAsState() val saved by vm.saved.collectAsState() + val isPaired by vm.isPaired.collectAsState() + val deviceSerial by vm.deviceSerial.collectAsState() + val pairingInProgress by vm.pairingInProgress.collectAsState() + val pairingResult by vm.pairingResult.collectAsState() + val sshStatus by vm.sshStatus.collectAsState() + val sshToggleResult by vm.sshToggleResult.collectAsState() + val wifiStatus by vm.wifiStatus.collectAsState() + val wifiConnectResult by vm.wifiConnectResult.collectAsState() // Local edit state — initialized from current settings var beeApiUrl by remember(currentSettings) { mutableStateOf(currentSettings.beeApiUrl) } @@ -62,6 +75,24 @@ fun SettingsScreen( vm.clearSaved() } } + LaunchedEffect(pairingResult) { + pairingResult?.let { + snackbarHostState.showSnackbar(it) + vm.clearPairingResult() + } + } + LaunchedEffect(sshToggleResult) { + sshToggleResult?.let { + snackbarHostState.showSnackbar(it) + vm.clearSshResult() + } + } + LaunchedEffect(wifiConnectResult) { + wifiConnectResult?.let { + snackbarHostState.showSnackbar(it) + vm.clearWifiResult() + } + } Scaffold( containerColor = Background, @@ -86,7 +117,7 @@ fun SettingsScreen( actions = { IconButton(onClick = { vm.save( - VarroaSettings( + currentSettings.copy( beeApiUrl = beeApiUrl.trim(), adamapsApiUrl = adamapsApiUrl.trim(), adamapsApiKey = adamapsApiKey.trim(), @@ -111,6 +142,15 @@ fun SettingsScreen( .padding(16.dp), verticalArrangement = Arrangement.spacedBy(16.dp) ) { + // Device Pairing section + PairingSection( + isPaired = isPaired, + deviceSerial = deviceSerial, + pairingInProgress = pairingInProgress, + onPair = { vm.pairDevice() }, + onClearPairing = { vm.clearPairing() } + ) + // Device Status navigation card Card( onClick = onNavigateToDeviceStatus, @@ -157,12 +197,30 @@ fun SettingsScreen( } } - SettingsSection("BEE DEVICE") { + // WiFi Config section (only show if paired) + if (isPaired) { + WifiConfigSection( + wifiStatus = wifiStatus, + onConnect = { ssid, password -> vm.connectWifi(ssid, password) }, + onRefresh = { vm.refreshWifiStatus() } + ) + } + + // SSH section (only show if paired) + if (isPaired) { + SshSection( + sshStatus = sshStatus, + onToggle = { enabled -> vm.toggleSsh(enabled) }, + onRefresh = { vm.refreshSshStatus() } + ) + } + + SettingsSection("ADACAM DEVICE") { SettingsField( - label = "Bee API URL", + label = "AdaCam API URL", value = beeApiUrl, onValueChange = { beeApiUrl = it }, - hint = "http://192.168.0.10:5000" + hint = "http://10.77.0.1:5000" ) } @@ -227,6 +285,265 @@ fun SettingsScreen( } } +@Composable +private fun PairingSection( + isPaired: Boolean, + deviceSerial: String, + pairingInProgress: Boolean, + onPair: () -> Unit, + onClearPairing: () -> Unit +) { + SettingsSection("DEVICE PAIRING") { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Row(verticalAlignment = Alignment.CenterVertically) { + Icon( + if (isPaired) Icons.Default.Link else Icons.Default.LinkOff, + contentDescription = null, + tint = if (isPaired) Color.Green else Color.Gray, + modifier = Modifier.size(20.dp) + ) + Spacer(Modifier.width(8.dp)) + Column { + Text( + if (isPaired) "Paired" else "Not Paired", + color = if (isPaired) Color.Green else Color.Gray, + fontFamily = FontFamily.Monospace, + fontSize = 14.sp, + fontWeight = FontWeight.Bold + ) + if (isPaired && deviceSerial.isNotBlank()) { + Text( + "Serial: $deviceSerial", + color = Color.Gray, + fontFamily = FontFamily.Monospace, + fontSize = 10.sp + ) + } + } + } + } + + Spacer(Modifier.height(12.dp)) + + if (!isPaired) { + Text( + "Connect your phone to the AdaCam WiFi network (adacam-XXXXXX), then tap Pair.", + color = Color.Gray, + fontFamily = FontFamily.Monospace, + fontSize = 10.sp, + lineHeight = 14.sp + ) + Spacer(Modifier.height(8.dp)) + Button( + onClick = onPair, + enabled = !pairingInProgress, + colors = ButtonDefaults.buttonColors(containerColor = Amber), + modifier = Modifier.fillMaxWidth() + ) { + if (pairingInProgress) { + CircularProgressIndicator( + modifier = Modifier.size(16.dp), + color = Background, + strokeWidth = 2.dp + ) + Spacer(Modifier.width(8.dp)) + } + Text( + if (pairingInProgress) "Pairing..." else "Pair with AdaCam", + color = Background + ) + } + } else { + OutlinedButton( + onClick = onClearPairing, + colors = ButtonDefaults.outlinedButtonColors(contentColor = Color.Red), + border = androidx.compose.foundation.BorderStroke(1.dp, Color.Red), + modifier = Modifier.fillMaxWidth() + ) { + Text("Clear Pairing", fontSize = 12.sp) + } + } + } +} + +@Composable +private fun WifiConfigSection( + wifiStatus: com.adamaps.varroa.data.WifiStatus?, + onConnect: (String, String) -> Unit, + onRefresh: () -> Unit +) { + var ssid by remember { mutableStateOf("") } + var password by remember { mutableStateOf("") } + var showPassword by remember { mutableStateOf(false) } + + SettingsSection("HOME WIFI NETWORK") { + // Current status + if (wifiStatus != null) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Row(verticalAlignment = Alignment.CenterVertically) { + Icon( + Icons.Default.Wifi, + contentDescription = null, + tint = if (wifiStatus.connected == true) Color.Green else Color.Yellow, + modifier = Modifier.size(16.dp) + ) + Spacer(Modifier.width(8.dp)) + Column { + Text( + if (wifiStatus.connected == true) "Connected" else "Disconnected", + color = if (wifiStatus.connected == true) Color.Green else Color.Yellow, + fontFamily = FontFamily.Monospace, + fontSize = 12.sp, + fontWeight = FontWeight.Bold + ) + if (wifiStatus.ssid != null && wifiStatus.connected == true) { + Text( + wifiStatus.ssid, + color = Color.Gray, + fontFamily = FontFamily.Monospace, + fontSize = 10.sp + ) + } + if (wifiStatus.ip != null && wifiStatus.connected == true) { + Text( + "IP: ${wifiStatus.ip}", + color = Color.Gray, + fontFamily = FontFamily.Monospace, + fontSize = 10.sp + ) + } + } + } + IconButton(onClick = onRefresh) { + Icon( + Icons.Default.ChevronRight, + contentDescription = "Refresh", + tint = Amber + ) + } + } + Spacer(Modifier.height(12.dp)) + Divider(color = SurfaceVariant) + Spacer(Modifier.height(12.dp)) + } + + Text( + "Configure home WiFi for internet access", + color = Color.Gray, + fontFamily = FontFamily.Monospace, + fontSize = 10.sp + ) + Spacer(Modifier.height(8.dp)) + + OutlinedTextField( + value = ssid, + onValueChange = { ssid = it }, + label = { Text("SSID", fontFamily = FontFamily.Monospace, fontSize = 11.sp) }, + singleLine = true, + modifier = Modifier.fillMaxWidth(), + colors = OutlinedTextFieldDefaults.colors( + focusedBorderColor = Amber, + unfocusedBorderColor = SurfaceVariant, + focusedLabelColor = Amber, + unfocusedLabelColor = Color.Gray, + cursorColor = Amber, + focusedTextColor = OnSurface, + unfocusedTextColor = OnSurface + ) + ) + Spacer(Modifier.height(8.dp)) + + OutlinedTextField( + value = password, + onValueChange = { password = it }, + label = { Text("Password", fontFamily = FontFamily.Monospace, fontSize = 11.sp) }, + singleLine = true, + modifier = Modifier.fillMaxWidth(), + visualTransformation = if (showPassword) VisualTransformation.None else PasswordVisualTransformation(), + colors = OutlinedTextFieldDefaults.colors( + focusedBorderColor = Amber, + unfocusedBorderColor = SurfaceVariant, + focusedLabelColor = Amber, + unfocusedLabelColor = Color.Gray, + cursorColor = Amber, + focusedTextColor = OnSurface, + unfocusedTextColor = OnSurface + ) + ) + Spacer(Modifier.height(8.dp)) + + Button( + onClick = { onConnect(ssid, password) }, + enabled = ssid.isNotBlank(), + colors = ButtonDefaults.buttonColors(containerColor = Amber), + modifier = Modifier.fillMaxWidth() + ) { + Text("Connect", color = Background) + } + } +} + +@Composable +private fun SshSection( + sshStatus: com.adamaps.varroa.data.SshStatus?, + onToggle: (Boolean) -> Unit, + onRefresh: () -> Unit +) { + val isActive = sshStatus?.active ?: false + + SettingsSection("SSH ACCESS") { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Column { + Text( + "Enable SSH", + color = OnSurface, + fontFamily = FontFamily.Monospace, + fontSize = 12.sp + ) + Text( + "SSH over home WiFi network", + color = Color.Gray, + fontFamily = FontFamily.Monospace, + fontSize = 10.sp + ) + } + Switch( + checked = isActive, + onCheckedChange = { onToggle(it) }, + colors = SwitchDefaults.colors( + checkedThumbColor = Amber, + checkedTrackColor = Amber.copy(alpha = 0.5f), + uncheckedThumbColor = Color.Gray, + uncheckedTrackColor = SurfaceVariant + ) + ) + } + + if (isActive) { + Spacer(Modifier.height(8.dp)) + Text( + "SSH enabled. Connect via:\nssh root@", + color = Color.Green, + fontFamily = FontFamily.Monospace, + fontSize = 10.sp, + lineHeight = 14.sp + ) + } + } +} + @Composable private fun SettingsSection(title: String, content: @Composable ColumnScope.() -> Unit) { Card( diff --git a/app/src/main/java/com/adamaps/varroa/viewmodel/DeviceStatusViewModel.kt b/app/src/main/java/com/adamaps/varroa/viewmodel/DeviceStatusViewModel.kt index 09ab8be..ae6e7fa 100644 --- a/app/src/main/java/com/adamaps/varroa/viewmodel/DeviceStatusViewModel.kt +++ b/app/src/main/java/com/adamaps/varroa/viewmodel/DeviceStatusViewModel.kt @@ -10,6 +10,7 @@ import com.adamaps.varroa.data.BeeDeviceInfo import com.adamaps.varroa.data.BeePlugin import com.adamaps.varroa.data.GnssStatus import com.adamaps.varroa.data.SettingsDataStore +import com.adamaps.varroa.data.SshStatus import com.adamaps.varroa.data.StorageStatus import com.adamaps.varroa.data.WifiConfig import kotlinx.coroutines.flow.MutableStateFlow @@ -25,6 +26,7 @@ data class DeviceStatusState( val wifiConfig: WifiConfig? = null, val storageStatus: StorageStatus? = null, val gnssStatus: GnssStatus? = null, + val sshStatus: SshStatus? = null, val plugins: List = emptyList(), val error: String? = null ) @@ -47,6 +49,9 @@ class DeviceStatusViewModel(app: Application) : AndroidViewModel(app) { private val _uploadModeResult = MutableStateFlow(null) val uploadModeResult: StateFlow = _uploadModeResult.asStateFlow() + private val _sshToggleResult = MutableStateFlow(null) + val sshToggleResult: StateFlow = _sshToggleResult.asStateFlow() + fun refresh() { viewModelScope.launch { _state.value = _state.value.copy(isLoading = true, error = null) @@ -54,6 +59,10 @@ class DeviceStatusViewModel(app: Application) : AndroidViewModel(app) { try { val settings = store.settings.first() val client = BeeApiClient(settings.beeApiUrl) + // Set auth token if paired + if (settings.apiToken.isNotBlank()) { + client.apiToken = settings.apiToken + } beeClient = client // Fetch all status in parallel @@ -62,12 +71,14 @@ class DeviceStatusViewModel(app: Application) : AndroidViewModel(app) { val storageResult = client.getStorageStatus() val gnssResult = client.getGnssStatus() val pluginsResult = client.getPlugins() + val sshResult = client.getSshStatus() val deviceInfo = (deviceInfoResult as? ApiResult.Success)?.data val wifi = (wifiResult as? ApiResult.Success)?.data val storage = (storageResult as? ApiResult.Success)?.data val gnss = (gnssResult as? ApiResult.Success)?.data val plugins = (pluginsResult as? ApiResult.Success)?.data ?: emptyList() + val ssh = (sshResult as? ApiResult.Success)?.data val isConnected = deviceInfo != null @@ -78,8 +89,9 @@ class DeviceStatusViewModel(app: Application) : AndroidViewModel(app) { wifiConfig = wifi, storageStatus = storage, gnssStatus = gnss, + sshStatus = ssh, plugins = plugins, - error = if (!isConnected) "Cannot connect to Bee device" else null + error = if (!isConnected) "Cannot connect to AdaCam device" else null ) Log.i(TAG, "Device status refreshed: connected=$isConnected, plugins=${plugins.size}") @@ -129,6 +141,24 @@ class DeviceStatusViewModel(app: Application) : AndroidViewModel(app) { } } + fun toggleSsh(enabled: Boolean) { + viewModelScope.launch { + val client = beeClient ?: return@launch + _sshToggleResult.value = null + + when (val result = client.setSshEnabled(enabled)) { + is ApiResult.Success -> { + _sshToggleResult.value = if (enabled) "SSH enabled" else "SSH disabled" + _state.value = _state.value.copy(sshStatus = SshStatus(active = enabled)) + } + is ApiResult.Error -> { + _sshToggleResult.value = "Failed: ${result.message}" + refresh() + } + } + } + } + fun clearWifiResult() { _wifiSaveResult.value = null } @@ -136,4 +166,8 @@ class DeviceStatusViewModel(app: Application) : AndroidViewModel(app) { fun clearUploadModeResult() { _uploadModeResult.value = null } + + fun clearSshToggleResult() { + _sshToggleResult.value = null + } } diff --git a/app/src/main/java/com/adamaps/varroa/viewmodel/SettingsViewModel.kt b/app/src/main/java/com/adamaps/varroa/viewmodel/SettingsViewModel.kt index a703b95..5d1ef53 100644 --- a/app/src/main/java/com/adamaps/varroa/viewmodel/SettingsViewModel.kt +++ b/app/src/main/java/com/adamaps/varroa/viewmodel/SettingsViewModel.kt @@ -1,30 +1,97 @@ package com.adamaps.varroa.viewmodel import android.app.Application +import android.util.Log import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.viewModelScope +import com.adamaps.varroa.api.BeeApiClient +import com.adamaps.varroa.data.ApiResult import com.adamaps.varroa.data.SettingsDataStore +import com.adamaps.varroa.data.SshStatus import com.adamaps.varroa.data.VarroaSettings +import com.adamaps.varroa.data.WifiStatus import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch class SettingsViewModel(app: Application) : AndroidViewModel(app) { + companion object { + private const val TAG = "SettingsVM" + } + private val store = SettingsDataStore(app) + private var beeClient: BeeApiClient? = null val settings: StateFlow = store.settings .stateIn(viewModelScope, SharingStarted.Eagerly, VarroaSettings()) + // Derived observables from settings + val isPaired: StateFlow = store.settings + .map { it.isPaired } + .stateIn(viewModelScope, SharingStarted.Eagerly, false) + + val deviceSerial: StateFlow = store.settings + .map { it.deviceSerial } + .stateIn(viewModelScope, SharingStarted.Eagerly, "") + private val _saved = MutableStateFlow(false) val saved: StateFlow = _saved.asStateFlow() + // Pairing state + private val _pairingInProgress = MutableStateFlow(false) + val pairingInProgress: StateFlow = _pairingInProgress.asStateFlow() + + private val _pairingResult = MutableStateFlow(null) + val pairingResult: StateFlow = _pairingResult.asStateFlow() + + // SSH state + private val _sshStatus = MutableStateFlow(null) + val sshStatus: StateFlow = _sshStatus.asStateFlow() + + private val _sshToggleResult = MutableStateFlow(null) + val sshToggleResult: StateFlow = _sshToggleResult.asStateFlow() + + // WiFi state + private val _wifiStatus = MutableStateFlow(null) + val wifiStatus: StateFlow = _wifiStatus.asStateFlow() + + private val _wifiConnectResult = MutableStateFlow(null) + val wifiConnectResult: StateFlow = _wifiConnectResult.asStateFlow() + + init { + // Initialize BeeApiClient with stored settings and token + viewModelScope.launch { + val s = store.settings.first() + val client = BeeApiClient(s.beeApiUrl) + if (s.apiToken.isNotBlank()) { + client.apiToken = s.apiToken + } + beeClient = client + + // Fetch initial statuses if paired + if (s.isPaired) { + refreshSshStatus() + refreshWifiStatus() + } + } + } + fun save(s: VarroaSettings) { viewModelScope.launch { store.save(s) + + // Update client URL and token if changed + beeClient?.updateUrl(s.beeApiUrl) + if (s.apiToken.isNotBlank()) { + beeClient?.apiToken = s.apiToken + } + _saved.value = true } } @@ -32,4 +99,165 @@ class SettingsViewModel(app: Application) : AndroidViewModel(app) { fun clearSaved() { _saved.value = false } + + // ── Pairing ─────────────────────────────────────────────────────────────── + + /** + * Pair with the AdaCam device. + * Fetches serial from /pair endpoint, derives token, stores both. + */ + fun pairDevice() { + viewModelScope.launch { + _pairingInProgress.value = true + _pairingResult.value = null + + try { + val s = store.settings.first() + val client = BeeApiClient(s.beeApiUrl) + beeClient = client + + when (val result = client.pair()) { + is ApiResult.Success -> { + val serial = result.data.serial + Log.i(TAG, "Pairing successful: serial=$serial") + + // Store pairing data (derives token automatically) + store.savePairing(serial) + + // Update client with new token + val newSettings = store.settings.first() + client.apiToken = newSettings.apiToken + + _pairingResult.value = "Paired successfully! Serial: $serial" + + // Fetch statuses now that we're paired + refreshSshStatus() + refreshWifiStatus() + } + is ApiResult.Error -> { + Log.e(TAG, "Pairing failed: ${result.message}") + _pairingResult.value = "Pairing failed: ${result.message}" + } + } + } catch (e: Exception) { + Log.e(TAG, "Pairing exception", e) + _pairingResult.value = "Pairing error: ${e.message}" + } finally { + _pairingInProgress.value = false + } + } + } + + /** + * Clear pairing data (for re-pairing). + */ + fun clearPairing() { + viewModelScope.launch { + store.clearPairing() + beeClient?.apiToken = "" + _sshStatus.value = null + _wifiStatus.value = null + _pairingResult.value = "Pairing cleared" + } + } + + fun clearPairingResult() { + _pairingResult.value = null + } + + // ── WiFi ────────────────────────────────────────────────────────────────── + + /** + * Refresh WiFi status from device. + */ + fun refreshWifiStatus() { + viewModelScope.launch { + val client = beeClient ?: return@launch + when (val result = client.getWifiStatus()) { + is ApiResult.Success -> { + _wifiStatus.value = result.data + } + is ApiResult.Error -> { + Log.w(TAG, "Failed to get WiFi status: ${result.message}") + } + } + } + } + + /** + * Connect device to a home WiFi network. + */ + fun connectWifi(ssid: String, password: String) { + viewModelScope.launch { + val client = beeClient ?: run { + _wifiConnectResult.value = "Not connected to device" + return@launch + } + _wifiConnectResult.value = null + + when (val result = client.setWifiConfig(ssid, password)) { + is ApiResult.Success -> { + _wifiConnectResult.value = "WiFi config sent. Connecting to $ssid..." + // Refresh status after a short delay + kotlinx.coroutines.delay(3000) + refreshWifiStatus() + } + is ApiResult.Error -> { + _wifiConnectResult.value = "Failed: ${result.message}" + } + } + } + } + + fun clearWifiResult() { + _wifiConnectResult.value = null + } + + // ── SSH ─────────────────────────────────────────────────────────────────── + + /** + * Refresh SSH status from device. + */ + fun refreshSshStatus() { + viewModelScope.launch { + val client = beeClient ?: return@launch + when (val result = client.getSshStatus()) { + is ApiResult.Success -> { + _sshStatus.value = result.data + } + is ApiResult.Error -> { + Log.w(TAG, "Failed to get SSH status: ${result.message}") + } + } + } + } + + /** + * Toggle SSH on/off on the device. + */ + fun toggleSsh(enabled: Boolean) { + viewModelScope.launch { + val client = beeClient ?: run { + _sshToggleResult.value = "Not connected to device" + return@launch + } + _sshToggleResult.value = null + + when (val result = client.setSshEnabled(enabled)) { + is ApiResult.Success -> { + _sshToggleResult.value = if (enabled) "SSH enabled" else "SSH disabled" + _sshStatus.value = SshStatus(active = enabled) + } + is ApiResult.Error -> { + _sshToggleResult.value = "Failed: ${result.message}" + // Refresh to get actual state + refreshSshStatus() + } + } + } + } + + fun clearSshResult() { + _sshToggleResult.value = null + } } From 0a5ded8febf70200023c023c6e59d9f8b83fc7b4 Mon Sep 17 00:00:00 2001 From: Kayos Date: Thu, 12 Mar 2026 08:32:27 -0700 Subject: [PATCH 08/26] Add BeeSettingsScreen with device info, WiFi, storage, GNSS, upload mode, plugins - New BeeSettingsScreen.kt: full Bee device settings UI - Device Info section (read-only): ID, firmware, serial, uptime, GPS lock - WiFi Client section (read/write): status, saved network, scan, configure - Storage section: cache status + FrameKM storage with visual bar - GPS/GNSS section: fix status, coordinates, satellites, accuracy - Upload Mode section: LTE/WIFI/APP toggle buttons - Plugin Status section: beekeeper, depth-ai, privacy-zones, map-ai states - Loading/error/offline states with retry - New BeeSettingsViewModel.kt: parallel fetching from all Bee API sections - Extended BeeApiClient: WiFi settings/status/scan/enable/reset, upload mode, config, cache status, FrameKM total, GNSS status, plugin state methods - Extended Models.kt: WifiClientSettings, WifiStatus, WifiNetwork, CacheStatus, BeeConfig, UploadModeResponse, GnssStatus, PluginState, FrameKmTotal - Navigation.kt: added BEE_SETTINGS route - DashboardScreen: added Bee Device router icon in top bar, CONFIGURE link in Device Status card --- .../java/com/adamaps/varroa/Navigation.kt | 10 +- .../com/adamaps/varroa/api/BeeApiClient.kt | 408 +++++--- .../java/com/adamaps/varroa/data/Models.kt | 80 ++ .../varroa/ui/dashboard/DashboardScreen.kt | 32 +- .../varroa/ui/settings/BeeSettingsScreen.kt | 972 ++++++++++++++++++ .../varroa/viewmodel/BeeSettingsViewModel.kt | 223 ++++ 6 files changed, 1558 insertions(+), 167 deletions(-) create mode 100644 app/src/main/java/com/adamaps/varroa/ui/settings/BeeSettingsScreen.kt create mode 100644 app/src/main/java/com/adamaps/varroa/viewmodel/BeeSettingsViewModel.kt diff --git a/app/src/main/java/com/adamaps/varroa/Navigation.kt b/app/src/main/java/com/adamaps/varroa/Navigation.kt index e23ca05..9896951 100644 --- a/app/src/main/java/com/adamaps/varroa/Navigation.kt +++ b/app/src/main/java/com/adamaps/varroa/Navigation.kt @@ -5,6 +5,7 @@ import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController import com.adamaps.varroa.ui.dashboard.DashboardScreen +import com.adamaps.varroa.ui.settings.BeeSettingsScreen import com.adamaps.varroa.ui.settings.DeviceStatusScreen import com.adamaps.varroa.ui.settings.SettingsScreen @@ -12,6 +13,7 @@ object Routes { const val DASHBOARD = "dashboard" const val SETTINGS = "settings" const val DEVICE_STATUS = "device_status" + const val BEE_SETTINGS = "bee_settings" } @Composable @@ -19,7 +21,10 @@ fun VarroaNavGraph() { val nav = rememberNavController() NavHost(navController = nav, startDestination = Routes.DASHBOARD) { composable(Routes.DASHBOARD) { - DashboardScreen(onNavigateToSettings = { nav.navigate(Routes.SETTINGS) }) + DashboardScreen( + onNavigateToSettings = { nav.navigate(Routes.SETTINGS) }, + onNavigateToBeeSettings = { nav.navigate(Routes.BEE_SETTINGS) } + ) } composable(Routes.SETTINGS) { SettingsScreen( @@ -30,5 +35,8 @@ fun VarroaNavGraph() { composable(Routes.DEVICE_STATUS) { DeviceStatusScreen(onBack = { nav.popBackStack() }) } + composable(Routes.BEE_SETTINGS) { + BeeSettingsScreen(onBack = { nav.popBackStack() }) + } } } diff --git a/app/src/main/java/com/adamaps/varroa/api/BeeApiClient.kt b/app/src/main/java/com/adamaps/varroa/api/BeeApiClient.kt index a5ed89a..6215393 100644 --- a/app/src/main/java/com/adamaps/varroa/api/BeeApiClient.kt +++ b/app/src/main/java/com/adamaps/varroa/api/BeeApiClient.kt @@ -6,29 +6,35 @@ import android.net.Network import android.net.NetworkCapabilities import android.util.Log import com.adamaps.varroa.data.ApiResult +import com.adamaps.varroa.data.BeeConfig import com.adamaps.varroa.data.BeeDetection import com.adamaps.varroa.data.BeeDeviceInfo import com.adamaps.varroa.data.BeePlugin +import com.adamaps.varroa.data.CacheStatus +import com.adamaps.varroa.data.FrameKmTotal import com.adamaps.varroa.data.GnssData import com.adamaps.varroa.data.GnssStatus +import com.adamaps.varroa.data.PairResponse +import com.adamaps.varroa.data.PluginState +import com.adamaps.varroa.data.SshStatus import com.adamaps.varroa.data.StorageStatus +import com.adamaps.varroa.data.UploadModeResponse +import com.adamaps.varroa.data.WifiClientSettings import com.adamaps.varroa.data.WifiConfig +import com.adamaps.varroa.data.WifiNetwork +import com.adamaps.varroa.data.WifiStatus import com.google.gson.Gson import com.google.gson.reflect.TypeToken import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.async import kotlinx.coroutines.withContext -import kotlinx.coroutines.withTimeoutOrNull import okhttp3.MediaType.Companion.toMediaType import okhttp3.OkHttpClient import okhttp3.Request +import okhttp3.RequestBody.Companion.toRequestBody import java.util.concurrent.TimeUnit -import com.jcraft.jsch.JSch -import com.jcraft.jsch.Session -import java.io.ByteArrayOutputStream class BeeApiClient( - private var apiUrl: String = "http://192.168.0.10:5000" + private var apiUrl: String = "http://10.77.0.1:5000" ) { companion object { private const val TAG = "VarroaBeeAPI" @@ -43,6 +49,9 @@ class BeeApiClient( var isConnected: Boolean = false private set + // Bearer token for authenticated endpoints + var apiToken: String = "" + fun updateUrl(url: String) { val oldUrl = apiUrl apiUrl = url.trimEnd('/') @@ -50,7 +59,7 @@ class BeeApiClient( } /** - * Bind to a specific network (e.g., unvalidated WiFi for Bee AP). + * Bind to a specific network (e.g., unvalidated WiFi for AdaCam AP). * This is the preferred method when using NetworkStateMonitor. */ fun bindToNetwork(network: Network) { @@ -107,7 +116,7 @@ class BeeApiClient( val allWifi = cm.allNetworks.filter { n -> cm.getNetworkCapabilities(n)?.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) == true } - // prefer unvalidated wifi (Bee AP has no internet) + // prefer unvalidated wifi (AdaCam AP has no internet) allWifi.firstOrNull { n -> cm.getNetworkCapabilities(n)?.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED) == false } ?: allWifi.firstOrNull() @@ -177,8 +186,37 @@ class BeeApiClient( } } + // ── POST helper ─────────────────────────────────────────────────────────── + + private suspend fun postRaw(path: String, json: String, authenticated: Boolean = true): ApiResult = withContext(Dispatchers.IO) { + val fullUrl = "$apiUrl$path" + Log.d(TAG, "HTTP POST $fullUrl body=$json") + try { + val body = json.toRequestBody("application/json".toMediaType()) + val reqBuilder = Request.Builder().url(fullUrl).post(body) + if (authenticated && apiToken.isNotBlank()) { + reqBuilder.addHeader("Authorization", "Bearer $apiToken") + } + val req = reqBuilder.build() + client.newCall(req).execute().use { resp -> + val respBody = resp.body?.string() ?: "" + if (resp.isSuccessful) { + isConnected = true + Log.d(TAG, "POST ${resp.code} OK") + ApiResult.Success(respBody) + } else { + Log.w(TAG, "POST ${resp.code} ${resp.message}") + ApiResult.Error("HTTP ${resp.code}: ${resp.message}", resp.code) + } + } + } catch (e: Exception) { + Log.e(TAG, "POST failed to $fullUrl", e) + ApiResult.Error(e.message ?: "Unknown error") + } + } + /** - * Check if Bee is reachable. + * Check if AdaCam is reachable. * Updates internal connection state. */ suspend fun ping(): Boolean { @@ -208,6 +246,25 @@ class BeeApiClient( isConnected = false } + // ── Pairing API ─────────────────────────────────────────────────────────── + + /** + * Pair with AdaCam device. Unauthenticated endpoint. + * Returns serial, version, and connection info. + */ + suspend fun pair(): ApiResult = withContext(Dispatchers.IO) { + when (val r = getRaw("/pair")) { + is ApiResult.Success -> try { + ApiResult.Success(gson.fromJson(r.data, PairResponse::class.java)) + } catch (e: Exception) { + ApiResult.Error("Parse error: ${e.message}") + } + is ApiResult.Error -> r + } + } + + // ── Landmarks API ───────────────────────────────────────────────────────── + suspend fun getLandmarks(): ApiResult> = withContext(Dispatchers.IO) { Log.d(TAG, "getLandmarks() called - fetching ALL landmarks") @@ -225,7 +282,6 @@ class BeeApiClient( } is ApiResult.Error -> { Log.e(TAG, "getLandmarks() failed: ${r.message} (code: ${r.code})") - // Connection failed, mark as offline if (r.message.contains("timeout", ignoreCase = true) || r.message.contains("connect", ignoreCase = true) || r.message.contains("refused", ignoreCase = true) || @@ -260,7 +316,7 @@ class BeeApiClient( } } - // ── v7.7 Settings API endpoints ─────────────────────────────────────────── + // ── WiFi API ────────────────────────────────────────────────────────────── suspend fun getWifiConfig(): ApiResult = withContext(Dispatchers.IO) { when (val r = getRaw("/api/1/wifi/status")) { @@ -273,31 +329,94 @@ class BeeApiClient( } } - suspend fun setWifiConfig(ssid: String, password: String): ApiResult = withContext(Dispatchers.IO) { - try { - val jsonBody = gson.toJson(mapOf("ssid" to ssid, "password" to password)) - val requestBody = okhttp3.RequestBody.create( - "application/json".toMediaType(), - jsonBody - ) - val request = Request.Builder() - .url("$apiUrl/api/1/wifi/connect") - .post(requestBody) - .build() - - client.newCall(request).execute().use { resp -> - val body = resp.body?.string() ?: "" - if (resp.isSuccessful) { - ApiResult.Success(body) - } else { - ApiResult.Error("HTTP ${resp.code}: ${resp.message}", resp.code) - } + suspend fun getWifiStatus(): ApiResult = withContext(Dispatchers.IO) { + when (val r = getRaw("/api/1/wifi/status")) { + is ApiResult.Success -> try { + ApiResult.Success(gson.fromJson(r.data, WifiStatus::class.java)) + } catch (e: Exception) { + ApiResult.Error("Parse error: ${e.message}") } - } catch (e: Exception) { - ApiResult.Error(e.message ?: "Unknown error") + is ApiResult.Error -> r } } + suspend fun setWifiConfig(ssid: String, password: String): ApiResult { + val json = gson.toJson(mapOf("ssid" to ssid, "password" to password)) + return postRaw("/api/1/wifi/connect", json) + } + + // WiFi Client methods (legacy API) + suspend fun getWifiClientStatus(): ApiResult = withContext(Dispatchers.IO) { + when (val r = getRaw("/api/1/wifiClient/status")) { + is ApiResult.Success -> try { + ApiResult.Success(gson.fromJson(r.data, com.adamaps.varroa.data.WifiStatus::class.java)) + } catch (e: Exception) { + ApiResult.Error("Parse error: ${e.message}") + } + is ApiResult.Error -> r + } + } + + suspend fun getWifiSettings(): ApiResult = withContext(Dispatchers.IO) { + when (val r = getRaw("/api/1/wifiClient/settings")) { + is ApiResult.Success -> try { + ApiResult.Success(gson.fromJson(r.data, WifiClientSettings::class.java)) + } catch (e: Exception) { + ApiResult.Error("Parse error: ${e.message}") + } + is ApiResult.Error -> r + } + } + + suspend fun saveWifiSettings(settings: WifiClientSettings): ApiResult { + val json = gson.toJson(settings) + return postRaw("/api/1/wifiClient/settings", json) + } + + suspend fun setWifiEnabled(enabled: Boolean): ApiResult { + val json = """{"enabled": $enabled}""" + return postRaw("/api/1/wifiClient/enable", json) + } + + suspend fun scanWifi(): ApiResult> = withContext(Dispatchers.IO) { + when (val r = getRaw("/api/1/wifiClient/scan")) { + is ApiResult.Success -> try { + val type = object : TypeToken>() {}.type + ApiResult.Success(gson.fromJson(r.data, type)) + } catch (e: Exception) { + ApiResult.Error("Parse error: ${e.message}") + } + is ApiResult.Error -> r + } + } + + suspend fun resetWifi(): ApiResult = withContext(Dispatchers.IO) { + when (val r = getRaw("/api/1/wifiClient/reset")) { + is ApiResult.Success -> r + is ApiResult.Error -> r + } + } + + // ── SSH API ─────────────────────────────────────────────────────────────── + + suspend fun getSshStatus(): ApiResult = withContext(Dispatchers.IO) { + when (val r = getRaw("/api/1/ssh/status")) { + is ApiResult.Success -> try { + ApiResult.Success(gson.fromJson(r.data, SshStatus::class.java)) + } catch (e: Exception) { + ApiResult.Error("Parse error: ${e.message}") + } + is ApiResult.Error -> r + } + } + + suspend fun setSshEnabled(enabled: Boolean): ApiResult { + val json = gson.toJson(mapOf("enable" to enabled)) + return postRaw("/api/1/ssh/toggle", json) + } + + // ── Storage & GNSS Status ───────────────────────────────────────────────── + suspend fun getStorageStatus(): ApiResult = withContext(Dispatchers.IO) { when (val r = getRaw("/api/1/storage/usage")) { is ApiResult.Success -> try { @@ -332,76 +451,97 @@ class BeeApiClient( } } - suspend fun setUploadMode(mode: String): ApiResult = withContext(Dispatchers.IO) { - try { - val jsonBody = gson.toJson(mapOf("mode" to mode)) - val requestBody = okhttp3.RequestBody.create( - "application/json".toMediaType(), - jsonBody - ) - val request = Request.Builder() - .url("$apiUrl/api/1/config/uploadMode") - .post(requestBody) - .build() + // ── Upload Mode ─────────────────────────────────────────────────────────── - client.newCall(request).execute().use { resp -> - val body = resp.body?.string() ?: "" - if (resp.isSuccessful) { - ApiResult.Success(body) + suspend fun getUploadMode(): ApiResult = withContext(Dispatchers.IO) { + when (val r = getRaw("/api/1/config/uploadMode")) { + is ApiResult.Success -> try { + val trimmed = r.data.trim().trim('"') + if (trimmed.startsWith("{")) { + val obj = gson.fromJson(r.data, UploadModeResponse::class.java) + ApiResult.Success(obj.currentMode() ?: "UNKNOWN") } else { - ApiResult.Error("HTTP ${resp.code}: ${resp.message}", resp.code) + ApiResult.Success(trimmed) } + } catch (e: Exception) { + ApiResult.Success(r.data.trim().trim('"')) } - } catch (e: Exception) { - ApiResult.Error(e.message ?: "Unknown error") + is ApiResult.Error -> r } } - // ── End v7.7 Settings API ───────────────────────────────────────────────── + suspend fun setUploadMode(mode: String): ApiResult { + val json = """{"mode": "$mode"}""" + return postRaw("/api/1/config/uploadMode", json) + } - suspend fun getDeviceIdViaSsh(): ApiResult = withContext(Dispatchers.IO) { - try { - Log.d(TAG, "Attempting SSH connection to root@192.168.0.10:22") - val jsch = JSch() - val session = jsch.getSession("root", "192.168.0.10", 22) - session.setConfig("StrictHostKeyChecking", "no") - session.connect(10000) // 10 second timeout + // ── Config ──────────────────────────────────────────────────────────────── - val channel = session.openChannel("exec") - val execChannel = channel as com.jcraft.jsch.ChannelExec - - // Command to find device_id from various locations - val command = "cat /data/registration/device_id 2>/dev/null || cat /opt/dashcam/config/device_id 2>/dev/null || grep device_id /data/persist/*.conf 2>/dev/null | head -1 | cut -d= -f2" - execChannel.setCommand(command) - - val outputStream = ByteArrayOutputStream() - execChannel.outputStream = outputStream - execChannel.connect(5000) // 5 second timeout for command execution - - // Wait for command completion - while (!execChannel.isClosed) { - Thread.sleep(100) + suspend fun getConfig(): ApiResult = withContext(Dispatchers.IO) { + when (val r = getRaw("/api/1/config/")) { + is ApiResult.Success -> try { + ApiResult.Success(gson.fromJson(r.data, BeeConfig::class.java)) + } catch (e: Exception) { + ApiResult.Error("Parse error: ${e.message}") } - - execChannel.disconnect() - session.disconnect() - - val output = outputStream.toString().trim() - Log.d(TAG, "SSH command output: '$output'") - - if (output.isNotEmpty() && !output.contains("No such file") && !output.contains("not found")) { - Log.i(TAG, "Device ID retrieved via SSH: $output") - ApiResult.Success(output) - } else { - Log.w(TAG, "SSH command succeeded but no device_id found") - ApiResult.Error("No device_id found via SSH") - } - } catch (e: Exception) { - Log.e(TAG, "SSH connection failed", e) - ApiResult.Error("SSH error: ${e.message}") + is ApiResult.Error -> r } } + // ── Cache / Storage ─────────────────────────────────────────────────────── + + suspend fun getCacheStatus(): ApiResult = withContext(Dispatchers.IO) { + when (val r = getRaw("/api/1/cache/status")) { + is ApiResult.Success -> try { + ApiResult.Success(gson.fromJson(r.data, CacheStatus::class.java)) + } catch (e: Exception) { + ApiResult.Error("Parse error: ${e.message}") + } + is ApiResult.Error -> r + } + } + + suspend fun getFrameKmTotal(): ApiResult = withContext(Dispatchers.IO) { + when (val r = getRaw("/api/1/framekm/total")) { + is ApiResult.Success -> try { + ApiResult.Success(gson.fromJson(r.data, FrameKmTotal::class.java)) + } catch (e: Exception) { + ApiResult.Error("Parse error: ${e.message}") + } + is ApiResult.Error -> r + } + } + + // ── Plugin State ────────────────────────────────────────────────────────── + + suspend fun getPluginState(pluginName: String): PluginState = withContext(Dispatchers.IO) { + when (val r = getRaw("/api/1/plugin/getPluginState/$pluginName")) { + is ApiResult.Success -> try { + val trimmed = r.data.trim() + val enabled = when { + trimmed == "true" -> true + trimmed == "false" -> false + trimmed.startsWith("{") -> { + val obj = gson.fromJson(trimmed, com.google.gson.JsonObject::class.java) + obj.get("enabled")?.asBoolean ?: obj.get("state")?.asString == "enabled" + } + else -> null + } + PluginState(pluginName, enabled) + } catch (e: Exception) { + PluginState(pluginName, null, e.message) + } + is ApiResult.Error -> PluginState(pluginName, null, r.message) + } + } + + suspend fun getKnownPluginStates(): List = withContext(Dispatchers.IO) { + val knownPlugins = listOf("beekeeper", "depth-ai", "privacy-zones", "map-ai") + knownPlugins.map { name -> getPluginState(name) } + } + + // ── Camera API ──────────────────────────────────────────────────────────── + /** * Try the given endpoint; returns raw image bytes. * The caller is responsible for trying fallback endpoints. @@ -429,9 +569,9 @@ class BeeApiClient( } /** - * Fetch detection image from Bee device. + * Fetch detection image from AdaCam device. * - * @param detectionId The landmark/detection ID from the Bee API + * @param detectionId The landmark/detection ID from the API * @return ApiResult containing image bytes (JPEG) or error */ suspend fun getDetectionImage(detectionId: Long): ApiResult = withContext(Dispatchers.IO) { @@ -452,7 +592,7 @@ class BeeApiClient( } /** - * Attempt to delete landmarks from Bee device after successful upload. + * Attempt to delete landmarks from AdaCam device after successful upload. * This tries various potential DELETE endpoints. * * @param landmarkIds List of landmark IDs to delete @@ -471,24 +611,13 @@ class BeeApiClient( "/api/1/landmarks/delete", "/api/1/landmarks/clear", "/api/1/landmarks/cleanup", - "/api/1/landmarks/remove", - "/api/1/cmd" // As a last resort using the cmd endpoint + "/api/1/landmarks/remove" ) for (endpoint in endpoints) { Log.d(TAG, "Trying cleanup endpoint: $endpoint") - val result = when (endpoint) { - "/api/1/cmd" -> { - // Use the cmd endpoint to try deleting landmarks via system commands - Log.d(TAG, "Attempting cleanup via cmd endpoint...") - tryCleanupViaCmd(landmarkIds) - } - else -> { - // Try standard DELETE/POST requests - tryCleanupEndpoint(endpoint, landmarkIds) - } - } + val result = tryCleanupEndpoint(endpoint, landmarkIds) when (result) { is ApiResult.Success -> { @@ -497,31 +626,34 @@ class BeeApiClient( } is ApiResult.Error -> { Log.w(TAG, "Cleanup failed via $endpoint: ${result.message}") - // Continue trying other endpoints } } } - Log.w(TAG, "All cleanup endpoints failed - Bee may not support landmark deletion") - return@withContext ApiResult.Error("No working DELETE endpoint found - cleanup not supported by Bee device") + Log.w(TAG, "All cleanup endpoints failed - AdaCam may not support landmark deletion") + return@withContext ApiResult.Error("No working DELETE endpoint found - cleanup not supported by device") } private suspend fun tryCleanupEndpoint(endpoint: String, landmarkIds: List): ApiResult = withContext(Dispatchers.IO) { try { val jsonBody = gson.toJson(mapOf("ids" to landmarkIds)) - val requestBody = okhttp3.RequestBody.create( - "application/json".toMediaType(), - jsonBody - ) + val requestBody = jsonBody.toRequestBody("application/json".toMediaType()) // Try both DELETE and POST methods for (method in listOf("DELETE", "POST")) { Log.d(TAG, "Trying $method $endpoint") - val request = Request.Builder() + val requestBuilder = Request.Builder() .url("$apiUrl$endpoint") - .method(method, if (method == "DELETE") null else requestBody) - .build() + if (apiToken.isNotBlank()) { + requestBuilder.addHeader("Authorization", "Bearer $apiToken") + } + + val request = if (method == "DELETE") { + requestBuilder.delete(requestBody).build() + } else { + requestBuilder.post(requestBody).build() + } client.newCall(request).execute().use { resp -> val body = resp.body?.string() ?: "" @@ -540,50 +672,4 @@ class BeeApiClient( return@withContext ApiResult.Error("Exception: ${e.message}") } } - - private suspend fun tryCleanupViaCmd(landmarkIds: List): ApiResult = withContext(Dispatchers.IO) { - try { - // Try to find where landmarks are stored and delete them via filesystem commands - val commands = listOf( - "find /data -name '*landmark*' -type f -ls", - "find /tmp -name '*landmark*' -type f -ls", - "ls -la /data/recording/", - "redis-cli KEYS '*landmark*'", - "redis-cli KEYS '*detection*'" - ) - - for (cmd in commands) { - Log.d(TAG, "Trying cmd: $cmd") - val jsonBody = gson.toJson(mapOf("cmd" to cmd)) - val requestBody = okhttp3.RequestBody.create( - "application/json".toMediaType(), - jsonBody - ) - - val request = Request.Builder() - .url("$apiUrl/api/1/cmd") - .post(requestBody) - .build() - - client.newCall(request).execute().use { resp -> - val body = resp.body?.string() ?: "" - if (resp.isSuccessful) { - Log.d(TAG, "Cmd '$cmd' result: $body") - // For now, just log the results to understand the data structure - if (body.contains("landmark") || body.contains("detection")) { - Log.i(TAG, "Found potential landmark storage: $body") - } - } - } - } - - // Note: We're not actually deleting anything via cmd yet, just exploring - Log.w(TAG, "Cleanup via cmd endpoint: exploration complete, actual deletion not implemented yet") - return@withContext ApiResult.Error("Cleanup via cmd: exploration only, deletion not yet implemented") - - } catch (e: Exception) { - Log.e(TAG, "Failed to explore via cmd endpoint", e) - return@withContext ApiResult.Error("Cmd exploration failed: ${e.message}") - } - } } diff --git a/app/src/main/java/com/adamaps/varroa/data/Models.kt b/app/src/main/java/com/adamaps/varroa/data/Models.kt index f9a9e64..87a5968 100644 --- a/app/src/main/java/com/adamaps/varroa/data/Models.kt +++ b/app/src/main/java/com/adamaps/varroa/data/Models.kt @@ -122,6 +122,86 @@ data class BeePlugin( @SerializedName("running") val running: Boolean? = null ) +// ── Bee Settings API models ─────────────────────────────────────────────────── + +data class WifiClientSettings( + @SerializedName("ssid") val ssid: String? = null, + @SerializedName("password") val password: String? = null, + @SerializedName("enabled") val enabled: Boolean? = null, + @SerializedName("security") val security: String? = null, + @SerializedName("freq") val freq: Int? = null +) + +data class WifiStatus( + @SerializedName("connected") val connected: Boolean? = null, + @SerializedName("ssid") val ssid: String? = null, + @SerializedName("ip") val ip: String? = null, + @SerializedName("signal") val signal: Int? = null, + @SerializedName("state") val state: String? = null +) + +data class WifiNetwork( + @SerializedName("ssid") val ssid: String? = null, + @SerializedName("signal") val signal: Int? = null, + @SerializedName("security") val security: String? = null, + @SerializedName("freq") val freq: Int? = null +) + +data class CacheStatus( + @SerializedName("enabled") val enabled: Boolean? = null, + @SerializedName("size") val size: Long? = null, + @SerializedName("samples") val samples: Int? = null, + @SerializedName("sizeBytes") val sizeBytes: Long? = null, + @SerializedName("numSamples") val numSamples: Int? = null +) { + // Normalize field names across firmware versions + fun displaySize(): Long = size ?: sizeBytes ?: 0L + fun displaySamples(): Int = samples ?: numSamples ?: 0 +} + +data class BeeConfig( + @SerializedName("uploadMode") val uploadMode: String? = null, + @SerializedName("pluginsLocked") val pluginsLocked: Boolean? = null, + @SerializedName("pluginDevMode") val pluginDevMode: Boolean? = null, + @SerializedName("pausePluginUpdates") val pausePluginUpdates: Boolean? = null, + @SerializedName("isProcessingEnabled") val isProcessingEnabled: Boolean? = null, + @SerializedName("isUSBRecordingEnabled") val isUSBRecordingEnabled: Boolean? = null, + @SerializedName("isLowPowerModeEnabled") val isLowPowerModeEnabled: Boolean? = null +) + +data class UploadModeResponse( + @SerializedName("mode") val mode: String? = null, + @SerializedName("uploadMode") val uploadMode: String? = null +) { + fun currentMode(): String? = mode ?: uploadMode +} + +data class GnssStatus( + @SerializedName("lat_deg") val latDeg: Double? = null, + @SerializedName("lon_deg") val lonDeg: Double? = null, + @SerializedName("alt_m") val altM: Double? = null, + @SerializedName("unix_milliseconds") val unixMs: Long? = null, + @SerializedName("fix") val fix: Boolean? = null, + @SerializedName("fixType") val fixType: String? = null, + @SerializedName("satellites") val satellites: Int? = null, + @SerializedName("satellites_used") val satellitesUsed: Int? = null, + @SerializedName("accuracy_m") val accuracyM: Double? = null, + @SerializedName("hdop") val hdop: Double? = null, + @SerializedName("speed_m_s") val speedMs: Double? = null +) + +data class PluginState( + val name: String, + val enabled: Boolean?, + val error: String? = null +) + +data class FrameKmTotal( + @SerializedName("total") val total: Long? = null, + @SerializedName("totalBytes") val totalBytes: Long? = null, + @SerializedName("count") val count: Int? = null +) + // ── App state ───────────────────────────────────────────────────────────────── data class SessionStats( diff --git a/app/src/main/java/com/adamaps/varroa/ui/dashboard/DashboardScreen.kt b/app/src/main/java/com/adamaps/varroa/ui/dashboard/DashboardScreen.kt index 0a5fb53..0b91b28 100644 --- a/app/src/main/java/com/adamaps/varroa/ui/dashboard/DashboardScreen.kt +++ b/app/src/main/java/com/adamaps/varroa/ui/dashboard/DashboardScreen.kt @@ -39,7 +39,8 @@ import org.osmdroid.views.overlay.Marker @Composable fun DashboardScreen( vm: DashboardViewModel = viewModel(), - onNavigateToSettings: () -> Unit + onNavigateToSettings: () -> Unit, + onNavigateToBeeSettings: () -> Unit = {} ) { val deviceInfo by vm.deviceInfo.collectAsState() val gnss by vm.gnss.collectAsState() @@ -101,8 +102,11 @@ fun DashboardScreen( Spacer(Modifier.width(8.dp)) } + IconButton(onClick = onNavigateToBeeSettings) { + Icon(Icons.Default.Router, contentDescription = "AdaCam Settings", tint = Amber) + } IconButton(onClick = onNavigateToSettings) { - Icon(Icons.Default.Settings, contentDescription = "Settings", tint = Amber) + Icon(Icons.Default.Settings, contentDescription = "App Settings", tint = Amber) } } ) @@ -150,7 +154,7 @@ fun DashboardScreen( gnss?.let { GpsMapCard(it) } // Device status - deviceInfo?.let { DeviceStatusCard(it) } + deviceInfo?.let { DeviceStatusCard(it, onNavigateToBeeSettings) } } } } @@ -582,14 +586,32 @@ private fun OsmMapView(gnss: GnssData) { } @Composable -private fun DeviceStatusCard(info: BeeDeviceInfo) { +private fun DeviceStatusCard(info: BeeDeviceInfo, onNavigateToBeeSettings: () -> Unit = {}) { Card( colors = CardDefaults.cardColors(containerColor = Surface), shape = RoundedCornerShape(8.dp), modifier = Modifier.fillMaxWidth() ) { Column(modifier = Modifier.padding(14.dp)) { - SectionHeader("DEVICE STATUS") + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + SectionHeader("DEVICE STATUS") + Text( + "CONFIGURE ›", + color = Amber, + fontFamily = FontFamily.Monospace, + fontSize = 9.sp, + letterSpacing = 1.sp, + modifier = Modifier + .clip(RoundedCornerShape(4.dp)) + .clickable { onNavigateToBeeSettings() } + .background(AmberDark.copy(alpha = 0.2f)) + .padding(horizontal = 6.dp, vertical = 2.dp) + ) + } Spacer(Modifier.height(8.dp)) val rows: List> = listOf( "Firmware" to (info.firmwareVersion ?: "—"), diff --git a/app/src/main/java/com/adamaps/varroa/ui/settings/BeeSettingsScreen.kt b/app/src/main/java/com/adamaps/varroa/ui/settings/BeeSettingsScreen.kt new file mode 100644 index 0000000..1a4533e --- /dev/null +++ b/app/src/main/java/com/adamaps/varroa/ui/settings/BeeSettingsScreen.kt @@ -0,0 +1,972 @@ +package com.adamaps.varroa.ui.settings + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material.icons.filled.* +import androidx.compose.material3.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.text.input.PasswordVisualTransformation +import androidx.compose.ui.text.input.VisualTransformation +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.lifecycle.viewmodel.compose.viewModel +import com.adamaps.varroa.data.* +import com.adamaps.varroa.ui.theme.* +import com.adamaps.varroa.viewmodel.BeeSettingsLoadState +import com.adamaps.varroa.viewmodel.BeeSettingsViewModel +import java.text.SimpleDateFormat +import java.util.* + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun BeeSettingsScreen( + vm: BeeSettingsViewModel = viewModel(), + onBack: () -> Unit +) { + val state by vm.state.collectAsState() + val message by vm.message.collectAsState() + val snackbarHostState = remember { SnackbarHostState() } + + LaunchedEffect(message) { + message?.let { + snackbarHostState.showSnackbar(it) + vm.clearMessage() + } + } + + Scaffold( + containerColor = Background, + snackbarHost = { SnackbarHost(snackbarHostState) }, + topBar = { + TopAppBar( + title = { + Text( + "BEE DEVICE", + color = Amber, + fontFamily = FontFamily.Monospace, + fontWeight = FontWeight.Bold, + letterSpacing = 3.sp + ) + }, + navigationIcon = { + IconButton(onClick = onBack) { + Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back", tint = Amber) + } + }, + colors = TopAppBarDefaults.topAppBarColors(containerColor = Surface), + actions = { + IconButton(onClick = { vm.loadAll() }) { + Icon(Icons.Default.Refresh, contentDescription = "Refresh", tint = Amber) + } + } + ) + } + ) { padding -> + when (val loadState = state.loadState) { + is BeeSettingsLoadState.Loading -> { + Box( + modifier = Modifier + .fillMaxSize() + .padding(padding), + contentAlignment = Alignment.Center + ) { + Column(horizontalAlignment = Alignment.CenterHorizontally) { + CircularProgressIndicator(color = Amber) + Spacer(Modifier.height(16.dp)) + Text( + "Fetching Bee device info…", + color = Color.Gray, + fontFamily = FontFamily.Monospace, + fontSize = 12.sp + ) + } + } + } + + is BeeSettingsLoadState.Error -> { + Box( + modifier = Modifier + .fillMaxSize() + .padding(padding), + contentAlignment = Alignment.Center + ) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier.padding(24.dp) + ) { + Icon( + Icons.Default.CloudOff, + contentDescription = null, + tint = Error, + modifier = Modifier.size(48.dp) + ) + Spacer(Modifier.height(12.dp)) + Text( + "Bee Unreachable", + color = Error, + fontFamily = FontFamily.Monospace, + fontWeight = FontWeight.Bold, + fontSize = 16.sp + ) + Spacer(Modifier.height(8.dp)) + Text( + loadState.message, + color = Color.Gray, + fontFamily = FontFamily.Monospace, + fontSize = 11.sp, + textAlign = androidx.compose.ui.text.style.TextAlign.Center + ) + Spacer(Modifier.height(20.dp)) + Button( + onClick = { vm.loadAll() }, + colors = ButtonDefaults.buttonColors(containerColor = AmberDark) + ) { + Icon(Icons.Default.Refresh, contentDescription = null, modifier = Modifier.size(16.dp)) + Spacer(Modifier.width(6.dp)) + Text("RETRY", fontFamily = FontFamily.Monospace, fontWeight = FontWeight.Bold) + } + } + } + } + + else -> { + // Success or Idle — show content + Column( + modifier = Modifier + .fillMaxSize() + .padding(padding) + .verticalScroll(rememberScrollState()) + .padding(12.dp), + verticalArrangement = Arrangement.spacedBy(12.dp) + ) { + // Device Info + state.deviceInfo?.let { DeviceInfoSection(it) } + + // WiFi Client + WifiSection( + status = state.wifiStatus, + settings = state.wifiSettings, + networks = state.wifiNetworks, + scanState = state.wifiScanState, + saving = state.wifiSaving, + onSave = { ssid, pass, enabled -> vm.saveWifiSettings(ssid, pass, enabled) }, + onToggleEnabled = { vm.setWifiEnabled(it) }, + onScan = { vm.scanWifi() }, + onReset = { vm.resetWifi() } + ) + + // Storage + StorageSection( + cacheStatus = state.cacheStatus, + frameKmTotal = state.frameKmTotal + ) + + // GNSS + GnssSection(gnss = state.gnssStatus) + + // Upload Mode + UploadModeSection( + currentMode = state.uploadMode, + saving = state.uploadModeSaving, + onSetMode = { vm.setUploadMode(it) } + ) + + // Plugins + if (state.plugins.isNotEmpty()) { + PluginSection( + plugins = state.plugins, + config = state.config + ) + } + + Spacer(Modifier.height(24.dp)) + } + } + } + } +} + +// ── Device Info ─────────────────────────────────────────────────────────────── + +@Composable +private fun DeviceInfoSection(info: BeeDeviceInfo) { + BeeCard(title = "DEVICE INFO", icon = Icons.Default.Info) { + val rows = listOfNotNull( + info.deviceId?.let { "Device ID" to it }, + info.serial?.let { "Serial" to it }, + info.firmwareVersion?.let { "Firmware" to it }, + info.apiVersion?.let { "API Version" to it }, + info.model?.let { "Model" to it }, + info.imei?.let { "IMEI" to it }, + info.ssid?.let { "Connected SSID" to it }, + info.uptime?.let { "Uptime" to formatUptime(it) }, + "GPS Lock" to if (info.hasGnssLock == true) "YES" else "NO", + "Internet" to if (info.internetIsHealthy == true) "HEALTHY" else "OFFLINE" + ) + rows.forEach { (k, v) -> + InfoRow(k, v, valueColor = when { + k == "GPS Lock" && v == "YES" -> Success + k == "GPS Lock" && v == "NO" -> Error + k == "Internet" && v == "HEALTHY" -> Success + k == "Internet" && v == "OFFLINE" -> Error + else -> OnSurface + }) + } + } +} + +// ── WiFi ────────────────────────────────────────────────────────────────────── + +@Composable +private fun WifiSection( + status: WifiStatus?, + settings: WifiClientSettings?, + networks: List, + scanState: BeeSettingsLoadState, + saving: Boolean, + onSave: (String, String, Boolean) -> Unit, + onToggleEnabled: (Boolean) -> Unit, + onScan: () -> Unit, + onReset: () -> Unit +) { + var expandAddNetwork by remember { mutableStateOf(false) } + var ssidInput by remember { mutableStateOf(settings?.ssid ?: "") } + var passwordInput by remember { mutableStateOf("") } + var showPassword by remember { mutableStateOf(false) } + var enabledInput by remember(settings) { mutableStateOf(settings?.enabled ?: true) } + + // Update SSID input when settings load + LaunchedEffect(settings?.ssid) { + if (settings?.ssid != null && ssidInput.isEmpty()) { + ssidInput = settings.ssid + } + } + + BeeCard(title = "WIFI CLIENT", icon = Icons.Default.Wifi) { + // Connection status + val connected = status?.connected ?: false + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween + ) { + Row(verticalAlignment = Alignment.CenterVertically) { + StatusIndicator(connected) + Spacer(Modifier.width(8.dp)) + Column { + Text( + text = if (connected) "CONNECTED" else "DISCONNECTED", + color = if (connected) Success else Color.Gray, + fontFamily = FontFamily.Monospace, + fontWeight = FontWeight.Bold, + fontSize = 12.sp + ) + status?.ssid?.let { + Text(it, color = OnSurface, fontFamily = FontFamily.Monospace, fontSize = 11.sp) + } + status?.ip?.let { + Text("IP: $it", color = Color.Gray, fontFamily = FontFamily.Monospace, fontSize = 10.sp) + } + } + } + // Enable/disable toggle + Column(horizontalAlignment = Alignment.CenterHorizontally) { + Text( + "CLIENT", + color = Color.Gray, + fontFamily = FontFamily.Monospace, + fontSize = 9.sp, + letterSpacing = 1.sp + ) + Switch( + checked = enabledInput, + onCheckedChange = { + enabledInput = it + onToggleEnabled(it) + }, + colors = SwitchDefaults.colors( + checkedThumbColor = Amber, + checkedTrackColor = AmberDark, + uncheckedThumbColor = Color.Gray, + uncheckedTrackColor = SurfaceVariant + ) + ) + } + } + + Spacer(Modifier.height(8.dp)) + Divider(color = SurfaceVariant) + Spacer(Modifier.height(8.dp)) + + // Current saved network + if (settings?.ssid != null) { + InfoRow("Saved SSID", settings.ssid) + settings.security?.let { InfoRow("Security", it) } + settings.freq?.let { InfoRow("Frequency", "${it} MHz (${if (it < 3000) "2.4GHz" else "5GHz"})") } + } + + Spacer(Modifier.height(10.dp)) + + // Action buttons + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + OutlinedButton( + onClick = { expandAddNetwork = !expandAddNetwork }, + modifier = Modifier.weight(1f), + colors = ButtonDefaults.outlinedButtonColors(contentColor = Amber), + border = androidx.compose.foundation.BorderStroke(1.dp, AmberDark) + ) { + Icon( + if (expandAddNetwork) Icons.Default.ExpandLess else Icons.Default.Add, + contentDescription = null, + modifier = Modifier.size(14.dp) + ) + Spacer(Modifier.width(4.dp)) + Text( + if (expandAddNetwork) "CANCEL" else "CONFIGURE", + fontFamily = FontFamily.Monospace, + fontSize = 10.sp, + fontWeight = FontWeight.Bold + ) + } + + OutlinedButton( + onClick = onScan, + modifier = Modifier.weight(1f), + colors = ButtonDefaults.outlinedButtonColors(contentColor = Amber), + border = androidx.compose.foundation.BorderStroke(1.dp, AmberDark), + enabled = scanState !is BeeSettingsLoadState.Loading + ) { + if (scanState is BeeSettingsLoadState.Loading) { + CircularProgressIndicator( + color = Amber, + modifier = Modifier.size(12.dp), + strokeWidth = 1.5.dp + ) + } else { + Icon(Icons.Default.Search, contentDescription = null, modifier = Modifier.size(14.dp)) + } + Spacer(Modifier.width(4.dp)) + Text("SCAN", fontFamily = FontFamily.Monospace, fontSize = 10.sp, fontWeight = FontWeight.Bold) + } + } + + // Network config form (expandable) + AnimatedVisibility(visible = expandAddNetwork) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(top = 10.dp), + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + Text( + "CONFIGURE NETWORK", + color = Amber, + fontFamily = FontFamily.Monospace, + fontSize = 10.sp, + letterSpacing = 1.sp + ) + BeeTextField( + value = ssidInput, + onValueChange = { ssidInput = it }, + label = "SSID", + hint = "Network name" + ) + BeeTextField( + value = passwordInput, + onValueChange = { passwordInput = it }, + label = "Password", + hint = "Leave empty for open network", + visualTransformation = if (showPassword) VisualTransformation.None + else PasswordVisualTransformation(), + trailingIcon = { + IconButton(onClick = { showPassword = !showPassword }) { + Icon( + if (showPassword) Icons.Default.Visibility else Icons.Default.VisibilityOff, + contentDescription = null, + tint = Color.Gray, + modifier = Modifier.size(18.dp) + ) + } + } + ) + + // Scan results picker + if (networks.isNotEmpty()) { + Text( + "SCAN RESULTS", + color = Color.Gray, + fontFamily = FontFamily.Monospace, + fontSize = 9.sp, + letterSpacing = 1.sp + ) + networks.forEach { net -> + if (net.ssid != null) { + Row( + modifier = Modifier + .fillMaxWidth() + .clip(RoundedCornerShape(4.dp)) + .clickable { ssidInput = net.ssid } + .background(SurfaceVariant) + .padding(horizontal = 10.dp, vertical = 6.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(6.dp) + ) { + Icon(Icons.Default.Wifi, contentDescription = null, tint = Amber, modifier = Modifier.size(14.dp)) + Text( + net.ssid, + color = OnSurface, + fontFamily = FontFamily.Monospace, + fontSize = 11.sp + ) + } + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(6.dp) + ) { + net.security?.let { + Text(it, color = Color.Gray, fontFamily = FontFamily.Monospace, fontSize = 9.sp) + } + net.signal?.let { + Text("${it}dBm", color = Color.Gray, fontFamily = FontFamily.Monospace, fontSize = 9.sp) + } + } + } + } + } + } + + Button( + onClick = { + onSave(ssidInput, passwordInput, enabledInput) + expandAddNetwork = false + passwordInput = "" + }, + enabled = ssidInput.isNotBlank() && !saving, + colors = ButtonDefaults.buttonColors(containerColor = AmberDark), + modifier = Modifier.fillMaxWidth() + ) { + if (saving) { + CircularProgressIndicator(color = Amber, modifier = Modifier.size(14.dp), strokeWidth = 2.dp) + Spacer(Modifier.width(6.dp)) + } + Text( + "SAVE & CONNECT", + fontFamily = FontFamily.Monospace, + fontWeight = FontWeight.Bold + ) + } + } + } + + // Reset button + Spacer(Modifier.height(4.dp)) + TextButton( + onClick = onReset, + modifier = Modifier.align(Alignment.End) + ) { + Icon(Icons.Default.RestartAlt, contentDescription = null, tint = Color.Gray, modifier = Modifier.size(14.dp)) + Spacer(Modifier.width(4.dp)) + Text("RESET WIFI", color = Color.Gray, fontFamily = FontFamily.Monospace, fontSize = 10.sp) + } + } +} + +// ── Storage ─────────────────────────────────────────────────────────────────── + +@Composable +private fun StorageSection( + cacheStatus: CacheStatus?, + frameKmTotal: FrameKmTotal? +) { + BeeCard(title = "STORAGE", icon = Icons.Default.Storage) { + if (cacheStatus == null && frameKmTotal == null) { + Text( + "Storage info unavailable", + color = Color.Gray, + fontFamily = FontFamily.Monospace, + fontSize = 11.sp + ) + return@BeeCard + } + + cacheStatus?.let { cache -> + Text( + "DATA CACHE", + color = Amber, + fontFamily = FontFamily.Monospace, + fontSize = 10.sp, + letterSpacing = 1.sp + ) + Spacer(Modifier.height(4.dp)) + InfoRow( + "Status", + if (cache.enabled == true) "ENABLED" else "DISABLED", + valueColor = if (cache.enabled == true) Success else Color.Gray + ) + InfoRow("Samples", cache.displaySamples().toString()) + InfoRow("Cache Size", formatBytes(cache.displaySize())) + Spacer(Modifier.height(8.dp)) + + // Storage bar + val sizeBytes = cache.displaySize() + val maxBytes = 500L * 1024 * 1024 // 500MB limit from docs + val fraction = (sizeBytes.toFloat() / maxBytes).coerceIn(0f, 1f) + StorageBar(fraction, label = "Cache: ${formatBytes(sizeBytes)} / 500 MB") + } + + if (cacheStatus != null && frameKmTotal != null) { + Spacer(Modifier.height(10.dp)) + Divider(color = SurfaceVariant) + Spacer(Modifier.height(10.dp)) + } + + frameKmTotal?.let { fkm -> + Text( + "FRAMEKM STORAGE", + color = Amber, + fontFamily = FontFamily.Monospace, + fontSize = 10.sp, + letterSpacing = 1.sp + ) + Spacer(Modifier.height(4.dp)) + fkm.count?.let { InfoRow("Processed Frames", it.toString()) } + val totalBytes = fkm.total ?: fkm.totalBytes + totalBytes?.let { InfoRow("Total Size", formatBytes(it)) } + } + } +} + +@Composable +private fun StorageBar(fraction: Float, label: String) { + Column { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween + ) { + Text(label, color = Color.Gray, fontFamily = FontFamily.Monospace, fontSize = 9.sp) + Text("${(fraction * 100).toInt()}%", color = Color.Gray, fontFamily = FontFamily.Monospace, fontSize = 9.sp) + } + Spacer(Modifier.height(3.dp)) + Box( + modifier = Modifier + .fillMaxWidth() + .height(6.dp) + .clip(RoundedCornerShape(3.dp)) + .background(SurfaceVariant) + ) { + Box( + modifier = Modifier + .fillMaxWidth(fraction) + .fillMaxHeight() + .background( + when { + fraction > 0.85f -> Error + fraction > 0.65f -> Amber + else -> Success + } + ) + ) + } + } +} + +// ── GNSS ────────────────────────────────────────────────────────────────────── + +@Composable +private fun GnssSection(gnss: GnssStatus?) { + BeeCard(title = "GPS / GNSS", icon = Icons.Default.GpsFixed) { + if (gnss == null) { + Row(verticalAlignment = Alignment.CenterVertically) { + Icon(Icons.Default.GpsOff, contentDescription = null, tint = Error, modifier = Modifier.size(16.dp)) + Spacer(Modifier.width(6.dp)) + Text("No GNSS data available", color = Color.Gray, fontFamily = FontFamily.Monospace, fontSize = 11.sp) + } + return@BeeCard + } + + val hasFix = gnss.fix ?: (gnss.latDeg != null && gnss.latDeg != 0.0 && gnss.lonDeg != null && gnss.lonDeg != 0.0) + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween + ) { + Row(verticalAlignment = Alignment.CenterVertically) { + Icon( + if (hasFix) Icons.Default.GpsFixed else Icons.Default.GpsNotFixed, + contentDescription = null, + tint = if (hasFix) Success else Amber, + modifier = Modifier.size(16.dp) + ) + Spacer(Modifier.width(6.dp)) + Text( + if (hasFix) "FIX ACQUIRED" else "NO FIX", + color = if (hasFix) Success else Amber, + fontFamily = FontFamily.Monospace, + fontWeight = FontWeight.Bold, + fontSize = 12.sp + ) + } + gnss.fixType?.let { + Text(it, color = Color.Gray, fontFamily = FontFamily.Monospace, fontSize = 10.sp) + } + } + + Spacer(Modifier.height(8.dp)) + + val rows = listOfNotNull( + gnss.latDeg?.let { "Latitude" to "%.6f°".format(it) }, + gnss.lonDeg?.let { "Longitude" to "%.6f°".format(it) }, + gnss.altM?.let { "Altitude" to "%.1f m".format(it) }, + (gnss.satellitesUsed ?: gnss.satellites)?.let { "Satellites" to it.toString() }, + gnss.accuracyM?.let { "Accuracy" to "%.1f m".format(it) }, + gnss.hdop?.let { "HDOP" to "%.2f".format(it) }, + gnss.speedMs?.let { "Speed" to "%.1f m/s".format(it) }, + gnss.unixMs?.let { "Last Fix" to formatTimestamp(it) } + ) + rows.forEach { (k, v) -> InfoRow(k, v) } + } +} + +// ── Upload Mode ─────────────────────────────────────────────────────────────── + +@Composable +private fun UploadModeSection( + currentMode: String?, + saving: Boolean, + onSetMode: (String) -> Unit +) { + val modes = listOf("LTE", "WIFI", "APP") + BeeCard(title = "UPLOAD MODE", icon = Icons.Default.CloudUpload) { + Text( + "Select how the Bee uploads data.", + color = Color.Gray, + fontFamily = FontFamily.Monospace, + fontSize = 10.sp + ) + Spacer(Modifier.height(10.dp)) + + if (saving) { + Row(verticalAlignment = Alignment.CenterVertically) { + CircularProgressIndicator(color = Amber, modifier = Modifier.size(16.dp), strokeWidth = 2.dp) + Spacer(Modifier.width(8.dp)) + Text("Saving…", color = Color.Gray, fontFamily = FontFamily.Monospace, fontSize = 11.sp) + } + } else { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + modes.forEach { mode -> + val selected = currentMode?.uppercase() == mode + Box( + modifier = Modifier + .weight(1f) + .clip(RoundedCornerShape(6.dp)) + .background(if (selected) AmberDark else SurfaceVariant) + .border( + 1.dp, + if (selected) Amber else Color.Transparent, + RoundedCornerShape(6.dp) + ) + .clickable { onSetMode(mode) } + .padding(vertical = 10.dp), + contentAlignment = Alignment.Center + ) { + Column(horizontalAlignment = Alignment.CenterHorizontally) { + Icon( + when (mode) { + "LTE" -> Icons.Default.NetworkCell + "WIFI" -> Icons.Default.Wifi + else -> Icons.Default.PhoneAndroid + }, + contentDescription = null, + tint = if (selected) Amber else Color.Gray, + modifier = Modifier.size(20.dp) + ) + Spacer(Modifier.height(4.dp)) + Text( + mode, + color = if (selected) Amber else Color.Gray, + fontFamily = FontFamily.Monospace, + fontWeight = if (selected) FontWeight.Bold else FontWeight.Normal, + fontSize = 11.sp, + letterSpacing = 1.sp + ) + } + } + } + } + + Spacer(Modifier.height(8.dp)) + val modeDescription = when (currentMode?.uppercase()) { + "LTE" -> "Uploading via cellular LTE connection" + "WIFI" -> "Uploading via WiFi network" + "APP" -> "Upload controlled by companion app" + else -> "Upload mode not set" + } + Text(modeDescription, color = Color.Gray, fontFamily = FontFamily.Monospace, fontSize = 10.sp) + } + } +} + +// ── Plugins ─────────────────────────────────────────────────────────────────── + +@Composable +private fun PluginSection( + plugins: List, + config: BeeConfig? +) { + BeeCard(title = "PLUGINS", icon = Icons.Default.Extension) { + config?.let { cfg -> + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween + ) { + if (cfg.pluginsLocked == true) { + Row(verticalAlignment = Alignment.CenterVertically) { + Icon(Icons.Default.Lock, contentDescription = null, tint = Amber, modifier = Modifier.size(12.dp)) + Spacer(Modifier.width(4.dp)) + Text("PLUGINS LOCKED", color = Amber, fontFamily = FontFamily.Monospace, fontSize = 9.sp, letterSpacing = 1.sp) + } + } + if (cfg.pluginDevMode == true) { + Text("DEV MODE", color = Error, fontFamily = FontFamily.Monospace, fontSize = 9.sp, letterSpacing = 1.sp) + } + if (cfg.pausePluginUpdates == true) { + Text("UPDATES PAUSED", color = Color.Gray, fontFamily = FontFamily.Monospace, fontSize = 9.sp, letterSpacing = 1.sp) + } + } + if (cfg.pluginsLocked == true || cfg.pluginDevMode == true || cfg.pausePluginUpdates == true) { + Spacer(Modifier.height(8.dp)) + Divider(color = SurfaceVariant) + Spacer(Modifier.height(8.dp)) + } + } + + plugins.forEach { plugin -> + PluginRow(plugin) + if (plugin != plugins.last()) { + Spacer(Modifier.height(4.dp)) + } + } + } +} + +@Composable +private fun PluginRow(plugin: PluginState) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + Icon( + Icons.Default.Extension, + contentDescription = null, + tint = when (plugin.enabled) { + true -> Success + false -> Color.Gray + null -> Color(0xFF6B7280) + }, + modifier = Modifier.size(14.dp) + ) + Text( + plugin.name, + color = OnSurface, + fontFamily = FontFamily.Monospace, + fontSize = 12.sp + ) + } + when { + plugin.error != null -> Text( + "ERR", + color = Error, + fontFamily = FontFamily.Monospace, + fontSize = 10.sp + ) + plugin.enabled == true -> Text( + "ENABLED", + color = Success, + fontFamily = FontFamily.Monospace, + fontSize = 10.sp, + fontWeight = FontWeight.Bold + ) + plugin.enabled == false -> Text( + "DISABLED", + color = Color.Gray, + fontFamily = FontFamily.Monospace, + fontSize = 10.sp + ) + else -> Text( + "UNKNOWN", + color = Color(0xFF6B7280), + fontFamily = FontFamily.Monospace, + fontSize = 10.sp + ) + } + } +} + +// ── Shared components ───────────────────────────────────────────────────────── + +@Composable +private fun BeeCard( + title: String, + icon: ImageVector, + content: @Composable ColumnScope.() -> Unit +) { + Card( + colors = CardDefaults.cardColors(containerColor = Surface), + shape = RoundedCornerShape(8.dp), + modifier = Modifier.fillMaxWidth() + ) { + Column(modifier = Modifier.padding(14.dp)) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(6.dp) + ) { + Icon(icon, contentDescription = null, tint = Amber, modifier = Modifier.size(14.dp)) + Text( + title, + color = Amber, + fontFamily = FontFamily.Monospace, + fontWeight = FontWeight.Bold, + fontSize = 11.sp, + letterSpacing = 2.sp + ) + } + Spacer(Modifier.height(10.dp)) + content() + } + } +} + +@Composable +private fun InfoRow( + label: String, + value: String, + valueColor: Color = OnSurface +) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 2.dp), + horizontalArrangement = Arrangement.SpaceBetween + ) { + Text( + label, + color = Color.Gray, + fontFamily = FontFamily.Monospace, + fontSize = 11.sp, + modifier = Modifier.weight(0.45f) + ) + Text( + value, + color = valueColor, + fontFamily = FontFamily.Monospace, + fontSize = 11.sp, + fontWeight = FontWeight.Medium, + modifier = Modifier.weight(0.55f), + textAlign = androidx.compose.ui.text.style.TextAlign.End + ) + } +} + +@Composable +private fun BeeTextField( + value: String, + onValueChange: (String) -> Unit, + label: String, + hint: String = "", + visualTransformation: VisualTransformation = VisualTransformation.None, + trailingIcon: (@Composable () -> Unit)? = null, + numeric: Boolean = false +) { + OutlinedTextField( + value = value, + onValueChange = onValueChange, + label = { Text(label, fontFamily = FontFamily.Monospace, fontSize = 11.sp) }, + placeholder = { Text(hint, color = Color.Gray, fontFamily = FontFamily.Monospace, fontSize = 11.sp) }, + singleLine = true, + visualTransformation = visualTransformation, + trailingIcon = trailingIcon, + keyboardOptions = if (numeric) KeyboardOptions(keyboardType = KeyboardType.Number) + else KeyboardOptions.Default, + modifier = Modifier.fillMaxWidth(), + colors = OutlinedTextFieldDefaults.colors( + focusedBorderColor = Amber, + unfocusedBorderColor = SurfaceVariant, + focusedLabelColor = Amber, + unfocusedLabelColor = Color.Gray, + cursorColor = Amber, + focusedTextColor = OnSurface, + unfocusedTextColor = OnSurface + ) + ) +} + +@Composable +private fun StatusIndicator(active: Boolean) { + Box( + modifier = Modifier + .size(10.dp) + .background( + color = if (active) Success else Color(0xFF6B7280), + shape = RoundedCornerShape(50) + ) + ) +} + +// ── Formatters ──────────────────────────────────────────────────────────────── + +private fun formatUptime(seconds: Long): String { + val h = seconds / 3600 + val m = (seconds % 3600) / 60 + val s = seconds % 60 + return "%02d:%02d:%02d".format(h, m, s) +} + +private fun formatBytes(bytes: Long): String { + return when { + bytes < 1024 -> "$bytes B" + bytes < 1024 * 1024 -> "%.1f KB".format(bytes / 1024.0) + bytes < 1024 * 1024 * 1024 -> "%.1f MB".format(bytes / (1024.0 * 1024)) + else -> "%.2f GB".format(bytes / (1024.0 * 1024 * 1024)) + } +} + +private fun formatTimestamp(ms: Long): String { + return try { + val sdf = SimpleDateFormat("MM/dd HH:mm:ss", Locale.US) + sdf.format(Date(ms)) + } catch (e: Exception) { + ms.toString() + } +} diff --git a/app/src/main/java/com/adamaps/varroa/viewmodel/BeeSettingsViewModel.kt b/app/src/main/java/com/adamaps/varroa/viewmodel/BeeSettingsViewModel.kt new file mode 100644 index 0000000..0a33a53 --- /dev/null +++ b/app/src/main/java/com/adamaps/varroa/viewmodel/BeeSettingsViewModel.kt @@ -0,0 +1,223 @@ +package com.adamaps.varroa.viewmodel + +import android.app.Application +import android.util.Log +import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.viewModelScope +import com.adamaps.varroa.api.BeeApiClient +import com.adamaps.varroa.data.* +import com.adamaps.varroa.network.NetworkStateMonitor +import kotlinx.coroutines.Job +import kotlinx.coroutines.async +import kotlinx.coroutines.flow.* +import kotlinx.coroutines.launch + +private const val TAG = "BeeSettingsVM" + +sealed class BeeSettingsLoadState { + object Idle : BeeSettingsLoadState() + object Loading : BeeSettingsLoadState() + data class Error(val message: String) : BeeSettingsLoadState() + object Success : BeeSettingsLoadState() +} + +data class BeeSettingsState( + val deviceInfo: BeeDeviceInfo? = null, + val wifiStatus: WifiStatus? = null, + val wifiSettings: WifiClientSettings? = null, + val wifiNetworks: List = emptyList(), + val cacheStatus: CacheStatus? = null, + val frameKmTotal: FrameKmTotal? = null, + val gnssStatus: GnssStatus? = null, + val uploadMode: String? = null, + val config: BeeConfig? = null, + val plugins: List = emptyList(), + val loadState: BeeSettingsLoadState = BeeSettingsLoadState.Idle, + val wifiScanState: BeeSettingsLoadState = BeeSettingsLoadState.Idle, + val saveState: BeeSettingsLoadState = BeeSettingsLoadState.Idle, + val uploadModeSaving: Boolean = false, + val wifiSaving: Boolean = false +) + +class BeeSettingsViewModel(app: Application) : AndroidViewModel(app) { + + private val settingsStore = SettingsDataStore(app) + private val networkMonitor = NetworkStateMonitor.getInstance(app) + private val beeClient = BeeApiClient() + + private val _state = MutableStateFlow(BeeSettingsState()) + val state: StateFlow = _state.asStateFlow() + + // Toast/snackbar messages + private val _message = MutableStateFlow(null) + val message: StateFlow = _message.asStateFlow() + + init { + viewModelScope.launch { + val s = settingsStore.settings.first() + beeClient.updateUrl(s.beeApiUrl) + val beeNet = networkMonitor.getBeeNetworkForBinding() + if (beeNet != null) { + beeClient.bindToNetwork(beeNet) + } else { + beeClient.bindToWifiNetwork(app) + } + loadAll() + } + } + + fun loadAll() { + viewModelScope.launch { + _state.update { it.copy(loadState = BeeSettingsLoadState.Loading) } + try { + // Fetch all sections concurrently + val deviceInfoDeferred = async { beeClient.getDeviceInfo() } + val wifiStatusDeferred = async { beeClient.getWifiStatus() } + val wifiSettingsDeferred = async { beeClient.getWifiSettings() } + val cacheDeferred = async { beeClient.getCacheStatus() } + val frameKmDeferred = async { beeClient.getFrameKmTotal() } + val gnssDeferred = async { beeClient.getGnssStatus() } + val uploadModeDeferred = async { beeClient.getUploadMode() } + val configDeferred = async { beeClient.getConfig() } + val pluginsDeferred = async { beeClient.getKnownPluginStates() } + + val deviceInfo = when (val r = deviceInfoDeferred.await()) { + is ApiResult.Success -> r.data + is ApiResult.Error -> { + Log.w(TAG, "deviceInfo failed: ${r.message}") + null + } + } + + if (deviceInfo == null) { + _state.update { + it.copy(loadState = BeeSettingsLoadState.Error("Bee not reachable — check connection and URL")) + } + return@launch + } + + val wifiStatus = (wifiStatusDeferred.await() as? ApiResult.Success)?.data + val wifiSettings = (wifiSettingsDeferred.await() as? ApiResult.Success)?.data + val cacheStatus = (cacheDeferred.await() as? ApiResult.Success)?.data + val frameKmTotal = (frameKmDeferred.await() as? ApiResult.Success)?.data + val gnssStatus = (gnssDeferred.await() as? ApiResult.Success)?.data + val uploadMode = (uploadModeDeferred.await() as? ApiResult.Success)?.data + val config = (configDeferred.await() as? ApiResult.Success)?.data + val plugins = pluginsDeferred.await() + + _state.update { + it.copy( + deviceInfo = deviceInfo, + wifiStatus = wifiStatus, + wifiSettings = wifiSettings, + cacheStatus = cacheStatus, + frameKmTotal = frameKmTotal, + gnssStatus = gnssStatus, + uploadMode = uploadMode ?: deviceInfo.uploadMode, + config = config, + plugins = plugins, + loadState = BeeSettingsLoadState.Success + ) + } + Log.i(TAG, "BeeSettings loaded successfully") + + } catch (e: Exception) { + Log.e(TAG, "loadAll failed", e) + _state.update { + it.copy(loadState = BeeSettingsLoadState.Error(e.message ?: "Unknown error")) + } + } + } + } + + fun scanWifi() { + viewModelScope.launch { + _state.update { it.copy(wifiScanState = BeeSettingsLoadState.Loading) } + when (val r = beeClient.scanWifi()) { + is ApiResult.Success -> { + _state.update { + it.copy( + wifiNetworks = r.data, + wifiScanState = BeeSettingsLoadState.Success + ) + } + _message.value = "Found ${r.data.size} networks" + } + is ApiResult.Error -> { + _state.update { it.copy(wifiScanState = BeeSettingsLoadState.Error(r.message)) } + _message.value = "Scan failed: ${r.message}" + } + } + } + } + + fun saveWifiSettings(ssid: String, password: String, enabled: Boolean) { + viewModelScope.launch { + _state.update { it.copy(wifiSaving = true) } + val settings = WifiClientSettings( + ssid = ssid.trim(), + password = password, + enabled = enabled, + security = if (password.length >= 8) "WPA2" else "Open" + ) + when (val r = beeClient.saveWifiSettings(settings)) { + is ApiResult.Success -> { + _message.value = "WiFi settings saved" + // Refresh WiFi status after save + (beeClient.getWifiStatus() as? ApiResult.Success)?.data?.let { status -> + _state.update { it.copy(wifiStatus = status, wifiSettings = settings) } + } + } + is ApiResult.Error -> { + _message.value = "Save failed: ${r.message}" + } + } + _state.update { it.copy(wifiSaving = false) } + } + } + + fun setWifiEnabled(enabled: Boolean) { + viewModelScope.launch { + when (val r = beeClient.setWifiEnabled(enabled)) { + is ApiResult.Success -> { + _message.value = if (enabled) "WiFi client enabled" else "WiFi client disabled" + _state.update { s -> + s.copy(wifiSettings = s.wifiSettings?.copy(enabled = enabled)) + } + } + is ApiResult.Error -> { + _message.value = "Failed: ${r.message}" + } + } + } + } + + fun resetWifi() { + viewModelScope.launch { + when (val r = beeClient.resetWifi()) { + is ApiResult.Success -> _message.value = "WiFi reset triggered" + is ApiResult.Error -> _message.value = "Reset failed: ${r.message}" + } + } + } + + fun setUploadMode(mode: String) { + viewModelScope.launch { + _state.update { it.copy(uploadModeSaving = true) } + when (val r = beeClient.setUploadMode(mode)) { + is ApiResult.Success -> { + _state.update { it.copy(uploadMode = mode, uploadModeSaving = false) } + _message.value = "Upload mode set to $mode" + } + is ApiResult.Error -> { + _state.update { it.copy(uploadModeSaving = false) } + _message.value = "Failed: ${r.message}" + } + } + } + } + + fun clearMessage() { + _message.value = null + } +} From 08a88f8218513bbbdcd1cd6c4eebc8aea3b83990 Mon Sep 17 00:00:00 2001 From: Kayos Date: Sat, 14 Mar 2026 11:56:46 -0700 Subject: [PATCH 09/26] =?UTF-8?q?feat:=20adacam=20migration=20=E2=80=94=20?= =?UTF-8?q?update=20IP,=20pairing,=20bearer=20auth,=20wifi/ssh=20config,?= =?UTF-8?q?=20remove=20JSch=20and=20cmd?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Default API URL: 192.168.0.10 → 10.77.0.1 - BeeApiClient: apiToken field, bearer auth on POST endpoints, pair() method, getSshStatus(), setSshEnabled() — JSch/SSH-from-app removed entirely - Models: PairResponse, SshStatus, WifiStatus data classes - SettingsDataStore: deviceSerial, apiToken, isPaired persistence + deriveApiToken() - SettingsViewModel: pairDevice(), connectWifi(), toggleSsh(), refreshSshStatus(), refreshWifiStatus(), isPaired/deviceSerial/sshStatus/wifiStatus StateFlows - SettingsScreen: Pairing section, Home WiFi config section, SSH toggle section renamed BEE DEVICE → ADACAM DEVICE, hint URL updated - build.gradle: removed JSch dependency (no longer SSHing from app) --- app/build.gradle.kts | 2 +- .../java/com/adamaps/varroa/data/Models.kt | 15 ++ .../adamaps/varroa/data/SettingsDataStore.kt | 57 ++++- .../varroa/ui/settings/SettingsScreen.kt | 136 ++++++++++- .../varroa/viewmodel/SettingsViewModel.kt | 228 ++++++++++++++++++ 5 files changed, 430 insertions(+), 8 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 903d76a..41c5850 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -77,7 +77,7 @@ dependencies { // WorkManager (background uploads) implementation(libs.work.runtime.ktx) // SSH connectivity for device_id fallback - implementation("com.jcraft:jsch:0.1.55") + // QR Code scanning implementation("com.google.zxing:core:3.5.2") implementation("com.journeyapps:zxing-android-embedded:4.3.0") diff --git a/app/src/main/java/com/adamaps/varroa/data/Models.kt b/app/src/main/java/com/adamaps/varroa/data/Models.kt index 87a5968..af348ae 100644 --- a/app/src/main/java/com/adamaps/varroa/data/Models.kt +++ b/app/src/main/java/com/adamaps/varroa/data/Models.kt @@ -122,6 +122,21 @@ data class BeePlugin( @SerializedName("running") val running: Boolean? = null ) +// ── Pairing API ─────────────────────────────────────────────────────────────── + +data class PairResponse( + @SerializedName("serial") val serial: String, + @SerializedName("version") val version: String, + @SerializedName("ap_ip") val apIp: String, + @SerializedName("api_port") val apiPort: Int +) + +// ── SSH API ─────────────────────────────────────────────────────────────────── + +data class SshStatus( + @SerializedName("active") val active: Boolean +) + // ── Bee Settings API models ─────────────────────────────────────────────────── data class WifiClientSettings( diff --git a/app/src/main/java/com/adamaps/varroa/data/SettingsDataStore.kt b/app/src/main/java/com/adamaps/varroa/data/SettingsDataStore.kt index c97c981..44a352c 100644 --- a/app/src/main/java/com/adamaps/varroa/data/SettingsDataStore.kt +++ b/app/src/main/java/com/adamaps/varroa/data/SettingsDataStore.kt @@ -10,11 +10,12 @@ import androidx.datastore.preferences.core.stringPreferencesKey import androidx.datastore.preferences.preferencesDataStore import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map +import java.security.MessageDigest private val Context.dataStore: DataStore by preferencesDataStore(name = "varroa_settings") data class VarroaSettings( - val beeApiUrl: String = "http://192.168.0.10:5000", + val beeApiUrl: String = "http://10.77.0.1:5000", val adamapsApiUrl: String = "https://api.adamaps.org", val adamapsApiKey: String = "***REMOVED***", val pollIntervalSeconds: Int = 30, @@ -22,9 +23,23 @@ data class VarroaSettings( val cameraRefreshSeconds: Int = 30, val forwardingEnabled: Boolean = true, val cachedDeviceId: String = "unknown", - val walletAddress: String = "" + val walletAddress: String = "", + // AdaCam pairing + val deviceSerial: String = "", + val apiToken: String = "", + val isPaired: Boolean = false ) +/** + * Derive the API token from the device serial. + * Token = first 32 chars of SHA-256("adacam-api-{serial}-token") + */ +fun deriveApiToken(serial: String): String { + val input = "adacam-api-$serial-token" + val bytes = MessageDigest.getInstance("SHA-256").digest(input.toByteArray()) + return bytes.joinToString("") { "%02x".format(it) }.substring(0, 32) +} + class SettingsDataStore(private val context: Context) { companion object { @@ -37,11 +52,15 @@ class SettingsDataStore(private val context: Context) { private val KEY_FORWARDING_ENABLED = booleanPreferencesKey("forwarding_enabled") private val KEY_CACHED_DEVICE_ID = stringPreferencesKey("cached_device_id") private val KEY_WALLET_ADDRESS = stringPreferencesKey("wallet_address") + // AdaCam pairing keys + private val KEY_DEVICE_SERIAL = stringPreferencesKey("device_serial") + private val KEY_API_TOKEN = stringPreferencesKey("api_token") + private val KEY_IS_PAIRED = booleanPreferencesKey("is_paired") } val settings: Flow = context.dataStore.data.map { prefs -> VarroaSettings( - beeApiUrl = prefs[KEY_BEE_URL] ?: "http://192.168.0.10:5000", + beeApiUrl = prefs[KEY_BEE_URL] ?: "http://10.77.0.1:5000", adamapsApiUrl = prefs[KEY_ADAMAPS_URL] ?: "https://api.adamaps.org", adamapsApiKey = prefs[KEY_ADAMAPS_KEY] ?: "***REMOVED***", pollIntervalSeconds = prefs[KEY_POLL_INTERVAL] ?: 30, @@ -49,7 +68,10 @@ class SettingsDataStore(private val context: Context) { cameraRefreshSeconds = prefs[KEY_CAMERA_REFRESH] ?: 30, forwardingEnabled = prefs[KEY_FORWARDING_ENABLED] ?: true, cachedDeviceId = prefs[KEY_CACHED_DEVICE_ID] ?: "unknown", - walletAddress = prefs[KEY_WALLET_ADDRESS] ?: "" + walletAddress = prefs[KEY_WALLET_ADDRESS] ?: "", + deviceSerial = prefs[KEY_DEVICE_SERIAL] ?: "", + apiToken = prefs[KEY_API_TOKEN] ?: "", + isPaired = prefs[KEY_IS_PAIRED] ?: false ) } @@ -64,6 +86,9 @@ class SettingsDataStore(private val context: Context) { prefs[KEY_FORWARDING_ENABLED] = s.forwardingEnabled prefs[KEY_CACHED_DEVICE_ID] = s.cachedDeviceId prefs[KEY_WALLET_ADDRESS] = s.walletAddress + prefs[KEY_DEVICE_SERIAL] = s.deviceSerial + prefs[KEY_API_TOKEN] = s.apiToken + prefs[KEY_IS_PAIRED] = s.isPaired } } @@ -72,4 +97,28 @@ class SettingsDataStore(private val context: Context) { prefs[KEY_CACHED_DEVICE_ID] = deviceId } } + + /** + * Store pairing data after successful pairing with AdaCam. + * Derives and stores the API token from the serial. + */ + suspend fun savePairing(serial: String) { + val token = deriveApiToken(serial) + context.dataStore.edit { prefs -> + prefs[KEY_DEVICE_SERIAL] = serial + prefs[KEY_API_TOKEN] = token + prefs[KEY_IS_PAIRED] = true + } + } + + /** + * Clear pairing data (for re-pairing or reset). + */ + suspend fun clearPairing() { + context.dataStore.edit { prefs -> + prefs[KEY_DEVICE_SERIAL] = "" + prefs[KEY_API_TOKEN] = "" + prefs[KEY_IS_PAIRED] = false + } + } } diff --git a/app/src/main/java/com/adamaps/varroa/ui/settings/SettingsScreen.kt b/app/src/main/java/com/adamaps/varroa/ui/settings/SettingsScreen.kt index 1698807..76605f4 100644 --- a/app/src/main/java/com/adamaps/varroa/ui/settings/SettingsScreen.kt +++ b/app/src/main/java/com/adamaps/varroa/ui/settings/SettingsScreen.kt @@ -27,6 +27,9 @@ import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import androidx.compose.material.icons.filled.Link +import androidx.compose.material.icons.filled.Wifi +import androidx.compose.material.icons.filled.Terminal import androidx.lifecycle.viewmodel.compose.viewModel import com.adamaps.varroa.data.VarroaSettings import com.adamaps.varroa.ui.theme.* @@ -54,6 +57,19 @@ fun SettingsScreen( var cameraEndpoint by remember(currentSettings) { mutableStateOf(currentSettings.cameraEndpoint) } var walletAddress by remember(currentSettings) { mutableStateOf(currentSettings.walletAddress) } + // Pairing & device state + val isPaired by vm.isPaired.collectAsState() + val deviceSerial by vm.deviceSerial.collectAsState() + val pairingInProgress by vm.pairingInProgress.collectAsState() + val pairingResult by vm.pairingResult.collectAsState() + val sshStatus by vm.sshStatus.collectAsState() + val wifiStatus by vm.wifiStatus.collectAsState() + val wifiConnectResult by vm.wifiConnectResult.collectAsState() + + // WiFi config input state + var homeWifiSsid by remember { mutableStateOf("") } + var homeWifiPassword by remember { mutableStateOf("") } + // Show snackbar on save val snackbarHostState = remember { SnackbarHostState() } LaunchedEffect(saved) { @@ -157,15 +173,129 @@ fun SettingsScreen( } } - SettingsSection("BEE DEVICE") { + SettingsSection("ADACAM DEVICE") { SettingsField( - label = "Bee API URL", + label = "AdaCam API URL", value = beeApiUrl, onValueChange = { beeApiUrl = it }, - hint = "http://192.168.0.10:5000" + hint = "http://10.77.0.1:5000" ) } + // ── Pairing ─────────────────────────────────────────────────────── + SettingsSection("PAIRING") { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.fillMaxWidth() + ) { + Icon( + Icons.Default.Link, + contentDescription = null, + tint = if (isPaired) Amber else Color.Gray, + modifier = Modifier.size(16.dp) + ) + Spacer(Modifier.width(8.dp)) + Text( + if (isPaired) "Paired — serial: $deviceSerial" + else "Not paired", + color = if (isPaired) Amber else Color.Gray, + fontFamily = FontFamily.Monospace, + fontSize = 12.sp + ) + } + Spacer(Modifier.height(8.dp)) + pairingResult?.let { + Text(it, color = if (it.startsWith("Error")) Color.Red else Amber, + fontFamily = FontFamily.Monospace, fontSize = 11.sp) + Spacer(Modifier.height(4.dp)) + } + Button( + onClick = { vm.pairDevice() }, + enabled = !pairingInProgress, + colors = ButtonDefaults.buttonColors(containerColor = Amber, contentColor = Background), + modifier = Modifier.fillMaxWidth() + ) { + Text( + if (pairingInProgress) "Pairing…" else if (isPaired) "Re-pair AdaCam" else "Pair with AdaCam", + fontFamily = FontFamily.Monospace, + fontWeight = FontWeight.Bold + ) + } + } + + // ── Home WiFi config ────────────────────────────────────────────── + SettingsSection("HOME WIFI") { + Row(verticalAlignment = Alignment.CenterVertically) { + Icon(Icons.Default.Wifi, contentDescription = null, + tint = if (wifiStatus?.connected == true) Amber else Color.Gray, + modifier = Modifier.size(16.dp)) + Spacer(Modifier.width(8.dp)) + Text( + wifiStatus?.let { + if (it.connected) "Connected: ${it.ssid} (${it.ip})" + else "Disconnected" + } ?: "Unknown", + color = if (wifiStatus?.connected == true) Amber else Color.Gray, + fontFamily = FontFamily.Monospace, fontSize = 12.sp + ) + } + Spacer(Modifier.height(8.dp)) + SettingsField(label = "SSID", value = homeWifiSsid, + onValueChange = { homeWifiSsid = it }, hint = "Your home network") + SettingsField(label = "Password", value = homeWifiPassword, + onValueChange = { homeWifiPassword = it }, hint = "WiFi password", + keyboardType = KeyboardType.Password) + wifiConnectResult?.let { + Text(it, color = if (it.startsWith("Error")) Color.Red else Amber, + fontFamily = FontFamily.Monospace, fontSize = 11.sp) + Spacer(Modifier.height(4.dp)) + } + Button( + onClick = { vm.connectWifi(homeWifiSsid, homeWifiPassword) }, + enabled = isPaired && homeWifiSsid.isNotBlank() && homeWifiPassword.isNotBlank(), + colors = ButtonDefaults.buttonColors(containerColor = Amber, contentColor = Background), + modifier = Modifier.fillMaxWidth() + ) { + Text("Connect AdaCam to WiFi", fontFamily = FontFamily.Monospace, fontWeight = FontWeight.Bold) + } + if (!isPaired) { + Text("Pair device first to configure WiFi", + color = Color.Gray, fontFamily = FontFamily.Monospace, fontSize = 10.sp) + } + } + + // ── SSH access ──────────────────────────────────────────────────── + SettingsSection("SSH ACCESS") { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween + ) { + Row(verticalAlignment = Alignment.CenterVertically) { + Icon(Icons.Default.Terminal, contentDescription = null, + tint = if (sshStatus?.active == true) Amber else Color.Gray, + modifier = Modifier.size(16.dp)) + Spacer(Modifier.width(8.dp)) + Column { + Text("SSH over home WiFi", + color = Color.White, fontFamily = FontFamily.Monospace, fontSize = 12.sp) + Text(if (sshStatus?.active == true) "Active — ssh root@" else "Inactive", + color = Color.Gray, fontFamily = FontFamily.Monospace, fontSize = 10.sp) + } + } + Switch( + checked = sshStatus?.active == true, + onCheckedChange = { vm.toggleSsh(it) }, + enabled = isPaired, + colors = SwitchDefaults.colors(checkedThumbColor = Background, checkedTrackColor = Amber) + ) + } + if (!isPaired) { + Text("Pair device first to toggle SSH", + color = Color.Gray, fontFamily = FontFamily.Monospace, fontSize = 10.sp) + } + } + SettingsSection("ADAMAPS") { SettingsField( label = "ADAMaps API URL", diff --git a/app/src/main/java/com/adamaps/varroa/viewmodel/SettingsViewModel.kt b/app/src/main/java/com/adamaps/varroa/viewmodel/SettingsViewModel.kt index a703b95..5d1ef53 100644 --- a/app/src/main/java/com/adamaps/varroa/viewmodel/SettingsViewModel.kt +++ b/app/src/main/java/com/adamaps/varroa/viewmodel/SettingsViewModel.kt @@ -1,30 +1,97 @@ package com.adamaps.varroa.viewmodel import android.app.Application +import android.util.Log import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.viewModelScope +import com.adamaps.varroa.api.BeeApiClient +import com.adamaps.varroa.data.ApiResult import com.adamaps.varroa.data.SettingsDataStore +import com.adamaps.varroa.data.SshStatus import com.adamaps.varroa.data.VarroaSettings +import com.adamaps.varroa.data.WifiStatus import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch class SettingsViewModel(app: Application) : AndroidViewModel(app) { + companion object { + private const val TAG = "SettingsVM" + } + private val store = SettingsDataStore(app) + private var beeClient: BeeApiClient? = null val settings: StateFlow = store.settings .stateIn(viewModelScope, SharingStarted.Eagerly, VarroaSettings()) + // Derived observables from settings + val isPaired: StateFlow = store.settings + .map { it.isPaired } + .stateIn(viewModelScope, SharingStarted.Eagerly, false) + + val deviceSerial: StateFlow = store.settings + .map { it.deviceSerial } + .stateIn(viewModelScope, SharingStarted.Eagerly, "") + private val _saved = MutableStateFlow(false) val saved: StateFlow = _saved.asStateFlow() + // Pairing state + private val _pairingInProgress = MutableStateFlow(false) + val pairingInProgress: StateFlow = _pairingInProgress.asStateFlow() + + private val _pairingResult = MutableStateFlow(null) + val pairingResult: StateFlow = _pairingResult.asStateFlow() + + // SSH state + private val _sshStatus = MutableStateFlow(null) + val sshStatus: StateFlow = _sshStatus.asStateFlow() + + private val _sshToggleResult = MutableStateFlow(null) + val sshToggleResult: StateFlow = _sshToggleResult.asStateFlow() + + // WiFi state + private val _wifiStatus = MutableStateFlow(null) + val wifiStatus: StateFlow = _wifiStatus.asStateFlow() + + private val _wifiConnectResult = MutableStateFlow(null) + val wifiConnectResult: StateFlow = _wifiConnectResult.asStateFlow() + + init { + // Initialize BeeApiClient with stored settings and token + viewModelScope.launch { + val s = store.settings.first() + val client = BeeApiClient(s.beeApiUrl) + if (s.apiToken.isNotBlank()) { + client.apiToken = s.apiToken + } + beeClient = client + + // Fetch initial statuses if paired + if (s.isPaired) { + refreshSshStatus() + refreshWifiStatus() + } + } + } + fun save(s: VarroaSettings) { viewModelScope.launch { store.save(s) + + // Update client URL and token if changed + beeClient?.updateUrl(s.beeApiUrl) + if (s.apiToken.isNotBlank()) { + beeClient?.apiToken = s.apiToken + } + _saved.value = true } } @@ -32,4 +99,165 @@ class SettingsViewModel(app: Application) : AndroidViewModel(app) { fun clearSaved() { _saved.value = false } + + // ── Pairing ─────────────────────────────────────────────────────────────── + + /** + * Pair with the AdaCam device. + * Fetches serial from /pair endpoint, derives token, stores both. + */ + fun pairDevice() { + viewModelScope.launch { + _pairingInProgress.value = true + _pairingResult.value = null + + try { + val s = store.settings.first() + val client = BeeApiClient(s.beeApiUrl) + beeClient = client + + when (val result = client.pair()) { + is ApiResult.Success -> { + val serial = result.data.serial + Log.i(TAG, "Pairing successful: serial=$serial") + + // Store pairing data (derives token automatically) + store.savePairing(serial) + + // Update client with new token + val newSettings = store.settings.first() + client.apiToken = newSettings.apiToken + + _pairingResult.value = "Paired successfully! Serial: $serial" + + // Fetch statuses now that we're paired + refreshSshStatus() + refreshWifiStatus() + } + is ApiResult.Error -> { + Log.e(TAG, "Pairing failed: ${result.message}") + _pairingResult.value = "Pairing failed: ${result.message}" + } + } + } catch (e: Exception) { + Log.e(TAG, "Pairing exception", e) + _pairingResult.value = "Pairing error: ${e.message}" + } finally { + _pairingInProgress.value = false + } + } + } + + /** + * Clear pairing data (for re-pairing). + */ + fun clearPairing() { + viewModelScope.launch { + store.clearPairing() + beeClient?.apiToken = "" + _sshStatus.value = null + _wifiStatus.value = null + _pairingResult.value = "Pairing cleared" + } + } + + fun clearPairingResult() { + _pairingResult.value = null + } + + // ── WiFi ────────────────────────────────────────────────────────────────── + + /** + * Refresh WiFi status from device. + */ + fun refreshWifiStatus() { + viewModelScope.launch { + val client = beeClient ?: return@launch + when (val result = client.getWifiStatus()) { + is ApiResult.Success -> { + _wifiStatus.value = result.data + } + is ApiResult.Error -> { + Log.w(TAG, "Failed to get WiFi status: ${result.message}") + } + } + } + } + + /** + * Connect device to a home WiFi network. + */ + fun connectWifi(ssid: String, password: String) { + viewModelScope.launch { + val client = beeClient ?: run { + _wifiConnectResult.value = "Not connected to device" + return@launch + } + _wifiConnectResult.value = null + + when (val result = client.setWifiConfig(ssid, password)) { + is ApiResult.Success -> { + _wifiConnectResult.value = "WiFi config sent. Connecting to $ssid..." + // Refresh status after a short delay + kotlinx.coroutines.delay(3000) + refreshWifiStatus() + } + is ApiResult.Error -> { + _wifiConnectResult.value = "Failed: ${result.message}" + } + } + } + } + + fun clearWifiResult() { + _wifiConnectResult.value = null + } + + // ── SSH ─────────────────────────────────────────────────────────────────── + + /** + * Refresh SSH status from device. + */ + fun refreshSshStatus() { + viewModelScope.launch { + val client = beeClient ?: return@launch + when (val result = client.getSshStatus()) { + is ApiResult.Success -> { + _sshStatus.value = result.data + } + is ApiResult.Error -> { + Log.w(TAG, "Failed to get SSH status: ${result.message}") + } + } + } + } + + /** + * Toggle SSH on/off on the device. + */ + fun toggleSsh(enabled: Boolean) { + viewModelScope.launch { + val client = beeClient ?: run { + _sshToggleResult.value = "Not connected to device" + return@launch + } + _sshToggleResult.value = null + + when (val result = client.setSshEnabled(enabled)) { + is ApiResult.Success -> { + _sshToggleResult.value = if (enabled) "SSH enabled" else "SSH disabled" + _sshStatus.value = SshStatus(active = enabled) + } + is ApiResult.Error -> { + _sshToggleResult.value = "Failed: ${result.message}" + // Refresh to get actual state + refreshSshStatus() + } + } + } + } + + fun clearSshResult() { + _sshToggleResult.value = null + } } From 59bcbb3d7d4934fa1c8a43cff224e7b3e5c45e54 Mon Sep 17 00:00:00 2001 From: Kayos Date: Sat, 14 Mar 2026 15:49:28 -0700 Subject: [PATCH 10/26] =?UTF-8?q?feat:=20wigle=20wardriving=20UI=20?= =?UTF-8?q?=E2=80=94=20account=20linking=20and=20stats?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/adamaps/varroa/api/BeeApiClient.kt | 33 ++++ .../java/com/adamaps/varroa/data/Models.kt | 18 ++ .../varroa/ui/settings/SettingsScreen.kt | 154 +++++++++++++++++- .../varroa/viewmodel/SettingsViewModel.kt | 67 ++++++++ 4 files changed, 269 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/adamaps/varroa/api/BeeApiClient.kt b/app/src/main/java/com/adamaps/varroa/api/BeeApiClient.kt index 6215393..a6fcfeb 100644 --- a/app/src/main/java/com/adamaps/varroa/api/BeeApiClient.kt +++ b/app/src/main/java/com/adamaps/varroa/api/BeeApiClient.kt @@ -672,4 +672,37 @@ class BeeApiClient( return@withContext ApiResult.Error("Exception: ${e.message}") } } + + // ── WiGLE Wardriving API ────────────────────────────────────────────────── + + suspend fun getWigleStatus(): ApiResult = withContext(Dispatchers.IO) { + when (val r = getRaw("/api/1/wigle/status")) { + is ApiResult.Success -> try { + ApiResult.Success(gson.fromJson(r.data, com.adamaps.varroa.data.WigleStatus::class.java)) + } catch (e: Exception) { + ApiResult.Error("Parse error: ${e.message}") + } + is ApiResult.Error -> r + } + } + + suspend fun getWigleStats(): ApiResult = withContext(Dispatchers.IO) { + when (val r = getRaw("/api/1/wigle/stats")) { + is ApiResult.Success -> try { + ApiResult.Success(gson.fromJson(r.data, com.adamaps.varroa.data.WigleStats::class.java)) + } catch (e: Exception) { + ApiResult.Error("Parse error: ${e.message}") + } + is ApiResult.Error -> r + } + } + + suspend fun setWigleConfig(enabled: Boolean, apiName: String, apiToken: String): ApiResult { + val json = gson.toJson(mapOf( + "enabled" to enabled, + "api_name" to apiName, + "api_token" to apiToken + )) + return postRaw("/api/1/wigle/config", json) + } } diff --git a/app/src/main/java/com/adamaps/varroa/data/Models.kt b/app/src/main/java/com/adamaps/varroa/data/Models.kt index af348ae..3575c3d 100644 --- a/app/src/main/java/com/adamaps/varroa/data/Models.kt +++ b/app/src/main/java/com/adamaps/varroa/data/Models.kt @@ -217,6 +217,24 @@ data class FrameKmTotal( @SerializedName("count") val count: Int? = null ) +// ── WiGLE Wardriving ────────────────────────────────────────────────────────── + +data class WigleStatus( + val enabled: Boolean = false, + @SerializedName("api_name") val apiName: String = "", + @SerializedName("total_networks") val totalNetworks: Int = 0, + @SerializedName("pending_upload") val pendingUpload: Int = 0, + @SerializedName("last_scan") val lastScan: Long? = null, + @SerializedName("last_upload") val lastUpload: Long? = null +) + +data class WigleStats( + @SerializedName("total_networks") val totalNetworks: Int = 0, + @SerializedName("uploaded_networks") val uploadedNetworks: Int = 0, + @SerializedName("pending_upload") val pendingUpload: Int = 0, + @SerializedName("scans_today") val scansToday: Int = 0 +) + // ── App state ───────────────────────────────────────────────────────────────── data class SessionStats( diff --git a/app/src/main/java/com/adamaps/varroa/ui/settings/SettingsScreen.kt b/app/src/main/java/com/adamaps/varroa/ui/settings/SettingsScreen.kt index 76605f4..c6402f5 100644 --- a/app/src/main/java/com/adamaps/varroa/ui/settings/SettingsScreen.kt +++ b/app/src/main/java/com/adamaps/varroa/ui/settings/SettingsScreen.kt @@ -30,6 +30,9 @@ import androidx.compose.ui.unit.sp import androidx.compose.material.icons.filled.Link import androidx.compose.material.icons.filled.Wifi import androidx.compose.material.icons.filled.Terminal +import androidx.compose.material.icons.filled.NetworkCheck +import androidx.compose.ui.text.input.PasswordVisualTransformation +import androidx.compose.ui.text.input.VisualTransformation import androidx.lifecycle.viewmodel.compose.viewModel import com.adamaps.varroa.data.VarroaSettings import com.adamaps.varroa.ui.theme.* @@ -65,6 +68,14 @@ fun SettingsScreen( val sshStatus by vm.sshStatus.collectAsState() val wifiStatus by vm.wifiStatus.collectAsState() val wifiConnectResult by vm.wifiConnectResult.collectAsState() + val wigleStatus by vm.wigleStatus.collectAsState() + val wigleStats by vm.wigleStats.collectAsState() + val wigleConfigResult by vm.wigleConfigResult.collectAsState() + + // WiGLE config input state + var wigleEnabled by remember(wigleStatus) { mutableStateOf(wigleStatus?.enabled ?: false) } + var wigleApiName by remember { mutableStateOf("") } + var wigleApiToken by remember { mutableStateOf("") } // WiFi config input state var homeWifiSsid by remember { mutableStateOf("") } @@ -296,6 +307,132 @@ fun SettingsScreen( } } + // ── WiGLE Wardriving ────────────────────────────────────────────── + SettingsSection("WIGLE WARDRIVING") { + if (!isPaired) { + Text("Pair device first to configure WiGLE", + color = Color.Gray, fontFamily = FontFamily.Monospace, fontSize = 10.sp) + } else { + // Enable toggle + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween + ) { + Row(verticalAlignment = Alignment.CenterVertically) { + Icon(Icons.Default.NetworkCheck, contentDescription = null, + tint = if (wigleStatus?.enabled == true) Amber else Color.Gray, + modifier = Modifier.size(16.dp)) + Spacer(Modifier.width(8.dp)) + Column { + Text("WiFi Scanning", + color = Color.White, fontFamily = FontFamily.Monospace, fontSize = 12.sp) + Text("Scan networks & upload to WiGLE", + color = Color.Gray, fontFamily = FontFamily.Monospace, fontSize = 10.sp) + } + } + Switch( + checked = wigleEnabled, + onCheckedChange = { wigleEnabled = it }, + colors = SwitchDefaults.colors(checkedThumbColor = Background, checkedTrackColor = Amber) + ) + } + + Spacer(Modifier.height(12.dp)) + + // API credentials + Text("WiGLE Account", color = Amber, fontFamily = FontFamily.Monospace, + fontWeight = FontWeight.Bold, fontSize = 10.sp, letterSpacing = 1.sp) + Spacer(Modifier.height(8.dp)) + + if (wigleStatus?.apiName?.isNotBlank() == true) { + Text("Configured: ${wigleStatus?.apiName}", + color = Color.Gray, fontFamily = FontFamily.Monospace, fontSize = 10.sp) + Spacer(Modifier.height(4.dp)) + } + + OutlinedTextField( + value = wigleApiName, + onValueChange = { wigleApiName = it }, + label = { Text("API Name", fontFamily = FontFamily.Monospace, fontSize = 11.sp) }, + placeholder = { Text("From wigle.net → Account → API", color = Color.Gray, fontSize = 10.sp) }, + singleLine = true, + modifier = Modifier.fillMaxWidth(), + colors = OutlinedTextFieldDefaults.colors( + focusedBorderColor = Amber, unfocusedBorderColor = SurfaceVariant, + focusedLabelColor = Amber, unfocusedLabelColor = Color.Gray, + cursorColor = Amber, focusedTextColor = OnSurface, unfocusedTextColor = OnSurface + ) + ) + Spacer(Modifier.height(8.dp)) + OutlinedTextField( + value = wigleApiToken, + onValueChange = { wigleApiToken = it }, + label = { Text("API Token", fontFamily = FontFamily.Monospace, fontSize = 11.sp) }, + placeholder = { Text("●●●●●●●●●●●●", color = Color.Gray, fontSize = 10.sp) }, + singleLine = true, + visualTransformation = PasswordVisualTransformation(), + modifier = Modifier.fillMaxWidth(), + colors = OutlinedTextFieldDefaults.colors( + focusedBorderColor = Amber, unfocusedBorderColor = SurfaceVariant, + focusedLabelColor = Amber, unfocusedLabelColor = Color.Gray, + cursorColor = Amber, focusedTextColor = OnSurface, unfocusedTextColor = OnSurface + ) + ) + + Spacer(Modifier.height(8.dp)) + wigleConfigResult?.let { + Text(it, color = if (it.startsWith("Failed")) Color.Red else Amber, + fontFamily = FontFamily.Monospace, fontSize = 11.sp) + Spacer(Modifier.height(4.dp)) + } + + Button( + onClick = { vm.setWigleConfig(wigleEnabled, wigleApiName, wigleApiToken) }, + enabled = wigleApiName.isNotBlank() && wigleApiToken.isNotBlank(), + colors = ButtonDefaults.buttonColors(containerColor = Amber, contentColor = Background), + modifier = Modifier.fillMaxWidth() + ) { + Text("Save WiGLE Config", fontFamily = FontFamily.Monospace, fontWeight = FontWeight.Bold) + } + + // Stats (when enabled) + if (wigleStatus?.enabled == true) { + Spacer(Modifier.height(12.dp)) + HorizontalDivider(color = SurfaceVariant) + Spacer(Modifier.height(12.dp)) + + Text("Statistics", color = Amber, fontFamily = FontFamily.Monospace, + fontWeight = FontWeight.Bold, fontSize = 10.sp, letterSpacing = 1.sp) + Spacer(Modifier.height(8.dp)) + + Row(modifier = Modifier.fillMaxWidth()) { + Column(modifier = Modifier.weight(1f)) { + Text("Networks Found", color = Color.Gray, fontFamily = FontFamily.Monospace, fontSize = 10.sp) + Text("${wigleStats?.totalNetworks ?: wigleStatus?.totalNetworks ?: 0}", + color = Color.White, fontFamily = FontFamily.Monospace, fontSize = 14.sp, fontWeight = FontWeight.Bold) + } + Column(modifier = Modifier.weight(1f)) { + Text("Pending Upload", color = Color.Gray, fontFamily = FontFamily.Monospace, fontSize = 10.sp) + Text("${wigleStats?.pendingUpload ?: wigleStatus?.pendingUpload ?: 0}", + color = Color.White, fontFamily = FontFamily.Monospace, fontSize = 14.sp, fontWeight = FontWeight.Bold) + } + } + + Spacer(Modifier.height(8.dp)) + + wigleStatus?.lastScan?.let { ts -> + val ago = formatTimeAgo(ts) + Text("Last scan: $ago", color = Color.Gray, fontFamily = FontFamily.Monospace, fontSize = 10.sp) + } + wigleStatus?.lastUpload?.let { ts -> + val ago = formatTimeAgo(ts) + Text("Last upload: $ago", color = Color.Gray, fontFamily = FontFamily.Monospace, fontSize = 10.sp) + } + } + } + } + SettingsSection("ADAMAPS") { SettingsField( label = "ADAMaps API URL", @@ -384,7 +521,8 @@ private fun SettingsField( value: String, onValueChange: (String) -> Unit, hint: String = "", - numeric: Boolean = false + numeric: Boolean = false, + keyboardType: KeyboardType = if (numeric) KeyboardType.Number else KeyboardType.Text ) { OutlinedTextField( value = value, @@ -392,8 +530,7 @@ private fun SettingsField( label = { Text(label, fontFamily = FontFamily.Monospace, fontSize = 11.sp) }, placeholder = { Text(hint, color = Color.Gray, fontFamily = FontFamily.Monospace, fontSize = 12.sp) }, singleLine = true, - keyboardOptions = if (numeric) KeyboardOptions(keyboardType = KeyboardType.Number) - else KeyboardOptions.Default, + keyboardOptions = KeyboardOptions(keyboardType = keyboardType), modifier = Modifier.fillMaxWidth(), colors = OutlinedTextFieldDefaults.colors( focusedBorderColor = Amber, @@ -576,3 +713,14 @@ private fun WalletLinkingSection( ) } } + +private fun formatTimeAgo(timestampSeconds: Long): String { + val now = System.currentTimeMillis() / 1000 + val diff = now - timestampSeconds + return when { + diff < 60 -> "just now" + diff < 3600 -> "${diff / 60} minutes ago" + diff < 86400 -> "${diff / 3600} hours ago" + else -> "${diff / 86400} days ago" + } +} diff --git a/app/src/main/java/com/adamaps/varroa/viewmodel/SettingsViewModel.kt b/app/src/main/java/com/adamaps/varroa/viewmodel/SettingsViewModel.kt index 5d1ef53..70a3cfa 100644 --- a/app/src/main/java/com/adamaps/varroa/viewmodel/SettingsViewModel.kt +++ b/app/src/main/java/com/adamaps/varroa/viewmodel/SettingsViewModel.kt @@ -9,6 +9,8 @@ import com.adamaps.varroa.data.ApiResult import com.adamaps.varroa.data.SettingsDataStore import com.adamaps.varroa.data.SshStatus import com.adamaps.varroa.data.VarroaSettings +import com.adamaps.varroa.data.WigleStatus +import com.adamaps.varroa.data.WigleStats import com.adamaps.varroa.data.WifiStatus import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted @@ -64,6 +66,16 @@ class SettingsViewModel(app: Application) : AndroidViewModel(app) { private val _wifiConnectResult = MutableStateFlow(null) val wifiConnectResult: StateFlow = _wifiConnectResult.asStateFlow() + // WiGLE state + private val _wigleStatus = MutableStateFlow(null) + val wigleStatus: StateFlow = _wigleStatus.asStateFlow() + + private val _wigleStats = MutableStateFlow(null) + val wigleStats: StateFlow = _wigleStats.asStateFlow() + + private val _wigleConfigResult = MutableStateFlow(null) + val wigleConfigResult: StateFlow = _wigleConfigResult.asStateFlow() + init { // Initialize BeeApiClient with stored settings and token viewModelScope.launch { @@ -78,6 +90,7 @@ class SettingsViewModel(app: Application) : AndroidViewModel(app) { if (s.isPaired) { refreshSshStatus() refreshWifiStatus() + refreshWigleStatus() } } } @@ -260,4 +273,58 @@ class SettingsViewModel(app: Application) : AndroidViewModel(app) { fun clearSshResult() { _sshToggleResult.value = null } + + // ── WiGLE ───────────────────────────────────────────────────────────────── + + /** + * Refresh WiGLE status from device. + */ + fun refreshWigleStatus() { + viewModelScope.launch { + val client = beeClient ?: return@launch + when (val result = client.getWigleStatus()) { + is ApiResult.Success -> { + _wigleStatus.value = result.data + } + is ApiResult.Error -> { + Log.w(TAG, "Failed to get WiGLE status: ${result.message}") + } + } + when (val result = client.getWigleStats()) { + is ApiResult.Success -> { + _wigleStats.value = result.data + } + is ApiResult.Error -> { + Log.w(TAG, "Failed to get WiGLE stats: ${result.message}") + } + } + } + } + + /** + * Set WiGLE configuration. + */ + fun setWigleConfig(enabled: Boolean, apiName: String, apiToken: String) { + viewModelScope.launch { + val client = beeClient ?: run { + _wigleConfigResult.value = "Not connected to device" + return@launch + } + _wigleConfigResult.value = null + + when (val result = client.setWigleConfig(enabled, apiName, apiToken)) { + is ApiResult.Success -> { + _wigleConfigResult.value = "WiGLE configuration saved" + refreshWigleStatus() + } + is ApiResult.Error -> { + _wigleConfigResult.value = "Failed: ${result.message}" + } + } + } + } + + fun clearWigleResult() { + _wigleConfigResult.value = null + } } From 9d89b6f3d0224765e99f6743267818f28d0d3592 Mon Sep 17 00:00:00 2001 From: Kayos Date: Sat, 14 Mar 2026 21:01:36 -0700 Subject: [PATCH 11/26] Add release signing config for persistent keystore --- app/build.gradle.kts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 41c5850..ffa88f5 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -9,6 +9,15 @@ android { namespace = "com.adamaps.varroa" compileSdk = 34 + signingConfigs { + create("release") { + storeFile = file("/keystore/varroa-release.keystore") + storePassword = "adacam-varroa-2026" + keyAlias = "varroa-release" + keyPassword = "adacam-varroa-2026" + } + } + defaultConfig { applicationId = "com.adamaps.varroa" minSdk = 26 @@ -24,6 +33,7 @@ android { buildTypes { release { isMinifyEnabled = false + signingConfig = signingConfigs.getByName("release") proguardFiles( getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro" From 2815cc8db792f78abe5787a7ce0316c554d15d68 Mon Sep 17 00:00:00 2001 From: Kayos Date: Sat, 14 Mar 2026 21:06:44 -0700 Subject: [PATCH 12/26] Fix compilation errors: remove duplicate GnssStatus, add getDeviceIdViaSsh stub, fix nullable Boolean condition --- .../main/java/com/adamaps/varroa/api/BeeApiClient.kt | 5 +++++ app/src/main/java/com/adamaps/varroa/data/Models.kt | 11 ----------- .../com/adamaps/varroa/ui/settings/SettingsScreen.kt | 2 +- 3 files changed, 6 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/com/adamaps/varroa/api/BeeApiClient.kt b/app/src/main/java/com/adamaps/varroa/api/BeeApiClient.kt index a6fcfeb..d4e0bc4 100644 --- a/app/src/main/java/com/adamaps/varroa/api/BeeApiClient.kt +++ b/app/src/main/java/com/adamaps/varroa/api/BeeApiClient.kt @@ -415,6 +415,11 @@ class BeeApiClient( return postRaw("/api/1/ssh/toggle", json) } + // SSH-based device ID retrieval (stub - not implemented) + suspend fun getDeviceIdViaSsh(): ApiResult { + return ApiResult.Error("SSH device ID lookup not implemented") + } + // ── Storage & GNSS Status ───────────────────────────────────────────────── suspend fun getStorageStatus(): ApiResult = withContext(Dispatchers.IO) { diff --git a/app/src/main/java/com/adamaps/varroa/data/Models.kt b/app/src/main/java/com/adamaps/varroa/data/Models.kt index 3575c3d..b59e965 100644 --- a/app/src/main/java/com/adamaps/varroa/data/Models.kt +++ b/app/src/main/java/com/adamaps/varroa/data/Models.kt @@ -104,17 +104,6 @@ data class StorageStatus( @SerializedName("recording_hours_available") val recordingHoursAvailable: Double? = null ) -data class GnssStatus( - @SerializedName("has_lock") val hasLock: Boolean? = null, - @SerializedName("satellites") val satellites: Int? = null, - @SerializedName("hdop") val hdop: Double? = null, - @SerializedName("lat") val lat: Double? = null, - @SerializedName("lon") val lon: Double? = null, - @SerializedName("alt") val alt: Double? = null, - @SerializedName("speed_kmh") val speedKmh: Double? = null, - @SerializedName("last_fix_age_sec") val lastFixAgeSec: Int? = null -) - data class BeePlugin( @SerializedName("name") val name: String? = null, @SerializedName("version") val version: String? = null, diff --git a/app/src/main/java/com/adamaps/varroa/ui/settings/SettingsScreen.kt b/app/src/main/java/com/adamaps/varroa/ui/settings/SettingsScreen.kt index c6402f5..c6d2652 100644 --- a/app/src/main/java/com/adamaps/varroa/ui/settings/SettingsScreen.kt +++ b/app/src/main/java/com/adamaps/varroa/ui/settings/SettingsScreen.kt @@ -243,7 +243,7 @@ fun SettingsScreen( Spacer(Modifier.width(8.dp)) Text( wifiStatus?.let { - if (it.connected) "Connected: ${it.ssid} (${it.ip})" + if (it.connected == true) "Connected: ${it.ssid} (${it.ip})" else "Disconnected" } ?: "Unknown", color = if (wifiStatus?.connected == true) Amber else Color.Gray, From 7e67463467f1012d6f24fd6e9fec5bf654597c05 Mon Sep 17 00:00:00 2001 From: Kayos Date: Sat, 14 Mar 2026 21:10:36 -0700 Subject: [PATCH 13/26] Merge GnssStatus fields from both API versions --- app/src/main/java/com/adamaps/varroa/data/Models.kt | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/adamaps/varroa/data/Models.kt b/app/src/main/java/com/adamaps/varroa/data/Models.kt index b59e965..fc59376 100644 --- a/app/src/main/java/com/adamaps/varroa/data/Models.kt +++ b/app/src/main/java/com/adamaps/varroa/data/Models.kt @@ -181,17 +181,25 @@ data class UploadModeResponse( } data class GnssStatus( + // v7.7+ API fields @SerializedName("lat_deg") val latDeg: Double? = null, @SerializedName("lon_deg") val lonDeg: Double? = null, @SerializedName("alt_m") val altM: Double? = null, @SerializedName("unix_milliseconds") val unixMs: Long? = null, @SerializedName("fix") val fix: Boolean? = null, @SerializedName("fixType") val fixType: String? = null, - @SerializedName("satellites") val satellites: Int? = null, @SerializedName("satellites_used") val satellitesUsed: Int? = null, @SerializedName("accuracy_m") val accuracyM: Double? = null, + @SerializedName("speed_m_s") val speedMs: Double? = null, + // Legacy/status API fields + @SerializedName("has_lock") val hasLock: Boolean? = null, + @SerializedName("satellites") val satellites: Int? = null, @SerializedName("hdop") val hdop: Double? = null, - @SerializedName("speed_m_s") val speedMs: Double? = null + @SerializedName("lat") val lat: Double? = null, + @SerializedName("lon") val lon: Double? = null, + @SerializedName("alt") val alt: Double? = null, + @SerializedName("speed_kmh") val speedKmh: Double? = null, + @SerializedName("last_fix_age_sec") val lastFixAgeSec: Int? = null ) data class PluginState( From 8a2837bdaa61cbbd2c4566392d698ecad5169d7d Mon Sep 17 00:00:00 2001 From: Kayos Date: Sun, 15 Mar 2026 09:10:15 -0700 Subject: [PATCH 14/26] v1.8.0: new app icon (varroa-final, green skull on black) --- app/build.gradle.kts | 4 ++-- .../res/drawable/ic_launcher_background.xml | 4 +--- .../res/drawable/ic_launcher_foreground.png | Bin 0 -> 74022 bytes .../res/drawable/ic_launcher_foreground.xml | 13 ------------- app/src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 4064 bytes .../main/res/mipmap-hdpi/ic_launcher_round.png | Bin 0 -> 4064 bytes app/src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 2268 bytes .../main/res/mipmap-mdpi/ic_launcher_round.png | Bin 0 -> 2268 bytes app/src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 5965 bytes .../main/res/mipmap-xhdpi/ic_launcher_round.png | Bin 0 -> 5965 bytes app/src/main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 10959 bytes .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin 0 -> 10959 bytes app/src/main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 17558 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin 0 -> 17558 bytes 14 files changed, 3 insertions(+), 18 deletions(-) create mode 100644 app/src/main/res/drawable/ic_launcher_foreground.png delete mode 100644 app/src/main/res/drawable/ic_launcher_foreground.xml create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png diff --git a/app/build.gradle.kts b/app/build.gradle.kts index ffa88f5..bc01df9 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -22,8 +22,8 @@ android { applicationId = "com.adamaps.varroa" minSdk = 26 targetSdk = 34 - versionCode = 14 - versionName = "1.7.9" + versionCode = 15 + versionName = "1.8.0" vectorDrawables { useSupportLibrary = true diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml index 0f2972b..fdc2db5 100644 --- a/app/src/main/res/drawable/ic_launcher_background.xml +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -1,4 +1,2 @@ - - - + diff --git a/app/src/main/res/drawable/ic_launcher_foreground.png b/app/src/main/res/drawable/ic_launcher_foreground.png new file mode 100644 index 0000000000000000000000000000000000000000..b5b73062cfe2642c16bbb16e7b2a008619d0527a GIT binary patch literal 74022 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

>0G82}8uG(9~U8RFUyF&m??H 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml deleted file mode 100644 index ea5eaeb..0000000 --- a/app/src/main/res/drawable/ic_launcher_foreground.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..f8871203df4bf65bc8f0a7a63ea4a3745a9b68ef GIT binary patch literal 4064 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 SC#hQi0000& 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 vXl*dYo*pMkh@zST$B{Tr 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 vXl*dYo*pMkh@zST$B{Tr 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..9c81fa08c329521cfd0b51786007632fbd6066e0 GIT binary patch literal 17558 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 literal 0 HcmV?d00001 From 446873d7817a16b7aaadbf343766f7c962f46ee4 Mon Sep 17 00:00:00 2001 From: Kayos Date: Sun, 15 Mar 2026 09:23:54 -0700 Subject: [PATCH 15/26] =?UTF-8?q?feat:=20v1.8.0=20=E2=80=94=20new=20app=20?= =?UTF-8?q?icon=20(varroa-final.png)=20+=20fix=20GnssStatus=20model=20conf?= =?UTF-8?q?lict=20+=20remove=20SSH=20fallback?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 9 +++++++ .../varroa/service/BeeCollectorService.kt | 23 ++---------------- .../varroa/ui/settings/DeviceStatusScreen.kt | 16 ++++++------ .../res/drawable/ic_launcher_background.xml | 4 ++- .../res/drawable/ic_launcher_foreground.png | Bin 74022 -> 72738 bytes app/src/main/res/mipmap-hdpi/ic_launcher.png | Bin 4064 -> 4353 bytes .../res/mipmap-hdpi/ic_launcher_round.png | Bin 4064 -> 4353 bytes app/src/main/res/mipmap-mdpi/ic_launcher.png | Bin 2268 -> 2536 bytes .../res/mipmap-mdpi/ic_launcher_round.png | Bin 2268 -> 2536 bytes app/src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 5965 -> 6562 bytes .../res/mipmap-xhdpi/ic_launcher_round.png | Bin 5965 -> 6562 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 10959 -> 11946 bytes .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin 10959 -> 11946 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 17558 -> 18451 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin 17558 -> 18451 bytes 15 files changed, 22 insertions(+), 30 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index bc01df9..9a5b76d 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -30,6 +30,15 @@ android { } } + signingConfigs { + create("release") { + storeFile = file("/keystore/varroa-release.keystore") + storePassword = "adacam-varroa-2026" + keyAlias = "varroa-release" + keyPassword = "adacam-varroa-2026" + } + } + buildTypes { release { isMinifyEnabled = false diff --git a/app/src/main/java/com/adamaps/varroa/service/BeeCollectorService.kt b/app/src/main/java/com/adamaps/varroa/service/BeeCollectorService.kt index 9708f7d..af427a0 100644 --- a/app/src/main/java/com/adamaps/varroa/service/BeeCollectorService.kt +++ b/app/src/main/java/com/adamaps/varroa/service/BeeCollectorService.kt @@ -202,36 +202,17 @@ class BeeCollectorService : LifecycleService() { settingsStore.updateCachedDeviceId(deviceId) Log.i(TAG, "Device ID retrieved via API: $deviceId (from ${if (result.data.deviceId != null) "deviceId" else if (result.data.serial != null) "serial" else "fallback"})") } else { - Log.w(TAG, "API returned unknown device ID, trying SSH fallback...") - fetchDeviceIdViaSsh() + Log.w(TAG, "Device ID unknown from API, using cached or unknown") } } is ApiResult.Error -> { Log.e(TAG, "Failed to get device ID via API: ${result.message}, code: ${result.code}") - Log.i(TAG, "Trying SSH fallback...") - fetchDeviceIdViaSsh() - } - } - } - - private suspend fun fetchDeviceIdViaSsh() { - Log.d(TAG, "Attempting SSH fallback for device ID...") - when (val result = beeClient.getDeviceIdViaSsh()) { - is ApiResult.Success -> { - val deviceId = result.data - _currentDeviceId.value = deviceId - // Update persistent cache - settingsStore.updateCachedDeviceId(deviceId) - Log.i(TAG, "Device ID retrieved via SSH: $deviceId") - } - is ApiResult.Error -> { - Log.e(TAG, "Failed to get device ID via SSH: ${result.message}") _currentDeviceId.value = "unknown" } } } - private fun startPollLoop(intervalSeconds: Int) { + pollJob?.cancel() Log.d(TAG, "Previous poll job cancelled") pollJob = lifecycleScope.launch { diff --git a/app/src/main/java/com/adamaps/varroa/ui/settings/DeviceStatusScreen.kt b/app/src/main/java/com/adamaps/varroa/ui/settings/DeviceStatusScreen.kt index 0403e38..82da999 100644 --- a/app/src/main/java/com/adamaps/varroa/ui/settings/DeviceStatusScreen.kt +++ b/app/src/main/java/com/adamaps/varroa/ui/settings/DeviceStatusScreen.kt @@ -364,7 +364,7 @@ private fun StorageStatusCard(storage: StorageStatus) { @Composable private fun GnssStatusCard(gnss: GnssStatus?, hasLock: Boolean?) { StatusCard("GPS / GNSS") { - val hasGps = gnss?.hasLock == true || hasLock == true + val hasGps = gnss?.fix == true || hasLock == true Row(verticalAlignment = Alignment.CenterVertically) { Icon( @@ -390,21 +390,21 @@ private fun GnssStatusCard(gnss: GnssStatus?, hasLock: Boolean?) { Spacer(Modifier.height(4.dp)) StatusRow("HDOP", "%.2f".format(it), if (it > 5) Color.Yellow else OnSurface) } - if (gnss.lat != null && gnss.lon != null) { + if (gnss.latDeg != null && gnss.lonDeg != null) { Spacer(Modifier.height(4.dp)) - StatusRow("Position", "%.5f, %.5f".format(gnss.lat, gnss.lon)) + StatusRow("Position", "%.5f, %.5f".format(gnss.latDeg, gnss.lonDeg)) } - gnss.alt?.let { + gnss.altM?.let { Spacer(Modifier.height(4.dp)) StatusRow("Altitude", "%.1f m".format(it)) } - gnss.speedKmh?.let { + gnss.speedMs?.let { Spacer(Modifier.height(4.dp)) - StatusRow("Speed", "%.1f km/h".format(it)) + StatusRow("Speed", "%.1f km/h".format(it * 3.6)) } - gnss.lastFixAgeSec?.let { + gnss.unixMs?.let { Spacer(Modifier.height(4.dp)) - StatusRow("Last Fix", "${it}s ago", if (it > 30) Color.Yellow else OnSurface) + val ageMs = System.currentTimeMillis() - it; val ageSec = (ageMs / 1000).toInt(); StatusRow("Last Fix", "${ageSec}s ago", if (ageSec > 30) Color.Yellow else OnSurface) } } } diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml index fdc2db5..f777e22 100644 --- a/app/src/main/res/drawable/ic_launcher_background.xml +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -1,2 +1,4 @@ - + + + diff --git a/app/src/main/res/drawable/ic_launcher_foreground.png b/app/src/main/res/drawable/ic_launcher_foreground.png index b5b73062cfe2642c16bbb16e7b2a008619d0527a..29254bfbd86283da61098659beeaec40620772ac 100644 GIT binary patch literal 72738 zcmX6@1ymh9)5hK1-Q69ETqy4D?q1xXxVt+ScXuf6P~4%oLy-%`f8Osud-m+v+00}z zPcoY)NtCkUcVq+t1TZi#WEp9IDi|1e!+$p%#FykdQ@Q`w1IkKFK@1G6F#++-1p4cp z%uHHU0SwHC1`I4H6b$U;OB8ep2IkHN26k=?1}2aR28QdL+odA(^#R6I?mGbN^S`UG zw>;%b0_!HDAPIX8iHZh>W}zdu@}&$W0}xa9+PHl8?4cg>CV~5Qqqn?GyUpfl$>t4% z2Y?_0OC}QwQlX%iE3Pz`X`{Zl|FEyWy3vF6Zmn+Bw9{+)rq^b_o^GMHBAd=^W1*+n zz=18zo&pA*2$=n4?3$mWczRv+wEayWS7dLeGi46!WZ)fH@pRF^zyP$A6Z54T0v}$5 zkyK^r6jkw}@$CJmK~%o;dd{vU&pwj&vx>m1m<}&HO5(odO(~T5#6N%QKdmyo)4z`fB8#-Z?644GT;3>MAkgEPk^QRmjGk-+AQ?Y*s94u{P(EHA0n@bS>Wc|eAnLcnxBg7BZPP*i?tMEhr2W~RaF#&)A*9% zCW-6avdqq|Wmm1V!z!=FG!e8>)w=MUB*SFzii+R9l(6s`@6^C=Mts3Vsb-(qT1GSP z{xvFw*oD|hMV4GhJa(lFQ^ziX;=GQgj)&axX<~Zzd76;Am>Lyj z1GZi!;tLThEu7^_1LUfhgVd}ZT(j6#DL`Fcq8A9Rk%8H>TUY~Rn3XQ zn2PY9DRLwkIf{h~>K$KozzE}sYbOQ9zA~FiGEisVfE)1$VSDZi>021lG@Rb2*-o;C zEREafQ3k^RaVGlQtbIQ0q-pPv%Fh3)>p)YNq&w-cot{>~ibSF-%s|h`LaXj_CMvf% z55GP?e>nKh(&(?kip-PGFq0rz%PEVVEepzFc@}_J;OsM%OBSg=1Kxi)XM~7h=2BAF zDV*7K#(qRjNkoQeCdfC%9Y9HLOr#?J=eOK)0LbaPrvXAYx9p1luVIQ#oq?zpV$*394 zcyt5w8OYN;W*7(!tA{V?%QE*BLR7(L_!>=>dZ_d<$kQDdxGm)4!v8nKzAw{doXuuP z5_C(8*JyU+HSPU396*gYwEGy=x2AB71oR>nL;o4kHDRJPj7~TTfoG4Y#lNG^i0fW? z=N$Qhx|8kD4F5sU=W4A14`T&2jZj2E}Fnb5oy{j-FA$pzY|yJFtm_JpDmS zieK@6eYPWB8AB@@#ooLN$Lsw^rENQ*?@f9B(SJVr06H^l+6fD^#*W-)lY7SjGnGw3 z|3xf;?#zzTagRMz8z+Bec<2te`qKAbgXvjdsQoR(DHxb|p$sp^ruC2eIkz@&C2JsiQ2qN9$W*5fk2+JSV+wrhX0C`Lq6qJXS_D z2NU}}hFNZ+_1MQK|4FXV_lBOH+MX3+?*GCMRY$j(Ix2e7!#<10dCo{eHXLoGBl*B_ z?a_r3eUDlAs|#1R9~&1?#pU$|bDh9a{CFK^U_!z6g7nbyZS5bitqZX3Hsu^#A|kHM ztd=oe_}QB^&b_}&x8ot^M9KM3%+dP65xBW(`;=*PwvhmCheX^f5SntE!~U)`2^mg3 zEUy!PfXImMLQke#yQAuLs5zHJ&%WVg=7<%9@)iol+?~%rs;THMg3;&qr2YI29DyO{ zJE7yyOG$Ib)4T}bj|>Un!slvq8-K`!SLoYF$Dp*TIq5MT_ie^=0(IY=2obSR=_6@y z6_XEwj?W}mwdtJss1)J#IOk2>?LLn&`c#BLI?VkHqHEFWP`gRsU;M5n*&Y5#de;Vj5K`48k=lNEF%oHR{X9-K z28kdP>jXM#>B@19=R`Lmy7M!^+fC;qFg^*|&J4lf52Y{hKaQV=RSky4_Yj}WvBTzO zls7z@NYIkL-h=eC{cB1leV?CoWk*l7v`g3}j8ta*kJw07$u-auR)|WLdDl4eCG_OD1>FA%r(6j!H)^ z`@hvGP9ZFaf0ApwIKExDj9c=9P?=j7-Q$$aW~eD03h#apy!hpcWW(#F`A&q(-{v&R zJ#=hRidKHhZp5)est~9;m@QpyTh!LbgE68CRk?BLeGz+GXk*ug*um3ZWiyYLC#;v`G^=yTm`b z9_hucx;2^4_|>pSR%N#ba;=%E&o0?=SXy(sDpEL7Dr^sFwItw$t8#T!efn-Djo9KA z8}()@24+<19@aBnKsFWif)|07kO3WD0Y0}l^lxo(KXHp=jqbR;iC@>VnF+e|2=4bb z>?8X)q|5@0Y{W{}-=AAX;UoGDzQ+?TkF2Oh%)MIkM79EZKy&MI#ga#=~UV?GiY z+CxNKKr1wkd1vbxVr~#}1s!CKuD9}0b?{=G$N1>6Fm|?z$yrS-lLob}S>cg@UR}}Q zep!4oq=8tf?vMJ6r3)1e>6Rn(jHc~(>XOXBu1BFt5;oUBi934}UL6U+(LeBG0UQG7 zY_n_av+GiB)#4fSsmqOwe@|y>S1rtTYWfpFn-vHB#BJ~kaOklloNch-YqheZcu)5# z!Felu#y|0hc_daQKt5IgH(j}6pbIVj<`9=7)0uPvHZH>w4S{-w4Um4s2?5u1gpw0Q z;F>+HV6p3+(;BBxJeIj7-_-m_fmHA}9MA%;P?hEW_|0uurR)~DV){r#W9LOgx;QMGTI?`ol|Aa?m-wX@RQtDH}!QDpuX zrq0FEh4hDn1D+r{&l)7oX15F-VG%^51p@H z@sTR2CgAzyX*Z#R8`}8Li}T&+1*pF}*_lLAN5az10QC<$w&9S3>vO~9=jHcpiRr>ipa7+%=wy4Tl|QF z^_|f>x~t)4MIF2Q)Z>M;QvA-6uIeM*so!DKK+a&-oE!1XpM7UT3{qmwlQfg>bIzT7 zE*1C^3~w(GHyt9Ea-0&i#P?frDJn&41|W7LA6a#7nFX?O{&}1x3ILFc((u*P$gEaH zt4O5;r{$g`!QJZLDHVQiUZY2Q^*t0%$4<>iI4)N;FGT`s-W~@_gha}ZQx=vRyv=bL zmUymJc71)HpJVxc%o572!V>)>e+c-uHU``20{i+xy|8hDT4A0}-%ZW^5_$*b^L6Y(Y)Ha{ z`q)dFP9(QB);Q~(K!lsjT5yD&t*s9)j9io}N=OY_|=S*7y-t+6|YHHcjdyg>Xb)x!?YZc$9oU=_BVCh10qpNxHe zZpL$CUVOq@jYLN@zs!gV_g+)TK0b1w5jPse&S$vLw_}EU(@n9W6aZC(IWi4tCXl{d z={odv2ok?LSD2I(BoNnMjQ=$IhR^s}cW?SmZSqc{y^Ex9LEP0jQEAj)G zwdO`?*TN8H?Xt3}fcz~&NZU6s^&RYTpTA-l&3_qO91?nWr$;)Y6La|7XdsaIh=FMt zqS~ZwOo4C)7}x^T|3Q)fxW|mC>M% z+FIKM7kBk{9>(5TsD4q1Vm#cI$It^w#VD)NHO@0xK&jra0{@hv&VPGv;V9$+xyTjyqzXv~qV{O#Xnt8vk`WNfvgG!>qf1F7*Vx0R42- z3tFeSGd6AAkUPtf`rsw(rpn~G2n{={|}Z|B+= zkf;u7nDAo2jg8A}WPQ*|Nob`q!oRgNe8{V#wns5=p7T*Uf|F<>ulpsT|NY8;(R7?o@9H^DZ{ps- zC{q1L`88Ar?-jsqa_w&eAt74B!2M||a{Z4QQdrN<*>59g?{Vkz85kKn;A9kpV`r_7 zqts^pi+55hxgZ1NFa7|%;_(AnlHSy8e+0(iNekU2SNG-}xFkvxgLt*#KFHC~hG;?I zgJQPsyrj*(@>gKM<1H4f_sOGp-MM2q4VTa}?L5-!8#tfzW8KT zt)h}Ibdf9mKaKjz2xw#bK4^pg(bx;<6jKSIZ#$>Sf1ovTb7(f?UAF&pO@?;_z`1Zl zo9M&WkgAAybZ1g5Dz!tFOxNyh`keI9*CE#QLN5{$q`0-O za*`{mdhe)I=|+G~{RUFIMk6k8FT6QtOL?J2ugU+7`o&`g%3;bxQg_#+TS*5A+-+Q>pOAGQx!EJf zfmrMs1#jP8spFqa`Z{bWc$2)mYubE)oy-mt_}l1fe(y$YyKnFWf1Is<&QgP38I`pC71`N@Yq{wx)M*w>|kw6Y0HUT|dV* z#bt_8XJ9155I8M#UW?5%?Db(Uw6L?nkM%L>66>uIm<`3npunZ+eYfl^s7)Zrb=35w z*;J^y^p2bj;uKR3D$Evsy0ke-Ei2wHf|yxT z;aCoejJ{1O5N#FG041Gu6%V>cPjqHPrJ~jUK)On=%36^ty^|AVK{02&_m%*5_KcHZ zK&z*6s)#c-AB6}fmgugj>-OB?={u@J=SBs}G63X(#wsOfklG2+8uq3X!sRaH_xDAV^?X{sSQxJv-lo$*lr^xsyhG0<6iPUIxtYykFSjbIt&DoEJ zV}=TsG^5%gZ2J(RE6D94h2TbYL7Lo)854FihOwGQaQH+F(-2Q7Ll+C;;h9u(N{oy~D3; zRjUw)!3gxtwj#9ZHjpM8xEqi3w1!kLri$NHxn+{V2N?8= zaAC8Mhha?Ul}gA~CEtTojYy1pD6C13JTv{^U6x9;p8sWkVVMD|+a-5*IzT}y7M04q z8sA%y9L-JxS0g9+i_Gh0Jg5e@fGYd1CAXf}SRXTI&M}JrFT48(6*PaDTl8Mc3mI&fYY|nHGDrK|z;%(eR!`m%-xm#U+BA zJWfxsr`3&*2!GnVco}N}r{ba3A<>@O(9jSC_*)KReiXQBs= zfOFpfU_B6E5(84cJsdxj9^(7|KI)P7@LxSc8u9(zA{7a|tG4o;DpPc~!>iem@W*IS zvFl0KJRUSGmSo~pj@w_MIQv2Tos`CW{eloT9R6>1GdUP0)LyFtIJyquqs0B468oEJ zG8@+0f)l01Mvdxjb+Tk8z4U#W{*o?b@gx!V(C}Dwm+B_&)~l6M;M1q;4e@TT=d~lI zeT=UM_mTNn9dSgz7ncYQY&f<)0+{5-q{T(v#1u-uBO9JpqVW4&m~xU)sXZU8H<5PN$)#mncDd)*TJ}mSwjCmH8I6}D3Y|XU#nt>=8#rX;I z5o#UAtn$nKWWS7e$2;%^yqgg{c)SxUW1GY+I8zAzw#VBSb5WqMzNgS>cYYs?Mbk-x z8oCmfXB37<5rYsq+`ZIu*8W8B^P$%1owMDczZ-isJ>$)$18d8@mkSifoMWY@S3+Hb zCJ7ANg*iwok?XIQ`K?pO8tb+8LQ01x4u!!z>bk$_xz&HthPu-uUoM$$S}@Gk>HyOF ze0%IA-FQAPNAW+x%@=%5v&ab#cBs%sC&!_|)-X+hu0a_`wx}{PGN1mOH}z8Iyr>r@ zj2|;c?wX(+zJT@T4!uLdV(^>N(Ii2igu;U0CyO6}fg_l4^wYEyZ1;D&+3dfmt@Y$- zO|N+=+3}3xCp*9vGyfCkw!TtAfy0cb(Y=pTG?Bx(3X71SMPgEzDmmgMPR8)ZeAj<7 zq}GO47xRCn|lWZKF2OaW&3q_kG%__upZ?aR0+yr@#ybJ3TzsB!Z^bEc3X2 zC(&KYH}%Wd4N?lsi6DuJB!UdNm}&R0@1`*~ zy-V10&nn{l+J5=;<4Q7c!3RDmx|046x-XwuT%;%CfyQ2xt?SuG-AtCkYQ+P&wn97V$sQDtH?9}*<5MD{X;^uW?gE8NxR>v zAtsVBWfV49Yu-UJ*yh}A)xMugE2!^3J+D5bFKYOAQVc~`DmZKC^}sQE5~F{YIiD0M z<@+A58o}+4|9bd=-;#>J^5l=rx}%=UXU@{`n~+oyelE@%fIy+7=Tg~GET0p8b)gx(Q(n`0Gy9M?t^`txy%;+dzduaY1q7uaRN7(OFs`7DVeKVF5vQB z@Exfz$dXsLIdAoDvN$*_R-2V!AL+iWo;MI^98v(w;@6zxT#hT*Ybsu4Y_h~zFE^)Ry zuj1EK46g4iX0XWf1K0?1ORaz^yNIF+tS@RMIi z_D0zLcus*Ir@iMKs!!hYiVva!Xgem>N2U1R8}L1KG&=d9nS4 z|KEt6kGlSsbdL+Sb03@RbeXsXsR7&|AiFS>o!Yvzu>WvIo zib2B5ZQXNF;kl{A5a*e$Y$VsQB(?bu*+`3a{esX)3In4`)@smu06^J-ok*9^b*vV~ zH>LhH;->Gw_tD*D&^GZO)@XafRjU$9z&>q%ra=4W0O2OqlQbYoemk%7+ed-XWnWO2 z4c`eqT{yDqLM;%&aP)P7TcjtIF^)zMrLtm3_hfnSw|^Xu>(9L3PJSjsYG|qW0E%=m z5skD4Ln8rK#fzo`gYJcf@?rVmeP-Ttlt+@+Io7Gl4}w--Ofj!9UP7FSiYm=)wT&wE2)~npCOxb`fQSKSTIc@xEOkVHZ)9YT9BUxK3bbhm zyo>63!|zzvooc`@G%?O+(eWUeTs5gx&uPb}`M|gQ&5ws;&fcLUhtgg{%9eY_MYGSR zsld0Agl_E5zJBk(1_K{gmc!IN%It3->61<)pQ4q@q~sJP1cl~FG%mKC`qUuz9Sxhy zlagXmm~TY`KO>|gMjKDOS$$l7x8epNcDeB{#x$$2N2iUW^H)Sy9f_`QknugVn(eh^ z2|wLjpGAi?Rna-=cIlLXwcQ9^`K z0ojm-{(_~d0GreowqQMNIv-7Vf36F@V3Ml-B!K% zS63R8stUR!93$mVAp#*$Qjn=i;Y_Ll{%Os)WN;?i)vU=Je7J2HfvSssuHsr(_b+hQ zX_Nej!c&c#YUBYVEx%TMIG?I>U5{VNh0%#+_Q0n?;tH-*q|f#|_}15wL{Xyj_?S}e z#K=5;YsoRVdN%rYHyjvHYQY=BXLi*&Yuokv`OoKPk>2Ov2el_mc=8gbIz4uzW%N&5 z^y8q9m`P^!93N)({N#GCrD153JGjPZcgq*lD*SYN>*rw# z`Vry9sQ?gOD5~MD#!A7Jtwy^1uXpr6!Hg5>q9lYo?I^c!Rysh-M!tKM-$d{>H1~UI zSJS&=djDQVe0==$^jqCozOMAC#(^Spkp)tOvFR@9vwB!odf=`cJ=n!9P;#R?i?$AqNiuDH=Z>i387=onAjLjX>YmoFT5B&A+!CtQQ2H zH5Pc6Ro(**wMaw?{|082TBT>?8`Z4;A|Q7?G}D}S^}1LSN}F<0BJwFB z{8vp9#r!iY4I&GmJ$qilkv#_Tl7=q!=%GAhV;mF zV{Q@8gR#e+d#9aF`#CiH+x=xH8;3y90jYXN?Ny!oT3v z8osBEoF7QlO~R{f!H}$NR(ce4 zD={XJgY9>^%%9|LXi-Rlln6u%R(zd3Kk5{<8LBBjyd)5@0r^ z@NcGOOj`x+y97=W=zT3m{Pa5MZu?sU+_*i|;G;TF{4<>*hf~y`AhZNlmWn(@-jeR@ z@qD8Rj(?cc_@b3&R0SoVsaTmj{`+>hQ)tpff{CYU^PG(ET$(cC^fDxT4(-acA@7;` z#iSAejU90uQk-dW=Vuhs>TGYzmhcLS1C(i!HnM)G!;?MA@oxhK-5F-3&lN_a_hqf0 z*5BJNAP?w%m(H&DqB3vYpEzA-A5AycKu8+W{aE^^i;W$z5-YPeYj*=JdUh=JUTF%2u<} zvc#nXmi(^A*TsaC*Z*=k`XbM>P$_VI_0J_^#PZ}yOH{OY{4yF?m0t4C zYeNu^5bB(oj2cAIyPl)hDr`b9h8C@VvG@6~d|fDJ^AE`iS40b(^m}+w+iKy{vI_1U{V`_|ZsS6=mr!t7x;kqyGLCRio`Hb7K~LPbwKBXs?B=xrxW3A4+x8 z$a+HX(StBKBe97%b?f_j4E@Y*O6M7E3zOP-Y&E8lA&lfc0v+KC&@5#oWA&ae&|pP9 z=6c@kxd{{@Yt5`I7yM@26s zTUn0;o;~Mk8hmREi-t$wwrJQ9bN&9q^T9TT2)Kerxq(n}4gP!{7%1Y#e529Z43{$O zFi>28Ks`0{r)zb-o)8Z*kOxQOJ}9|ZPuw=SPdv|A_0OfT`8 zeOLBh+9Ni(;Va+TC@jFnzjtCt%Pcb#HgLz=fR0fwx7?I!FEp`~n+(|_X6oPO=TnrJ zc?LC;Bb~R=Rj(ZS%=#MBq|@zE!SYife(y8NozZKdW=o$hxqk(@#N;yqg_xH*FCk5#tD6|;UHS$fj;1x6Of^m0JGAL77+kZb z<5gyWs>(EDb|!s{l0X&8gNjx613XY{0!nr8PwX%QXoN7IA!in3 z=xSvQ4rN=e?7Shb{Z%7`h3pC_fRtL|P^@F%n57dJwu&hoCw@?R!b4(s;W#;(vnA_O zgZS4E4>7ur31iL+iA7jf)r939OZTb}Czln~-#y*cGC6mnf??W1+~iR~^6l`=|2b3U zhvl?FoBtV`uey%K-DQ^!8y5D;_TW;^SRs<|CgNR%{+|X0xmE_TCR^iY7%(&%)Fye5 z8+^H;p2toI1YIsnh-j_j`nM_W+e$CgTay_sr9k)FWu=0@GI0)zjIVT;=jXGqyf=O) z;@!=;xcHR#+Fet$ypBs3d@p?znRNbM|8)`}CoMx+Bh<5l1Njs#j;f|lkUx>Jib;B% zol~Io=9Yi0`o<_jQZmpz+j|f3yP}9c3lc#TTBFHrGH_35?#iL`G#DufJ&gK7@OZdd zE(p3gLZdAR%YL3UgjbspD#Km%gjjjpRi_>;&GOfN&~&1*l&^V;!LsZQ1#}?;8MuXr zCixu{xmWR>l4uCi5c4axOx9mbVDHge<)1#xoK$H#wXCR^)Wle}m>%wPLIDJ|eMv7h zmRj7T8%l6$Xhdy%5+DSjwyN)A+6a)C5mJ6SsI1B{;7R|tu}fp4{m4g;@z!yEB zW)YAQ+%CEYV+_uy(=_@Fu)Ad@-LN_ye#KJOByEx=68Wrz)TX{;*vEw38;@GjrO9TG zrV52MU0!Nh#=auCpt#*n`O5XM8*Hf>$~Nv3k{P}AG1hT?*mXC9WQA+`tw@pTlW(+% z;wspnur%(JKdyNhWT_Eec6;ozF;C_q*aZI=={wkz4s#i+s?cU^d(pfbu#{-#&Sn_1 zPLivSS=RPZJ6qOC!7}XC3E3L46v<|KX1w1iDNM;VIRL55s7OqGeN42yX}>CXH(Jq> zJ4A5T!r6mVXeTiIY^nHe|1~xzgcRC+l27_T1dS^DI)l@f#^+J)gDhU9z3 z8o4xe)vcke!ji{p*hHYL{jny&ew2ciAQm_&Y&Dz`D+@|YMi8aZw^g6sRr&v@ME38Zk%}N~y=*K{SkFb$|(?euFRw}yE#1}`Z>zIbqT2O0} zl_U?E#n|HN)95ngQk3EqA*5X@(Rb^p&{b-Mpm}TVZTSvT1(~@{ll+x*j0p=zqoIye zfmesnsGekyTCU*uRRt6uuZ<2jE|lUX<5pIK;)nkeMU+$#{UpMP4*}J149J0frB6bW zD?7%uoQu`ue4MfN`gP$4A0qD(GQF3Q@kR%*FQ~-=>U2^(+F}_~(8VHeSnVH%h708Y zYbqGU;l8_@QRV>mF+ln1LOo?M*CXEoCRkVH`3k7Qhtg9F&APYVcD84!#OWzbIu<(Q zPweh3h=rVp^iOi+4YuxqNm&iyK%IFG1P`XI(p)9q5C;5%1@p1Z$7K(4kQ@k&K^gn! zMo~n<@b?8vzG`Ln$?!cWL+^g zl#xpQT^~FIbY;!MxuYDnBwP*j84C$+hM-1>imG5W4JB!Ked&7ds>ov(62VALyg|DoNG(j7}{3BEy(mC11e`U!p)p>;( zYPwm!Uw8JFvxJP=9eqZvWGs$O4t!=KSNe-oLRAIC&_1AMeV;Qv8^4=)j2jR?rznT7 zBp}lPO~?{Jkz?(Lr9qfU+ihb`!S;I?UkEyKNlkzvLzcQ$6hI2RbNK=*@Lr?nn5F8c zY$Z)Mc(nhZOelcMUyQEa)Y`_-pt4xY$~4T}OgCyA$b#azso>-U`b{p{kzOWbWS1Av z;_ zV=*A%f%0wTIw!zdw-i^-SIy&2s3|*&zS#c(Q|9ZX^`&R55m;@Zq>Fy6VWyD$8^B~q=6rp?`A+cU~9bXjq(JQl>3 z=e>`QMkkTATrrrWB!o|6rV+Vb?s7gd$m0_wX-#dYv7{B>zFh@nUL>_Q0Hg?yYdQNh zRDS(>E!fDG(uJ}#y5PXV#k1S`Jbp5}41|UVMR=OeeqtLD=n#QN*SZ|g9P``N{@z*8 z>I9?#M@J$9?8tcD?`ZzuZDxU`LLkMfEOPnVTu<}eK$+7}XB}P$j>=wJYp$!rNT>4^ z(ie$2>R%B5!1=LoGpFxR8ZB8L2t(w2yQb*CsMw^9)RdZ|Y z&e}r7jHC%896@kp7Mp2nP0c76aL^E*B0Y>NOsOYfqWW1*89xd>RH@fJ zVkFxUx1Ds<5=-xrjxbhxTSG|Dl4uD&XW$}|EtQSzIM97g1KkA@k4MgkJa-JeD{eW0S-uzyh#fX4^SEQeNtTlaYs;IiszwDW;CY<%Xz&kKLlO326 zU#(*%WJ|pp7akTQfX@czA9j&A*fsG)nZ2YE7Q7VLns>rV+i)Ae2 zk6Vexb`B<=gM%|a?%QW)uN=7BH=UbXIY*az;We^CLwf`i!Z7Ek*!+ipbd?B#*d+ic zT=d}Br0mjXv~WyWpAEg}P1-KG2(7u^2EWJBQqFqTI+BC~i+Pgo9*U~&$p8}o4OTER z9!s$2w9ZnDt1(nDNv!;XhGmK;*wZR*YVs2sObMros!;*U4>5M51}NFzO!2ZNYXlxU z<=JivM`7)Nz;n_bs}FF_SoC#BPP|^ZDG{a;iU~%eaU+nshx?N3oq*F>os#0b83b?v>N zQd%OmRR|CY!Z6@4D;DUD`$~Ocg53(U&cC>UJKOJT66gcT{QQ_T+gY;R%-W_TylUv@ zz%P>iMHdBnH9V3UlYCmu*9cVHDUj;En{=;+CDW=x=%fgc2H_)tM$|~J+Xq~-zP-w} zA5EuxUk9V|CPyo^$2}4SQ?VG@k>$N|uJ+vJz4H1~zophqD{vN%QJx+v_q%59+k2&& zRe`>Z)V&1-nsg+X1&>(Qmhj`F#OL|AoVL@r=9cAWJT#7nV^YtQFGD7^)Ksab zqe%4w;zWke!t1D^+au1pzJ`#WQ^oR=Y!_QT6@oT=t-%F}@62PdG|ZO(HDGSj>9sA- zP2xn|FI_xT-N>fhO7H$k>wWQa4zW#*6WzC7Tt3%=&xeAJY2nY(k8)o*8?MdRMa~r< zgq~hzrBk#|A1tOhJ4ii#r?1xd9}Evk^<&v!PBEWB;UO@f<=?MBcSFQ+n_ZH zhvj$fLX4Lz%kXy;iWH$b}Xjk^Wj`Qw}^b5^v%k--n~nA^L@4VdntXKoZ3!NV(R}m!`vQX zB>KUeeKrE01f$tqiHVT-@(YiWp);k5)#%9ZeJhOTkI}5bTXse*xwz(P_<(AcrV#q( zG^+)Ld*tT!(Z=lGKBvz2t6krzOXBOA-1{p>EClt9D?{}Hjl}#|8$L#w&1l@%jjb` z>6&6$9#h!_61gxRxkrs7Jhq9g601vI$AODqOFXq41lMqp`+f6 zTL==wEOQ;|R}R@=^KgUuyi)aq#O9GlnbnGj8@+py-ipd{$u@abwvY5o6HtGj`a%G z5HwOvZcFM^Po(d-Jx?|1?pODt1&u)_iAAAGA7F*eyduP5T=RLj@7lMMn5iz_Q=A~Z zn*1>eYj1;Yt9E#+LULU#4i&lKV@+5i4VAwfA=;G5M0!qOHcVSnS22`Ky_b}jsA7p> zv6$z5M{A@l)OPpBsP`#c@6qF6*__{T1zB=NEv7t2#bI@Ix8eS(`}%EdqW)u+GosJA z*k9f1yg7XNDKuA`BV^iBb{L9&J4VvlAZs#R67pxvlDSx=vK@}ta4@%Ful0En)k0Qp ziN{!bFDM=L#-O7Sn~NXRi`^cgXiEpS%mm*IDrSk(XdPRTQW;K)7E{`wO<6v;8ab$D zjL_0bXw_15KTvb&I@~J0cbLz&WpYln=riY^u!!Ws4ip#6k)|G`-np@YH=ZzU^h{{TF@<6K#Ltier z`#S8(KL&i!osTVI-T>!zJ#8IDC7J!7g*d*f*()W?$cqB+fY;l=&r`d8_nM<)@y*lc zP4~@*1alF%$o6sA>g<~@*WjS0qE!{*~Q_c zP-N+T_0lKs=|jdiISC&NmAjw&cWU|NbE^ltos&Ix&S{4>cy%+b#Ev>lr}r~C<(B(jWHkjAZA6!5~z zL3!-gUwr;oUWuRAhM6}oaTbY+A{OQ?U58ZX(lUttp#n;a~aoYM?eP)i8JDn~E0jakT zf+#!zMa7@e(oi~Xl{1T@2#7_bQIAsR$x^S^)>?1(dv@IW?Kj`_k?m%?Uv|fnI0-@n zNmNYOsD)@PpQRT+edO8y^4jSyF4j8DSzA+uSLa398WyKsiCKv~X9jD>L@EMr+9%r5 zOKqJXDU)TZT+1Y|a-+R`)z;FkJm^y%RfWcWd)cslXzV0mA_=kN!7JBUx)vvH=%)a( z!KvAd^DZr~`pw6F_ufBq z$K8MN&S;;+t{0~+u&!>yR8=UYNVdFp_8WiZJ&*m|voHUb!*y?z`hV_v*YXC8r}u1TkPxAn@HZQHG6X z0z{oN$-x(v4t@E=;YUuKeg3RFnj#4-*Fv5PLu;ZeXq=Q-y;Ds!FHdNo(l_)|xmQiL zJ45E?5hxJ}d~WwD!bq}!KMI&(LSz9#>irVhi|OjKr(QKj!W($UTQ}eK4L9z2XlH%T zHpiX7FN%uuFjhPy@v$>Yv|#pp_m&&px%Gu#JNV3hd3EK)nR!~6`B(Y z6F`uDG0eU(PN7DXf`BE7eJbswz4`5p2mkVew|{iM-`ZPRJ%goGYXL`*_q`BdSlhIC zGI{n>hhO=FV@E%A%AB#Y!JM%yUOd38fq{r8ls+b5A%PLK%wPfwb3-lXHu)R zMwtN049S@XC36!|k&&b$6Kho7&?_l~To;%~Lc9vNxW`^(G$@KXDg=RudQun;ydc<;L@bE z$L{~Ool@_rrj|^dTE`wat5{F?u;%0I}4t@F9YhOHY z;`!6f+WZZ>H`khh`cy<3p{Yj=kX4sBw>r14cH@J4Z3{FXZ@FVX_b}gUooKzEpHZ-!zNSd`)Xt$hsCjQKi zefg=Md&Rv#wRW>%8t_6Q0t2hmE|BWQ=ZGv>e95a!`}KWdv!K}CPW&U*6~Nr z9s2aC01-jCC_z~nxC2#VOYR$DL!37X;}WOa;Ec;;*YbXe$hj1`NPErj=93UFxVNhV zrue;PSy>Da8}g}#SB8obQ6OMr0uvZA7CvJHpEGElfd@in8WSQ$wqF&ol412MU04o+ zkW#z&#g)%K`njiWd1CkbZoKQy+_vSu-N#mr+Q`_3VJqj7)o_pA{{y!+Z#IAO55Kzd z+G;aw`XmVrc}=_`%eEFJPliZTz4ycjlGKsmuvJ@0mxG=1!N30Ct>3x3YmZCPtJNdb z#AC+!UPQebg*M%C;_=0&e(#A_KKXj*^)!-t)6T)FVN1q{LPb<9F+;_P5GkuNT;_`? zoD#X?M9V^AN8DT4 z)Hac3)pNY4Ct_0a2ozxTIX9|R4T+s$-%Wd++R6w1&I2F({(Fxm2hv`*W;8*n8d%#1 zOylg!?ulRf$_u}FsP|G@*ZD>idP!5C5-a(vRCv}sWiiUq=35pdR)SX|;y?u6Wh*RY zsYU(OBv(K*r_>_Ipopd%7}G-*DO%(8m79iCiHn^aL*ko{5HnnWcv$9v_Def3@~{;s zL&zo0C(ghS2kbN{Km5VZcMoZ!(MS@n3d7{H ztYZe4AeLZ&01VN@Sijgkv;BeQM}F|Vo8Hk}meb%kWSf%0cRA@rL8C_7mY?fA_1_7$C5~DbGDJ`{i5Q$5HW~}jfg5~w#TN3%Xb~9h$=-ft|E0gFHccU)XafVO(53F zPMSr{gu>~SMND%R>;fz5l^J9+rTj<+V!r7|LK!QKY4bhzDXV6)L7%ZS2D!Pd9m(ngDX)1 z1(f)-7cGD2e|zUU|HeJX^_WA#QJDHwjeB#=9onfq`@09e{O_MydSbPX)@;yn4!w>u zYy=*v#&D(LaNh^gFUY3IbqbIXuqZ*42?Q{asDU9sya1WClSPEGB9(c^4vh6RgUgCf zuJSCLG`#s#=29it@M#I?o3rwttAa$7nZ0*rv+V!a9=jl3QwxeXSb8yi^|wz%)KJR8 zN04vrTm)~RD5sd0msc>9A%F-I#4`bw$v}jNY*efD&LyutaiCeR@4IP-34G#GVroV~ zyxQ)@y&Lb{QJ)K5{n`<9L#P4B5-SAElo<|{3>%kv4%Jh-%I9wWwjJN_4<4Ak$1Zly zuqxZofn)E}PJ7N8TAF$CzaIX~zx>LfPaZRiLESVBgoQDY+N#NS`rT6}=d*AcFyzO*!E>2g%TCglh>e7YgOy_lc`X7Du@t=Cu zzs4;y6OcvftWki(dCR#yL5jWQGaq(2Fg2@PmMJ7f@mXR{lXP}}bo+Pi)0vbdAw~_e zhdz7q$QRBAh)~2@<%VP8VhmF`@G(M*<%=s@-(grAuTJ(!QJLSwgt+0uGx{n=CgoM) z;fWZ=i$}}v|JeTeHY4Hiy=q3Bu<>w`yk>{B(DzqAxHB?&5R45cjF1M zuysxeG4GYDPl%(dTEW*0`=q;q$+7fZ7y> zfp?mppe!;^MENG{h3SY2*@=h%)@&Cdf>P!x&g^SpR3jH0c**#+FxrZd=Eq% zBA|r0x9zq)uH%n>?FehYFzS$1F3j8-Vi0SGX1gExfd}6GS8rR1&NxR%1VVPfk`Rz&lGAg<#H%7ltdZK$jVVW z>+gS1+73#)7Xf`CvFM_bF$=<^yv9ZrP(+l~8O*kS;e5j@5;j@hooAEn)hFHwdhEd_ z3bc0W>BR$&9^HJyrkyt}kT}cAh7upwo1ssf2JV*k?p2XPj~)pr3_#w+mf?kzn1lgJ zG;Q-R3_8dv44#Su}C+a5@Oyfql9=x0|7R!AyiWKILrLV2W(NZGh20;Qtj^l@UBg_ zH4@*cF~A(S#>>BRxN|JEVd%XwYq1ktzP*v=9#zWItBQ2MLTJdOC>Veg86R0vpTxvm z4_Zf`Jb2>iLvMNaz0KX5mfP)y6=O`?P3tYY@4g$)9A7^C6vJH=Qwu9Cyhx)FjkVxP{pr?qu_^|Mb$AfAWd$%W)HROM!DPk7&#C4gpe8 z&4`*fmv1sjmWv{*xu!<_G-~S0d408bu=@vPm5Otr7$!wJOZGp?YvN{1OT|v4MA<%w z83!p?l|`6@86+ak115@^y0)}Uj+-h7dCuMRu`u&y1&t%32fRq_TOue+b_o40kKf1fiZGyMP266L+ z-#&cisdiv%rLDFN@ia^Pl zzUS67jW0U7eFi_uRiPSx%2XanhQa zVWR>f^~QIDrMG^^jUW2K_jfj(TJ4=g7<-MAB$=trv^rZp{bOH!`8N*DM4L!JUcp|~ z5tW5-G#^uiB#c69sE*Dus4L&pIlIw3;j?$!hxhLM(B>Ys8DcrqrSO?wJFxsx8rl$r zsTGAqrX)nIbnsXi6(IyJe}Lkh}FV<0cJ)Uo)OL}wcF z2Y&y+Xa2<}o2&JO#`fOX6sZ?+K3Qq(?)|ABdT`S{jZSwt3XL^LRJ+mY2mj_fZu-_u zr#mk&cc8*lcgED_HnseYr~aP!r|NGyv=j%7OJLgO`Q4Ci7 zxtrg&<->pHq1CxF-S{L$zDvF>vC(99b<-FA$&=6gr&s38ET>tm6e^q{bGiRq% zF(t&cQHmwV7W2xO&CF#;3BtyD;nia?sS&ChZYDnE0Q39j#Hfl>N{SS}LP*HZSd3N8 zr**ccXQe=w7MiAJH6uLv6ku+M5Qt~%o1gy0=YIc3KOX80?Ot=a<7&00BP^|)+5C>^ z-9P*`zeiT%HU;SD;=SK<%bnl5|8Q~&5wp%2+3b38Bbw9Yg-`#)*B<_-PtgI}pc(a+ z)QW&ftqNxqp!1H%cQ(#!TSahYzTt5p+LQr<{Xm;?%3W-@S!M&RUWM`!y+F1m6y^b!8LmJ5}wONJjRJjULL}mOl2<5d479IF@y@6Z}FngUm*f#D1b3Z64!Q_TRw7= zt+TieDTk)fJ0q|B;bH29#@KQqXdwcYN-|1|dez0yH?b5szQoF;l^q*8vrZ*-%~@EK^I%`mMQ#sZ>tO*HxoJIr0k= z>7c%R;SWr!W=IsCE}+tLnO7&XTxNMJg&i}?aAAnaRu)#UG8m#R_4URkbi$V(du`?P z%GO(Un0e#lUT7tZxYv#s_HLcoG5^ZvUtc}hp1;$6^hX}t{I2Gy?org7z`N8Np&hjE z(I0>9Yd`x^ixyZy@4OOHui}-MOVL>>W&k* zjM2QNlCg4AxPn1>#;B-@7oK;wedC^Bj#QSJV2usZ#;bpDNEZVRo8HHTMMn!8dTISe zRel_>v}vYyYCkbU#lVyLR0Qe`iIOFWPCj#JW*)aaw67hv18%@X>|L6!?0M^+`fRY` z&wlKO-=llemEP%EWKA&F^XKMgn>aQ9yZ`6s5B=`RIo@nMX&kc+oVQ9nHYw$m=(Dn; zlvxNUpI%g&!SZJ~@VKgxhUnWzFZHoyXI8B^oHG{8UH00)2<3L$+^?TIxO4TH0pppo z$a-U#6l8)x!$V&>uzYawz7O10pHod^Hmpj{rMvIk(P)OJ&z$>Tw!3;wkHF5+YG+$TCqk zUEO~F{C(eZi)$>4rUYu3xdWd&^U7}>2_;n4JndRdretT-TQ&JsCjku#ZC8pDN6p4l zLR<&Kgg8`<=n8dC`C!gd_J(qQ3g>!TZr&zC0LVj$$O=S)?#&@$DbDD+%Hx)}YpO+D$p#cHfq_d}Qy!{mqqd)obkG z6jst?OSt3N|9bS(|NQBCI>%z*g;=w~Yn(ZIB-^m1tfQE7cV*U(bcb z3__ep8)9ilxn@06y}Xu_xiFyh&3XpP5Nk~Dlt2B_N*$3vEs%Q83%dQ@EsjNQ{Y1QRJb}IkkWB!m?8cL{wyqkx>{=7Gwfqz!Rl`fuRt~txx>pM^1g=oasusdM*r% zk`E~9`cr1E({<;fFjNi_^39;$>Tdm$fBNO;e)+(bXj6zl9Hm}`R5&%?uTENdPgbq>?=r=M`j( z5GmKAO^^Tb3!nbU$7a%9ftm5%n}AYu+H{up`Bj{9affX~5~It@`-0uC{mSXj{MZlt}fx@biWKSHKV~0@II73A?tjcGFi;W8J5Rr)5z}P^&IPaaUVf&q%V_JsB z0f}KqTUQRQ7*#TPicl^Tt5#>w(3PsIgkA+IJkJB2Ab-w865_Q))%jMn;s9mfA(Y^= z`q?7z%0UoGk550pNZuM_#q~%jrDXg4o5MK*v6F0P-Ev!}yh!VifqBRYh}^)~_pIiX zV<;;IlgSz4lUO|krkTE)eENq!we)z>Y;InSp<%O8?Zcho467-Aaiz(PDMGQ*O-g?K@`kq?1x58wV zjFdrQMb25Nd6p_ARp$r#lfH0Cmaa)sAyFt%XPG^Sb*EkE1&($9VE*z=OwP2E(4 zU~TBB(Subc7T@C5^S}T4qrdRnjND+U;Zp0xLcwH3LWRkSK`50Im4Pz&qjZ#Lb(>2a zVJ|9TiAMT;oj(lnV^tAH=Xxy!V$YvQy4W6!*yP5e)3;` z{^VDe>NA^T=a`s`AFH)5QJQ)Wk!&v(P!XP#n^1GDV*F66 zY9TqLX<6Gfiv)E}9RN+WyKvupbW0ec2Zjd$;n_ngi?4UshF-IMBQy)2r^{fsy)u}@ zHudqQB*gW2kOn7J86ne;g2*OgN`cdmhz(MBZ>e_X+0~_kD-l{MfwB`9H@Ano-oDdG zTyM6V&nQCj?E{JvD)lL^$_iC6Rg1vHtUXpG5Jw85q)=noxi%psH9;7idVKNGpLx3d zYHEEL3K^FeI3{wgJKI=jKW{((&z~~KOyq4q29}(7;Zzu2t*Q~Q=2;(^u1C4{&*;9Vmg`pAk7zq5PvLWYP^sf@G zUVzWV<4sA3bl$J~6Fd)mL1FntuJ|%MLsTSlcmzR|obV@}I$gtzs6s8A(V6xgU%%VT zY7meLnIK>?h1Q%YpQJ+rw!fuFU&_j=L}wE$%L8G}O06;p8Fk{9RTJ?bt<|J)=#LK_ z`s|TrYd%4cqUJ^5dNsD_*8k++e|7O0*Wei+CqC|J>P#M%A_hVNC_}RX-;%Yk;JdP= z<+7kfbTJjtljzUIF~fiCCqv(kN}RyJN-=sK8h7R~6q8$Jvprn2sJ4;EdJ`9mltIcs z$gP(?{@Sa*e{eS3A-?7ufy15`8->eflTZB2qv`9g62-lk4FI8htI)F5MU%7;O2^yE z3TsF%+-c$TQ6YB3N}(bWGO17J_cr#tXLkax2vRT`t35jOwG&2aY^?VLwV5gd2q2}0 za#AqavSEfdB_U=Ql0wEG0iZ}u%dL4N$^ZgQEmDRmu~RUC5gmErY})fdy{3l5xL7*7 z?%%ZKw$|dgleLIIPE-oxd3Ngj!es`=n1Be)9h0T`ET6>;C9o=aM?Mi3!zJRpa|q@e z;cV!AVj?5P8CG~d6KsC!f4uza?;V`Avw&5mR8l5K>Qq5U2@W8@WGU3hswEJUnlc1L zi%J1~`6m~1``bi~w}s~SHde$#am)}8V(@KMiKfBxZ%{j|XxK$P9R+3KIF3n)6^3Cg z*aU%F=CA#amljV*RBM9B0mhotdu~RzK6Im4Cx(SB*lM1=Q1TEdDsQnUp?UiS))AX(HISk*EyKqk;MUfz7`%pKpb&!g?q zwq@rfuGLy6o?LzS-#%Git_KKG=RFA-A@m5IG6_XBPp0TQ9|}+Iz^=rlg4{3FFi7DT zr}_4OUb)Na+qD>NEw2l0mztD;5IQTeMv|W}PiWNj;wAd`RZu6C$WvDN4F+n&TlG#- z-o*R9ZdVemkY5d{D~K84p{Gu*ywYI<^{F9&N|_E**qU*D2xb$5$s@$8Z}V8^ii*os-V^idgL?tBFCS=*LOS<+3dHZ+V7A-hMFR%uwXK)nHKk{=gtiGU8FzaGC zC4o_}vO%_c0SxkDqPelM;u}FJ5*Dq|kvTrL6LoE=a405iNV*)d z;sbFksUZ=t!ZHU@T6^keo`3m|PBeq9sw@d0UR`W=>fPUW2erCM+Ex`Z0tjaXfmHI{ zR@O6Vj$JnTwg>|c^#HsI8A;vh!rimmzHU zX3tKmU3`r(@#H*tgjnJ#FylB6%18tH6Sz{u5Jk=dk-(6Z)RQ7`(erj;PRJs0RVn1GC(S-n+HP*Lh}@AhxL z>8@|u3)2th>nUK0~ch#9WvlCr_6 zT^_EQ*GMfnF^i{A2T56A5gmW*?CB?08Z_^`VOW(OW>)Y0_S;R%iT4DSR22|~K*1p5 zEX6CI7=#gzB%zEx&gK@C86ylZ$QVzCP1pD4-ZJ<0zwnMu)Q;1hvJvN_TC@EseevHt zZI-MNw(3P#VROh9nSW zun?ytY1(r^y?N|Y$G`9sPXzI1ai&&l3E(8v|N0Ai<=UF(6z7$} zKw5CBG7}m0X!h>=&O2w`5_WXeSTf!P&d=+nCx7Fmb1$ye>NC!ZD#SY=MQ-rQ&W*f^ z)_@y{sy$I?%2ly()Vf#I>L-2bWfiG&B@<9Jf&jVpD`Lot28PL-$jm?gyRRJh-NV~w z_xqG3an^*(>B^1|Z{Gcp8xq$sUMLqfljtswen> zgdkuG4q&M3SMUDV{<(X@Ww%OPQzeVQC+4+3ILz(9d8CPlu>#IaD3!Xr?v6eb9s>Th*os{KD7y>wBf(Y|Z{o0EspIxcfXS%6V6QpS;om;){FW(v7kho+u ztT$By<U+ z9s5%EYrph-LqhT_YE+T?PqcrcOxhe_Fs=TtAG9*iUlSDNcEPBmk0XDAqND^5O%-5p z0HxSj8wTy*;eYc;`;d69J0CK$OM5}n-TN1A;vETDaZa3PLZG66C^;ojE#jn#5=KSn zbyfVxx|e_6OBpx>M5MpMgEMyGGohd}xH~9CP*l!9!Nrh09onYgk zGOM?L>#e+7JF*HBxil~~3gVeB{HLdT2PxE=bP{5#9HQ`_pN3?Owy6 zV?;-K?K%Ph%JA7<%z0i3AVev#kU&uj8YjQD{N-PKx^6a^s17D(_L6SBfA040ys?um zgOrIKQeuNb_U!LLT?mILLtrwgoGP*+h(I1JWLU|ku?;n*<-7j;t&RH|ZP&5Jsyc#U zM9+WxfIA`V2u_R%Sef!dl|?C{HJ-)wxprMWyy*!M>xwud^P03W%296Yp$q~7s1@(( zrupLU96a?zw_cwU@0bW0yYSq7fA#L>z4dl)sa`iVWPm{fSbSL zrr;)0cvTM=;z;w#61k_oD0^6xddMq0^!1GFFeBb-Y6u|qFl20d_1waJvv+>aZ7X`p ztAd)|`~ud zyE{=)yd`5SmTcc5HYzIWrIFt8v73Y4-lxkA7MHHtNd4*XD?k4nPO4GyP7s9bosoq5 zhW37ba&g~OZ1zDqc*7fGxa)5k_CpB@vqW~QtRRI^SQVcn3Jx00?(4}*zxr}Znl%d4 zCy_zdEp57|cI$WUjd__!ts0*?kS!%a)qe1OW_D1eyf{T(L9MtpXKSrD1{4GyztUlOAhfMJ5TjD9t%)mE%N31L)wV?j|A0IyY z*x7o}lsL8;2ja7hN(U;bBr@wMJ4v zwCCG5et6s6-?eAOor;2*Atm3NF|%i$?Y#IK2O~cxDLE&kL|}yyXEko9QgJ=MP<2h8 zVtq^ru|Rd`jtzk8ADIhqnZc$=ELKv-AQEp>j0xhy`lbK;By~f!Byb>wb7ph$z+ZW5 zV=L6AVC%(j5Q)#OtU$4=O6G^`hpA;vkus(&OD|EcY{Y5%_K)0L-@_8G29fa)w#J_{ zFaP$Tpku6!h{IG#RKOnadD<+Bm^Gzw3|0ou751%b{d}$*O5!NIv6&1FL6#RxUg1d; zVnTcNweGXO{hE;$bLd3^8z$-M!dq%Lebdgaw0%}5PJ}?IsHi9jL5j?nLS8J2A!i{n z3J^Sz_fS#TFcdhmC3*K>e{0-2D{h$$LDZ5lzWLm59*kcR8`RZ=LU72J(Q?Hud|xyw z;J<+%;^o4W5J#|1sCJIRw{yJpFj**uzWBa8^@EJrZIeDnx~W5W^8Z98!4u%=pGevFwzxbFq&WiK3Uf zjyliK8aJQ#t>@3Y)~*G078MavLxJ1#!EM1dPQCL=P72>KK~L{y&j zJ%9PFJKnp!<5q*vuy~V|bI$hj)S@g{7V=l_Ad(f%rYzT3j0!1xa8KfG@7g)@*14W* zhxI^(0%}I_!t)=0JvpP!*DT|@dun^A-B}K6B;Kp!sr}0E%pe2hzUi`P25DBT+cP;eQa8A*Lyi&`r$B!@qDq-G<6p&$~45mOXyV1TJFR+J(U^#dV`K6qneC_S(g4-2zx z5ha7HYOX3+C9pxLE`0S54%u#vS*$ujk0owxrrqz}LNy2JiHae^UPw*fJymRjrG(69 zBcQCtu!yY&x-owvf^Q(Vw?C2k!n>Di*-jFhR77s7^ zK6_dzdRw`{DJ}>TLWl+BOq7dqO+$25K+HlZGEJ69B|scVOyUV5DMe^K`U_us{j-N> zTYDglH1Q^wU1~4xc+c!Z|LX&8Chdu1W0(xosFpHdUT$8YT4Anyb9OS`2g-pXH@m04 z@7-HtzsMwxoIowr4m@&f=|F5v$l@YHKJG$$BtF-&?8790C*w>2)<88hjpT&+ z%uhX<9By$qyWC#3WQ6o|=g58Ev;Y3TbXROUT!WGlTTjHIflrtcf`>}6nMp~zC{kJ= zs!6vyyLYyBW4)`3qA`&TY)w--{MZrhk*ZHz*C$=eLX<*%=BZIFU7P)Fj)x}EC|OL% z)3~BfxbUm>jjVaHhmt2^0$l7Q32R~ZMEcsN4_eGRgdi`HdX|~}wT0Veyoh9s$638` z27%Qxgy6H9ZGa9*6-T6lq@xBQN(I}(lE7FXWJt`{o>*i zy@=|Pq=kB-MLJ3z5}-xp!#CLKbu}>|gjis5rX3AS4r?5h$4M8Br>veGz?oUlr&15* zPJV9b@t=LPMf3H>LbrR8ty9Lbo_NpSxaan7+}=(X>$5fIQ&y5x0rF4}1sjFQHKE)! zoUi{(9-1nlr(`wN9vf9ncoS{9bzTr#&Xnvd z0-*>$gk-_|(lE0^}gASZ+@I!aTGi_D|d!vy{d24Xv;ZH1m{@*@bPZrp# z_@1pZRI}7epxD%+ICCBfHZj)t>kgAdh*WuT&7Wj7U|}e_cJP?Yxkvey@>*0LRbdDL z1|lv9YjP(1%ujsL9dL7V+m_oMXc9$c*`1Sp@wfcbuiJ8WK&^P zd=aOe*(h_hnaQXCkqK`I0pU#i04#Xr$f{d))-vD~o```U3mwmrP_&#sC^nk!yOH1E zK^nR-n(qtz0Fe+R@ZN~1iZNjIMASLnJ^TEb0FeUDIb!xUZSDwy7CYwwBDSP?RsXDz zZx&ZCnHSE()k?T$cWT>v8}I-7_h55d(_Umb1cl!1h<3D}=1=_N$AS~}deBT$ z5;fxC5sF%=?1ZTK>q!tLu5U~dAy!j*C*iSHl*%zi`0^Mkx3w%xa(Q(wRv1x`_v$o^ zW;)NupZY&PQ9BbX%x`rGlSm*g?k?8u!bgAn>sogQ9oIG?R1;`IB+QcSQ;>VH^IAAi zRU+}?Y%6SSZwL}nPf&(f(CYDyc9n@$0Wc|uj1uRYMrDLO(Cz&#H)taI{Yqi%GaOM! z^5o{?O)gL~grVXAHGr_;bX8BkeAd8-&s-Y{FO98XFi+m4hNuYfEEXHemm?;QYBdvD z7kBG3wbZt^ekAr&UtD_1)x)Msdd8433}Mllz~=dv zD%r>=8b8G)VUh@OVlmdUP)+r#l#9m@5DpnyVhLCeesOY^_~Pai(`xz%PcpLA0%j>)TZ4V;Cr8WPTgLhV~Wrnpp05+P2M z$~@Gtb?k{c=2%dX=a7}h1Cci>;Y@Sk>0fx|bN}p-1#GbtkW(kl)`D}LGYbzkzwO7r zzI7{ieWx|k@-av)yk!_e3Ux%nMA;_fOyqpp+-mFd2JlQEQ9~>at0#K~Oiao~GHH-d z?3TjFC0P{Z9RMn;0ni%v^ajD8=VaiMks}LF2vND!VHRj*h#*4nq-OcZYPuS;0o0Rv zRUgcvc|#qnMPxS@kpToH5@ANISrgS%-Kw0s`9ljI`-!&)yH?uCITQGRjB`od)Ek|x zfAY`1_Udn)nzx$>hD_)NBmGW}_)iCClr}_rS}g41u&Z)$0wT;-JbfL?k_QPbZ{I3iY0fv;~C$3@8iCF3Lq{ z>6RP_0DnUo5iiQgP=?3msmDfB@-Ex>fywkv$DQR^j1e|MUZEUN(6S08W|iErpBE}p z%N&rJ-fFCp^z_`_-@Ef8|MDH}-JQkc90!I`^Rd*q)`++K(ZBfWQ@{B7j4ZgsN!n!v zs3x#XfvRY>XDs!t9M|jf$~Be=T7M^r5C^iwu61KZ+g4=@8ZklT9z-g(lt;0}s7mg- zNDTGJc>>S)O;7*a%b)(yM}zJ{5H(ykb%@o<>CWls?ZLPH>_=|;*v`|dCsU-#%4A45 z-}@*dMkWP;WoFw9*R6#?MqWMrrR8f?*QRIeOP7|0gB#Vwa^Um+x_o2Uc zUuSQ7$;Ajv4K)*ob*(M9-Jkz=&p+|=ug;nK^yh_ThYcya4qELtwbt0zTy;4hf6{@#Og=%BKR9Q~>rW;I2`?1g&uM6aVq~hyTUHGu_$1&XA8%pPIm~ ztem5L$+!H}2j2PDZaeFbce+baGcwkQmu#!PJfTFDEY04~q(C8g?t>xnsY^SK6;RD$ z7>jz$0{DCb)vQ@X6@Z~mAMzMY@&mb|7+mrSZX;4Mq<{*sNmnIZiX*^y&+1ggSgN%` z0IH%{V73qwt5<7LYX-|JOL1-S>;CRNANbL`m*-A)lEegBN0cOSEtGlP{^@`IwXgo$ z7iQx5$kw4mLc|tEnNyZjyfT18UPUsyEz9j7S1J9M_Zs2CSx@Ktv$~#)_wOb>(d9 zw)*@3!9)ALt$9?AdH8w+cByf;UY|9q^*{ROk3an%4>sdXmbshuR2gK5Nqq_skpqZe z3S&$^D4<%S^E#M7;}ZcU0ux4v*D3mRQj{E_ymY*Rk!27oSkw{(Mu?10RjC;)JpaoF zR+qZ(`^WEzb~JnKQ)pROPBcB@Uccje_iVd$%cuUy=MQ}9c%5cL8>s>UWma_*DQ`A0 zdZLM?Pym8)PP}tu2ux)F9IIv(7qjB{R5%I>>sArs3t$3A=;gqu1z@bZdE`fB8XRT5 zC{-a601#ntYM!zHX*4duHE(RJ3g}Y13&y$dZ(VnY2k=L zNSB*5+pKS1d6ho(ufO`r|2Q`57MLtl0S{(Xg@B0jmZ)TD?o^cf$`;&`8g+1On_$pn zs{0eBGGD(Ka6@OI&e>J~LL{t0l(~LEhGQcsJQE3MwzlQiADsS^ANbVrW8JydF7b7j zs@Ig+99})td?)>xpZ|sr{=@sQyL+~C+OwK45EW73z?ixR> z6o)7%uU`pZREnt5B8{Vx>kd_kNh=d^BYZKqNGOwLH4ZbU@Ctio004jhNklJ zPC=x^-V>PEP!yC^Ehz|ulCCVJt@Z=|zgxfYr|viJ=q$RUU?+wX?;Eta8SFUt#m;a2 zoiDulD<@~%0*guGSVRnny;yjolEq$UoA+>T_9(WK$m@G+^-ix7x7ft7A*K@IWy8qQ zoV#AKVhlv25MmEf6;?2TGz}&4Ofz==>?apLzxd^ce(1rucg&>RVH2CsP%S(epP8MF z-uAcey!m}MJ@T_pJ^%4Toip9})~tB(p#Y>nTfYfrtcMZ>Sus2Tt7O%l86XwOs-Z~H zu+n-sP{m;cRc@!j`@8NiR${vF8!301@8{tWNQHco)es_~FkALaGvvUM8dgfuUME@J zdRObcf9t(FzJ2awcuaJ~M3Gk`ac@gBJJ;L%!3$@(5n$20++yAmtp*+sV{97t8^D-2B#oRmd#}Qzd5#eBy$(6A z+3Qsu5lBQ31nL|h#OWln^f)%BlcG;bIJn zV^D%J(#-MVti-Gllv5`?X+^E>>+P@nv#+)fE#Ccoch+_`yS*g}yeBBT)%f%(pV{=M zw|w+H?|JF>4t@FOpXjaj!cZ8b>@$~1CWB@w9p!FhQ4yjtgSjk3QSL-E25VV&uD5A7 zd@n=N?mGOg1ql|L9BZDPsibO%HD72*2*j`q7Ox&m&}b8f`^@I8n{NEzmVFQ1Fn3oy ziQ<#-*}w*tLXi|?HkqyNK=3>N;>)l8_oJI=GZ{|eE(KP^Y3c#6$=9!3rY{S673vMl@EYspEnf@O(RU=k@t5Fm&& zzyO$>XF8o!ReP^>|EQ|dr+WrTgoGYUuipT3=5+Vzs$ILjTH#yYnoMSVzjNXf3!nYT zN4gJ{GkzBou`H?-iiMIn0Yj^fqE%{C#a~#xG%wbDxZ!Q}Nwu*ui5m|PUn^E~p{)r} zF(T+zx+F}-V#>yWT?A8zW(1ySX?Eq4i=RFE*}dPs?XEv^aOUB4mo_~TXA1i~-${o~Dy zP4%gn7HbXRo#0Y2n8iX8Z*ZXq$z0jqhHf|=%b_CguoehI6s=?`vzetw2T%O`<41n+ z1YPXR$h3u`G*Rk^520KZX0==(T#a2JYdX-W)+Blj%5Q8SG{hz#{sGzQMQ(HyM*d<= zXsgA%5I~U?P@+H(m{COPXY*sdBR_HC{HHD-{Eq#*Kd?JHFiq(ogvB@P?g+dM$^Mn_V|B)=E#3KG5C7Xp(*E5jltBwL>9Fug*_8gqiO_* zNLG)5RvSXG*5SQwY`nDpJf*yiV4HyW%GkQR+j=TX{jeYTY6iqnwUR4o8dkGF3{#7R zOMEM%iQXf<2cJCh)Gwdh^R0XLeCyWM!OV47W8cGCUWWBOqXI-kz~CeMN}3eRqN+Cf z{WY8JrH*2IVMg2ah>UpIn7;0N4S=zcf!QLIJo!lnD*V5y zv&B=taqgL4ISsf_3JH-^b%Hdt!;$h zd1C|Ob>)-LwW6(2y?HdxZdH8{K&>=SN)$=RNCRNTVnijPX2}FB8-9IoqIjKLoR`75 z8CAem&$9WuV!xY_kr)DZD4li;r5MSCg%N|6IgC2Vl(Xr@Gx<}$b?n(+J+b`NzFSVF zXjWKMgPJlsH7014EHtW~Vl)cP>yC3O)>efv#&Ikq7Iaf6cBU+RI6pF#ajot>|p z!g?HKt+Z|Rv=kL4&&;Z%3Z`7F43SK@m9{M;#bd+cPn(>MZNhjp))dZ@M*6YGf!6&TfYdx#TN z_HCcEk`NfKqo%ATR<@E^#>Dx@yH9@X>&HHMcIoRqztZZs85WSKYE&>0vxd4Wkho4U zGthi~l~`Gw4sfl@v1xx0wjlxWI=6L5zK#MtieK3ptRz%Zdts`uO6N{%?uW8LgBnQu zM2FhtbG9rd}vX0UHPk;Y1@^>9j7izw)1-eDoKe>pos2MQfU8 z2t=hBQbjZ_0c9g1YPdYjud_-Fz4>(w9*>UZy0`0Lqih1=D{1RO=I7f0brUKzG#sFw zw@f3Sq^eO{r2|R?A~919nz}6IG(TTH{qIg)IDhGV|MYbed(uLOAYy_upc;ZnT<4%_ z%G@Y}_&hCpFKDAw_%(%aHY_HNAJxl0$o2SN7v}41tTT`#&Ut}ARby11Cs!=VrcTYU<$6r6QD=oqbi6=FM0#Z*4s2VAVsnH8- zqV{h{#pdclC%%!7`-8M~c=i&rFLteq*aWh5&s;PlSr$Sm5(qw^2;>OxUV6jg>|>W@ zWy14>mVJiBh{$ZrLW;V?#dZHl9S>XGTW;eX;l^Fq-{+NhzJIR$%Byah;tsE{z4X0X z*PiR?cho#U8yC)MrR#vy;G;S-3L%z8%2x97&EK+h*E^Btj_4 ze9P|HH+|&wI#=YeG-^jn6)u8lG|$GMR?m~Ilo>`Xl2<=tUJGc5s}kxP7!9!rh_9?& ztIza>tl{I-G+eSq#M%fDGbVsdO$j7S0%j2mY%q0r;_mO;&r^9cG69(;pedSv>Nk&H zd2GqMM59qP6TO-_P)`MESdgz=(P~4LR$jV{&5L~usJ;wVB@d|{17HL=7KFVRy2&?*v z*$ZyE4RWTNfcT2p)fuaoVxt?a@f&D$_Ba_KI4TGcgb>Zx6s7w7&423dU2mC*wn8p4 z5~z4?VWso%S-e+^5us{{fvxI?+PKg23Sr^~dA6_9q*(X=YqrkXnwK`}BcnB6 zuT!vAWiBW(+0w$5<=#qIvd4buNVp=xtq1~Jsd?cpzV3h7zyH1S!?=t@io%4e%!Q8H z@zxzOHIJ=kOKcQ$c!iCw6+W;1`m1i6fcVPTH5FoBsx=r$HFQ}snX1vct_T^P$ypC8 zw|@KH-5=WB$8u^eAtqx`*5+MLe(dD=FD*@Urph8zilt7quG`SsgKQ48p7s0+VcG^k zMqE|e!dg#uU07X-I8=Yefz%paO!FlGOjebc3scIHspo#@)YHH6^piy6WIJ?Nzr;K>SAS zn!e#TnhvK`6%aB|jLxAfbDb>S`e)xbdw4=@=o}PKMB(;M-15*b9y$M|rO8$rW8Y#y zN(NRQ4+U!-e5!aowh9Ae)%2>-%&F_|-dqLA7TzDAB*``|sTO&bcBi6O(dMwLgPaxrU8< zXyZroTFV<=ZzkR+ukH~Lk#h;^9;LC(@Y0*DUTvdm{qq|_!)jgXyQUgXkAB%b_`yAI z`oTMUe9;MvO*OQrGm-Cl@c%yk#h-axdM&32d0-|ZQ6g@b-!-z1@?q5I#n$a$gw^Eb z%f2z!U8q<0a=k!ZWo@a`P!Ei)@&^n8jIAabu5u2k$-QdrOpX$=Dg-Et^QRv>JGrxc z(|z+rvFx2N`!Z;&Ga+a{^IOmQG9${&BA~TwO*I&MHLJU(uky8Nv~GW_*)(FYBdHDW1=)}zjvNyibM!Zs?iar#r&uK&xb$u zcRo{`gqUb)OagGl&o&qc3=$=-8excys8o$mB?i+PMX4&|_;P$idkOFS!P(1qXq(!x zkr7wy7mzVUvPg(b3L_&5pb{uy0Tu}gh>3?946GWh_{7L#z_Jqesr#Jmzbtmh$`B0UPApHwqwb+Q_WAU2MeEhN?!8 zNlL^@oC6UW+SED!$l}94e`GSwrs_gBR|%RRzzC5n!jveQ6+{YVC5dV!z!Paqq^v6n z3X4P$HDl3ghFpEizR2#rI#9j3nEMC1P&D=%U(Bs-xGz;r!^BL?n5@bySS_^x1F->; zrY`Lytr@K!C;=3y7S4tMtYC&I#HV)Bf8uX`;mX(ht#mrXyv4yncXn@b)9ZEyE16Vk zf3(qyu+gX#qO0NkkPCK^6@WfLazpW|Tyx0Kl_(?J<^;|QRFf5u%823hTJ)}T+^)MT{0|`lT578s33qi#F%&&hXo6x zEd>TS@)nXDMS?yP5i@Bd@OYb0Q>n&_`?WWQZID{tCLn&JcFh9BH@?R!2cn+4R^7vq z2N#B1P|}jzhRnnupLixX=0YX1831XUMb!diJ_&<-Mdym!zIoTq!`lXz%jbUY^uw7SW~h^i_=tuj$}Y`n-xt@8|C@we_yt#H&98W|PadUY z>tb$o8U{pSB4Yx~g2E&rCe@O}ndar7H?@~<{gZq5f8Ts-AA8@rC+2Y zI-_PFFjK5^3n;Cm7PqISB9Z_{U?Nuzv2<=gDheXfF&YJ7byaUbM{(U{M6)QbzLU5K zh_8&%+Sk{N>8|G(-WnI|+LWVIV$L1okk^*(6Dm z-f(5=4*%`{_>G-64lm`8As-lt+dFmN5AM17E%$x(r;ncf)I}Gvq?Lr+1vPdA3Zsl+ z1OP_>EL41v##4M=OdNkO*i|R{4c_qUn1(OwqpVIEHl%bIjED(l)c}($O`}nWkv)Yl zu&jIFd$+&&&+MCgL$536hDCSSm&w=f`j#KR_me;T$nnoDw)|Wf;FyWXRE0QNaFaAK zpDKnzC0W8i-Wv?A3>*uYX(c^GH5qZ;;k}fNaedf2PP{%(cJLc&?fL_>4OvvVZd1+c zaB#HI*O78wunyaF`%|TlXJ!TqOmOUmmx_ET5D+jVP>G^5@t3_1Cu_i046{Jo zg^9!AoBqk6cl`JPeZ!UWgJZ+tl^FUF!={$;1Ea^gI9Q{p~w^uYY-V+NWvB03#uY7`&-R zU@aNSqs#H~>xb)jnlI%C$IkJd;+E`gU<#+b>+`nC|lxiZg0bnLJ*wprRYg>DO zpyW!hA?cne!?Lj?P4dvzLB&gfxtF_o*O_gwQ`!ufUtwE^bnBwUml{H@evM$v?4Ty> z^X2aF%z{T&V33fO8ZnK|-ppi$*kUkZZwj!2G?|lJmzhW%X*9A3z&N3lrV=Rzy~X86 z=f3sUcieo>!$1D`iQhV!@nquLWdH-FC9}ay>*@A&ub(v$GrhEY;x!lKMKT6gRZhlf zhU>m}?dQIlQ~%JiMdTYm8FJ>QY^S|^qUxk*S87tM@3 zIf%t!P|wZ~`h%WhAtI~eK~fMz=Wm&EQ>E%kn?hkEkp6{%4NBo?YNHVjWT^78<41q> z_W1p+-o~;Qfbgr+D;vW$3Lsujm-z+l`L^t})5uqUTP;|)CMRi7W^*L{p7u`<0T)Oz zNVJm0rf-@GIIFRQMu1~QH8$>@TgrG=38Boz2`tuKXVK-|0wK(Irk01N)4K<6`#*2p z@gFC@{xiq>rUYt8MNNxWLA7(exEF9@(cmU`aPf597MZI$ME zYx`C&WWzYDeZ{K1N23`N6Sydc>@<*n`aHtU;zYn5?%MiPz$HRrWFWQMSR$r%S;;y z5MEJx@e|u{H~mHd#MhEteM`R#GD?%CGjLtlrK-$91VYKn=LaD;mUcj&KtPKVO`?D|`D7zJBY6T1%bN-N8j9!9?=1fOjNHq!bH8gJS`) zBJ{ZEvCCRkL?cyJ0W=0bm$VPgm4L!ZgJ)m#;+3PzV$6(4reG!o%#5jCGQx}P2YRuG z#C2{P&Jo&pImFk*^{ERd8=0+-7RgQq6mcIEv)dD~n5`Yp7x zyS#iJY$8cH$SE4bB1lQ9nVF!GLe5B);ALF4t6jxwck-^4-+2+&a>Nm;^=e>5YD`s0 zmy{V$Wwlhi^OA>NE>~{-z8&xTnK#dUR2SsoSf0&V5|xKap7(&Z1;Hn4~Il9^`LkzGwTxCTGfMl+l%Z7v0fNEyWHS56M&g7K^h3~&)~ zVoNKX@o}XPV}hxvskv18=ZdA1gB^!kLuC;Oq)wwT6s#7Dq7N?xBFZ4Cfb(8?p68cu z`-|JR+<*61e*EZ#FD|Fl@t)11Au!BL>WHs8(gqA0PZ2M0!Cruwc+D@Z^1xqo1Jnw* z1Y4aLV?=dha#dp{iot=2I1Y+lKlZZyeD7bl`_@0UWvPAa((wFb2GcMol$eP$L5?l6^znIm7-B(Pvp!f7)5wi&w*hshOo38 zo|}7H|9wAk`#pbQMl1m$3)c{nwg>}mzTNEY|PbS)rOWWSndDl1HL=a|1$(Ns65=~V(gh1k~rrunAD$lz; zu2*~MHGqwWo0}fut6=MjEtReMO7$*L&}hU5Gl49aMpn1@R5uJMfXqnEBErnU37O=w zh@}}A8iOjJu6K4gSj?*?U9t!$aV3tg%?H<_`7kvU%AqtXV;~8{#q#2bEpH!u^S?NJ z^S4j;!xf7;fh{Q1V)UdUri{pqc9s#>qWd6v{$v1M3kb$rT!4CjLJfIWEmX~cBvx|@ zY0{ab!~t5U8W~dr6>$B4rS9-@>vnp_Kfi0=pL2`$XbcyeD@xPSV8qqP6w(MYG9}aM zY?{%nW>7M)NEo2JU08ou%sF=Pg*%GWX7bhf(!!RyU>e^#^{u$L=s@F z{b{n*DWW*m$Z95R-udE^_Rg0O7ppcvu4=JW>Q5^nz-p3`F-1m%mO%?eL8hL_VNhJ; zy$f&uM~CkF(|ZTSqQ(Hn3gX0pEQUg3S%u3smQ-B5-Zwii)`htKfm{lKYl2_erYM3PdW+v38#+D*dkj=TAGzc zYO6xHRJ+ero|0jp2sX%IDHbb6PQV0G1|w3Sa^~O^Wj3_PBhK{@UNN}oRMk_L`)O`Dwb_6O?>(vJ+g4*@q7%?SGgKpP2aS0EK<#fNj{;wY1{(Y@wI$m@KlP&6%%cXKAA*wQg zG&ZB2Z0t~|z6|wmA_kyH;8zx)eG?KGGnrWtEpn=JH%&SjLI+C1a31N>@n!46d5W;0 z%EW|9Ke5rfS<{|bpYn!)gjdk6L)zS?hxjVn`WwEErmTKR03ga_42YRIM+lT($TdOR1`w8mj^pS)7%UUkaj$szf+tF*KD^;!5Pe2uc>2l-NLQlEhAXdD}ZC zzw|@1O4$OGXw!8iKgepr~Wmk*Z5QU(Om< z&I@$4j=#3G$%JMNRyl?Zd{pslr96>XOrahM$KIthD3<2mmc8pg+`jdrH)AnjtXS1J@gFymGd|V(Lvme3k8LG75kSJ=B^3aEKt7 zMN^P??Z@tkmC!8&#sHfs(cE2gevVAdW!|VNmM8@toX=x5iqD|bjL}@1z#;-ML^NTu zQq%tAyQaVELlb}Yt=V_Jed;@2H}Rg%a66EJ<6?dxxqIb<|Kk2T|HMt*{EDOK#DJ7J z39;&(Q?jP;VO9+`H8c$As~Y&O&NI?g!FBalrHL>?MuJwfNq#qBu2KzVq(C`T3k+g2 zB}C^aTG^gYXZOzL=m|h(!d!Mk@60e^4`5i9#wJj}c*HCnJ3G7jWM516Qaac-l5K{} z8_Q||g{t;j^_^KDg^7|W0vS8x(mz*N8H13eVpPJ)i9PLXS2DaDm^}cHm??w1=ZZK0 zw}ebd&6KNw#AxQ!8;qc0%Bl$^+RwJE-2Bc1-?`Hc#$kD@zq=(_9NIB@@SHnR5Egr9 zI@@~h{yTRh6YgvO=IJa>b2>FF296w)Rzj$#qzoDvvs%J{nr>qn@qL~*s2X>iYetQ& zzE|b<$SPqpg9TM@%+gAihUaei$n5<;x@TqQss3QWL=erfmd3%92vFt*vjHO={l-oa zM$okqCO5WifQ8ru#EoTZVS=p6=~r%w5?~_C=6c7=@}kQQbb>{p_AnO7mQLrEiN%K( z+A$$g(!#lxa9UjM>yT{1YZM^`sF?(A21b+8VrEtYCg*wIo2NdoM{be80-w_CK7SaD zI2aZ@Tu3hYDc|jOi}txU{;flyg~xvKxwJ4LpE#$n7-jcV4NMH6`Zd%bWiGE$3#AuH;KT6VnR*Ym#+^ z%i2J!Hcy>(QR}EQ)aX5vfkNbb0kJfnX(hIeXezqx_Ia6%rU4`oj*$%)qICfla7J1X ziduzi{4#H(IPulD4YL}XA@fGFQ3h_5%Cg9;NKCzQ#^PP#`WL!qK69RzTAl}V@utgLhMlusJ zGEkL0TjTGIY}{2GCv1%2XVb)3MYRkl5@~riNo4JgTv|DK=XY#*|3AEYA$zu0EFeJ{ zjfk|$IH*>|=xWSp728*H>YF221x2fe*;mw`I*rI+%A<#n^J*SQqL!{r^M~dlN--iR znI>=x$CifYhF&OIpR^FQQ9O=n$SYmLFWdyljbIzdL)_dDsL2?$Ld&=h6~WFiG-w%^ zNm7~JGrQOuNHEWi!I8t{z6qK#G66)1$buNkWi6Itn}yZ5@(97G`75=2f+w~dQ@m@x zyza1n*EZV2q3Xn|B2!6X`%vp1$JrML{FL=#w&FS{ihMxl-tbor3{;Q&Ltk6lVC-bqLPd>VwMK&~V8YcYn3k$hXLihKjTad!g!sQHvxl zL~YGvbGxS$Ljn=R!3eZ?d|6j2u6vYf93L@R94V(=9U9v6c*-}5ZJ5*ED1f+Tm)z*P zV|*d1J5$L58#F?WHTwB%q}N>yLDAHE zg_WTc5_VogNZaoG!kJs%z5h@DZ-3GygM-EW`DF6U;Bl7_IGPw>h}HCfdRXd7Ozd3aVKu{QiBp3v6h8Tc?E+4zH zySJ-7okEAKWv;`S`N?!5DMEA|lp&fik?GKiE72n(WzrIGATUS~sba>cT9hf8+mi1; z;9j>q*~?LwMFkuqQ1zm5jExx5Bt4y{7_e~SYjHNY;yR{A3cbb;@MxkFqLkB zCBkWlWnPrA$i0ZLn2nGIO;_IYUf4C&lM1k^?rF8DnVJ5eKO7DvP0AS4)D3ib`e6EJ z{>J~bW$X5&oH#W3ff@cnxBqkJdQWo8M2upfS|E1pnLEm7R!7q?VaA5X1N9(QGc{uX z6jurdvlz*gCEfbB>ff%3S;NEMiR@` zI*F@mfeNbFIb6JS(K(kUNen89477)B<^TE*f9UQ*ceQP5d;35>)Hk&!rYBmT?Ek{! z`GX~wM$uOax1$obtp@Eh`rXz%d>SzyG|378%u)=vzi3Qet)l9bKJop_%U7Pw_rGy& zfW?aG?%4U%!$oE;6(RtXSX3(|jL}Kn7@PZgvkl{_-q?1v=t~pA)#`p?WkQ8R1V~B! zc9(8pjgJ83(7Gb=Xu?EK~eMo>&;(Qq*JLf%@F%%v9!N2_%-t*RX z&)ANeTlY?4%Y;tMhFx#W;al^_N& zSSXDPF27JDY2utS@_2?QFRTQ?9(ny6uD*5CcPr z35hV9b1}Vh;;uXSzKQN!%QDB7`WSl`TKgB@`J=bZ+~)_yQdtbNRAB*2h$T_U6hNU? z^Pv}7gfS;^)vE)bYDKBct|&_1aznTB(I5E8N5B8iwz~8Cl0#XUp@342wYTa!otiV z%sz1!E?@lMcfaq4|I%Md2ko0WH@B_L(K2xfOJa_L%i@+>(%YwaDwgJIE_U@xD^;HQ z0IYFJM=T$%iu_tV3>-8eWJ2wW&kVyd890_h&&$vO1g+N8;?s+Z&kRz`l`2krD3@D1 z%G*A8b7&V4gCt?LQczIStAn~<$VO7S8zXzRX~4z-MBG5ocMYqBziP%!<(yTL1(n>2 zGxNk=D~aqG+3}Xy)^ucxjwm9eXg~XzGlLU(Dv3rl2ZK5@QiVzHT!B7=IFJJ~StO7t z6PO}xvF$gxL(^_fz-sD5NZ81T3BpD~R0(WF(-MFdK_JeOt=Mx*a<7!F7)#+$VnD9! z4NmWRck-6McsQg>a8Pb3NCbhoAXcKW)PTK646v#Y*~sUZvNJIHWpV$T@BjOM>mO`Q zw$AcI#=a;bfgOvKh9ab#CTLAzCbf(rsaE}VRkT{;kfItRsH5iPsuW$v^H#}4LWohq zS*Lqq5c?&$2#Zk1(MhQkEv&dBpFHL?V7E$yw z$pgHwQ6+p`tMm|y0^Ykqi>WQsfBzr-<9&N>Mc>-K9hI`wJ7!VxlCXF)Gm71~I0!?4 zpt11~8~S166^_<>6v$WaFo>1Nh?NqT`nX-#eA`az7>xNQRB#<10bg4GCAyNOlo92sgw z5kgsZ!rpJ$HMz4B41qWpQgqIKZt?PiE1Bv`w>ZnL+@OCvjHIDybvKy7U0Rv>L(`)zrXM*xSLRM*=leVmXZHV@dsfdigCs z`1*-M4mwPeB$_kxVp7riU}MhFRTS;UW2)DoNoB$e!crD7#`3TJ)xYw#2Oh}#c`EH# zC`cT!LrKtzokK*CipzGYbX|5rRCTP3ep@Bna4qMwCP*?7F$tN3k`g~LJc)}Bt~j3( zF>5sB1*wXQYEInBzc}y@7G*Kq77COB3{Rd_aWg(Q_C(#0k>$E71nz`4MxHoMh z*d`#p`bKLLNmi?7aihTyiK94$P-(;ITeH1yop;D#k<3Jxhv{>_b&{6NF@wa&2qjd7 zB}QZB{A|4RP_N@=qB{0jh*6A~91M*WsRz-dxI}Q6QEO$!SVfX>Bz+wbu$1uM*9r8WGi! z)GsYAeCzu@_@_Ver;5QKbDo8n9ceU;L?QyRpfP}vQ}1kiHn?FmB~VQo?qAi;U9a~D zbkX1?fhtKbQxjnUWo~leYk4?ZCXQjj*r~EJPk{{<5vWT~{>oD_OjAishs;sS;l$19 zp%3pbT(1O_ChE@dnx4+Jt4JenbrTaeglz)it8X^u7L5v^wI&w;GE*tlMX@3&4t?O} z`F*p64MD0_rf4lb-GBD?PA8nj5G~d|SnY#EWARk_Q@?r9U!H6w(^`sSAI(K$z$<;WU+J#gdGDQn_wW2@yVW9P zHcp&#qKQC+#U(7>iMWIVhhy={$GQ(ij*%>)8Glu2DApOh69?M8;eab9 zBn(i4n!$`gW2!qhJSJ`sH+d5fUwvyjb!z};ceM|F@KDZqw7|$2 zAw}!iPo6GL7fDr4q1ec@C|PtC$;|ug?3Wf!{qEAF-&QP^3>QhrqFFE(m-yo4^1O=0 zV2F`P*#a~Yaou4@2&D!q3g0~|$G(z(?3b4Q`TxEAcYf~5KYmKSXzf8Bhm{Nsu>ept z5)AXpZpYG_{^Ff}zm^!1hg!j8&Ola88Nn(a@9KNOnn}i{rewyY1!Da0|K~5=eaCIR zZZGqRb150IMaL>IQwq__7({d+7k;z%Ur*4plseUDwaO1F(1RJqp~yg$y1#-9j*^6? zT5c+JZOeJmZ(aGSUi$oE>M{_i5*xgh9yz063l1z!hL`Q}|Mp~xiPDk~%n=u%J9{|Y z^UfVX`eXwJkx3IR8p?2aV$6lQ{v97YFmo^~ zYyb*u+TyH#DjfaQW7)tngh`YDD4R0OOo>a<(ut2(+*g18$^J#xnV$@{BJM+*e`c~<_qyxFHSrgCs#@y zhzJ=QxRNsfk)c#82KmK(@0i&0o-IY%HMXdvTz#zQNbL3b)`c40Hi3j#czJpG+dlZM z-~CN_BW73EqEsD|PicWq|Mud~e{Sj5%1%*86&0gNjT(%&8i>^_`p73` zu&AVN%2Or=Q#jY2816js?=QrY>b*CO@P$bPfh{tWsU+)jEWIX2fA{p|<3l7#RD}hY zBFS%l&#q(#sYQ?|oYun{6r`y1$~TKD8_6~pAl|rZ7szFYbF;0vdUCw)dNJ_M)m#9Zk_8$e)ocC!EM?=Jr8gZf*ir;Zj|hW)Gv zSuDr~!AkK3i9*y|G#4q!&AIkeKX$w6mBWAZR^Cdb26Cjas0DCW`G40+B#yf4YPir4 zi>ayUkACDMo%Tc#$}CF=rW%OJh@$}#)eSD{i7$75_y7OwzxoeP{hQ^u!cioU8Cj(S ziPauv8ubK@dQ`w<%$8|jjTR!6dDzz8aq{B}&;8o@)KBJR2ujfRRQOH8NGzXlf*_ zbdKBvJSdF?57n1Ofe2mzEs3h+AQ6~I3=A3)4T`cqv9s+uuJXuOTUF4!+7fa#8aC~1 z4L`+*C@=Dv*_m4o9xSwQ&NF!;;)W#?NtcS1qsvc!`t+wCUplgFa^Hvc|M73z{=Ktt zhYn2$WGYy7#+sr^J(hq$%*14lxRpwpkGrIK&8xo{_b^JHgfnkWs!f#YD-pV^V@e&g=ammvm=CbfGy0^~8-$5(qdzPh$S zd}Q%Hd6gG}$hoA^{v4Zceggy&tFTYi7`H}#s(zDTMC1Ymp3dL&(L1-^J*S$3sIWYf z?829NkN(utdP$&(NHF98TT_}J*kW){p8!~}sU2$*ioTp27p(|?#J2FAw1Y(^jl5*UmOOjK1S*ukK==X5&R z@y!3Y^xS_tmy{h<$HZo#Duwgt6ft(;#~y^E-nI%UqUi%~edE1{?=C|@O3r%)!9rf< zgOC?mlE~J{?RU()?m+AIThiO_%if?=Jl%cNT&xbYs$w+7ld3(MLCj2SOe$UyCU)Yr zkcrkDo}2xhzyA2r!qtOh3QEjzGpH zQlA?g@NI(uBGKxD@-?_M zu66nV>b_d8MjO?R4A@Ly!fe5Y**4t&$L?tD&4LXgMYHIbAOEEjr+@8oipe5KH7nD= zFdp$4Q4Y{c3EDjW zs?jcutJsvcM-DR+VCuZ@5BevLAAjF_zjWjl8^KOMcOBh>ofl_ELn(Ju-CA*N2ISzLm&9N1L0Jw3nmcmCF+=RPxR zyLQaA<|BcP8eg;4hp0<6BE;lYmX|SA-1^>IhTMx{0BIR_?%A{O?BLkv&t#mKnN$~r z>)8sUyufP{4e{cDSnG6d3_#pSA+wFWcx`Tt^|tmb*C^x~08-+bLR<{O(#jwQhA13@ zSxnHH-8#wQCR$s-C`tpl(N9y;>S91(4kDz{0~pSnj21rcMSL2a;b{IU9p*X>ZRYv&b3wv?PydrA%M*@bKoKFP@JhbXb>R9;YYzeAP9j!2>xe+6B;t!a zJo&`a?|%2Y_Uzg<%yVHD5#gH14hRu55y6=n6B~lM;S=45&xOadLJ)Ql-otus*pnNjV%5{N}aRf{laA8>E{ zp?iz1{Ze!9vsi?gboR=_9I@Rbxb1= zQIiLCbp=Nwn#jOpY9MZ>7bs%>l06X`MTFW?1GQc>nvHbIiVHD}{xqtz2^&UWMkYyx7vTsJTvHudEuk7NLhiHxAc24;3Jj%Jd`!pX%g z2WNKQxuei!;;0dmt#)~uPJH@oCTUqJ3)RU-ZlIx-+NvqmK?@Pb>|y}8Ku5ofKFRvW zh8Lf_uw~!W)XftnLk*>*G^AnQ_m*{Gp?86!OsBJ65`tJTQ&k}4R*>#f#TRnFL^ZA{ zlM$qfMN?}}(w_AEO`raIUn!3%bAq5G#x?JbxRH7pm5pkyQ3HTxb&p-;f)bG#giK=& zren_?z5o9E4;?&M#vm+UQbVI&XaGlzPFO9%(aLiLkZ3 zg?`@gb8~jfGe5WVnZNUBaXgtwC(5FTLQ0N^VWpW8M8TkHJ=wS#jwpbDIcPw8c;BBp zJb8~xt?L9)X)@{bj__xH;t{!!rR-D#6Vw zn(UmW3Quf?NR+?~F*wIQb;DD|@z0!{-Zr!Kjy)^G2rayqB5_xe{zaTR*FTwuVav5U zNyj2(1$KYM}BvrAx+7-kF*lX%MFAl=@%^Qqsy^0oi)j1P&HB@vqm*P{e0 zc!pKLSOeksY>|e0L`}$FuZ?N6Buy3<7SEhH_r2fyJxP+NMkWFxRkd(4(*Pq8r()*y zQT_ZUSN==iEtkv&7J#BUl0crlcv$F{v#p)&#l2ttm(PCT|9HkPPA1|*QH@eHQmaNW zR5fa@YC+905fMR!n1%br%B}~d@BDuq?q&Vd6EnkThPOWQUr#>y-;TG@))1KuVx#~G zDy#F?QCt~ zo^?r-13FtLddK_6KYK1~PZ|sw$f&9TRLl@kfsukMULOOjWF#6b1Wj5K)^*1|eL4(i z-Co*F*4P@FEyyyIJ0+O7r+59IPA#i!5PX>wfYL9l?K3Hcba zsdOh@n)~>V{hppADak_axMC*ObC8=9XX75Ox!TpZ02@sWKr;u=z)qYva^64v^wF(b zwmk6WH|J$xK`lmO0j%=AU{qMi37w{=esSrip7W1!D+UT65`|-U5h$^e57}IMZ+vR% zcYpNA(?559GR`~c6a|;Y<^riQBGo;AXpWsg#v_FaGRLeMa_ZjmXYQDPR~EwMPL`@6 zl?)fsPye%rif1|Xtyos9`^cjCrFOeJY$(@%Z;86Ktzi6Jmct({Pb2UPV` zifknzP^JDr74gG`0_=QZ>De!xKKIz!9S3$#?wQDoKE;qa$|(-AZ~+&d9Xx%hJbih1 z4oRe>e7gA3L&M+gwYpkOB4CiIFj1*&6I+r)U;XK4p8d}!S}{ct7z8roYNOXP+Nqff zU6+v7+$uF=$i}HS6$X)l5j7S?eCVNv-u@2kxqMRQ46z&(B%U1tE0L>`7flC| zSkusV-g@kJ&OQ9^j&$OTDMdI2hy#I8ac@?A z;nyY}u1CQ2w3~H+xalD_ORCMORLy}W*~`_mhj%eJC3>Kp?~vx zZ_y5yTC$MYV0P#HeOvDJJvE81yd}VR#-E(_xor`w4%^J#;c9LYRsUshI*2e_Ircg5?CIUgB0CQkA z@ThhpbvrFUN~RW~u}j($;j*6m%$db!mfG`EbNjX;BNY8fXe5-04wI!tcmA9mT}m&8 zOcg^%FnBd#PGYXpZhI%+_nE)<@bVXXiEkk~V}=3D2IDH=v^rzDK3E>VH%2v=(GU_e z{mm8=?=>LH+F$+ZLzk{x`j!v8U%b26zjSGMZqXLb4L|Wf>7U z=P_1o1#T=rtXBa*BM?0j?kAA5=&S@(ErD~zpa|&Q_APr$v1sJEQ^wxx_W6sCE}#4Q zg7>LKfp}Fgj6@BHYN70!bBQMpL$2h(vhW;pE%t=ZIjz)XZOw* zIkeD}VnR_IY2piqg2BS76iI}TFv^^Eb`Cx9OXnZ`=_lKv9R`IHRuxlE9GR4;k-x6W z$hDZo)uZRCq+0!us3piMCJ{5S#ApH+qp@>eeejEZ&hCE8ohyFv%<$M3y1(`7#lQUu zf2=#PjFyyKP&F_R7KWD5-1h7sj!b^_UmSnvr;e4+q2njb5JOp&v0zmRrCKQ&b>=Bx zFeQyv5}Bz$8RXg%Q&~G(>Rp<>xBa%i{H8F~RSdyS)J^lYhkowpb00g?p-zkuMnFO! zs`S~YX^3e2!`JeD-vq>$WsQf38#`pSMsT1)!a8wQjR~$s=$Ub8ns~Q-d13n-cTDZs z+A9k$hS2w&iF9h>ncsfag_g8@F$l~V0g3<;g^HO}1FR}Pk?K4?6BwihRb@6ph(($D zmLImSJhXJ;(`Ob>7gKw;PwbkEl!v@1hC>!5kX3p(L@W>sF{ioC{`_S7Q~&V6@Ei#z zTupO_F%coqYK+{p6as5w=H|NFXx0fgw@jnrS|MXVr#)+?CP72|`l*MrZ)ra_fBsB* zVR7o*#nzF+uTYW{D7+P3odgnV&1N^vb@v_rg_Y0!{~kO4JBzJy#vuzuj8+PWo0%#j zy#<0*185W!!A1@wOrA{0K*Uk2rs&w`%kTP;yLW!m+)`X>OBSN0oZ`~Nr~mm^%M&2; zh{{qiCDq6bT+7p1T|as)hs@(N?5hEB_2niYjx9wEh`7N$#F}L?zH0;)RK|b7a%l3SsIQDxd#W+cv5umWB1PGMKDj|c4?Ex4Q*kGVQ z6q%~f&tgctM`Vp8F40B5^l1Ow!)KN+Z7(rELkY8>%nSvUGLi0Xg}tBq z@voly^@TRiX{bmw%4|%rDxx$vSbKcezQSYP#CY=t8Hk#GIVFNu3n7%^{c?9P+%{af zd-==7$1m8|BK5_g&_IM1z?@9`9aF{Lg)hjL|M8Pg{Ok!j(QfmUl}09PWD?a_iBwS0 zA+2%}P0dW9p;{|dQ)2RBTxsw~niAV$_x#~MvhO`VboX*{3K%-d2FepMcl@&#AN#+K zWO>K9MMOXjj;IAQfF@H+WBkk4xIHf#jv7@q0TK05toIPFYVLXMTLTu>Ojfdy|5Ue( zDMO(_S=e0v^1}WHZpwDg4|4Y2=Qgw??7nYzUWRi|UJOHZmAVVF1A~G!O{3%EkgY2A z)~YWchKN`-(uI@&2xr`BcRS}lzj*4i7xGIXZB4Z&w`c9G-p{rCmQ1#!Zu=zm$l{g< z|NWCs{o7|+gEq(1c`nKv0#jv>5v-Yv+!Pb&+Lw2<1~#g%RH@Xa6axZ@GSo^2*d<|5 zEO#$%dGns!h2>#?afp)JtzSvZDDJFxg4Y-(F&#{rHRk~ z%tH&G?Pio2jE%@*Gii`k>xT{L=rz90M+!#QW+PVDeWL&(wc8V~;;%O|V$QOTnbo}A zky<9*Fqk-YnvLQ?GmaES(5B4|AX79~CBiQhi` z&`&>l{_{)JOS0A^LCU<(1YuSK%#2A1tgCT4YgH*0kiwwkNQ^C-DI2@2P3>%X=}MWb zB-?rJ;N1LebM37er;eS3LF}I&UVP%>l}E26iwTutCW;7+QdcRs(x|4@N=}UebunRF z2#`j4t##i{Jz=0Kx>NnMh=7oano=53#EJf{@7+K3=E;D5x7S0jY_TgZl<{KhpUPLB z8pO+-Qp<}k%SwUQ>hf{b%80TGV5^bM4V}FbQDZO}6ai`km#Q#nQN+HRjrV+L|GR(W z4f)N3g>s?gT0~(GmM7ev?$`BK{@=fer!%J#Vqmn0qLI>QW&-L#v#;{q|A)WHY0W#e zs7JL9M8jd#F08r+HJKEUNQ0Ylg!$V|&eD%1j0!s0q2Ow65G-e*eGz zaB^>PvH#p`t5YHra)Lb?vela^yLRvTsf8a7A$IDG9^K@N|G;-RT(}N z1;HsXSw%P&Lsg3i>*gsm9k5oLehtQDoJigc#HbLF-W_Ms>Zs zaq^n%$hs%rdSo;-DK@KTXEBk&G!nr~2nH94mCBF|dBzaUj1WkNYB`)TtB54dI~bV; z5HvE8HV$~S(J(D&qvTx~S7MY#Y_Vigc3hO9djHNmxv-*3;?ojk zw1U-5xr3knyAM9{6OT{wOhQ~l4ccJ_w9hos0k3p^#w0CX^ESHHH2zLKS!d$_;>HP? zuhqVEL+q61OsSElq49i6z0K$V;`m@=Y6U{X{go{XuY^Ta?9mc*M& z)Vm;Fy_DX=J9bt^m|9gxVW}C4G-48s)lsMsK%}vAzj`UCjxNnctsF{Cp9|IUs!FCT z;)2IA^FUlbaYdF`mQt2b;>9z=ENUzXOpx(2rK+TCR{3l70azpkj6)|bj)CgwKSg8O)!KjAU0~Mc~Pz@cfo9{rYuWGQb1I1M^ur;SY z<}3q^maEkz@@9-N$cPorNC2u5fb0?>&k9vB2#8cQ5Q0hdIMJ%%1gf=s1k|)NHD;@4 zys#)oV>4oS=Y#|*#W1e4Z}s>6soUT6H{W{KpV;5N#rp125f_9MOeN4OhtSw@(^P2Wvr6NDIHFwGRSS26 z8P$xz(IoXs^3Q@rApsowH1^_|qsQkDP0a1vmYd6sq#A2y!E*w{mEj^y^Nt6$-u#~3 z**3Ryda--H$Kp+-ER9K+pjvUJsnH&+@^;V~w2aBfB2bZ&tL<9Ln^sO0k7@(d$T6FDlInQ;cJ?2L=$T)&dqGlA~=-WscqaI^GjbJQF7NMwe0I0%$Q$nP=2f1$B zF(Q*ns9c3a!D# z`@U(PO!+G(t_;rivsM;W70#R|1DjTwVTxur6%jy8^|-6*O4US>MuDON%=(C_Y4TW2 zpVvi@vF5a~4!-dh51qRI%U3e-V)l#HlK z-XqE<*tvwv5JHuxRG9Z5E(UsK>Y%&-Pu}*IFVnj@ok;?u` zsg@*@+HIeGxcA`C9eLvCpGhvX5~LOasqy$-e~k!e#K^p1Y#ps?_5F%6TF5qSiinn0H-Q;REj;+sEe+?H~y`*=0lt2KO1eWx)WKe5|38# zJA$eb$R#egKzr<#_wB#`d+*qC*YqH}+$;O4Ajb~5*l<`%L?Tp5Ob5%!xhEG-e&Y1y zhnBlf^|NkEN?!%$)ufze6-J}O8d*q;o$?K0<7|r2&|RzfN7xuel}$k0khXebTXpwX z#oB;XT3%okVO3LAPKCsV#d7qab89y9rs-X8+`jG5-2C3mwaVe3Fw>;%6f_8d!PD)@ zq@R8DUqAMRfBCrYPFRt~kbCdcSYV*4&x|WESE@rx9uZ*xRDNW2psRVXX387VfT1HKM=nT#ePk#OKsV`nycxbtJuH=;@ zHJ?&4EHxTAA=6l~da8PQeY%woz0Og*L2T?1*pLS{93XDIkQrKoLWK87_)sdaX* z(rqw}AT?nljaF*PEwAnTye>bweDU*_&VTjF%Bevy09lr_+8P2vQIfeVv7!sneQ)2_ znduz;3=hU>n0Y#UVA+{_NGA!aNWSC^q_4cyir*6@2q^N<@|u^O6`2?RLyLBp`!d)ME1 z;P&r6uuz_b^)u(Ah8XiQ4!v(7q;Z%GPT=^jpa1%QIC}KIoLc^3&s}hSn5IafD9fN# z)kLc*&Zrl=9tOSIO#W(bu~)}v{mW50XoG~zn}E1cZIunPxUMizNU@m z5SzH7sk&_fVzn}xfcOGdM_CQ7GyxY?VpV_=M`mS52DVZNOiU`Go^q#GZ0U)`(_cQ- zUmVWt*^^9nir9ywXio+mN@jCAcRc;u&#s*6Z<*g-42H%UNem=S;<9n|#=CB5wH0^u zFh{L5W0!C=^>5Amq|q>yYhB-U?1Ht$mJxTSR*h^N%|<+J6^*N=#I+w{Ye08ZRe?j9 zrs-r+4vY5SEr0oqv#;+Aiz|tfU@oeoG8>%3@y}m+^yiK~_3w|K{f$d@!l%VVVwn&z zCuR&LBQb>mLeezptpa}|r*Xq?=&Oni zc@k$K2!o)As#=mnCULIiIip^3>51jjkDhE#wfEe<100#4JU}dFc5PjFu6yQ7=Vzy8 zEHBF#jJfi0P;Ju@4mQ*oTI1ub(qzU7Gu5#-ZP~yGR&5OJo{vay&5OP!njYtBVzrpl zIIfN2qFwugs;pw=@&c7)O>1(ZJGiprO|x(Pq1Wf_D~w@j(2yj{tz(}!f8@U&d-~^} zTlnN6pG`ZMNW>E{h$C5ustRUSuvL;mFe4r@(nm~EY;I><0}!!EyIE&1q?mo9*HxP7 zo{cWp7y{KdT+M-wv7!>HL6z7GGZ58a&L=EMC+igFSI$0i{HD8iZMk)JFj&sgY!FLw zoVBKpe&V_Qg}zHN$Bd{<0;am{4rowk{7S4Lj?lHiZEc>fPJ@n3l%nTBL%Y_Os5t~{ z2;|KzRkO!!Z1-F>sAas^(!gaiqJY>rlD5lT|1Cdo&z5>`v*x5dyV&;^?oP`oceb zWZ}yzI8PI9(z7RVMr2w^-Y8Vb6fu$+$Td%Z`VTda6l#w9hTYD!ZFoTBZ~P-(VY|_{ zOk)i+jOfW#SzuNDqGD=;pw)1=YRo+ov%*EBB#wz<87!1x&`o7>aN2$DXCGZUZQPm| z76pUz(BJlknO$$+nrrSl6XX&EB~vveja(}bjEBA>WNZq_V@2R=ox|w6nw;ON+xUVf zzJ`6>6pP1s`#_CuqtLYZW=8dtSoK-0{@o29*eYf=0YFqC-|H@If8E?IAHJ!FD+Lz? zge3`GdG@nsdQT5KGB?ql3nfT6unu9lA((1aB4rp?N{y=3TGd*!Rz>e4wT{iL4Xgq3kMKbgc+S_0$mPsE|`1!>G!=h+x_ zX3Y!#qYswha1?!r-PIbI&V{5f(K$FD zH@r0*zLBIet#!w!@!RS_b_P~sQ^25R8UZsBAx=1ze){CEJyTv$NwQKS5%sLM_dUC| zzHw${agmvQN<9HZ#I?(v{7utGFV$iebsFf5I3}~V!+rvR;j+!w_sEG7|AJFGvpISS~1^DVWz5D zM(2EfF`oRyDW5whsT#9siKWBu-9LZl#LCiTg@O|TiMSBeKngLit!kSZjmNFk;Z(;{ z`D|20uv7)KbxLgvNXJ52JU=qh=x1u6trDnBZlF?(T~!yXdmN2cJ&t-=8u{_jqlFa? zBr)jhj!9|RmP6TX-?}?;X=X*t#khruALv|(t4gQM{!tW7G5O-Xa85(vs1l4GAc zd*R`}^IP*^o_J6ce!oBb{kQgYIcSWg&ZS9~fZ0G~LXn$t&YE&<9h{mH#oA4SnVQzs zm#BRMR#HtUTY6^j z?C+f5Tv*aVm0>x$w|&QfZLgaubkLsZl+lXFiStB82tWX;W?`Nrb!hL_{Fm@Khvhb8q5gq&DA|CsNCuLDMvMEQ( zQ~(Oz)IoXF6lkO2s5FYmn`tey?h0<&I=kjJ-Uzk{h}VIwQt`$)dh5bXYZ~P0$gv4k zK>(L5B~uV1M+_v?T6}zP^fykXAtMq4t7s34hraF5{Oe{HFJ5%ckwqcE)J9z$RrqQ; z-kDOp4O1p13*>1q7$tOD#W%Xaoilmv(88-jre4h%1R$R}l1Pd1OuYSr z`_ma%7&_6QAxS6lGx5p){G?y>oK3Q5`-1qjKhd+Gt zpj>7#=ZH8)idrAG5=D?&L;N0rq^g@B|QaV!b!ny5x~* zp&$i8Mc72cbvAmIk8RxzxIUYJcsT$^jG1Ybru zE~_;4A*RISi4!#l90tjekDu(FE?Vu0!b)PqSjv2U_R3%Z>02Ns`BCQ}Ka6_m(^U{5^ClMGFy;wR8!q{NX?2qLkuLs7<12Di=K z`;ps|Evgu(LmAO>t-(os{8yj${Y-qK)z}2C_(r5wYebF`n;5TYUGo}k+PKj*{<#6y zYU2UoW)9{mqZe2byZVi0)_OJCy>e-Yw892JNyQ97Jhv}DvUu!Mrx>kJs=y00j|+$1 zw`>j?|(NqQ-_rXd`ML0}LW7nDV0N9(?yLTi&#T4$%H@-J821dk;#Z zGRViOYFrPUqA`wl;|&@NT6tntjYS5;nvx96D3aN*=YY}@sY`IT}}r63AmOLg+de?NBRiwhId=70!9c99qc zHt-r|I*s~+*7!TCjpv)T*2epmH}>jmJV4yE=P`P|ch~oHG(*s>A;X}a;#p+?#Y#d+ z6ccf@lpOiB6UBMQzCA2s6t9upR=fGT_gT9PW#JOf-YEzqL`gVM-z_j#R_; zJLY)42->^-+YV$0dGeNwXX$b(i$=9Jj@au}8dWP6BU>be3j1u34`mA-`rezv#84@B#4!eO>C&nA z_^%!J{RvZv%Af?sY(PV=LDL3P!!uS}l$+}EH9B%OZPl&^UuP2#uYViA|3(tgRNtgk zBVXNcYYK~IRy%ePS<=4r$in$gUYPTHvLqd-gRcd=g<*SL51~w4D zM8amQAzBe9hPQwB-gLH1hCSC`!R0&-B0Kg1uCX7hs`5u8k@IeV3U}8_H*Y>e{IR5RpwEQ#UdkCL?bh)%T!lA zoW~V@u=f9(_L6MlE!GAD#LYMF=@pKcQbb9cO8)FzG%^Iry6nj_!6r#a4=Cg zZE~Ar3=<1e^_Ta)aeL=rGPEl#3Kq+$WF|j>Cw}G0bRa(Mm^dQ@9V!Y? zRq19lLK;}>9$vlXo16D~w+#k}>v?yt_VHZLHkxXN)tYGiHyel*R!|5qPAt3lrNy&f zzL-!uA|=X9i=<;a-@b!pE%nBsOxOsjun1O=8pt9Vxt$0yX5plJvN-#-%MqU7qUN33 z-GOi0rLCf*01`1FZR99GwptBu+V`4EULmHbHzcJIr`+!^?SAY0&Uegob$OuuQc*0X zrypJl0}~M=w@S6C3rlrZH-Q+Grk{h{Ie+$xmlI9OqEsV@ zn3yt%8|QCq!rwR=j+33n|G#PLjjo%UdLseiSPR-_mF|@?TIJ={LxKsFKn;x9#2d)7 z(}m>dCoW@E!mYg%#ElZvaOY?gP^UQ4!-0C8={#pYgnWo)dU%tqo527m%U zVoVkTQ%p{L`uyUf%M+Z677ox*%<@KzQHg9DU*RnqiP@^OR|e! zy1e+cZkswrQOe%zAwK-!y}|X%7$aOB)wpfOFmR%{_a)+eQM!YlymR+ACpX zqc)D!+66#}7W0@FFeL1H$9fkYIhUbjXhm*;5+q^&dv@_wuz*0O#vn6_aLTNJF{=nU zGAazJ>eLV};^gn10n&h0AZ@qeo_Ejede=;;%V2ZdDr0~|vS_AIgAu8X1RY{M6h>jn zv2;GdmC0`0{GoZ9DU`J=hD1^<`xC!+$;XMZFk)shL)moInHfc}kuiuPl2(fn?`CD+ z1G|YG;6p4kPKOuivEMvJ-4X;$ATN*HpKdE zBtYD>KR~0=ja{|Mje0={ASN}lp`3aA;-Ehi?-^`1bO^g&zkTL#$0Q`}lqIw1nnD9$ zW?H>eqTtC$O9MEjCq8jz@oZNliHL^f_U`iL5ARF2Q7H2?cOQmU+ZRQ0cFZQXqepLcfNkU zq@pZBsR~EJa1>w>l?07Opc=KQrX`b0e5d>9@YJU+8@!qhwbz~w^KWSFc=tBteo^#I zjT8n}K@&|{rc12XlG^QuMbiTY4M{IB&X>3EpM8feQRgIa?R2~fvIfw#V73IBYnr&u!u^j8G~TNpi0OB zAQKcsRA{E`V;pR`ee(A2ySZ!22~FmaB7Faxp8Uc^r&4~wA z6~CZB6lZ491d=7a3zyT$kj<2gSV|EzB;2}uw7dLFFLRzy)>9GCOp3BP>pB`RPuD`j z&5o3pXZFIb^@do>O+b9j+Zq_Ghd$L>#cU=ZA`4n{3D!y{R-PUlfAGAZO-PxfEC$ni z_}zc)jqYY$9xhU9X{V)dr4oq)6Nz(7RBfafttgnuOEx@KocPqK6bS=RCRgsbDcgR} zTr$HJqiOCm2t^>)^lVgE$Oa+yp-^KDF`qu1?taT$Ec$IzgnYI$x$;DK`d3aRrH^F} zEmcF6{v$PsWblN9%#})<8MM2+a&E_)=HK$i?nNh-ngeAb!ck5=bYXZ|g+wDj1h%m< zGGPRTG}3Ya#`^QFX`7++3)zN1yce-eK)eyG=Cq6k8e3ghA()UcQM4G?m_?|R%a1J& z%MxN{0m4hVxc>vYzyDu<`>o%%Lnl{;!v)Vpd%~HJmeCoQf@&ZTL(KxB3bD)6)4#Q_ zd>YPALerowcEbGYw@)0H&WpZFxYCDZk}6gfsI$NpO(PUwAAumn*0<05J(;OvC8w^1 zsk5K&hO^GQG+Km4N0AT|qU4OoLJ~s;_6F~gu zG))~NhQO|M_Uo786_83%8cPD%UWf2F&81s1bT5<|!a6~NXm6V|$Z*Sv!_)@{9EZH>h7Mx@t? z_sOe!1VrRqf>pZj23)1<+{U~bqNcgI_8CE3rQC#FqF@sAE-g+TXz#lB;7~&hdCHNz z^kNZwNDs6Ry=&jDd$;tKdJ89(LlIi-mLt&+o%bOEFrOr5Moh(0$(io@=G`$V2^xvf z?wmi-Tlmrfz>R_Ce+H0(nH>w|aWHu(Irt}b$E^cL(Lyob+IHgO!>|9$NmsU` zMWV_lRZ|dJ2oOgWi+;H>d&kWC{@Od=`q%ElEz3*!SqFi&hz$ra#+KiH`m^05|K+G3 zBmk2(i&QmFk4*#st=_oL*P61~#r*PY90^xb6huTJl+`%ySMlgJ5+IHiXd^y3u3Hb6{de zcIo)#?%8hQJh7;XsxqT2EhstmWaus~?|AdhiG8git`H5#yWX<9_^B)06;lX{suIIY ziK-e8peE$WNnZ43-!QS~k8DG$pQ#bi#LBiW{@AhQN25<$2qjY)BZ*I{9&Mk{An)r$ z@rFNs_`^T?_8lLXxa`jqx;*Jw%o}AIhs5H-bpLqzwV!)x;VVlCwPI8m%~!TcNzz90 zVMOcEa9x1BLH(Q!XzOwfn}GOyw#GwTJFjQcUIDvSm2_3czj7DND*8*p8uC)a4=)do zf92^iA8fyMS2{g2424($i>;)UTG}0StzGVa^UnQm-n|kQ&m6s|J#yYDk*XQkiMYg* z*x<@glXBlXwq>nY42F@YRZgG$9~bn3iOYz^EH(;>mBy>F!ibD1FNYHc+K0b=UtwJ> zbZ_U-$zNZ2=%=1-^OVI>*sBsGEptmV&#c|S!rVRCdw=weZ~pVQm)ozbgtNjdl$t>$ zl;~6H(qik^e&O6#e)@^apS_X|T9rCvom8btP|-#?!%7pIuDyg~i}`9_;p@ZfDm26< zAYRMX03wZv&TVeASIVxH_OB+*H;uzYL~KztGC&~DKAaC{zI^uFQx~X}ZM|t;(j-JK zL*d2Jc1PI_LpR%<-1@%#(|e}QAHC2!-%B`gB1Dlih>#MCxrHN_i(x)@@BVCh+obI{ z{_7V{e*8jGX0cQwS0x{rswq=deFFw2A+v%q+y1&8+YauUk*$M=_>+JC!Ejc*WKlJ$ zf>MQ>+%lCHizRw@fA699{n(r5zuA`K`8<@~w>?v|G!$j(;oZ#2sq}L{{q?W?tEa=$ zDqY7~&AsBfuW{r-6OG-cYnJVrD%qR%Qtj$R+yum{Z8K#4gSFLs*xHnK#kZr%2Wr%7 zKpsyr_MQ5#bBR&?!gZ||M<~pbn}fH=|LLr?ULyY4@S<~IF4Y5SH6Ay##={p zr+@t`x4!r9Uv-x`RF@lM`^T5+PfAJrD>QjIBse^}Z567uSjANZJT02CZ^rL|~y72w~e)+5a z>|5{sj~ly(gT9M7vrGLfw7yEvFRq%!nzL=wTC8-OrG=i1N)l{WyUe(=GuYtenHhTbG+x_S-KlRvO`cwBm zF<^UqFuLV{BOOI#5U_79m{I@xU;6$xe*4A!AF+m>(XmFblM#?6#33AmPT7Lt*l^^W zU)LEOFxwOX5Qve0Eg^zsR0-SNBj?LU0yy_c>q>kE%Q(7U*oGPu++I|mB6=i=c*AHV;}KmU~5!JC(E zCf7%?)zrkW%K311ZlUB|W#(X7#;k&{Zq_u`rLYKCuf%)`-fmck9TUVNr~=aB zmP#d7%2P4Smw7^DB1FZk*eP(xoLVMh005Y^j-oUk{doM&3-7-7!^^!v^vGk6#5;X! zCv4HW!-L`bL$(h*a`9uIdHlX7_HJFh{r;s}Xd_gywbrH+V`4x~*(wvK=|m~T7Kq3( zFsfpSQeZ!ww|34cU?4?`37thqkxdj&lH+6Af9CU#fBv6-_WbkFjr5I(Q0ab}j?s-H zwG*jbJ-&G94{m(zH^2R(|9pww>kZV7VsfcPU`7pW0A11Aa5`%PPCw)$dkUmIE&&n5 z`BhZ05JeDILB+zEdT>f^F+XC%ub_gU9BBYRXCbu;SQBc~!;w1p;r=`S_tN|C9rW(s z+k5Z>tdxaGFYb@iWPf;*bh`JE{!_pBIPcPp*KSVU9|42u#m)iS1W9q+bI$qRQi?>R z{iF&iTRjz!k%<67p<@Tu83t6E9P`1Q{qv9Se)c#1+_Qh@nd67<+;$^GRFtHsQE8*J z!M*)A-8X;dl|TCJSMI!&?D4=5C6hD-O-4Il$A*v)Q(*Q36Ef#vHfaxH&^rk&L}3Yt zAkMEM5ybV-T(MbbzX-E3f+NiGQ2@YMWJawmqjM=ZhZOgsJ-$s>zkB)e%a>E;y~p>W zbJ|YEy(l)3No+E<$8aHe>eG*Y_!EyF9~>TBJ#Zs1y*Sz(jECc7JW)z}DR#~hL1Z*A zrw$nzfqXg;qe8Om9H~AUIvyTifAs8M{m5VbC!hU^zcRR~-*)yO)`UtqH&JAE<8yQr z|H+r%{mK`<_3r<=8XpV>X5bQb>`;M4m#{@b#SEB0KHpC^R2ipn=4sWScy5-B2;%%I z53OGnr5-1qR_?tL2k zOx0)&5v2+9@!+xO!=HO{@3H>Pt2alt5*RZx_v5}BCxl=$Du&eB)LO?#N+Fm2%N9V*UIv*chCStMRFAa5r6YnkzluZ<*Gy$*0?v~X zrTZ?i1VwcMG&Puu9QmC%vQF?e10%5l)eo+a@nvJ zMgxH4ELgCPm5DB#fAH|F(T~6O*8bJ~a~~d@f2hYU;WUkm0dlE5PAN6_nTLM%{)a#L z;H}I1H(tLUtG@2Vhbv_twm`s<9A@V1k!SG!PKcA#gU6H2T~@)H*U` z;E1oiboK2QuEhPFhn{{|^>)U`Oo(LS6imX2j)wgYsmFfdiQXeSm*0Bt;N1gFky7ZG zkd*J6_xe54QRDn}0$Bh68xBcCK~xJ25gB4|;M0kma||?n(dNGne2*M3TXlfF z{}O`HQquaCPv-@(ONrT-r^F#hdiJ{E(tD`LnXRlV$5^aWtbMwULN!S&D5r=>(RehT z>>fV#E028Y*Z=H&KXq=H-WaDx91&>XWC)0$bszWkzc+sMi?6@=Kd%ihB??W{-$hMe zc|0Ck81Mds{^?)*$R~d7Q#;SW6?|hj8XGX609-=cSE$d$=dQnU_(%WlrMG|YN_=Oh zr~2qzIvJBTmSW3H;FMp@02n|45<)h+Pq9(~BKzF*{4Z&4D`p!KvqcaE0H_KgAhUx! z9VJ_*s;~?5Iiv{yh*2Q`CV=FaSSbhT(eeJ^+1@99{h6nK>4S7(I2zsoa;Vs%a?A>8 zSM5g0?yc{PUj5y--umjLJMSC~-*-B~-3Q~RKl9;F{_4lh|14aOZypbi5Q#M@0&6EK z;l94FH#-0QFTM87-+5{D8tg^q6>^d!pvkX;$BfLr51vhfn=d5F*=WUF=QA@f7^^4E zZMBFX&aWa7#O_d@RwKU;oC`^ymk8_!{bgCr3A&~v zgCE;DO7;^w0>w%LAWkNhSVz6xXtImf=*qXQy#D1kAN}BCfBILR9zQld();_#`-+?a zO5BcB3i$|~y%;@w_$GbxUw`+F|MreK?(akacNvK0J!_qF5E(PF2@)X#vIAtl>{QW; z*r!`_<{`{n7@SR`QDS<8_r>?W^?z=C?O%Q8&WnfV;2byt28#}yCO}7y5y&|}KVbv{Q|A3- zpKMLVf~-(9E-sCY5?0GL2x9kwDiG+PG}ZM}p0{o~S56j&&9ZW`z-%^kAd<71(Sb=4 zL_nvI4YGq+@7inp#@F9`_tndfef0781HG|xgb^T+Qc5Z3(n&fwm>eFf(E%L}(~(9} ziYz-;hSQXo2fh1ujvn~q|M==3e&L1m2Yk+4VCSr3LD>?hnI{GxcyK&9R6IgpcGi&98bMKttRsw#R-Ce(Xacm@qrrLe$l?F9 zU-=g={PBPIVZ7hpG4W)a02)Th$6^y=%HXn<|BA78K7P!QFARXw-y(U}n?eL}eiexz zb`s8qvS6Wjn1x#)9ORa%5W0L+3xXqd;DC^Pyq7KZb_U1q9bbF<>dq5`iyu61j0RwK zDZ2@}5h5Tt<;DiAMgTC88l2M)scVBD{MWaB|2MyJ;~RI**^3C|oI^$chiJV(L%vFA z7C`ZW=NBaKYVDN~P<$|(r}Kg+K188Pn1z@5d5S(}_Qy0voB0_1KM39!^U-JvXb}x+ zND@bcDm_-W`p?9VKmWm}o`3SPL&QSY(%{txwbAL{QsxYN5B z`_OeVu}9ZNciug^_1gaJ*ADb8vrD@GJQ`Vy27|snJibMs6*7ZGB*%n)LFJ-wMJJKO!|u3G1WlM2LW^5$<7ps!TELl5Qwd{ z&QTOasFZayaY>R6ts6P86eAb}LO`~u1B;aE$eCF8G$OOLju{oA=5$C3I59{XXu`k% zdaAvUXBXx_13`kwU1A#qQE$4E0xG$|qK0cEoItiRQ9b}nJ#fJ|mfQhUtlF8+DhPfA zwEsX38JHMJM+C^pBxMGpvDfRf?EwQj<{>j7A%Js8L=n1#tp$s0odR}oj47uM2>|^< z(%BNz90(u=OLSDrDF_SEB|(&F9X3Q**_Da%r%5>ZtOr>xVjz~@5aS{OldVWX?7fXh z44|Wk9ZZshNFz9~X_`97V!s4PgvgEowPUskA}~-20E)D=WPw~d0YK*L5DYH|^ z0Fy$lIMYstfM9*U8<~h12^f)>8PTFv);T~Tc7CZo1m!JKE)LJ)$=M~RgRn&uw}I~5 zrS1u$s9^`FG|(_v+kai^N*5+%ctGHP1Ykex8Nd~mWEPChbj<-WWr@`;xCR0{+5QPm`$;Oqk$KUTst;l}!48d$cj3->l6i1#%0 ziEWnEcV6f`v+}&dKN_t|$yePvwxqWwf_SepD{Om~6D}H2#3nZ)l#g#5AH(IHg{=U~ zZ{SYSJs+a`9wID155kw)Q?zr2G+O!0e!yUpdv-BZ^ zCDF+)v#=o=gB**f%@QKim5IEt>0FJ`Yf-~(VH;juU9k|Q-_sE)+0YB#mnF3GhH1ru z|H6Hwp=>Ew>xNYR=m26N3RUWJ#Lmi%S^ibAsmmPhvkg{;uqfQQD47VN&`jN>7=8t_ zrRl8QXFS8VLI-JS3LR5J5J41Lz*eMUbG1FqZ}kv9!Ky7wC|MC~PB( zEK({60FB<|X?(6DEob{J#E!gK1VK0(UC=}V0OlvF*TOZCV2zM7KSJ`=MO(`y$gvG{+%_!663&M(s zW1~0TS)N0EfLLWVXRMi3TLmw$1!^MLIHGRVhS%ge`@cw;}DOao)69 zV{5nSspT1BK5IUO@Y`7<>Q*ttn42!Ty31{fS=!f9A+Yf4c&lsX-k~FcIM1{TGk~%7 zMB;`ukxy>2HLk|)7G7Sd(JE;yz!__zQls^CSC)IZP0?cIEyY6Q1zvbU7koh)0GI(W zi?ej4D(VaG;SBA=8RvubJ)I|AC6H&R+aiZAGEHIQ{-@|=*UP!!<+kgA=Xq8h&pv9w z1c1BawXrI=^8dA8KMOGf0BA(idgFoSrmDahf3$>CDy#agjfa@ER*0w((HNbkcB6?m z%P^KJJvvxZriUZWm;aL{^c0KSs``*o3*sAh^X@3jCwpGaT zske-v#y49z%wUX0MBioRO{XC)>GjZPr>*AE$8e38>yz_#xXr^>%M+0~?F0;f8*V9H^oYp+p_y50a!yYs z$z}wxscB>(Z!eOkGdU``Ayx^*{Qn#0DugPz`WU#F>@3dt;spg(r%c!7~Z_`am`st zfFZkuXDCmtO`Yz2RFCJjr>6k`0@t0zE$Y@1y@lG-a`w0CQe1)U9>(^VPyN{T0xan# z7mW(IS%T))(}^mq;k1ifX+>6civ$;!yQktmh3GM7o#ret+ZgSfv(~L&4G{qWIOsP{ zb=ix`88q6bSWZLx{v+h?q=nB~e{^O&`N4CJO&gmE*KSD$)M0A8=38-rM{|+hbK|4V^@j=^ir z8_a-QI;8&77HKI(t%mX)7I49iJDhMgZu;2xba6$xg3>lN1fE^uCPj5ATtn0+e4kc~u==ixyg{L?hO=tEQ792-KUQad543dSG0?w~i=@H1x<0(n z`5c?vmw{Q@jgPaWsr7c_VrsM;7^cyOsdI;cVE$byqR=gSGIRCW2cLymJWIZRIyE$K zp(b4K#x1v2V3C*ZQVX!SX9~iu@ky?}h#^xcPrFf4c^l3uN#R4Bd(ov+@i3n;N&^ct zyjkYmS!q|S#sW=mAp(H!9COn_>kc#*6F3l26dA93kWcQAnTt`R$Kzz(UFNJwm+X1O z{E3_Q+Q|GM3tE%@FE@W^PQ|O#q)(@d1kP!op(k{N`zfBcx(N>=KfCJ|vLUE?RkjOl^2n<;5G8dPucbR$Wu`F1OAcX#Vvxv&LMlqB&;-na&BdnmFF1t=h`!up59h^P6Su#x+UztA9nD=?vKP#A9iMj1K{hh51ZF=(4){DRXf*#t2*LXQj zy4<_t&*o-TdTVcezOjt+xAW{hXTC*h-85{{Q<&jH>?}+13pd`T>UU>2?Ls@X`gF7Y zbC$P(XUwuY0mTG}yW~0LFVx5{E0*;a(WRF2-iDJY!vDGVC4o2 z(WfP@x7Uo!E_{b!;E^h2_Xp0c86~SHSJZarrXEy79?NWd|D^Y=Ug*3ZmoAEdyTE|=u%sd(%))Mv0pJsoVpQUyOxpn8tsb)Ru zGYfr&AKcQ?+Mo7QUvz_gq?Q6KUMf8K4LTiL*Q;Kf6+Ogr-%oR5Fj#&suKWggL357d z*!cKzWrva=79nOLB+4z>Rgzt?S}bn)w2+T$o-1qqx2wA~b3Kqtzx2kR^E3X;_0-bc z%}T55EH3d%hS%x^2LgSoUs2Z&U}mkgwQe#=h-lSD;>Kqi69nO2AU6+P6dA2`F*+rL z>c>FdaaL;OYA6VTupYd%rxX>P<$9Ny>c@nO^7)NjTWUoR1mQ#kbX$vv^EcFS1iHMMM-CM4U_#F%ktq=qwc*u0HSKEKXn{`oF_zX2nhv1ff%eqEv|0 z;uhjUf(XINd_KV9uPOl+K@hq_A$-hSj68;uggq7&Look3zOf*`D&P=9<+IS4DC_-v=J5T^^4ia${hgbmW@H(Wf)rp{>u5lV{(iY5wz zumNU8QQ!L|T|(%TYlwE}R=;O;soe!uC@{OZ?o|L=?xy z&&mobh~F+*5TWc+j3NU7`wb5N1&cB@x=^e6+*l z>Nqw;sI~SI0h^c=Vcmil)`E{8DFuE&%Vd(KsSRri#AXzPyNKN43&$ga)+&m$R|5dp ztS4;)f>;P#7=s3YI5u%?Mx#mo#wc0KqKtyDrBtHB;YS~64gG1SSSoLcY{o)_a$ZjO z`+Z!=T05Df#aB?Cl2(*a5V}t&eBpykL{X%@8C2|9dQ zN#dMCMCV+ZTFfbq6>$`VP7ywmh27`peDP_>L=;7)NX6^g462(K#DdVAE(YuP7lw0= zC`H61NxejRziho8MIr@ZZPXvP{Nc@3qbuak7)?ap>Mt7SpB`v^_EU?XU){A5=EfQVX?-^J@7Y}!lMPUi&C6S^M4z`Rn^ zOmZRi_@%C3Hnt zbYx+4WjbSWWnpw>05UK#HZ3qSEipAzF*Q0dGdeLeD=;uRFfg*XzNG*F03~!qSaf7z zbY(hiZ)9m^c>ppnGBzzRGc7SSR53L=F*713rDg5{WRQvCN0Q>$jB11><1L(&OaUp&s*UXC#mrNo_jcsV3oX`oX zc^milG{eUgZqt}`!==n7s`0%jA|gJ#)^>)PLe&vG$HOkSAQ<=GK?v;(T7Y%|V0-{D z^+kQ9>9qdk?N-O_e6y5^5Z6=8NM(6-+12UoCihE*tgNc4v9XAV$N()4t;f@qa`lhU zA0s>02*BlwZvh1(cfiNL>K_9znJpGtt>l#BiSWl2_(Zp2EK})C?#=GQ)fVyFHeg#< zN<&9HcwI{g6p=v*w4|?oEug4vs@$!lM}uG%f;857rgh)m)^hJpz2#tffaV5WP>8Tk7KL_^_0OqaDU{Hh0kZwS?vAC^+CQW zF6n;p^j?BA_GHtnDh~;v8KfE1?(X_1Q-tT34SrJWB@M5_ZzW@McS;_q#ds?%w^yxAVL8ThJvwWAyU}hs?W_4t5YN z72pU36{cU#9bJA^72h^zwy>fexDfHPX;J0y(E6My8ZQ>8{EO_4d$Sc|tXTce(3qNt zL#g~~ib>gL4rM5`(00b@Kxh~<7M&+859?CxiDpri^pp*m|w(WWS3)IJp84`LQwsW=@d4> z%=sb8U{Qsc&n#qReCO*&XL9>LZ4AK$g-)IvGdq}woaG^{N?9sL{~NCw5GwYQ*7;Wa zA|G)@qe=h2z%8{e$|b0asFLXt1 zYtr5SgsV0ZLZT#t@{xbu@kdfCVu?KXjX$mRKTQdR$iy%HR3Ou0_`!}=I49NoXB$Bw zq>dYwI%omCrV7u0=3H2TK<^*?1V&K1v{w6{I+60%LTo+8zjgZm(UjJaD~)dK zzHcH;NAyy3{1*w|u4HLNc&v3KslAOS`$zQcJQI8T^7u?-zo>ZCDvtl8$25(A?jt;= z@^izTdZ_px8JL~7mV*pn>HN^u$DW|V^1rT0e&zL!H7PmsPt)POTG$F(Q&?1q_^fyi#YRjoY2B0B>)(78iINOL_*1-bwoF{Bwm-hFgGXJR`@g| zL+C5R@v-tD)jlYmQa;7rnVsqUo@S%}PEjv&Oi@1-pKI(v+H7~BBFo!<^LEZBDw~^Y zxxKc~Wj(WeuH*UlQfkNqcO?j;A1lkH!XQ&|$pg&r3EvVfu2fQ^UwSbVllSEmJAXeX z*>+@wBbRVHXXW6w*~UqBADy?VYClzIWU&c=NDPRIO1a%1I`UrS339c1$ih2#IZMW` zqgbtJ0<(loNN=hJG$(~xhSp2tS5M6{l-vf#(U2Crx+nlb z0dfxqWXXJ*$}cq?qw`DGDUG1gcI67Hb2$m}FEE-DfU=VjmrB1|RvKgYWF!BQTUXlt za^uOw&leIkpUpfv9v7pFE;@%J#?6LmFLi6*io49EV#ds?#;7NbWe!WJ zwV9gb)Fy+YG{{5G;lIyM(+{2n)>|j3(2HA9ueN`V&w0A#V3U4OAe%^$sh&5bKp1k0 zhF!2CE`r@K?Vf7YhFvRfD1YP~4wMoR!xDlxObTT!xM7ZVap({Rg;}&IZnz&L9fL-R zj1_{{!}-;HWU>w|lA!;vpG|_awRs$uTGEK5H8Pd7I@bq5UD#;^5qH=mT;gRdsaY-g z{U$QKOLY{S`M5__Yo45A7j=8gV2-~GS+h*o@DPpN^C)7}p&rSbD&8Y%PqMc;@i2v! zfemsF37pYdtVA$)Qf)p7XE|qnxBJV?ypz&Bm z00L{kYCkIHxt&akxoFXKYLF= z%WPOhv38!z+26<3wVgg=LN^r4x(>E)FltFVuYA&?9e9`q+rRvd4J_-*spHpU2It}F zy45Gv2E@+P@zuh~;nhYTZYd4iA%I{|X+EyJNY*K$3ev{S1AF5olEC+B4xK)E(Kszr za4|BXc`q|__Op7>z2fxb?U>Pb`Ry~!dD}EXJYrPE3q=!D(>~`ny&X^doXM0&-iYOW z=6($=So7E&?sZ2*n|j5<*;sETYrBxOt9e;8s!-uZ?Knxds~ck1uB^`&6R;zV)cxZM zX6(cA{R7F);Fg;-Ci{68@9CSB-Ng&ib&|j2rYO2WUOJNs%d7hJiuiTTR)&LF}^oS+oQc=kl=%PXu!~aT0ZxdbCN^JnXI|1l19@lcomnI>J7(d&TBZ zni94tqd6(H+PPQv;_I$~URU=D6~C1UBg0xwf{rYQcc*p67DW#D5@Cb*lU-SzI)l!2 zs^qK>I@ECkkakQbNg3)ikY#2W=nyw!3ueAy9w#;L-cHTfx*)TavL=q{MUoo;WsUYa z?(ypetED10c*j~P?J}N`9&`=zY$K}7U9_&C=~f6em6x?(WlIF+hbLj#g_cXk`DL#Q z0n6H;Em5A0Q}?Z-)T_sw3rQ0s%}2vZC>87cCnh?4#^u+nscSL4XR&FQ2^AbX4jH^q zbL|X6VENvM2J;>!@H!l89d+Go_KsAx!SUH@bGDH~^KpATJ%zgUkfvRichAB{myU<} z@yGb;5+!Is`MP~(jOpI<%c0b;4$bCmP#>EpECdyxs;DL+$YRGvh-lwA?9L12{PJw7 zkY`PXq9_VtUkWQ=GSj7NxqzZ?vV51OXzfaCx=TCztxb<|V_oDx@nb7;=+J8De&Ft8 zr$C?Xo@Cl34e^c;4$A^BJXToVs%Pg=d?g%SpD|+n*}paII%|@dG|Q4Z0~YzSdxI@O zNnL*q&WRgXCzBsCy_w$p_w4u))9r)Pss*3A_HQr_!tEa%x@?9$h^PKzX)&nCa zCBcq_`8KVZ*_{ecr1`NSgg^K-A7iZ#%x@y2%)5=cb`bYZaO*;X1>-W7a)CBQWX09g z(Hgn{wj&Q4GJUkM*nm+l;TYtB}g^0=#lwCqUEE{wM{$AI_E%0m-ybnIt^WciP^m`~=g{CHss;He( z2~soUtP(!oH)#BFxSnMtRL&ZAn?mkSR7mLvn8E=ejm8d$z%$5UoigEXQ;V1m;y7+? z7;TgJ^H4iWR72b#8PIN9Lj^NGtGi^92@59kt6W09)mqTN^qv7%Td!pd?$d!2P3z>& zX2{H&B$8*uF)U`4A|v5$aCKwYWcc>wangi~i&luUd93)$p~Z3Tg?1^OdAGCO7;S74 z-+huZQk=d#-0{kx<@Aq1f$*(@A!VaJ<(@zgv=%wryep5QYj4qj$$A~?TKi9}3+L?1 zqe}yQj~nzKQ-hgZoU%a>n&H<-79brA1e4WS-z4@HdgZwWqXxbmjmA@PNx45 z1~m;suyM+ic?v$Iloym=tz>GCrFV+3wkn^avNmZ=!O(QqA+o9p!%}_!9nT+ZGdX7V zg6&~<@FtQ|)oK{=A1JEwbN^UDAP|_lK2GVtM#nimM{QsAW4yZj50s%JCF~K9?s~GE zuMY#+!@hp}+RyCSxLna-Lr-~1|CbEdx7EUq9t-rc_kbveCdeU6{R8lvXTPmr!P*d9 zP(&_uGn9#!;{StkR-6}H!0W}Y8~@-c8)>F(_MEjMKqW!``@Nb-3(`%HRPPv3El6D4ir7`m2Mt5+aOoLgj+I%Q z-+WM6kHK2&F2C05G|q2M(Kd%rlI_5Z;s+6X zmqaj{luuZUXc|6mGwgWOl-A<}WF%`XEr?M54qUVqDWrwLvtO#D zXn)7=9Y3-DPkAMQ#I}K4j7WM6KLk%D#&BXie&XmYx#(U5h;lQC{#$kH8J1o*x0|Kb zi~j}?5Bt3g&4=Tyf_D!=ZFO`hnppC0O*DgLIo2Q&TDZjM!g}7 zJtP>{>n2r+md54TTm|zxl|PpJENT@9gY(venaiQ0K#@ay>E#(1RmA2K3CO&b^VT)< zuHVGY>PX_Thw@YGPZl^2t>J#&dBcJ&ASlf+Tt-O5=*;FN)a==>NI{fi0K+2JiicYO z695e5GmuaQAPl53k+9q_qsS0QtN}F;k?gp(!*A993@W~*RN^Ivm<*9$bS4K=-mTOE zure4`h!_Xq-?Le6^R2@Z?|sSi=c&hv_X-ZMUG1NEr_uRXsJICx?n5YS3`pWUp2u>y zl^^$72SLu6$74<7M+N@5q!#IpAj_&wyl%Scv#AqqGc{@&J+G@%WL_dZ2lE@~fgP+b z-8UQ`epYg}1K4uTj1+6LKT0+i5w0qo(FP04tIKd`?Et*qnz;6RW&x30MlPl1fq|>Q zVRGCrDS#4&QmFlqjq?Z>kOMKF$3uJVmK^!jpxV-U*~RKGxqMw>-_KMf$XICP`8W(S z{k7mIRuV*yW*0|vJ~NAnwbO`CkyJwoL4%VA2CIj3l4fjjZDF?!zcsA6?SbqzefRfR z_h59}HN8NWUEHNiQgcnsvX^3ul1|Hp4jr19OtRG!uTP&id-$g3jC&A_;x>S-#(J76Bw4wiFzgC~XiG zh&EY-vPk#-r1sDKZ|bY(=8V=u&Fw$goa=$7H0;_cnSysDukR?hZwWa@54$vzRQ(|q zL*Oj+j9O9)jcpHg&KGB)qLsHc^Eh1Ct$K07-cu6#z5PxJ6uRJ zrJsiq*5@s2M`dhT`+r07y_Jy<7itOf(oAg%{IxP6xf%>hI(31boCFpHI={nD@(G~+ z_3pc>jgL*+!(Q$Ad#nhmU}UklfXhAi=#7?@7MJK#ub&a^fntVcx+~S1Etu%o`_NrV zeId#dc|&xtHNiwp2i{4V+xFPDA2U%@j+qE(ryZHyYU$wV8-GE-TOa#XZ}&=c_bGWl zh(vqZ#o}4T5?IQUcR&n4RDk(G>6^6*M8QgB=-fv~YJmE(d7hjzIqbo6NouXb^X{2B zCiBmP;v5_+itM#cpFmg;=gP?4yA%Ugm2F<4&s)Djt_)tY6az*2!hN!KJoNVb_mssL zB7y|r>)Sd2Z_Xq?^Wqhsi1!jka6jr4!e4K zHh{RmQj3`ac6d7eP-Ai0BJIK>?CE#cH%o+x^6C3(DZNV}}2nFyy+cTQ;X@LD(K>3nY@Z z-vaUz0`P`HHV{HL!F}r5QaTrrVKkq6EdiPb>1V^rBR%}|5lWS6l=3GfVcVeXmw%#M zJ7~54dB9yAe)BoFoY*GMysya(id_Rk(%Q%kN1`)6Nkz!4FR}MM?skscpUxqYW@LiM zPu3GC%WmU}SF4&@)d@AExOxy4jC@j%(-ne`wi&zA^^VF&u?=*SfENxS#YaSLaKs!8 z!YR?p0p*z3=*f86zOkuxe>(TP8txYON)mQ%dfIsf6#+It^1Q+GkE3Wg{!XCMhGyK) z2CRd`VR6vuTQ;SwWy@~I`o$(B%u{H(zQFMHZkxyq45*|bfYvSM)>tDL|Ah07e)yb- zbf1fE58r1c6Ss)vhtqRclADoQ``4*m#-31!Z~=&6e{K$A$GmS8jF6ASCn5k_NQ8PrP%6k(aHfTy_`pzW6EpCquLFDK)6_ysDV`=*wl zhYYk`G|w5Frh71T-IgBn9^g{+Z2*?b0S4xg(B8 zAbp_m_|d^MJRbAQ@I?t60L4{3(N1r8>~_Sevl~U5nVYAc`6RL==9l95d_6FD;Gj1E zd5y$1dV+~XBq$h;A$a3QBpnMnZuoU8zCoE?Rv0zj;Y+w9cbVtP5rK_aL4FsdUW`=R zrA$QS@YJF`+48>mIEkSOyng0*b%A^fld^ICJ(I++f<#Dq~q zUSaXAM7$1>l%8mozakjS1>ArcBF&iy@KPn{Y#$ST-Dr1S#%%RE+(9g9`YZbXmKs&= ze6CaeeEXI~=k>CGLh3rNAXqs!qAv|ik~9dt+aYj4Ixdxk$G3C;hwOJo;MA&_F=lP*uvQ%l`@1yKIL%!rQ$|f zO2)*MXv$Y2D?40{zaE@hHqv_QKit;LVWhry&tAMqX$LZJ1+xl`%B<(Rm>z(0&&Qtqq*BhMu%f& zWneIW=~STf7Gat|4@5>*xYH;{kZ523eHv=pRj?Aw#}$U5h)z3x3mwIG74TJ35JFy? zbi?^9LwCFA>E_pjLzJTm4;=)ZR%X?aJ`O!h@Y$#Ie6R5e+nWTtRLNipxH^N>@bK_F z-uA+Mdf$Pqd^SAL@crHzn0euV}VBPX6z(G4h%w4|Jtw=8?

>0G82}8uG(9~U8RFUyF&m??H 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 diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png index f8871203df4bf65bc8f0a7a63ea4a3745a9b68ef..8f88f752e50fe49e3bd9b6a1261deb933ca1f35d 100644 GIT binary patch literal 4353 zcmV+c5&rIpP)004R>004l5008;`004mK004C`008P>0026e000+ooVrmw00006 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 literal 4064 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 literal 4064 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 diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png index 6b96a9765ed99150bf7ec5dd623ae3f0088da6a4..2a38a94f4e1144830b8dc85002c68294dcdb4c05 100644 GIT binary patch literal 2536 zcmVP)004R>004l5008;`004mK004C`008P>0026e000+ooVrmw00006 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 diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png index 22274ee435d9b67c22f2ad40f9b0d7db925e5d66..8b51cb463316d7c6160dc9c70401d2b3f63ec307 100644 GIT binary patch literal 6562 zcmV;T8C~XyP)004R>004l5008;`004mK004C`008P>0026e000+ooVrmw00006 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 literal 10959 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 literal 10959 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 diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png index 9c81fa08c329521cfd0b51786007632fbd6066e0..7c51289957c3a01c28a17ec0f7d8a033ad2dd024 100644 GIT binary patch literal 18451 zcmV)0K+eC3P)004R>004l5008;`004mK004C`008P>0026e000+ooVrmw00006 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 From c49f4faed3957ca05a60606a29691c9f1fc30afd Mon Sep 17 00:00:00 2001 From: Kayos Date: Sat, 21 Mar 2026 15:21:55 -0700 Subject: [PATCH 16/26] =?UTF-8?q?fix:=20allow=20cleartext=20HTTP=20to=20al?= =?UTF-8?q?l=20IPs=20=E2=80=94=20Bee=20AP=20varies=20by=20network=20(10.77?= =?UTF-8?q?.0.x=20on=20truck)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/res/xml/network_security_config.xml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/app/src/main/res/xml/network_security_config.xml b/app/src/main/res/xml/network_security_config.xml index 81d34f7..2439f15 100644 --- a/app/src/main/res/xml/network_security_config.xml +++ b/app/src/main/res/xml/network_security_config.xml @@ -1,7 +1,4 @@ - - 192.168.0.10 - - + From 163a32068cec71fe5a5b676f633f7764c8b7bc09 Mon Sep 17 00:00:00 2001 From: kayos Date: Sun, 22 Mar 2026 00:41:13 -0700 Subject: [PATCH 17/26] Change default Bee URL from 10.77.0.1 to 192.168.0.10 (factory AP IP) --- .../main/java/com/adamaps/varroa/data/SettingsDataStore.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/adamaps/varroa/data/SettingsDataStore.kt b/app/src/main/java/com/adamaps/varroa/data/SettingsDataStore.kt index 44a352c..51d0642 100644 --- a/app/src/main/java/com/adamaps/varroa/data/SettingsDataStore.kt +++ b/app/src/main/java/com/adamaps/varroa/data/SettingsDataStore.kt @@ -15,7 +15,7 @@ import java.security.MessageDigest private val Context.dataStore: DataStore by preferencesDataStore(name = "varroa_settings") data class VarroaSettings( - val beeApiUrl: String = "http://10.77.0.1:5000", + val beeApiUrl: String = "http://192.168.0.10:5000", val adamapsApiUrl: String = "https://api.adamaps.org", val adamapsApiKey: String = "***REMOVED***", val pollIntervalSeconds: Int = 30, @@ -60,7 +60,7 @@ class SettingsDataStore(private val context: Context) { val settings: Flow = context.dataStore.data.map { prefs -> VarroaSettings( - beeApiUrl = prefs[KEY_BEE_URL] ?: "http://10.77.0.1:5000", + beeApiUrl = prefs[KEY_BEE_URL] ?: "http://192.168.0.10:5000", adamapsApiUrl = prefs[KEY_ADAMAPS_URL] ?: "https://api.adamaps.org", adamapsApiKey = prefs[KEY_ADAMAPS_KEY] ?: "***REMOVED***", pollIntervalSeconds = prefs[KEY_POLL_INTERVAL] ?: 30, From 74d17c683f7f396f2c35587ee0372454f4d702ba Mon Sep 17 00:00:00 2001 From: kayos Date: Sun, 22 Mar 2026 00:41:31 -0700 Subject: [PATCH 18/26] Update BeeApiClient default URL to 192.168.0.10:5000 --- app/src/main/java/com/adamaps/varroa/api/BeeApiClient.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/adamaps/varroa/api/BeeApiClient.kt b/app/src/main/java/com/adamaps/varroa/api/BeeApiClient.kt index d4e0bc4..56a5467 100644 --- a/app/src/main/java/com/adamaps/varroa/api/BeeApiClient.kt +++ b/app/src/main/java/com/adamaps/varroa/api/BeeApiClient.kt @@ -34,7 +34,7 @@ import okhttp3.RequestBody.Companion.toRequestBody import java.util.concurrent.TimeUnit class BeeApiClient( - private var apiUrl: String = "http://10.77.0.1:5000" + private var apiUrl: String = "http://192.168.0.10:5000" ) { companion object { private const val TAG = "VarroaBeeAPI" @@ -711,3 +711,4 @@ class BeeApiClient( return postRaw("/api/1/wigle/config", json) } } + From d2be4f81e3a0df0af3257f2c805db9f26f600601 Mon Sep 17 00:00:00 2001 From: kayos Date: Sun, 22 Mar 2026 00:41:49 -0700 Subject: [PATCH 19/26] Update Settings screen hint text to show correct default Bee URL --- .../main/java/com/adamaps/varroa/ui/settings/SettingsScreen.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/adamaps/varroa/ui/settings/SettingsScreen.kt b/app/src/main/java/com/adamaps/varroa/ui/settings/SettingsScreen.kt index c6d2652..e5ad2f5 100644 --- a/app/src/main/java/com/adamaps/varroa/ui/settings/SettingsScreen.kt +++ b/app/src/main/java/com/adamaps/varroa/ui/settings/SettingsScreen.kt @@ -189,7 +189,7 @@ fun SettingsScreen( label = "AdaCam API URL", value = beeApiUrl, onValueChange = { beeApiUrl = it }, - hint = "http://10.77.0.1:5000" + hint = "http://192.168.0.10:5000" ) } @@ -724,3 +724,4 @@ private fun formatTimeAgo(timestampSeconds: Long): String { else -> "${diff / 86400} days ago" } } + From b955a19ab21952ea32c0f48405abe4d9a3009bfb Mon Sep 17 00:00:00 2001 From: kayos Date: Tue, 24 Mar 2026 09:37:45 -0700 Subject: [PATCH 20/26] varroa: strip Wigle integration (deferred, not in base stack) --- .../java/com/adamaps/varroa/data/Models.kt | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/app/src/main/java/com/adamaps/varroa/data/Models.kt b/app/src/main/java/com/adamaps/varroa/data/Models.kt index fc59376..18faff5 100644 --- a/app/src/main/java/com/adamaps/varroa/data/Models.kt +++ b/app/src/main/java/com/adamaps/varroa/data/Models.kt @@ -214,24 +214,6 @@ data class FrameKmTotal( @SerializedName("count") val count: Int? = null ) -// ── WiGLE Wardriving ────────────────────────────────────────────────────────── - -data class WigleStatus( - val enabled: Boolean = false, - @SerializedName("api_name") val apiName: String = "", - @SerializedName("total_networks") val totalNetworks: Int = 0, - @SerializedName("pending_upload") val pendingUpload: Int = 0, - @SerializedName("last_scan") val lastScan: Long? = null, - @SerializedName("last_upload") val lastUpload: Long? = null -) - -data class WigleStats( - @SerializedName("total_networks") val totalNetworks: Int = 0, - @SerializedName("uploaded_networks") val uploadedNetworks: Int = 0, - @SerializedName("pending_upload") val pendingUpload: Int = 0, - @SerializedName("scans_today") val scansToday: Int = 0 -) - // ── App state ───────────────────────────────────────────────────────────────── data class SessionStats( From 470898969833c86a4c727b28d498722235a5de67 Mon Sep 17 00:00:00 2001 From: kayos Date: Tue, 24 Mar 2026 09:37:56 -0700 Subject: [PATCH 21/26] varroa: strip Wigle integration (deferred, not in base stack) --- .../com/adamaps/varroa/api/BeeApiClient.kt | 36 +------------------ 1 file changed, 1 insertion(+), 35 deletions(-) diff --git a/app/src/main/java/com/adamaps/varroa/api/BeeApiClient.kt b/app/src/main/java/com/adamaps/varroa/api/BeeApiClient.kt index 56a5467..09a370a 100644 --- a/app/src/main/java/com/adamaps/varroa/api/BeeApiClient.kt +++ b/app/src/main/java/com/adamaps/varroa/api/BeeApiClient.kt @@ -677,38 +677,4 @@ class BeeApiClient( return@withContext ApiResult.Error("Exception: ${e.message}") } } - - // ── WiGLE Wardriving API ────────────────────────────────────────────────── - - suspend fun getWigleStatus(): ApiResult = withContext(Dispatchers.IO) { - when (val r = getRaw("/api/1/wigle/status")) { - is ApiResult.Success -> try { - ApiResult.Success(gson.fromJson(r.data, com.adamaps.varroa.data.WigleStatus::class.java)) - } catch (e: Exception) { - ApiResult.Error("Parse error: ${e.message}") - } - is ApiResult.Error -> r - } - } - - suspend fun getWigleStats(): ApiResult = withContext(Dispatchers.IO) { - when (val r = getRaw("/api/1/wigle/stats")) { - is ApiResult.Success -> try { - ApiResult.Success(gson.fromJson(r.data, com.adamaps.varroa.data.WigleStats::class.java)) - } catch (e: Exception) { - ApiResult.Error("Parse error: ${e.message}") - } - is ApiResult.Error -> r - } - } - - suspend fun setWigleConfig(enabled: Boolean, apiName: String, apiToken: String): ApiResult { - val json = gson.toJson(mapOf( - "enabled" to enabled, - "api_name" to apiName, - "api_token" to apiToken - )) - return postRaw("/api/1/wigle/config", json) - } -} - +} \ No newline at end of file From 96abfe15a56068637871e4bc186b604d22f147d6 Mon Sep 17 00:00:00 2001 From: kayos Date: Tue, 24 Mar 2026 09:38:07 -0700 Subject: [PATCH 22/26] varroa: strip Wigle integration (deferred, not in base stack) --- .../varroa/viewmodel/SettingsViewModel.kt | 66 ------------------- 1 file changed, 66 deletions(-) diff --git a/app/src/main/java/com/adamaps/varroa/viewmodel/SettingsViewModel.kt b/app/src/main/java/com/adamaps/varroa/viewmodel/SettingsViewModel.kt index 70a3cfa..8e16e29 100644 --- a/app/src/main/java/com/adamaps/varroa/viewmodel/SettingsViewModel.kt +++ b/app/src/main/java/com/adamaps/varroa/viewmodel/SettingsViewModel.kt @@ -9,8 +9,6 @@ import com.adamaps.varroa.data.ApiResult import com.adamaps.varroa.data.SettingsDataStore import com.adamaps.varroa.data.SshStatus import com.adamaps.varroa.data.VarroaSettings -import com.adamaps.varroa.data.WigleStatus -import com.adamaps.varroa.data.WigleStats import com.adamaps.varroa.data.WifiStatus import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted @@ -66,15 +64,6 @@ class SettingsViewModel(app: Application) : AndroidViewModel(app) { private val _wifiConnectResult = MutableStateFlow(null) val wifiConnectResult: StateFlow = _wifiConnectResult.asStateFlow() - // WiGLE state - private val _wigleStatus = MutableStateFlow(null) - val wigleStatus: StateFlow = _wigleStatus.asStateFlow() - - private val _wigleStats = MutableStateFlow(null) - val wigleStats: StateFlow = _wigleStats.asStateFlow() - - private val _wigleConfigResult = MutableStateFlow(null) - val wigleConfigResult: StateFlow = _wigleConfigResult.asStateFlow() init { // Initialize BeeApiClient with stored settings and token @@ -90,7 +79,6 @@ class SettingsViewModel(app: Application) : AndroidViewModel(app) { if (s.isPaired) { refreshSshStatus() refreshWifiStatus() - refreshWigleStatus() } } } @@ -273,58 +261,4 @@ class SettingsViewModel(app: Application) : AndroidViewModel(app) { fun clearSshResult() { _sshToggleResult.value = null } - - // ── WiGLE ───────────────────────────────────────────────────────────────── - - /** - * Refresh WiGLE status from device. - */ - fun refreshWigleStatus() { - viewModelScope.launch { - val client = beeClient ?: return@launch - when (val result = client.getWigleStatus()) { - is ApiResult.Success -> { - _wigleStatus.value = result.data - } - is ApiResult.Error -> { - Log.w(TAG, "Failed to get WiGLE status: ${result.message}") - } - } - when (val result = client.getWigleStats()) { - is ApiResult.Success -> { - _wigleStats.value = result.data - } - is ApiResult.Error -> { - Log.w(TAG, "Failed to get WiGLE stats: ${result.message}") - } - } - } - } - - /** - * Set WiGLE configuration. - */ - fun setWigleConfig(enabled: Boolean, apiName: String, apiToken: String) { - viewModelScope.launch { - val client = beeClient ?: run { - _wigleConfigResult.value = "Not connected to device" - return@launch - } - _wigleConfigResult.value = null - - when (val result = client.setWigleConfig(enabled, apiName, apiToken)) { - is ApiResult.Success -> { - _wigleConfigResult.value = "WiGLE configuration saved" - refreshWigleStatus() - } - is ApiResult.Error -> { - _wigleConfigResult.value = "Failed: ${result.message}" - } - } - } - } - - fun clearWigleResult() { - _wigleConfigResult.value = null - } } From ca6e385ebeaa3d5f75764825f171cf239d6e3f49 Mon Sep 17 00:00:00 2001 From: kayos Date: Tue, 24 Mar 2026 09:38:24 -0700 Subject: [PATCH 23/26] varroa: strip Wigle integration (deferred, not in base stack) --- .../varroa/ui/settings/SettingsScreen.kt | 143 ------------------ 1 file changed, 143 deletions(-) diff --git a/app/src/main/java/com/adamaps/varroa/ui/settings/SettingsScreen.kt b/app/src/main/java/com/adamaps/varroa/ui/settings/SettingsScreen.kt index e5ad2f5..39d2cf5 100644 --- a/app/src/main/java/com/adamaps/varroa/ui/settings/SettingsScreen.kt +++ b/app/src/main/java/com/adamaps/varroa/ui/settings/SettingsScreen.kt @@ -30,7 +30,6 @@ import androidx.compose.ui.unit.sp import androidx.compose.material.icons.filled.Link import androidx.compose.material.icons.filled.Wifi import androidx.compose.material.icons.filled.Terminal -import androidx.compose.material.icons.filled.NetworkCheck import androidx.compose.ui.text.input.PasswordVisualTransformation import androidx.compose.ui.text.input.VisualTransformation import androidx.lifecycle.viewmodel.compose.viewModel @@ -68,14 +67,8 @@ fun SettingsScreen( val sshStatus by vm.sshStatus.collectAsState() val wifiStatus by vm.wifiStatus.collectAsState() val wifiConnectResult by vm.wifiConnectResult.collectAsState() - val wigleStatus by vm.wigleStatus.collectAsState() - val wigleStats by vm.wigleStats.collectAsState() - val wigleConfigResult by vm.wigleConfigResult.collectAsState() // WiGLE config input state - var wigleEnabled by remember(wigleStatus) { mutableStateOf(wigleStatus?.enabled ?: false) } - var wigleApiName by remember { mutableStateOf("") } - var wigleApiToken by remember { mutableStateOf("") } // WiFi config input state var homeWifiSsid by remember { mutableStateOf("") } @@ -307,132 +300,6 @@ fun SettingsScreen( } } - // ── WiGLE Wardriving ────────────────────────────────────────────── - SettingsSection("WIGLE WARDRIVING") { - if (!isPaired) { - Text("Pair device first to configure WiGLE", - color = Color.Gray, fontFamily = FontFamily.Monospace, fontSize = 10.sp) - } else { - // Enable toggle - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween - ) { - Row(verticalAlignment = Alignment.CenterVertically) { - Icon(Icons.Default.NetworkCheck, contentDescription = null, - tint = if (wigleStatus?.enabled == true) Amber else Color.Gray, - modifier = Modifier.size(16.dp)) - Spacer(Modifier.width(8.dp)) - Column { - Text("WiFi Scanning", - color = Color.White, fontFamily = FontFamily.Monospace, fontSize = 12.sp) - Text("Scan networks & upload to WiGLE", - color = Color.Gray, fontFamily = FontFamily.Monospace, fontSize = 10.sp) - } - } - Switch( - checked = wigleEnabled, - onCheckedChange = { wigleEnabled = it }, - colors = SwitchDefaults.colors(checkedThumbColor = Background, checkedTrackColor = Amber) - ) - } - - Spacer(Modifier.height(12.dp)) - - // API credentials - Text("WiGLE Account", color = Amber, fontFamily = FontFamily.Monospace, - fontWeight = FontWeight.Bold, fontSize = 10.sp, letterSpacing = 1.sp) - Spacer(Modifier.height(8.dp)) - - if (wigleStatus?.apiName?.isNotBlank() == true) { - Text("Configured: ${wigleStatus?.apiName}", - color = Color.Gray, fontFamily = FontFamily.Monospace, fontSize = 10.sp) - Spacer(Modifier.height(4.dp)) - } - - OutlinedTextField( - value = wigleApiName, - onValueChange = { wigleApiName = it }, - label = { Text("API Name", fontFamily = FontFamily.Monospace, fontSize = 11.sp) }, - placeholder = { Text("From wigle.net → Account → API", color = Color.Gray, fontSize = 10.sp) }, - singleLine = true, - modifier = Modifier.fillMaxWidth(), - colors = OutlinedTextFieldDefaults.colors( - focusedBorderColor = Amber, unfocusedBorderColor = SurfaceVariant, - focusedLabelColor = Amber, unfocusedLabelColor = Color.Gray, - cursorColor = Amber, focusedTextColor = OnSurface, unfocusedTextColor = OnSurface - ) - ) - Spacer(Modifier.height(8.dp)) - OutlinedTextField( - value = wigleApiToken, - onValueChange = { wigleApiToken = it }, - label = { Text("API Token", fontFamily = FontFamily.Monospace, fontSize = 11.sp) }, - placeholder = { Text("●●●●●●●●●●●●", color = Color.Gray, fontSize = 10.sp) }, - singleLine = true, - visualTransformation = PasswordVisualTransformation(), - modifier = Modifier.fillMaxWidth(), - colors = OutlinedTextFieldDefaults.colors( - focusedBorderColor = Amber, unfocusedBorderColor = SurfaceVariant, - focusedLabelColor = Amber, unfocusedLabelColor = Color.Gray, - cursorColor = Amber, focusedTextColor = OnSurface, unfocusedTextColor = OnSurface - ) - ) - - Spacer(Modifier.height(8.dp)) - wigleConfigResult?.let { - Text(it, color = if (it.startsWith("Failed")) Color.Red else Amber, - fontFamily = FontFamily.Monospace, fontSize = 11.sp) - Spacer(Modifier.height(4.dp)) - } - - Button( - onClick = { vm.setWigleConfig(wigleEnabled, wigleApiName, wigleApiToken) }, - enabled = wigleApiName.isNotBlank() && wigleApiToken.isNotBlank(), - colors = ButtonDefaults.buttonColors(containerColor = Amber, contentColor = Background), - modifier = Modifier.fillMaxWidth() - ) { - Text("Save WiGLE Config", fontFamily = FontFamily.Monospace, fontWeight = FontWeight.Bold) - } - - // Stats (when enabled) - if (wigleStatus?.enabled == true) { - Spacer(Modifier.height(12.dp)) - HorizontalDivider(color = SurfaceVariant) - Spacer(Modifier.height(12.dp)) - - Text("Statistics", color = Amber, fontFamily = FontFamily.Monospace, - fontWeight = FontWeight.Bold, fontSize = 10.sp, letterSpacing = 1.sp) - Spacer(Modifier.height(8.dp)) - - Row(modifier = Modifier.fillMaxWidth()) { - Column(modifier = Modifier.weight(1f)) { - Text("Networks Found", color = Color.Gray, fontFamily = FontFamily.Monospace, fontSize = 10.sp) - Text("${wigleStats?.totalNetworks ?: wigleStatus?.totalNetworks ?: 0}", - color = Color.White, fontFamily = FontFamily.Monospace, fontSize = 14.sp, fontWeight = FontWeight.Bold) - } - Column(modifier = Modifier.weight(1f)) { - Text("Pending Upload", color = Color.Gray, fontFamily = FontFamily.Monospace, fontSize = 10.sp) - Text("${wigleStats?.pendingUpload ?: wigleStatus?.pendingUpload ?: 0}", - color = Color.White, fontFamily = FontFamily.Monospace, fontSize = 14.sp, fontWeight = FontWeight.Bold) - } - } - - Spacer(Modifier.height(8.dp)) - - wigleStatus?.lastScan?.let { ts -> - val ago = formatTimeAgo(ts) - Text("Last scan: $ago", color = Color.Gray, fontFamily = FontFamily.Monospace, fontSize = 10.sp) - } - wigleStatus?.lastUpload?.let { ts -> - val ago = formatTimeAgo(ts) - Text("Last upload: $ago", color = Color.Gray, fontFamily = FontFamily.Monospace, fontSize = 10.sp) - } - } - } - } - SettingsSection("ADAMAPS") { SettingsField( label = "ADAMaps API URL", @@ -714,14 +581,4 @@ private fun WalletLinkingSection( } } -private fun formatTimeAgo(timestampSeconds: Long): String { - val now = System.currentTimeMillis() / 1000 - val diff = now - timestampSeconds - return when { - diff < 60 -> "just now" - diff < 3600 -> "${diff / 60} minutes ago" - diff < 86400 -> "${diff / 3600} hours ago" - else -> "${diff / 86400} days ago" - } -} From 261b31c49ad9db056caf32b031425a9515631fd1 Mon Sep 17 00:00:00 2001 From: Cobb Hayes Date: Wed, 27 May 2026 09:17:23 -0700 Subject: [PATCH 24/26] Rotate AdaMaps ingest+read keys (env-required, no inline default) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previous values (***REMOVED***, ***REMOVED***, ***REMOVED***) were inline defaults across adamaps + adacam-api + varroa. The ingest key was briefly anon-visible during the 2026-05-27 Forgejo public-flip when adacam-api + varroa were public for a short window before the leak was spotted. New values live in Vaultwarden: - AdaMaps — API_KEY (ingest) - AdaMaps — READ_KEY Validators now hard-fail at boot if the env var is missing. Service is on hold today; when it resumes, both env vars must be set. --- app/src/main/java/com/adamaps/varroa/api/AdaMapsApiClient.kt | 2 +- .../main/java/com/adamaps/varroa/data/SettingsDataStore.kt | 4 ++-- blackbox/air-aggregator.service | 2 +- blackbox/air_aggregator.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/adamaps/varroa/api/AdaMapsApiClient.kt b/app/src/main/java/com/adamaps/varroa/api/AdaMapsApiClient.kt index 8fc65f9..5acaee7 100644 --- a/app/src/main/java/com/adamaps/varroa/api/AdaMapsApiClient.kt +++ b/app/src/main/java/com/adamaps/varroa/api/AdaMapsApiClient.kt @@ -38,7 +38,7 @@ private object AdaMapsDns : Dns { class AdaMapsApiClient( private var apiUrl: String = "https://api.adamaps.org", - private var apiKey: String = "***REMOVED***" + private var apiKey: String = "" ) { companion object { private const val TAG = "VarroaAdaAPI" diff --git a/app/src/main/java/com/adamaps/varroa/data/SettingsDataStore.kt b/app/src/main/java/com/adamaps/varroa/data/SettingsDataStore.kt index 51d0642..6d9adcc 100644 --- a/app/src/main/java/com/adamaps/varroa/data/SettingsDataStore.kt +++ b/app/src/main/java/com/adamaps/varroa/data/SettingsDataStore.kt @@ -17,7 +17,7 @@ private val Context.dataStore: DataStore by preferencesDataStore(na data class VarroaSettings( val beeApiUrl: String = "http://192.168.0.10:5000", val adamapsApiUrl: String = "https://api.adamaps.org", - val adamapsApiKey: String = "***REMOVED***", + val adamapsApiKey: String = "", val pollIntervalSeconds: Int = 30, val cameraEndpoint: String = "/api/1/camera/frame", val cameraRefreshSeconds: Int = 30, @@ -62,7 +62,7 @@ class SettingsDataStore(private val context: Context) { VarroaSettings( beeApiUrl = prefs[KEY_BEE_URL] ?: "http://192.168.0.10:5000", adamapsApiUrl = prefs[KEY_ADAMAPS_URL] ?: "https://api.adamaps.org", - adamapsApiKey = prefs[KEY_ADAMAPS_KEY] ?: "***REMOVED***", + adamapsApiKey = prefs[KEY_ADAMAPS_KEY] ?: "", pollIntervalSeconds = prefs[KEY_POLL_INTERVAL] ?: 30, cameraEndpoint = prefs[KEY_CAMERA_ENDPOINT] ?: "/api/1/camera/frame", cameraRefreshSeconds = prefs[KEY_CAMERA_REFRESH] ?: 30, diff --git a/blackbox/air-aggregator.service b/blackbox/air-aggregator.service index ebeae22..17be223 100644 --- a/blackbox/air-aggregator.service +++ b/blackbox/air-aggregator.service @@ -10,7 +10,7 @@ RestartSec=10 ExecStart=/usr/bin/python3 /home/pi/air-aggregator.py Environment=BEE_URL=http://192.168.197.1:5000 Environment=ADAMAPS_URL=https://api.adamaps.org -Environment=ADAMAPS_KEY=***REMOVED*** +Environment=ADAMAPS_KEY= Environment=DEVICE_ID=blackbox-pi Environment=PMS_PORT=/dev/ttyS0 Environment=SEND_INTERVAL=10 diff --git a/blackbox/air_aggregator.py b/blackbox/air_aggregator.py index 1370947..784c17b 100644 --- a/blackbox/air_aggregator.py +++ b/blackbox/air_aggregator.py @@ -25,7 +25,7 @@ from datetime import datetime, timezone # ── Config ──────────────────────────────────────────────────────────────────── BEE_URL = os.environ.get("BEE_URL", "http://192.168.197.1:5000") ADAMAPS_URL = os.environ.get("ADAMAPS_URL", "https://api.adamaps.org") -ADAMAPS_KEY = os.environ.get("ADAMAPS_KEY", "***REMOVED***") +ADAMAPS_KEY = os.environ.get("ADAMAPS_KEY", "") DEVICE_ID = os.environ.get("DEVICE_ID", "blackbox-pi") PMS_PORT = os.environ.get("PMS_PORT", "/dev/ttyS0") SEND_INTERVAL = int(os.environ.get("SEND_INTERVAL", "10")) # seconds From 84e8777290208a997abdfbf21d376c6b37a01b95 Mon Sep 17 00:00:00 2001 From: Cobb Hayes Date: Wed, 27 May 2026 10:30:02 -0700 Subject: [PATCH 25/26] Public-flip prep: env-driven keystore, README, hardened cleartext, leaner docs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - app/build.gradle.kts: remove hardcoded keystore password (was 'adacam-varroa-2026' in 4 spots across a duplicated signingConfigs block). Now reads VARROA_KEYSTORE_PATH + VARROA_KEYSTORE_PASSWORD + VARROA_KEY_PASSWORD from env. Password vaulted as 'Varroa — release keystore'. Drops orphan zxing/camera deps that aren't wired up. - app/src/main/res/xml/network_security_config.xml: tighten cleartext scope from global to just 192.168.0.10 (Bee AP). HTTPS strict for everything else. - app/src/main/java/.../api/AdaMapsApiClient.kt: drop apiKey.take(8) in log to apiKey.length — no need to leak prefix to logcat. - README.md: add. Public repo without one was a bad first impression. - docs/BEE-CAMERA.md: rewrite (811→467 lines). Keep all paths, pinouts, bus diagrams, depthai/VPU/xlink details, intercept architecture. Strip Executive-Summary framing, verdict box, phased roadmap, appendices. - docs/AIR-QUALITY-INTEGRATION.md: rewrite (712→369 lines). Keep BOM, sensor comparisons, wiring, IAQ calc, ingest endpoint shape. Strip feasibility-report scaffolding. - docs/AIR-API-PATCH.py: delete. Was a one-shot apply-and-discard patch script, not docs. --- README.md | 46 + app/build.gradle.kts | 34 +- .../adamaps/varroa/api/AdaMapsApiClient.kt | 4 +- .../main/res/xml/network_security_config.xml | 10 +- docs/AIR-API-PATCH.py | 145 ---- docs/AIR-QUALITY-INTEGRATION.md | 740 +++++----------- docs/BEE-CAMERA.md | 799 +++++------------- 7 files changed, 495 insertions(+), 1283 deletions(-) create mode 100644 README.md delete mode 100644 docs/AIR-API-PATCH.py diff --git a/README.md b/README.md new file mode 100644 index 0000000..ce62f5e --- /dev/null +++ b/README.md @@ -0,0 +1,46 @@ +# varroa + +Android companion for the Hivemapper Bee dashcam. Pulls detection +landmarks off the on-Bee `adacam-api`, queues them in a local Room +DB, forwards to AdaMaps when the phone has real internet. + +Sister piece: `blackbox/` — Python aggregator that runs on a truck +Pi (BME680 + PMS5003) and ships air-quality readings into the same +AdaMaps stream. + +## Build + +``` +JDK 17, Android SDK 34 +./gradlew :app:assembleDebug +``` + +Release signing needs: + +``` +VARROA_KEYSTORE_PATH=/path/to/varroa-release.keystore +VARROA_KEYSTORE_PASSWORD= +VARROA_KEY_PASSWORD= +./gradlew :app:assembleRelease +``` + +## Config (set in-app) + +- **Bee URL** — defaults to `http://192.168.0.10:5000` (Bee AP). +- **AdaMaps URL + ingest key** — required before uploads run. +- **Cardano wallet** — optional. Attaches to detection ingest for + rewards routing. + +## Architecture + +- `BeeCollectorService` — polls the Bee, writes landmarks to Room. +- `AdaMapsUploadWorker` — drains Room to `api.adamaps.org` once + validated internet is available. +- `ImageCollectorService` — pulls detection JPEGs from the Bee. + +## blackbox + +The air-quality side. `air_aggregator.py` reads BME680 + +PMS5003 over USB, posts to AdaMaps every 60s. Systemd unit at +`blackbox/air-aggregator.service` — set `ADAMAPS_KEY` and +`AGGREGATOR_BEE_URL` in the unit before enabling. diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 9a5b76d..20dbc60 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -11,10 +11,17 @@ android { signingConfigs { create("release") { - storeFile = file("/keystore/varroa-release.keystore") - storePassword = "adacam-varroa-2026" - keyAlias = "varroa-release" - keyPassword = "adacam-varroa-2026" + // Set VARROA_KEYSTORE_PATH / VARROA_KEYSTORE_PASSWORD / VARROA_KEY_PASSWORD + // before assembleRelease — see vault item "Varroa — release keystore". + val ksPath = System.getenv("VARROA_KEYSTORE_PATH") + val ksPass = System.getenv("VARROA_KEYSTORE_PASSWORD") + val keyPass = System.getenv("VARROA_KEY_PASSWORD") ?: ksPass + if (ksPath != null && ksPass != null) { + storeFile = file(ksPath) + storePassword = ksPass + keyAlias = "varroa-release" + keyPassword = keyPass + } } } @@ -30,15 +37,6 @@ android { } } - signingConfigs { - create("release") { - storeFile = file("/keystore/varroa-release.keystore") - storePassword = "adacam-varroa-2026" - keyAlias = "varroa-release" - keyPassword = "adacam-varroa-2026" - } - } - buildTypes { release { isMinifyEnabled = false @@ -89,19 +87,9 @@ dependencies { implementation(libs.osmdroid.android) implementation(libs.datastore.preferences) implementation(libs.coil.compose) - // Room (local database) implementation(libs.room.runtime) implementation(libs.room.ktx) ksp(libs.room.compiler) - // WorkManager (background uploads) implementation(libs.work.runtime.ktx) - // SSH connectivity for device_id fallback - - // QR Code scanning - implementation("com.google.zxing:core:3.5.2") - implementation("com.journeyapps:zxing-android-embedded:4.3.0") - implementation("androidx.camera:camera-camera2:1.3.0") - implementation("androidx.camera:camera-lifecycle:1.3.0") - implementation("androidx.camera:camera-view:1.3.0") debugImplementation(libs.androidx.ui.tooling) } diff --git a/app/src/main/java/com/adamaps/varroa/api/AdaMapsApiClient.kt b/app/src/main/java/com/adamaps/varroa/api/AdaMapsApiClient.kt index 5acaee7..39a8410 100644 --- a/app/src/main/java/com/adamaps/varroa/api/AdaMapsApiClient.kt +++ b/app/src/main/java/com/adamaps/varroa/api/AdaMapsApiClient.kt @@ -57,7 +57,7 @@ class AdaMapsApiClient( fun updateConfig(url: String, key: String) { val oldUrl = apiUrl - val oldKeyPrefix = apiKey.take(8) + val oldKeyPrefix = apiKey.length apiUrl = url.trimEnd('/') apiKey = key Log.d(TAG, "AdaMaps config updated - URL: $oldUrl -> $apiUrl, Key: ${oldKeyPrefix}... -> ${key.take(8)}...") @@ -80,7 +80,7 @@ class AdaMapsApiClient( .post(body) .build() - Log.d(TAG, "Sending POST request with key: ${apiKey.take(8)}...") + Log.d(TAG, "Sending POST request with key: ${apiKey.length}...") client.newCall(req).execute().use { resp -> val respBody = resp.body?.string() ?: "" Log.d(TAG, "HTTP ${resp.code} ${resp.message} - response length: ${respBody.length}") diff --git a/app/src/main/res/xml/network_security_config.xml b/app/src/main/res/xml/network_security_config.xml index 2439f15..e7f24f7 100644 --- a/app/src/main/res/xml/network_security_config.xml +++ b/app/src/main/res/xml/network_security_config.xml @@ -1,4 +1,12 @@ - + + + + + + 192.168.0.10 + diff --git a/docs/AIR-API-PATCH.py b/docs/AIR-API-PATCH.py deleted file mode 100644 index 24a5016..0000000 --- a/docs/AIR-API-PATCH.py +++ /dev/null @@ -1,145 +0,0 @@ -#!/usr/bin/env python3 -""" -AIR QUALITY API — Routes to add to /app/app.py on Rackham (adamaps-api container) -Review this file, then apply via: docker exec adamaps-api python3 /tmp/apply_air_patch.py -DO NOT apply until Cobb approves. -""" - -# ─── EPA AQI calculation ────────────────────────────────────────────────────── -def pm25_to_aqi(pm25): - if pm25 is None: return None - breakpoints = [ - (0.0, 12.0, 0, 50), - (12.1, 35.4, 51, 100), - (35.5, 55.4, 101, 150), - (55.5, 150.4, 151, 200), - (150.5, 250.4, 201, 300), - (250.5, 350.4, 301, 400), - (350.5, 500.4, 401, 500), - ] - for c_lo, c_hi, i_lo, i_hi in breakpoints: - if c_lo <= pm25 <= c_hi: - return round((i_hi - i_lo) / (c_hi - c_lo) * (pm25 - c_lo) + i_lo) - return 500 if pm25 > 500 else 0 - -def init_air_table(): - try: - conn = get_db() - cur = conn.cursor() - cur.execute(""" - CREATE TABLE IF NOT EXISTS air_quality ( - id SERIAL PRIMARY KEY, - device_id VARCHAR(64), - sampled_at TIMESTAMP, - lat DOUBLE PRECISION, - lon DOUBLE PRECISION, - alt DOUBLE PRECISION, - gps_fix BOOLEAN DEFAULT FALSE, - pm1_0 FLOAT, - pm2_5 FLOAT, - pm10 FLOAT, - temperature_c FLOAT, - humidity_pct FLOAT, - pressure_hpa FLOAT, - gas_resistance_ohm FLOAT, - aqi INTEGER, - created_at TIMESTAMP DEFAULT NOW() - ) - """) - cur.execute("CREATE INDEX IF NOT EXISTS air_latlon_idx ON air_quality (lat, lon)") - cur.execute("CREATE INDEX IF NOT EXISTS air_sampled_idx ON air_quality (sampled_at DESC)") - conn.commit(); cur.close(); conn.close() - except Exception as e: - print(f"air table init: {e}") - -# ─── /api/ingest/air ────────────────────────────────────────────────────────── -# POST — auth required (X-AdaMaps-Key) -# Body: {"device_id": "blackbox-pi", "readings": [{sampled_at, lat, lon, pm2_5_ug_m3, ...}]} -# Returns: {"inserted": N} -def ingest_air(): - if request.headers.get("X-AdaMaps-Key") != API_KEY: - return jsonify({"error": "unauthorized"}), 401 - data = request.json - if not data: return jsonify({"error": "invalid"}), 400 - device_id = data.get("device_id", "unknown") - readings = data.get("readings", [data] if "sampled_at" in data else []) - if not readings: return jsonify({"error": "no readings"}), 400 - init_air_table() - inserted = 0 - try: - conn = get_db(); cur = conn.cursor() - for r in readings: - try: - pm25 = r.get("pm2_5_ug_m3") or r.get("pm2_5") - cur.execute(""" - INSERT INTO air_quality - (device_id, sampled_at, lat, lon, alt, gps_fix, - pm1_0, pm2_5, pm10, temperature_c, humidity_pct, - pressure_hpa, gas_resistance_ohm, aqi) - VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s) - """, ( - r.get("device_id", device_id), r.get("sampled_at"), - r.get("lat"), r.get("lon"), r.get("alt"), r.get("gps_fix", False), - r.get("pm1_0_ug_m3"), pm25, r.get("pm10_ug_m3"), - r.get("temperature_c"), r.get("humidity_pct"), - r.get("pressure_hpa"), r.get("gas_resistance_ohm"), - pm25_to_aqi(pm25) - )) - inserted += 1 - except: conn.rollback() - conn.commit(); cur.close(); conn.close() - except Exception as e: - return jsonify({"error": "db_unavailable", "detail": str(e)}), 503 - return jsonify({"inserted": inserted, "device_id": device_id}) - -# ─── /api/air/heatmap ───────────────────────────────────────────────────────── -# GET ?metric=aqi|pm2_5 &hours=24 -# Returns [[lat, lon, intensity_0_to_1], ...] for Leaflet.heat -def air_heatmap(): - metric = request.args.get("metric", "aqi") - hours = request.args.get("hours", 24, type=int) - col = "aqi" if metric == "aqi" else "pm2_5" - max_val = 300.0 if metric == "aqi" else 150.0 - try: - conn = get_db(); cur = conn.cursor() - cur.execute(f""" - SELECT lat, lon, {col} FROM air_quality - WHERE sampled_at > NOW() - INTERVAL '%s hours' - AND lat IS NOT NULL AND lon IS NOT NULL - AND {col} IS NOT NULL AND gps_fix = TRUE - ORDER BY sampled_at DESC LIMIT 50000 - """, (hours,)) - rows = [[float(r[0]), float(r[1]), min(float(r[2]) / max_val, 1.0)] - for r in cur.fetchall()] - cur.close(); conn.close() - return jsonify(rows) - except Exception as e: - return jsonify({"error": str(e)}), 500 - -# ─── /api/air/latest ────────────────────────────────────────────────────────── -# GET — most recent reading per device -def air_latest(): - try: - conn = get_db(); cur = conn.cursor() - cur.execute(""" - SELECT DISTINCT ON (device_id) - device_id, sampled_at, lat, lon, - pm1_0, pm2_5, pm10, temperature_c, humidity_pct, aqi - FROM air_quality - WHERE sampled_at > NOW() - INTERVAL '1 hour' - ORDER BY device_id, sampled_at DESC - """) - rows = [{"device_id": r[0], "sampled_at": r[1].isoformat() if r[1] else None, - "lat": float(r[2]) if r[2] else None, "lon": float(r[3]) if r[3] else None, - "pm1_0": r[4], "pm2_5": r[5], "pm10": r[6], - "temperature_c": r[7], "humidity_pct": r[8], "aqi": r[9]} - for r in cur.fetchall()] - cur.close(); conn.close() - return jsonify(rows) - except Exception as e: - return jsonify({"error": str(e)}), 500 - -# ─── Flask route registration (add to app after existing routes) ─────────────── -# app.add_url_rule("/api/ingest/air", "ingest_air", ingest_air, methods=["POST"]) -# app.add_url_rule("/api/air/heatmap", "air_heatmap", air_heatmap, methods=["GET"]) -# app.add_url_rule("/api/air/latest", "air_latest", air_latest, methods=["GET"]) diff --git a/docs/AIR-QUALITY-INTEGRATION.md b/docs/AIR-QUALITY-INTEGRATION.md index a205091..2f4be27 100644 --- a/docs/AIR-QUALITY-INTEGRATION.md +++ b/docs/AIR-QUALITY-INTEGRATION.md @@ -1,275 +1,94 @@ -# Air Quality Sensor Integration — Feasibility Report +# Air quality sensor on the Bee -*Generated: 2026-03-13* +Can the Bee carry an air quality sensor alongside the existing Hivemapper pipeline and feed readings into AdaMaps? Short answer: yes, with the USB-C data port and a USB-to-I2C bridge. The polling overhead is negligible — the real work is on the AdaMaps side (new ingest endpoint, PostGIS table, heatmap overlay). ---- +## what's free on the Bee -## Executive Summary +Hardware is Keem Bay (RVC2), 4× A53 @ 1.5GHz, Myriad X VPU, 3.5GB usable RAM (1.34GB of that is CMA-reserved for VPU DMA), leaving ~2.2GB for userspace. -**Verdict: FEASIBLE** — Adding Bosch air quality sensor support to the Hivemapper Bee dashcam is technically feasible with minimal resource overhead. The primary path is USB-to-I2C adapter for BME680/BME688 sensors, or direct USB-C for Sensirion SEN5x sensors if particulate matter measurement is desired. - -**Key Findings:** -- Bee has sufficient headroom after Phase 1 bloat removal (~50% CPU, ~1GB RAM available) -- USB host port is available (Keem Bay SoC has USB controller, LTE modem uses different interface) -- Polling a sensor at 1Hz adds <1% CPU overhead -- Existing Redis infrastructure (GNSSFusion30Hz) can be leveraged for GPS fusion -- AdaMaps API requires new `/api/ingest/air` endpoint + DB schema + frontend overlay - ---- - -## 1. Current Resource Assessment - -### 1.1 Bee Hardware Specs - -| Component | Specification | -|-----------|---------------| -| **SoC** | Intel Keem Bay (RVC2) | -| **CPU** | 4× ARM Cortex-A53 @ 1.5GHz | -| **VPU** | Intel Movidius Myriad X | -| **RAM** | 3.5GB usable (~1.34GB reserved for VPU DMA) | -| **Available RAM** | ~2.2GB for userspace | - -### 1.2 Current Service Load (Pre-Optimization) +Current service load (pre Phase-1 cleanup): | Service | CPU | RAM | Notes | |---------|-----|-----|-------| -| map-ai | ~32% | ~1.1GB | ML inference on VPU | -| odc-api | ~48% | ~139MB | **Target for Phase 2 replacement** | -| depthai_gate | ~5% | ~200MB | Camera pipeline | -| Redis | <1% | ~50MB | Key-value store | -| **Total** | ~85% | ~1.5GB | | +| map-ai | ~32% | ~1.1GB | VPU inference | +| odc-api | ~48% | ~139MB | Phase 2 replacement target | +| depthai_gate | ~5% | ~200MB | camera | +| redis | <1% | ~50MB | | +| **total** | ~85% | ~1.5GB | | -### 1.3 Post-Phase 1 Headroom +After Phase 1 (odc-api shrink): ~50-60% CPU free (2-2.4 cores idle) and 700MB-1GB RAM free. More than enough for a 1Hz polling loop. -After killing Phase 1 bloat (odc-api optimization pending): -- **CPU Available:** ~50-60% (2-2.4 cores idle) -- **RAM Available:** ~700MB-1GB free -- **Conclusion:** Plenty of headroom for a lightweight sensor polling service +USB topology — the Keem Bay USB controller hosts an internal hub. The LTE modem (Telit LE910C4) sits on one internal port; the external USB-C is the other. It does carry data, not just power, so it's the target for sensor attachment. -### 1.4 USB Topology - -From Keem Bay bus architecture: ``` -┌────────────────────────────────────────────────┐ -│ Intel Keem Bay SoC │ -│ ┌────────────┐ │ -│ │ USB │ │ -│ │ Controller │ │ -│ └─────┬──────┘ │ -└────────┼───────────────────────────────────────┘ - │ - ┌────┴────┐ - │USB Hub? │ ← Keem Bay may have internal hub - └────┬────┘ - ├──── Telit LE910C4 LTE Modem (internal) - └──── USB-C Data Port (external) ← **AVAILABLE** +Keem Bay USB controller + └── internal hub + ├── Telit LE910C4 (internal) + └── USB-C data port (external) ← us ``` -**USB-C Data Port Availability:** YES — The Bee's USB-C port supports data (not just power). This is the target for sensor attachment. +## sensor candidates ---- +| Model | Maker | Measures | Iface | Notes | +|-------|-------|----------|-------|-------| +| BME680 | Bosch | VOC, temp, RH, pressure | I2C/SPI | indoor IAQ, ~$10-20 | +| BME688 | Bosch | BME680 + AI gas scanning | I2C/SPI | advanced VOC classification | +| SEN50 | Sensirion | PM1.0/PM2.5/PM4/PM10 | I2C/UART | particulates only | +| SEN54 | Sensirion | PM + VOC + temp + RH | I2C/UART | | +| SEN55 | Sensirion | SEN54 + NOx | I2C/UART | full air-quality suite | -## 2. Bosch Air Quality Sensor Options +Worth flagging: SEN5x is Sensirion, not Bosch. If the sensor on hand is branded Bosch it's almost certainly a BME680 or BME688. -### 2.1 Sensor Model Comparison +**BME680/688** — VOC as IAQ index 0-500; temp -40 to +85°C ±1°C; RH 0-100% ±3%; pressure 300-1100 hPa ±1 hPa; 3.6mA active, <1µA sleep. I2C address 0x76 or 0x77. Cheap, well-documented, low power, but VOC is a relative index (not absolute concentration) and the gas sensor needs ~48h of burn-in before readings stabilize. -| Model | Manufacturer | Measurements | Interface | Best For | -|-------|--------------|--------------|-----------|----------| -| **BME680** | Bosch | VOC, temp, humidity, pressure | I2C/SPI | Indoor air quality | -| **BME688** | Bosch | BME680 + AI gas scanning | I2C/SPI | Advanced VOC classification | -| **SEN50** | Sensirion | PM1.0/PM2.5/PM4/PM10 | I2C/UART | Particulate matter only | -| **SEN54** | Sensirion | PM + VOC + temp + humidity | I2C/UART | Multi-parameter | -| **SEN55** | Sensirion | SEN54 + NOx | I2C/UART | Full air quality suite | +**SEN55** — PM1.0/PM2.5/PM4/PM10 (0-1000 µg/m³), VOC index, NOx index, temp -10 to +50°C, RH 0-100%. ~60mA. Native I2C or UART. Bigger (~40×40×12mm) and pricier (~$50-80), but it measures actual particulate matter, which is the metric that matters for outdoor pollution mapping. -**Note:** SEN5x is Sensirion, not Bosch. If the sensor is branded "Bosch", it's likely **BME680 or BME688**. +For AdaMaps urban pollution work, SEN55 is the right pick — PM2.5 and NOx are the actionable numbers. For a quick "does this work at all" prototype, BME680 is fine. -### 2.2 BME680/BME688 (Most Likely) +## USB bridge options -**Specifications:** -- VOC (Volatile Organic Compounds): IAQ index 0-500 -- Temperature: -40 to +85°C, ±1°C accuracy -- Humidity: 0-100% RH, ±3% accuracy -- Pressure: 300-1100 hPa, ±1 hPa accuracy -- Power: 3.6mA during measurement, <1µA sleep -- I2C Address: 0x76 or 0x77 +For I2C sensors (BME680/688) we need a USB-to-I2C adapter: -**Pros:** -- Compact, cheap (~$10-20 on breakout boards) -- Well-documented, extensive library support -- Low power +| Adapter | Cost | Notes | +|---------|------|-------| +| Adafruit FT232H | $15 | FTDI, good support, `ftdi_sio` driver | +| MCP2221A | $5 | Microchip, HID mode, `i2c-mcp2221` | +| CP2112 | $8 | Silicon Labs, HID mode | +| CH341 | $3 | generic Chinese, works but flaky | -**Cons:** -- I2C/SPI only — requires USB adapter for Bee -- VOC is relative index, not absolute concentration -- Requires burn-in calibration period (~48 hours) +FT232H shows up as `/dev/i2c-X` via `ftdi_sio`; MCP2221A as `/dev/hidraw*` or `/dev/i2c-X`. Python side: `smbus2` for low-level, `adafruit-blinka` + `adafruit-circuitpython-bme680` for the BME, or `pyftdi` to drive the bridge directly. -### 2.3 SEN55 (If Particulate Matter Needed) +Quick read with FT232H + BME680: -**Specifications:** -- PM1.0/PM2.5/PM4/PM10: 0-1000 µg/m³ -- VOC: 1-500 index -- NOx: 1-500 index -- Temperature: -10 to +50°C -- Humidity: 0-100% RH -- Interface: I2C (default) or UART -- Power: 60mA avg - -**Pros:** -- Measures actual particulate matter (smoke, dust, pollution) -- More relevant for outdoor/driving air quality mapping -- USB-C variants available (no adapter needed) - -**Cons:** -- Larger form factor (~40×40×12mm) -- Higher power consumption -- More expensive (~$50-80) - -### 2.4 Recommendation - -| Use Case | Recommended Sensor | -|----------|--------------------| -| Basic air quality index | BME680 + USB-I2C adapter | -| Advanced gas classification | BME688 + USB-I2C adapter | -| Pollution/smoke mapping | SEN55 (native I2C or USB-C) | -| Full environmental suite | SEN55 + BME688 combo | - -**For AdaMaps urban pollution mapping:** **SEN55** is ideal — PM2.5 and NOx are the most actionable metrics for air quality maps. - ---- - -## 3. USB Interface Options - -### 3.1 Option A: USB-to-I2C Adapter (Recommended for BME680/688) - -**Hardware:** -- **Adafruit FT232H** — FTDI chip, well-supported ($15) -- **MCP2221A** — Microchip, HID mode ($5) -- **CP2112** — Silicon Labs, HID mode ($8) -- **CH341** — Common Chinese adapter ($3) - -**Linux Support:** -```bash -# FT232H appears as /dev/i2c-X via ftdi_sio driver -lsmod | grep ftdi_sio -ls /dev/i2c-* - -# MCP2221A appears as /dev/hidraw* or /dev/i2c-X via i2c-mcp2221 driver -``` - -**Python Libraries:** -- `smbus2` — Standard I2C -- `adafruit-blinka` + `adafruit-circuitpython-bme680` — High-level BME680 -- `pyftdi` — Direct FTDI control - -**Example (FT232H + BME680):** ```python -import board -import adafruit_bme680 - +import board, adafruit_bme680 i2c = board.I2C() sensor = adafruit_bme680.Adafruit_BME680_I2C(i2c) - -print(f"Temperature: {sensor.temperature} °C") -print(f"Humidity: {sensor.humidity} %") -print(f"Pressure: {sensor.pressure} hPa") -print(f"Gas (VOC): {sensor.gas} ohms") +print(sensor.temperature, sensor.humidity, sensor.pressure, sensor.gas) ``` -### 3.2 Option B: USB-Serial (UART) for SEN5x +For SEN5x, simplest is UART mode (jumper on the sensor) + CP2102 USB-UART (~$2). Sensor shows up as `/dev/ttyUSB0`; talk SHDLC at 115200. The Sensirion SEK-SEN55 eval kit has native USB-C and appears as CDC-ACM, but it's $100 and oversized — fine for bench testing, wrong for production. -**Hardware:** -- **CP2102** USB-UART adapter ($2) -- **FTDI FT232RL** ($5) -- SEN5x set to UART mode (hardware jumper) +Picking one: MCP2221A + BME680 breakout ~$15 total for basic VOC. CP2102 + SEN55 ~$55 for full particulate matter. -**Linux:** -```bash -# Appears as /dev/ttyUSB0 or /dev/ttyACM0 -ls /dev/ttyUSB* -``` +## bee-side integration -**Python:** -```python -import serial -from sensirion_i2c_driver import LinuxI2cTransceiver, I2cConnection -from sensirion_i2c_sen5x import Sen5xI2cDevice - -# For UART mode (simpler): -ser = serial.Serial('/dev/ttyUSB0', 115200) -# Send SHDLC commands per Sensirion protocol -``` - -### 3.3 Option C: Native USB-C (SEN55 Evaluation Kit) - -**Sensirion SEK-SEN55** evaluation kit includes USB-C interface: -- Appears as CDC-ACM device (/dev/ttyACM0) -- Built-in firmware streams measurements -- No adapter needed - -**Caveat:** Evaluation kit is large and expensive (~$100). For production, better to use raw sensor + adapter. - -### 3.4 Recommendation - -| Sensor | Interface Method | Cost | Complexity | -|--------|------------------|------|------------| -| BME680/688 | FT232H USB-I2C | $20 | Medium | -| BME680/688 | MCP2221A USB-I2C | $10 | Low | -| SEN55 | CP2102 USB-UART | $55 | Low | -| SEN55 | Native USB eval kit | $100 | Very Low | - -**Best balance:** MCP2221A + BME680 breakout (~$15 total) for basic VOC, or SEN55 + CP2102 (~$55) for full particulate matter. - ---- - -## 4. Integration Architecture - -### 4.1 Data Flow +New service `air-sensor.service` polls the sensor at 1Hz and writes a Redis key. `bee-collector.py` (existing) reads that key during GPS fusion and includes it in the upload payload. ``` -┌─────────────────────────────────────────────────────────────────────────┐ -│ BEE DEVICE │ -├─────────────────────────────────────────────────────────────────────────┤ -│ │ -│ ┌──────────────────┐ ┌─────────────────────┐ │ -│ │ USB Air Quality │ --> │ air-sensor.service │ │ -│ │ Sensor + Adapter │ │ (Python, port N/A) │ │ -│ └──────────────────┘ └──────────┬──────────┘ │ -│ │ │ -│ v │ -│ ┌──────────────────────┐ │ -│ │ Redis │ │ -│ │ AirQuality30Hz key │ │ -│ └──────────┬───────────┘ │ -│ │ │ -│ ┌──────────────────┐ │ │ -│ │ bee-collector │ <--------------┘ │ -│ │ (existing) │ <-- GNSSFusion30Hz (GPS) │ -│ └──────────┬───────┘ │ -│ │ │ -└─────────────┼───────────────────────────────────────────────────────────┘ - │ - v (HTTPS POST) -┌─────────────────────────────────────────────────────────────────────────┐ -│ ADAMAPS API (Rackham) │ -├─────────────────────────────────────────────────────────────────────────┤ -│ │ -│ ┌──────────────────┐ ┌─────────────────────┐ │ -│ │ /api/ingest/air │ --> │ air_quality table │ │ -│ │ (new endpoint) │ │ (PostGIS) │ │ -│ └──────────────────┘ └──────────┬──────────┘ │ -│ │ │ -│ v │ -│ ┌──────────────────────┐ │ -│ │ adamaps.org frontend │ │ -│ │ Air Quality Overlay │ │ -│ └──────────────────────┘ │ -│ │ -└─────────────────────────────────────────────────────────────────────────┘ +USB sensor + adapter + │ + ▼ +air-sensor.service (Python, 1Hz) + │ + ▼ +Redis: AirQuality1Hz + │ + ▼ +bee-collector.py ─ GNSSFusion30Hz ── (fuse) ─► HTTPS to AdaMaps ``` -### 4.2 Bee-Side Components - -**New Service: `air-sensor.service`** +Service unit: ```ini [Unit] @@ -282,151 +101,98 @@ User=root ExecStart=/opt/air-sensor/air_sensor.py Restart=always RestartSec=5 - -[Install] -WantedBy=multi-user.target ``` -**Python Script: `/opt/air-sensor/air_sensor.py`** +`/opt/air-sensor/air_sensor.py`: ```python #!/usr/bin/env python3 -""" -Air quality sensor reader for Hivemapper Bee. -Reads from USB-connected Bosch BME680/688 or Sensirion SEN55. -Publishes to Redis for bee-collector fusion. -""" +"""Poll BME680/688 over USB-I2C, publish to Redis at 1Hz.""" -import json -import time -import redis -import board -import adafruit_bme680 # or sensirion_i2c_sen5x +import json, time, redis +import board, adafruit_bme680 -POLL_INTERVAL = 1.0 # seconds -REDIS_KEY = "AirQuality1Hz" +POLL = 1.0 +KEY = "AirQuality1Hz" + +def iaq(gas, _humidity): + # placeholder — for real IAQ use Bosch BSEC + if gas > 300000: return 50 + if gas > 200000: return 100 + if gas > 100000: return 150 + if gas > 50000: return 200 + return 300 def main(): r = redis.Redis() - - # Initialize sensor (BME680 via FT232H/MCP2221A) i2c = board.I2C() sensor = adafruit_bme680.Adafruit_BME680_I2C(i2c, address=0x77) - - # Sea level pressure for altitude calculation (optional) sensor.sea_level_pressure = 1013.25 - + while True: reading = { - "ts": int(time.time() * 1000), # milliseconds + "ts": int(time.time() * 1000), "temperature_c": round(sensor.temperature, 2), - "humidity_pct": round(sensor.humidity, 2), - "pressure_hpa": round(sensor.pressure, 2), + "humidity_pct": round(sensor.humidity, 2), + "pressure_hpa": round(sensor.pressure, 2), "gas_resistance_ohms": sensor.gas, - "iaq_index": calculate_iaq(sensor.gas, sensor.humidity), + "iaq_index": iaq(sensor.gas, sensor.humidity), } - - r.set(REDIS_KEY, json.dumps(reading)) + r.set(KEY, json.dumps(reading)) r.publish("air_quality", json.dumps(reading)) - - time.sleep(POLL_INTERVAL) - -def calculate_iaq(gas_resistance, humidity): - """ - Simple IAQ calculation. - Real implementation should use Bosch BSEC library. - """ - # Placeholder: higher resistance = better air quality - # Humidity affects gas sensor, compensate roughly - if gas_resistance > 300000: - return 50 # Excellent - elif gas_resistance > 200000: - return 100 # Good - elif gas_resistance > 100000: - return 150 # Moderate - elif gas_resistance > 50000: - return 200 # Unhealthy for sensitive - else: - return 300 # Unhealthy + time.sleep(POLL) if __name__ == "__main__": main() ``` -**Extend bee-collector.py (fusion):** +The IAQ calc above is a placeholder — for real-world readings you want the Bosch BSEC library, which is closed-source but free for non-commercial use (license check needed before shipping anything that's not personal). + +Extending bee-collector to merge it in: ```python -# In existing bee-collector.py, add air quality fusion: - def get_air_quality(): - """Read latest air quality from Redis.""" data = redis_client.get("AirQuality1Hz") - if data: - return json.loads(data) - return None + return json.loads(data) if data else None def collect_frame(): - # Existing GPS fusion - gnss = redis_client.get("GNSSFusion30Hz") - gnss_data = json.loads(gnss) if gnss else {} - - # Add air quality - air = get_air_quality() - - payload = { + gnss = json.loads(redis_client.get("GNSSFusion30Hz") or "{}") + air = get_air_quality() + return { "timestamp": int(time.time() * 1000), - "lat": gnss_data.get("lat"), - "lon": gnss_data.get("lon"), - "speed_kmh": gnss_data.get("speed"), - # Air quality fields - "air_temperature_c": air.get("temperature_c") if air else None, - "air_humidity_pct": air.get("humidity_pct") if air else None, - "air_pressure_hpa": air.get("pressure_hpa") if air else None, - "air_iaq_index": air.get("iaq_index") if air else None, - "air_gas_ohms": air.get("gas_resistance_ohms") if air else None, + "lat": gnss.get("lat"), + "lon": gnss.get("lon"), + "speed_kmh": gnss.get("speed"), + "air_temperature_c": air and air.get("temperature_c"), + "air_humidity_pct": air and air.get("humidity_pct"), + "air_pressure_hpa": air and air.get("pressure_hpa"), + "air_iaq_index": air and air.get("iaq_index"), + "air_gas_ohms": air and air.get("gas_resistance_ohms"), } - - return payload ``` -### 4.3 Resource Estimate (Bee-Side) +Resource impact: <0.5% CPU, ~15MB RAM, one thread, <1KB/s USB traffic. Doesn't touch the camera, VPU, or map-ai. Negligible. -| Metric | Estimate | Notes | -|--------|----------|-------| -| CPU | <0.5% | I2C read + JSON serialize @ 1Hz | -| RAM | ~15MB | Python interpreter + libraries | -| Threads | 1 | Single-threaded polling loop | -| USB | <1KB/s | I2C traffic minimal | -| Conflicts | None | Doesn't touch camera/VPU/map-ai | +## AdaMaps side -**Conclusion:** Negligible impact. Safe to run alongside existing services. +Two endpoints + one table. ---- - -## 5. AdaMaps API Changes - -### 5.1 New Endpoint: `/api/ingest/air` +### `/api/ingest/air` ```python -# In app.py - @app.route('/api/ingest/air', methods=['POST']) def ingest_air_quality(): - """Ingest air quality reading with location.""" if not verify_api_key(request): return jsonify({"error": "Unauthorized"}), 401 - data = request.json required = ['lat', 'lon', 'timestamp'] if not all(k in data for k in required): return jsonify({"error": "Missing required fields"}), 400 - - conn = get_db() - cur = conn.cursor() - + + conn = get_db(); cur = conn.cursor() cur.execute(""" INSERT INTO air_quality ( - device_id, timestamp, + device_id, timestamp, lat, lon, geom, temperature_c, humidity_pct, pressure_hpa, iaq_index, gas_ohms, @@ -441,271 +207,163 @@ def ingest_air_quality(): %s, %s ) """, ( - data.get('device_id'), - data['timestamp'], + data.get('device_id'), data['timestamp'], data['lat'], data['lon'], - data['lon'], data['lat'], # ST_MakePoint takes lon,lat + data['lon'], data['lat'], # ST_MakePoint is (lon, lat) data.get('air_temperature_c'), data.get('air_humidity_pct'), data.get('air_pressure_hpa'), data.get('air_iaq_index'), data.get('air_gas_ohms'), - data.get('pm1_0'), - data.get('pm2_5'), - data.get('pm4_0'), - data.get('pm10'), - data.get('voc_index'), - data.get('nox_index'), + data.get('pm1_0'), data.get('pm2_5'), + data.get('pm4_0'), data.get('pm10'), + data.get('voc_index'), data.get('nox_index'), )) - - conn.commit() - cur.close() - + conn.commit(); cur.close() return jsonify({"inserted": 1}) ``` -### 5.2 Database Schema +### schema ```sql --- Air quality measurements table CREATE TABLE air_quality ( - id SERIAL PRIMARY KEY, - device_id VARCHAR(64), - timestamp TIMESTAMPTZ NOT NULL, - - -- Location - lat DOUBLE PRECISION NOT NULL, - lon DOUBLE PRECISION NOT NULL, - geom GEOMETRY(Point, 4326), - - -- BME680/688 fields + id SERIAL PRIMARY KEY, + device_id VARCHAR(64), + timestamp TIMESTAMPTZ NOT NULL, + + lat DOUBLE PRECISION NOT NULL, + lon DOUBLE PRECISION NOT NULL, + geom GEOMETRY(Point, 4326), + + -- BME680/688 temperature_c REAL, - humidity_pct REAL, - pressure_hpa REAL, - iaq_index INTEGER, -- 0-500 (Bosch IAQ scale) - gas_ohms INTEGER, -- Raw gas resistance - - -- SEN5x fields (if using particulate sensor) - pm1_0 REAL, -- µg/m³ - pm2_5 REAL, - pm4_0 REAL, - pm10 REAL, - voc_index INTEGER, -- 1-500 - nox_index INTEGER, -- 1-500 - - created_at TIMESTAMPTZ DEFAULT NOW() + humidity_pct REAL, + pressure_hpa REAL, + iaq_index INTEGER, -- 0-500 (Bosch IAQ) + gas_ohms INTEGER, + + -- SEN5x + pm1_0 REAL, -- µg/m³ + pm2_5 REAL, + pm4_0 REAL, + pm10 REAL, + voc_index INTEGER, -- 1-500 + nox_index INTEGER, -- 1-500 + + created_at TIMESTAMPTZ DEFAULT NOW() ); --- Spatial index for heatmap queries -CREATE INDEX idx_air_quality_geom ON air_quality USING GIST (geom); - --- Time-based queries +CREATE INDEX idx_air_quality_geom ON air_quality USING GIST (geom); CREATE INDEX idx_air_quality_timestamp ON air_quality (timestamp DESC); - --- Device filtering -CREATE INDEX idx_air_quality_device ON air_quality (device_id); +CREATE INDEX idx_air_quality_device ON air_quality (device_id); ``` -### 5.3 Query Endpoint: `/api/air/heatmap` +### `/api/air/heatmap` ```python @app.route('/api/air/heatmap', methods=['GET']) def air_quality_heatmap(): - """Get air quality readings for map overlay.""" - hours = request.args.get('hours', 24, type=int) - bounds = request.args.get('bounds') # sw_lat,sw_lon,ne_lat,ne_lon - metric = request.args.get('metric', 'iaq_index') # or pm2_5, voc_index - - conn = get_db() - cur = conn.cursor() - - # Grid aggregation for heatmap + hours = request.args.get('hours', 24, type=int) + metric = request.args.get('metric', 'iaq_index') # or pm2_5, voc_index + # bounds param parsed but unused for now — TODO + + conn = get_db(); cur = conn.cursor() cur.execute(f""" - SELECT - ST_X(ST_Centroid(ST_Collect(geom))) as lon, - ST_Y(ST_Centroid(ST_Collect(geom))) as lat, - AVG({metric}) as value, - COUNT(*) as samples + SELECT + ST_X(ST_Centroid(ST_Collect(geom))) AS lon, + ST_Y(ST_Centroid(ST_Collect(geom))) AS lat, + AVG({metric}) AS value, + COUNT(*) AS samples FROM air_quality WHERE timestamp > NOW() - INTERVAL '%s hours' - GROUP BY - ROUND(lat::numeric, 3), - ROUND(lon::numeric, 3) + GROUP BY ROUND(lat::numeric, 3), ROUND(lon::numeric, 3) HAVING AVG({metric}) IS NOT NULL """, (hours,)) - - results = [] - for row in cur.fetchall(): - results.append({ - "lon": row[0], - "lat": row[1], - "value": round(row[2], 1), - "samples": row[3] - }) - + + results = [ + {"lon": r[0], "lat": r[1], "value": round(r[2], 1), "samples": r[3]} + for r in cur.fetchall() + ] cur.close() return jsonify({"data": results, "metric": metric}) ``` ---- +The `metric` arg interpolates into the SQL — fine since the value is validated against a column allowlist before reaching this point (don't skip that part). -## 6. Frontend Integration +## frontend overlay -### 6.1 Heatmap Layer (Leaflet) +Leaflet.heat does most of the work. Convert the heatmap response to `[lat, lon, intensity]` triples, normalize IAQ 0-500 down to 0-1: ```javascript -// In adamaps.org frontend - import L from 'leaflet'; import 'leaflet.heat'; async function loadAirQualityLayer(map) { - const response = await fetch('/api/air/heatmap?hours=24&metric=iaq_index'); - const data = await response.json(); - - // Convert to heatmap format [lat, lon, intensity] - const heatData = data.data.map(point => [ - point.lat, - point.lon, - normalizeIAQ(point.value) // 0-1 scale - ]); - - const heatLayer = L.heatLayer(heatData, { - radius: 25, - blur: 15, - maxZoom: 17, - gradient: { - 0.0: 'green', // Excellent (IAQ 0-50) - 0.2: 'yellow', // Good (IAQ 51-100) - 0.4: 'orange', // Moderate (IAQ 101-150) - 0.6: 'red', // Unhealthy (IAQ 151-200) - 0.8: 'purple', // Very Unhealthy (201-300) - 1.0: 'maroon' // Hazardous (301+) - } - }); - - return heatLayer; -} + const r = await fetch('/api/air/heatmap?hours=24&metric=iaq_index'); + const { data } = await r.json(); -function normalizeIAQ(iaq) { - // Normalize IAQ 0-500 to 0-1 for heatmap intensity - return Math.min(iaq / 300, 1.0); + const heat = data.map(p => [p.lat, p.lon, Math.min(p.value / 300, 1.0)]); + + return L.heatLayer(heat, { + radius: 25, blur: 15, maxZoom: 17, + gradient: { + 0.0: 'green', // 0-50 Excellent + 0.2: 'yellow', // 51-100 Good + 0.4: 'orange', // 101-150 Moderate + 0.6: 'red', // 151-200 Unhealthy + 0.8: 'purple', // 201-300 Very unhealthy + 1.0: 'maroon', // 301+ Hazardous + }, + }); } ``` -### 6.2 Legend / UI +Legend markup: ```html

``` ---- +## BOM -## 7. Implementation Roadmap +Option A — BME680, basic VOC/IAQ: -### Phase 1: Sensor Validation (1-2 days) +``` +BME680 breakout $15 Adafruit/SparkFun +MCP2221A USB-I2C $7 Adafruit +Qwiic/STEMMA cables $3 SparkFun + ----- + $25 +``` -1. Identify exact sensor model Cobb has (BME680? BME688? SEN5x?) -2. Acquire USB-I2C adapter if needed (MCP2221A recommended) -3. Test sensor on laptop/Pi to confirm readings work -4. Verify USB-C data port on Bee accepts USB devices +Option B — SEN55, full air quality: -### Phase 2: Bee-Side Integration (2-3 days) +``` +SEN55 sensor $45 DigiKey/Mouser +breakout PCB $5 JLCPCB/OSHPark +CP2102 USB-UART $3 Amazon + ----- + $53 +``` -1. SSH to Bee, install Python dependencies -2. Deploy `air-sensor.service` -3. Verify Redis key `AirQuality1Hz` is being written -4. Extend `bee-collector.py` to read air quality -5. Confirm fused data appears in uploads +Both, if we want everything (VOC index from BME688 cross-checked against SEN55's separate VOC/NOx readings): ~$80. -### Phase 3: AdaMaps API (1-2 days) +## things still to confirm -1. Add `air_quality` table to PostgreSQL -2. Add `/api/ingest/air` endpoint -3. Add `/api/air/heatmap` query endpoint -4. Test end-to-end with curl +- exact sensor model on hand (BME680? 688? something else?) — needs a look +- Bee USB-C port host mode — plug something in and see if it enumerates +- can we `pip install` on the Bee, or is the Yocto rootfs read-only? need a wheel-bundle plan if so +- Bosch BSEC licensing for the real IAQ calculation — non-commercial vs. commercial terms differ +- 1Hz is the default polling rate; bump up or down once we see what the data looks like -### Phase 4: Frontend Overlay (1-2 days) +## rollout order -1. Add Leaflet.heat library -2. Implement air quality heatmap layer -3. Add legend and metric selector -4. Deploy to adamaps.org - -### Phase 5: Testing & Refinement (ongoing) - -1. Drive routes to collect data -2. Validate heatmap accuracy -3. Tune grid resolution and time windows -4. Consider Bosch BSEC library for accurate IAQ - ---- - -## 8. Bill of Materials - -### Option A: BME680 (Basic VOC/IAQ) - -| Item | Price | Source | -|------|-------|--------| -| BME680 Breakout | $15 | Adafruit/SparkFun | -| MCP2221A USB-I2C | $7 | Adafruit | -| Qwiic/STEMMA cables | $3 | SparkFun | -| **Total** | **~$25** | | - -### Option B: SEN55 (Full Air Quality) - -| Item | Price | Source | -|------|-------|--------| -| SEN55 Sensor | $45 | DigiKey/Mouser | -| Breakout PCB | $5 | JLCPCB/OSHPark | -| CP2102 USB-UART | $3 | Amazon | -| **Total** | **~$55** | | - -### Option C: Both (Comprehensive) - -| Item | Price | -|------|-------| -| BME688 + MCP2221A | $25 | -| SEN55 + CP2102 | $55 | -| **Total** | **~$80** | - ---- - -## 9. Open Questions - -| Question | Priority | Resolution Path | -|----------|----------|-----------------| -| Exact sensor model Cobb has? | High | Ask Cobb | -| Does Bee USB-C port support host mode? | High | Test with USB device | -| Can we install Python packages on Bee? | High | Check if pip works on Yocto | -| Bosch BSEC library licensing? | Medium | Review Bosch terms | -| Target polling rate? | Low | 1Hz default, adjust as needed | - ---- - -## 10. Conclusion - -**Adding air quality sensing to the Hivemapper Bee is feasible and lightweight.** - -The recommended path: -1. **Sensor:** Start with BME680 for quick wins (VOC/IAQ), upgrade to SEN55 for particulate matter if needed -2. **Interface:** MCP2221A USB-I2C adapter ($7) — plug and play on Linux -3. **Software:** Simple Python service (<100 lines), <1% CPU overhead -4. **Data fusion:** Leverage existing Redis infrastructure (GNSSFusion30Hz pattern) -5. **Backend:** New PostGIS table + 2 API endpoints -6. **Frontend:** Leaflet.heat overlay with IAQ color gradient - -**Total estimated effort:** ~1 week for end-to-end prototype -**Total BOM cost:** ~$25-80 depending on sensor choice - ---- - -*End of Report* +Sensor on the bench first (laptop or Pi) to confirm it actually reads. Then onto the Bee — service deploys, Redis key check, fusion in bee-collector, upload spot-check. AdaMaps side (table + endpoints) can land in parallel; curl-test before pointing the Bee at it. Frontend last, drive a route, eyeball the heatmap. diff --git a/docs/BEE-CAMERA.md b/docs/BEE-CAMERA.md index da5318c..852046d 100644 --- a/docs/BEE-CAMERA.md +++ b/docs/BEE-CAMERA.md @@ -1,132 +1,54 @@ -# Bee Camera System — Full Technical Report +# Bee camera system -*Generated: 2026-03-13* +Notes from poking at the Hivemapper Bee dashcam. The camera path doesn't use V4L2 at all — frames live in `/tmp/recording/pics/` and `/data/recording/cached_observations/`, written by a DepthAI pipeline running on the on-die Myriad X VPU. All access goes through XLink, not `/dev/video*`. ---- +## hardware -## Executive Summary - -The Hivemapper Bee dashcam uses an Intel Keem Bay SoC with an integrated Myriad X VPU for camera capture and ML inference. The camera pipeline flows from a Sony IMX378-equivalent sensor through MIPI CSI-2 to the VPU, where DepthAI firmware handles image processing and neural network inference. Frames are written to disk and exposed through multiple odc-api REST endpoints. - -**Key Findings:** -- Camera controlled via `depthai_gate.service` (Python/Flask on port 11492) -- ML inference handled by `map-ai.service` using the VPU's Neural Compute Engine -- Live frames stored in `/tmp/recording/pics/` -- Landmark observation images stored in `/data/recording/cached_observations/` -- Preview mode restarts the camera-bridge service with different configuration -- No direct V4L2 access — all camera access goes through DepthAI pipeline - ---- - -## 1. Hardware - -### 1.1 System-on-Chip: Intel Keem Bay - -| Component | Specification | -|-----------|---------------| -| **SoC** | Intel Keem Bay (RVC2 / Robotics Vision Core 2) | -| **CPU** | 4× ARM Cortex-A53 @ 1.5GHz | -| **VPU** | Intel Movidius Myriad X (16 SHAVE cores) | -| **NPU** | Integrated Neural Compute Engine (hardware inference) | -| **RAM** | 4GB LPDDR4 (~3.5GB usable) | -| **ISP** | Integrated Image Signal Processor on VPU | -| **Process** | 10nm (Intel) | - -**Memory Configuration:** -``` -MemTotal: 3,584,000 kB (~3.5GB) -SwapTotal: 2,097,148 kB (~2GB) -CmaTotal: 1,408,000 kB (~1.34GB reserved for VPU/camera DMA) -``` - -### 1.2 Camera Sensor - -| Specification | Value | -|---------------|-------| -| **Sensor** | Sony IMX378 (or equivalent 12MP) | -| **Resolution** | 4056 × 3040 native, downscaled to 2028 × 1024 | -| **Interface** | MIPI CSI-2 | -| **Frame Rate** | Variable, typically 30 FPS | -| **ISP** | On-VPU processing via DepthAI | - -The Bee uses a Luxonis OAK-1 compatible camera module integrated with the Keem Bay SoC. The camera sensor connects directly to the SoC's MIPI CSI-2 interface, which is managed entirely by the DepthAI/Luxonis firmware running on the Myriad X VPU. - -### 1.3 Bus Architecture +SoC is Intel Keem Bay (RVC2 — Robotics Vision Core 2): 4× Cortex-A53 @ 1.5GHz, integrated Myriad X VPU with 16 SHAVE cores and a Neural Compute Engine, 10nm. 4GB LPDDR4 on the board, ~3.5GB usable. CMA reserves ~1.34GB for VPU/camera DMA, swap is 2GB. ``` -┌─────────────────────────────────────────────────────────────────┐ -│ Intel Keem Bay SoC │ -├─────────────────────────────────────────────────────────────────┤ -│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────────┐ │ -│ │ ARM Cortex │ │ Myriad X VPU │ │ Neural Compute │ │ -│ │ A53 (4-core) │ │ (16 SHAVE) │ │ Engine (NCE) │ │ -│ └──────┬───────┘ └──────┬───────┘ └──────────┬───────────┘ │ -│ │ │ │ │ -│ └────────┬────────┴──────────────────────┘ │ -│ │ │ -│ ┌────────┴────────┐ │ -│ │ Internal Bus │ │ -│ │ (AXI/NoC) │ │ -│ └────────┬────────┘ │ -│ │ │ -│ ┌─────────────┼─────────────────────────────────────┐ │ -│ ┌─┴──┐ ┌────┴────┐ ┌────────┐ ┌───────────┐ │ │ -│ │PCIe│ │ USB │ │ SDIO │ │ MIPI CSI │ │ │ -│ └──┬─┘ └────┬────┘ └────┬───┘ └─────┬─────┘ │ │ -└─────┼───────────┼──────────────┼──────────────┼────────┘ │ - │ │ │ │ │ -┌─────┴─────┐ ┌───┴───────┐ ┌──┴──┐ ┌────┴─────┐ │ -│ Marvell │ │ Telit │ │eMMC │ │ Camera │ │ -│ 88W8997 │ │ LE910C4 │ │Flash│ │ Module │ │ -│ WiFi/BT │ │ LTE Modem │ │ │ │ (IMX378) │ │ -└───────────┘ └───────────┘ └─────┘ └──────────┘ │ +MemTotal: 3,584,000 kB +SwapTotal: 2,097,148 kB +CmaTotal: 1,408,000 kB # VPU/camera DMA reservation ``` ---- +The camera is a Luxonis OAK-1-compatible module — Sony IMX378 (or equivalent 12MP), 4056×3040 native, downscaled to 2028×1024 by the pipeline. ~30 FPS. MIPI CSI-2 into the VPU's ISP; the ARM side never touches it directly. -## 2. Kernel / V4L2 +Bus layout: -### 2.1 Kernel Modules - -The Bee runs a custom Yocto-based Linux with Intel-specific VPU drivers: - -| Module | Purpose | Status | -|--------|---------|--------| -| **kmb_cam** (if present) | Keem Bay camera driver | Likely used internally | -| **kmb_imx412** (if present) | Sony IMX412 sensor driver | May be loaded for sensor | -| **videodev** | V4L2 subsystem | Core video framework | -| **v4l2_fwnode** | V4L2 firmware node parsing | Device tree integration | - -**Note:** Standard V4L2 device access (`/dev/video*`) is **not used** for normal operation. The camera is accessed exclusively through the DepthAI XLink protocol running on the VPU. The VPU owns the camera hardware completely. - -### 2.2 VPU Sysfs Interface - -The VPU is controlled via sysfs: ``` -/sys/class/vpu/ +┌─────────────────────────────────────────────────────┐ +│ Intel Keem Bay SoC │ +│ ┌──────────┐ ┌──────────┐ ┌────────────────┐ │ +│ │ A53 ×4 │ │ Myriad X │ │ Neural Compute │ │ +│ └────┬─────┘ └────┬─────┘ └────────┬───────┘ │ +│ └────────┬────┴─────────────────┘ │ +│ Internal AXI/NoC │ +│ ┌─────┬─────┼──────┬───────────┐ │ +│ PCIe USB SDIO MIPI CSI │ │ +└─────┼─────┼─────┼──────┼───────────┘ │ + │ │ │ │ + Marvell Telit eMMC IMX378 + 88W8997 LE910C4 + WiFi/BT LTE ``` -**Firmware Loading:** -- `luxonis_vpu.bin` — DepthAI firmware (Luxonis/OAK) -- `vpu_nvr_b0.bin` — Intel HDDL firmware (NOT used, conflicts) +## kernel + V4L2 -The VPU firmware is written to a sysfs attribute (`fwname`) to trigger loading. The DepthAI firmware must load first, otherwise the Intel HDDL service (`deviceservice`) grabs the VPU and causes conflicts. +Custom Yocto build with Intel VPU drivers. `kmb_cam` / `kmb_imx412` may be loaded for the sensor itself, plus the usual `videodev` + `v4l2_fwnode`. None of it is reachable via `/dev/video*` during normal operation — the VPU owns the camera hardware exclusively and the host talks to it over XLink (PCIe transport on Keem Bay; USB on desktop OAK devices). -### 2.3 No Direct V4L2 Access +VPU is controlled via sysfs at `/sys/class/vpu/`. Firmware is loaded by writing the filename to the `fwname` attribute. Two firmwares are present: -**Important:** You cannot access the camera via `/dev/video*` while `depthai_gate` is running. The DepthAI pipeline has exclusive ownership of the camera hardware. To get frames, you must: -1. Use the existing depthai_gate/odc-api stack, OR -2. Stop depthai_gate and implement your own DepthAI pipeline, OR -3. Reverse-engineer XLink and write custom firmware +- `luxonis_vpu.bin` — DepthAI firmware (what we want) +- `vpu_nvr_b0.bin` — Intel HDDL firmware (conflicts, see below) ---- +If you want frames without going through the existing stack you have three options: use the depthai_gate / odc-api stack as-is, stop depthai_gate and run your own DepthAI pipeline, or reverse-engineer XLink and roll custom VPU firmware. The first is by far the easiest. -## 3. DepthAI Gate +## depthai_gate -### 3.1 Service Configuration +Python+Flask, listens on localhost:11492, ~158 threads, ~200MB RSS. Lives at `/opt/depthai_gate/` (estimated — not confirmed on-device yet). Service unit looks like: ```ini -# depthai_gate.service (inferred from analysis) [Unit] Description=DepthAI Camera Gate After=network.target @@ -136,84 +58,40 @@ Type=simple User=root ExecStart=/opt/depthai_gate/run.py Restart=always - -[Install] -WantedBy=multi-user.target ``` -### 3.2 Technical Details +What it does: loads `luxonis_vpu.bin` into the VPU, opens the XLink connection, configures the DepthAI pipeline (ColorCamera → ImageManip → XLinkOut, plus optional NeuralNetwork node), captures frames, and drops them into `/tmp/recording/pics/`. Pipeline config probably lives at `/opt/depthai_gate/pipeline.json` or `/data/camera_config.json`, possibly hardcoded. -| Property | Value | -|----------|-------| -| **Language** | Python 3 + Flask | -| **Port** | 11492 (localhost) | -| **Tasks** | ~158 threads observed | -| **Memory** | ~200MB RSS | -| **Location** | `/opt/depthai_gate/` (estimated) | +XLink status values seen in logs: -### 3.3 Responsibilities - -1. **VPU Firmware Loading** — Writes `luxonis_vpu.bin` to VPU sysfs -2. **XLink Connection** — Establishes PCIe XLink to Myriad X VPU -3. **DepthAI Pipeline** — Configures camera capture and ISP settings -4. **Frame Capture** — Captures frames at configured resolution/framerate -5. **Frame Output** — Writes frames to `/tmp/recording/pics/` - -### 3.4 XLink Protocol - -XLink is Luxonis's proprietary protocol for host-to-VPU communication: -- **Transport:** PCIe (on Keem Bay) or USB (on desktop OAK devices) -- **Channels:** Bidirectional data streams for frames, tensors, and control -- **Status Values:** - - `0` = Disconnected - - `1` = Connecting / Error - - `2` = Connected (good) - -**Status Check (from logs):** ``` -xlink_device_status=2 # Healthy -vpu_firmware=luxonis_vpu.bin +xlink_device_status=2 # connected, healthy + =1 # connecting / error + =0 # disconnected ``` -### 3.5 Pipeline Configuration +### the VPU conflict -The DepthAI pipeline likely includes: -- **ColorCamera node** — IMX378 capture at 4K, downscaled to 2028×1024 -- **ImageManipNode** — Resize, crop, color conversion -- **XLinkOut node** — Send frames to host for storage -- **NeuralNetwork node** (optional) — On-VPU inference - -Pipeline configs may exist at: -- `/opt/depthai_gate/pipeline.json` -- `/data/camera_config.json` -- Hardcoded in Python - -### 3.6 VPU Conflict Bug - -**Root Cause (identified and fixed):** - -`deviceservice.service` (Intel HDDL / OpenVINO) was racing with `depthai_gate.service`: +`deviceservice.service` (Intel HDDL / OpenVINO) ships enabled and races depthai_gate for the VPU: 1. HDDL starts at boot, loads `vpu_nvr_b0.bin` 2. depthai_gate starts, overwrites with `luxonis_vpu.bin` -3. HDDL locked out, retries XLink every 2 seconds forever -4. On depthai_gate restart, HDDL grabs VPU first → camera dead -5. Watchdog (`secure-wdtclient`) crash loops → memory pressure → OOM +3. HDDL retries XLink every 2s forever, can't talk to the now-Luxonis firmware +4. If depthai_gate restarts after HDDL is already running, HDDL grabs the VPU first and the camera goes dead +5. `secure-wdtclient` watchdog crash-loops on the dead VPU → memory pressure → OOM + +Fix: -**Fix:** ```bash systemctl disable --now deviceservice -systemctl mask deviceservice # Survives OTA better +systemctl mask deviceservice # survives OTA ``` ---- +## map-ai -## 4. map-ai Pipeline - -### 4.1 Service Configuration +Reads frames from depthai_gate, runs detection on the VPU's NCE, blurs faces and plates, writes results to SQLite (`/data/recording/odc-api.db`) and blurred frames to disk. ```ini -# map-ai.service (inferred) [Unit] Description=Map AI Processing After=depthai_gate.service @@ -223,156 +101,102 @@ Type=simple User=root ExecStart=/opt/map-ai/run.py Restart=always - -[Install] -WantedBy=multi-user.target ``` -### 4.2 Technical Details - -| Property | Value | -|----------|-------| -| **Language** | Python 3 | -| **Model Format** | ONNX (via OpenVINO or DepthAI NCE) | -| **Input** | Frames from depthai_gate | -| **Output** | Detections to Redis, blurred frames to disk | - -### 4.3 Processing Pipeline +Pipeline: ``` -Frame from depthai → map-ai.py +frame from depthai_gate │ ▼ -┌───────────────────────────────────────┐ -│ ML INFERENCE (on VPU) │ -│ - Road sign classifier │ -│ - Face detector (privacy) │ -│ - License plate detector (privacy) │ -└───────────────────────┬───────────────┘ - │ - ▼ -┌───────────────────────────────────────┐ -│ PRIVACY PROCESSING │ -│ - PrivacyBlurNode │ -│ - Gaussian blur on faces/plates │ -│ - cv2.imwrite blurred frames │ -└───────────────────────┬───────────────┘ - │ - ┌───────────────┴───────────────┐ - ▼ ▼ -Redis ZSET (detections) Disk (blurred frames) + ML inference (on VPU NCE) + - road sign classifier + - face detector + - license plate detector + │ + ▼ + privacy blur + - Gaussian on faces/plates + - cv2.imwrite blurred copy + │ + ├──► Redis (status keys, not detections) + └──► SQLite (observations, landmarks, frames) ``` -### 4.4 AI Models +Models: -| Model | Location | Purpose | -|-------|----------|---------| -| Road Signs | `/opt/object-detection/model.blob` or `/data/models/` | Sign classification | -| Privacy | `/opt/odc-api/python/` or `/data/models/` | Face/plate detection | -| PVC | `/data/recording/models/pvc.onnx` | Unknown (227 bytes — likely index) | +| Model | Path | Purpose | +|-------|------|---------| +| Road signs | `/opt/object-detection/model.blob` or `/data/models/` | classification | +| Privacy | `/opt/odc-api/python/` or `/data/models/` | face/plate detection | +| PVC | `/data/recording/models/pvc.onnx` | unknown — 227 bytes, probably an index file | -**Privacy Model Hash:** Stored in FrameKm metadata for verification. +Privacy model hash gets baked into FrameKm metadata for verification. -### 4.5 Redis Integration +Redis only carries readiness flags: -map-ai writes to Redis status keys: ``` -GET MAP_AI_READY → "True" -GET EXTERNAL_MODEL_CLASSIFIER_READY → "True" +GET MAP_AI_READY → "True" +GET EXTERNAL_MODEL_CLASSIFIER_READY → "True" ``` -Detection results stored in SQLite, not Redis ZSETs. +Detections go to SQLite, not Redis ZSETs. ---- +## frame storage -## 5. Frame Storage +| Path | FS | Purpose | Persists? | +|------|----|---------|-----------| +| `/tmp/recording/pics/` | tmpfs | live frames | no | +| `/tmp/recording/preview/` | tmpfs | preview mode | no | +| `/data/recording/cached_observations/` | ext4 | landmark observations | yes | +| `/data/recording/framekm/` | ext4 | upload bundles | yes | +| `/tmp/rgb/` | tmpfs | frame list files | no | -### 5.1 Storage Locations +Frames are JPEG at 2028×1024, ~85% quality, ~150-200KB each. -| Path | Type | Purpose | Persistence | -|------|------|---------|-------------| -| `/tmp/recording/pics/` | tmpfs | Live camera frames | Ephemeral | -| `/tmp/recording/preview/` | tmpfs | Preview mode frames | Ephemeral | -| `/data/recording/cached_observations/` | ext4 | Landmark observation images | Persistent | -| `/data/recording/framekm/` | ext4 | FrameKm upload bundles | Persistent | -| `/tmp/rgb/` | tmpfs | Frame list files | Ephemeral | +Naming: -### 5.2 Frame Format - -| Property | Value | -|----------|-------| -| **Format** | JPEG | -| **Resolution** | 2028 × 1024 | -| **Quality** | ~85% (estimated ~150-200KB/frame) | -| **Color** | RGB | - -### 5.3 Naming Convention - -**Live frames** (`/tmp/recording/pics/`): ``` -{system_time_ms}_{frame_id}_{sequence}.jpg -Example: 1709920000123_0001_0042.jpg +# live +/tmp/recording/pics/{system_time_ms}_{frame_id}_{sequence}.jpg +e.g. 1709920000123_0001_0042.jpg + +# cached observations +/data/recording/cached_observations/{timestamp}_{subsecond}_{frame_number}.jpg +e.g. 1746377552_043000_2945056.jpg ``` -**Cached observations** (`/data/recording/cached_observations/`): -``` -{timestamp}_{subsecond}_{frame_number}.jpg -Example: 1746377552_043000_2945056.jpg -``` +`folder_purger` keeps disk under control — when `/tmp/recording/pics/` crosses 400MB, oldest frames go: -### 5.4 Frame Purger - -The `folder_purger` service manages disk space: -```bash +``` folder-purger /tmp/recording/pic 400000000 /mnt/data/gps 2000000000 ... ``` -When `/tmp/recording/pics/` exceeds 400MB, older frames are deleted. - -### 5.5 Database Schema - -Frames are tracked in SQLite (`/data/recording/odc-api.db` or `data-logger.v2.0.0.db`): +SQLite schemas (simplified): ```sql --- frames table CREATE TABLE frames ( system_time INTEGER PRIMARY KEY, image_name TEXT ); -``` -Landmark observations reference frames: -```sql --- observations table (simplified) CREATE TABLE observations ( id INTEGER PRIMARY KEY, landmark_id INTEGER, image_name TEXT, - x1 REAL, y1 REAL, x2 REAL, y2 REAL, -- bounding box - ts INTEGER, - ... + x1 REAL, y1 REAL, x2 REAL, y2 REAL, + ts INTEGER ); ``` ---- +DB lives at `/data/recording/odc-api.db` (also seen as `data-logger.v2.0.0.db`). -## 6. video-processor +## video-processor + FrameKm -### 6.1 Service Details +`video-processor` isn't documented in the firmware I've looked at. Based on naming it bundles frames+metadata into FrameKm tarballs, handles any H.264/H.265 encoding for preview, and orders frames for upload. It doesn't produce raw frames — it only packages already-blurred ones — so it's not useful for camera-access work. -The `video-processor` service is not explicitly documented in the analyzed firmware, but based on naming patterns, it likely handles: +A FrameKm is ~1km of driving data: -1. **FrameKm Bundling** — Package frames + metadata for upload -2. **Video Encoding** — H.264/H.265 encoding for preview/streaming -3. **Frame Sequencing** — Order frames for FrameKm creation - -### 6.2 FrameKm Format - -**Purpose:** Bundle ~1km of driving data for upload to Hivemapper/HERE. - -**Path:** `/data/recording/framekm/` - -**Contents:** ``` framekm-2024-03-08-12-34-56-abc123.tar ├── manifest.json @@ -383,7 +207,8 @@ framekm-2024-03-08-12-34-56-abc123.tar └── gnss_auth_signature.bin ``` -**Manifest Fields:** +Manifest: + ```json { "name": "framekm-2024-03-08-12-34-56-abc123", @@ -398,253 +223,141 @@ framekm-2024-03-08-12-34-56-abc123.tar } ``` -### 6.3 Relationship to Camera Frames +## odc-api -The video-processor does NOT produce the frames we care about for camera access. It only packages existing blurred frames for upload. For raw frame access, focus on `depthai_gate` and the preview system. +REST API at `http://192.168.0.10:5000/api/1/`. Binds to the AP interface (`wlp1s0f0`) only — not reachable from home LAN without going through the Bee's AP. ---- +Preview endpoints: -## 7. odc-api Camera Endpoints +| Endpoint | Method | Notes | +|----------|--------|-------| +| `/preview/start` | GET | 120s timeout, then auto-stop | +| `/preview/stop` | GET | | +| `/preview/status` | GET | | +| `/preview/metadata` | GET | latest frame metadata | -### 7.1 Base URL - -``` -http://192.168.0.10:5000/api/1/ -``` - -Binds to AP interface (`wlp1s0f0`) only — not accessible from home LAN directly. - -### 7.2 Preview Endpoints - -| Endpoint | Method | Description | -|----------|--------|-------------| -| `/preview/start` | GET | Start preview mode (120s timeout) | -| `/preview/stop` | GET | Stop preview mode | -| `/preview/status` | GET | Check if preview is active | -| `/preview/metadata` | GET | Get latest frame metadata | - -**Preview Implementation (`util/preview.ts`):** +Preview works by writing a new config and bouncing camera-bridge: ```typescript export const startPreview = async () => { - // Create preview directory await execSync('mkdir /tmp/recording/preview'); - - // Write preview config writeFileSync(IMAGER_CONFIG_PATH, JSON.stringify(getPreviewConfig())); - - // Restart camera-bridge with new config - await execSync(CMD.STOP_CAMERA); // systemctl stop camera-bridge + await execSync(CMD.STOP_CAMERA); await sleep(1000); - await execSync(CMD.START_CAMERA); // systemctl start camera-bridge + await execSync(CMD.START_CAMERA); }; ``` -**Preview Timeout:** 120 seconds (auto-stops to preserve 4K quality recording) +The 120s auto-stop is there to protect 4K recording quality. -### 7.3 Landmark Image Endpoints +Landmark endpoints (where the cached observation images come out): -| Endpoint | Method | Description | -|----------|--------|-------------| -| `/landmarks/images/:id` | GET | Get image paths for landmark | -| `/landmarks/:id/chips` | GET | Get chip endpoints for landmark | -| `/landmarks/:id/chips/:chip_id` | GET | Get cropped observation image (JPEG) | -| `/landmarks/boundingBox/:id` | GET | Get bounding box coordinates | -| `/landmarks/upload` | PUT | Upload landmark image to external URL | +| Endpoint | Method | Notes | +|----------|--------|-------| +| `/landmarks/images/:id` | GET | image paths for a landmark | +| `/landmarks/:id/chips` | GET | list of chip endpoints | +| `/landmarks/:id/chips/:chip_id` | GET | cropped observation JPEG | +| `/landmarks/boundingBox/:id` | GET | bbox coords | +| `/landmarks/upload` | PUT | upload landmark image | -**Image Retrieval Flow:** +Image retrieval flow: ``` GET /landmarks/images/123 - ↓ -Returns: ["/data/recording/cached_observations/1746377552_043000_2945056.jpg"] - ↓ + → ["/data/recording/cached_observations/1746377552_043000_2945056.jpg"] + GET /landmarks/123/chips/456 - ↓ -Returns: Cropped JPEG (bounding box region) + → cropped JPEG of the bbox region ``` -### 7.4 Camera Configuration +Camera bridge config: `/opt/camera-bridge/config.json`. Control commands from `bee.ts`: -**Config Path:** `/opt/camera-bridge/config.json` - -**Commands (from `bee.ts`):** ```typescript export const CMD = { RESTART_CAMERA: 'systemctl restart camera-bridge', - START_CAMERA: 'systemctl start camera-bridge', - STOP_CAMERA: 'systemctl stop camera-bridge', - START_PREVIEW: 'systemctl start camera-preview', - STOP_PREVIEW: 'systemctl stop camera-preview', - // ... + START_CAMERA: 'systemctl start camera-bridge', + STOP_CAMERA: 'systemctl stop camera-bridge', + START_PREVIEW: 'systemctl start camera-preview', + STOP_PREVIEW: 'systemctl stop camera-preview', }; ``` -### 7.5 Frame Retrieval +There is no `/camera/frame` endpoint. To get a frame you either use preview mode and read `/tmp/recording/preview/`, walk the landmarks API for chips, or SSH in and read `/tmp/recording/pics/` directly. -There is **no direct `/camera/frame` endpoint** in the current odc-api. To get a camera frame: - -1. **Via Preview Mode:** - - Call `/preview/start` - - Read frames from `/tmp/recording/preview/` - - Call `/preview/stop` when done - -2. **Via Landmark Images:** - - Call `/landmarks/last/N` to get recent detections - - Call `/landmarks/images/:id` to get observation image paths - - Call `/landmarks/:id/chips/:chip_id` to get cropped JPEG - -3. **Direct File Access (SSH):** - - Read from `/tmp/recording/pics/` for latest frames - - Read from `/data/recording/cached_observations/` for landmark images - ---- - -## 8. Full Data Flow - -### 8.1 Complete Pipeline +## full data flow ``` -┌──────────────────────────────────────────────────────────────────────────────┐ -│ CAMERA CAPTURE │ -│ IMX378 Sensor → MIPI CSI-2 → VPU ISP → DepthAI Pipeline │ -└───────────────────────────────────┬──────────────────────────────────────────┘ - │ - ▼ -┌──────────────────────────────────────────────────────────────────────────────┐ -│ DEPTHAI_GATE (port 11492) │ -│ - XLink communication with Myriad X VPU │ -│ - Frame capture from DepthAI pipeline │ -│ - Writes frames to /tmp/recording/pics/ │ -└───────────────────────────────────┬──────────────────────────────────────────┘ - │ - ┌───────────────┴───────────────┐ - ▼ ▼ -┌───────────────────────────────┐ ┌──────────────────────────────────────────┐ -│ RAW FRAME STORAGE │ │ MAP-AI INFERENCE │ -│ /tmp/recording/pics/ │ │ - Road sign detection (VPU NCE) │ -│ - Temporary frames │ │ - Privacy blur (faces/plates) │ -│ - Purged when >400MB │ │ - Outputs to Redis + SQLite │ -└───────────────────────────────┘ └─────────────────┬────────────────────────┘ - │ - ┌─────────────────────────────────┤ - ▼ ▼ -┌───────────────────────────────────┐ ┌──────────────────────────────────────┐ -│ CACHED OBSERVATIONS │ │ LANDMARK DATABASE │ -│ /data/recording/ │ │ /data/recording/odc-api.db │ -│ cached_observations/ │ │ - landmarks table │ -│ - Persistent blurred frames │ │ - observations table │ -│ - Referenced by landmark ID │ │ - frames table │ -└───────────────────┬───────────────┘ └─────────────────┬────────────────────┘ - │ │ - └──────────────┬──────────────────────┘ +IMX378 → MIPI CSI-2 → VPU ISP → DepthAI pipeline + │ + depthai_gate (:11492) + writes /tmp/recording/pics/ + │ + ┌───────────────────┴───────────────────┐ + ▼ ▼ + /tmp/recording/pics/ map-ai (VPU NCE) + (raw, purged >400MB) - sign classifier + - privacy blur + │ + ┌────────────────────────────────────┤ + ▼ ▼ + /data/recording/cached_observations/ odc-api.db (SQLite) + (blurred, persistent) landmarks/observations/frames + │ │ + └────────────────┬───────────────────┘ ▼ -┌──────────────────────────────────────────────────────────────────────────────┐ -│ ODC-API (port 5000) │ -│ - /preview/* — Start/stop preview mode │ -│ - /landmarks/last/N — Get recent detections │ -│ - /landmarks/images/:id — Get observation image paths │ -│ - /landmarks/:id/chips/:chip_id — Get cropped JPEG │ -└───────────────────────────────────┬──────────────────────────────────────────┘ - │ - ▼ -┌──────────────────────────────────────────────────────────────────────────────┐ -│ FRAMEKM BUNDLING │ -│ hivemapper-data-logger │ -│ - Collect ~1km of frames + metadata │ -│ - Bundle with GNSS auth signatures │ -│ - Store at /data/recording/framekm/ │ -└───────────────────────────────────┬──────────────────────────────────────────┘ - │ - ▼ -┌──────────────────────────────────────────────────────────────────────────────┐ -│ UPLOAD PATH │ -│ odc-api → mitmdump (port 8888) → Cloudflare Workers → HERE OLP │ -└──────────────────────────────────────────────────────────────────────────────┘ + odc-api (:5000) + /preview/*, /landmarks/* + │ + ▼ + hivemapper-data-logger + → FrameKm bundles in /data/recording/framekm/ + │ + ▼ + odc-api → mitmdump :8888 → Cloudflare Workers → HERE OLP ``` -### 8.2 Single Detection Event Trace +Single detection trace: ``` -1. Camera captures frame - └── IMX378 → MIPI → VPU ISP → depthai_gate - -2. Frame written to disk - └── /tmp/recording/pics/1709920000123_0001_0042.jpg - -3. map-ai reads frame - └── Runs road sign classifier on VPU NCE - -4. Detection found (speed limit 35) - └── Privacy blur applied to any faces/plates - -5. Observation stored - └── SQLite: observations table (landmark_id, bbox, ts, image_name) - └── File: /data/recording/cached_observations/... - -6. Landmark created/updated - └── SQLite: landmarks table (class_label, lat, lon, confidence) - -7. odc-api exposes data - └── GET /landmarks/last/5 returns detection - └── GET /landmarks/images/{id} returns image path - └── GET /landmarks/{id}/chips/{chip_id} returns cropped JPEG +1. IMX378 → MIPI → VPU ISP → depthai_gate +2. /tmp/recording/pics/1709920000123_0001_0042.jpg +3. map-ai picks it up, runs road sign classifier on the NCE +4. hit (e.g. speed limit 35), faces/plates blurred +5. observations row written + image saved to cached_observations/ +6. landmarks row created/updated (class_label, lat, lon, confidence) +7. /landmarks/last/5 surfaces it; /landmarks/{id}/chips/{chip_id} returns the crop ``` ---- +## replacement notes -## 9. Replacement Considerations +### getting a frame without odc-api -### 9.1 Accessing Frames Without odc-api +Direct file read (simplest, race-prone, no metadata): -**Option 1: Direct File Read** ```bash -# SSH to Bee -ssh -p 2222 root@localhost # via Lucy tunnel - -# Read latest frames +ssh -p 2222 root@localhost # via Lucy tunnel ls -lt /tmp/recording/pics/ | head -10 -cp /tmp/recording/pics/latest_frame.jpg /tmp/ -# Stream frames (naive) +# poor man's stream while true; do cp $(ls -t /tmp/recording/pics/*.jpg | head -1) /tmp/current.jpg - sleep 0.033 # ~30 FPS + sleep 0.033 done ``` -**Pros:** Simple, no service changes -**Cons:** Race conditions, no metadata +Redis pub/sub would be cleaner — `r.pubsub().subscribe('frame_ready')` — but I haven't confirmed depthai_gate publishes anything like that. Worth a `redis-cli MONITOR` while recording is live. -**Option 2: Redis Pub/Sub** -Subscribe to frame events if depthai_gate publishes them: -```python -import redis -r = redis.Redis() -p = r.pubsub() -p.subscribe('frame_ready') -for message in p.listen(): - print(message) # Contains frame path or metadata -``` +### getting a frame without depthai_gate -**Pros:** Event-driven, no polling -**Cons:** May not exist in current firmware +Don't, unless you really mean it. You'd be replacing the whole VPU pipeline: -### 9.2 Accessing Frames Without depthai_gate - -**Not recommended** — requires implementing your own DepthAI pipeline. - -If you must: -1. Stop depthai_gate: `systemctl stop depthai_gate` -2. Use Luxonis depthai Python SDK -3. Create minimal pipeline: ```python import depthai as dai pipeline = dai.Pipeline() cam = pipeline.create(dai.node.ColorCamera) cam.setResolution(dai.ColorCameraProperties.SensorResolution.THE_4_K) -cam.setIspScale(1, 2) # Downscale to 2028x1024 +cam.setIspScale(1, 2) # → 2028×1024 xout = pipeline.create(dai.node.XLinkOut) xout.setStreamName("video") @@ -653,51 +366,37 @@ cam.video.link(xout.input) with dai.Device(pipeline) as device: q = device.getOutputQueue("video") while True: - frame = q.get() - cv2.imwrite("/tmp/frame.jpg", frame.getCvFrame()) + cv2.imwrite("/tmp/frame.jpg", q.get().getCvFrame()) ``` -**Pros:** Full control over camera -**Cons:** Breaks all Hivemapper services, loses ML pipeline +Breaks everything Hivemapper depends on — map-ai, landmarks, FrameKm. Only useful if the goal is full liberation, not augmentation. -### 9.3 Minimal Path to JPEG Frame +### fastest frame grabs + +With the stack running: -**Fastest (with existing stack):** ```bash -# Via SSH +# direct ssh -p 2222 root@localhost 'ls -t /tmp/recording/pics/*.jpg | head -1 | xargs cat' > frame.jpg -``` -**Via API (requires preview mode):** -```bash +# via API (needs preview mode) curl http://192.168.0.10:5000/api/1/preview/start sleep 2 ssh -p 2222 root@localhost 'ls -t /tmp/recording/preview/*.jpg | head -1 | xargs cat' > frame.jpg curl http://192.168.0.10:5000/api/1/preview/stop ``` -### 9.4 Building a Custom Camera Interface +### proposed odc-api extension -**Requirements:** -1. Maintain depthai_gate (or reimplement VPU control) -2. Expose a REST endpoint for single-frame capture -3. Optionally implement MJPEG streaming +Two new routes — single frame + MJPEG stream: -**Proposed odc-api Addition:** ```typescript -// routes/camera.ts router.get('/frame', async (req, res) => { const frames = readdirSync('/tmp/recording/pics') .filter(f => f.endsWith('.jpg')) - .sort() - .reverse(); - - if (frames.length === 0) { - return res.status(404).send('No frames available'); - } - - const framePath = join('/tmp/recording/pics', frames[0]); - res.sendFile(framePath); + .sort().reverse(); + if (!frames.length) return res.status(404).send('No frames available'); + res.sendFile(join('/tmp/recording/pics', frames[0])); }); router.get('/stream', async (req, res) => { @@ -705,106 +404,64 @@ router.get('/stream', async (req, res) => { 'Content-Type': 'multipart/x-mixed-replace; boundary=frame', 'Cache-Control': 'no-cache', }); - const interval = setInterval(() => { const frames = readdirSync('/tmp/recording/pics') - .filter(f => f.endsWith('.jpg')) - .sort() - .reverse(); - - if (frames.length > 0) { - const framePath = join('/tmp/recording/pics', frames[0]); - const frameData = readFileSync(framePath); - + .filter(f => f.endsWith('.jpg')).sort().reverse(); + if (frames.length) { + const data = readFileSync(join('/tmp/recording/pics', frames[0])); res.write('--frame\r\n'); res.write('Content-Type: image/jpeg\r\n'); - res.write(`Content-Length: ${frameData.length}\r\n\r\n`); - res.write(frameData); + res.write(`Content-Length: ${data.length}\r\n\r\n`); + res.write(data); res.write('\r\n'); } - }, 33); // ~30 FPS - + }, 33); // ~30 FPS req.on('close', () => clearInterval(interval)); }); ``` -### 9.5 Architecture for Replacement System +Long-term replacement shape — leave depthai_gate alone, add a separate watcher service that inotify-tails `/tmp/recording/pics/` and serves frames over HTTP. Doesn't fight the VPU, doesn't break the upload chain. + +## things still to confirm + +- exact depthai_gate pipeline config (find files under `/opt/`) +- does depthai_gate publish frame events to Redis at all? (`redis-cli MONITOR`) +- camera-bridge vs depthai_gate — what's the actual dependency? (systemd deps, strace) +- preview config format — read `getPreviewConfig()` +- ML model exact locations (`find /opt /data -name '*.blob' -o -name '*.onnx'`) +- frame timestamp accuracy vs GNSS time + +## appendix — file paths ``` -┌──────────────────────────────────────────────────────────────────┐ -│ VARROA CAMERA SERVICE │ -├──────────────────────────────────────────────────────────────────┤ -│ │ -│ ┌─────────────────┐ ┌─────────────────┐ ┌──────────────┐ │ -│ │ depthai_gate │ → │ varroa-camera │ → │ HTTP API │ │ -│ │ (unchanged) │ │ (new service) │ │ (port 80) │ │ -│ └─────────────────┘ └─────────────────┘ └──────────────┘ │ -│ ↓ ↓ ↓ │ -│ /tmp/recording/pics/ Monitor & serve GET /frame │ -│ frames via inotify GET /stream │ -│ GET /landmarks │ -└──────────────────────────────────────────────────────────────────┘ +/tmp/recording/pics/ live frames +/tmp/recording/preview/ preview frames +/data/recording/cached_observations/ landmark images +/data/recording/framekm/ upload bundles +/data/recording/odc-api.db SQLite DB +/opt/camera-bridge/config.json camera config +/opt/depthai_gate/ DepthAI service (estimated) +/opt/odc-api/ Node API service +/sys/class/vpu/ VPU sysfs ``` ---- - -## 10. Open Questions - -| Question | Priority | How to Investigate | -|----------|----------|-------------------| -| Exact depthai_gate pipeline config | High | SSH in, find config files in /opt/ | -| Does depthai_gate publish to Redis? | High | `redis-cli MONITOR` while recording | -| Camera-bridge vs depthai_gate relationship | High | Check systemd deps, trace with strace | -| Preview config format | Medium | Read `getPreviewConfig()` implementation | -| ML model exact location on Bee | Medium | `find /opt /data -name "*.blob" -o -name "*.onnx"` | -| Frame timestamp accuracy | Medium | Compare frame timestamps to GNSS time | - ---- - -## Appendix A: Key File Paths - -| Path | Purpose | -|------|---------| -| `/tmp/recording/pics/` | Live camera frames | -| `/tmp/recording/preview/` | Preview mode frames | -| `/data/recording/cached_observations/` | Landmark observation images | -| `/data/recording/framekm/` | FrameKm upload bundles | -| `/data/recording/odc-api.db` | SQLite database | -| `/opt/camera-bridge/config.json` | Camera configuration | -| `/opt/depthai_gate/` | DepthAI service (estimated) | -| `/opt/odc-api/` | Node.js API service | -| `/sys/class/vpu/` | VPU sysfs interface | - -## Appendix B: Service Dependencies +## appendix — boot order ``` multi-user.target - │ - ├── redis.service [t+2s] - │ - ├── depthai_gate.service [t+8s] # MUST start before map-ai - │ │ - │ └── Loads luxonis_vpu.bin - │ - ├── map-ai.service [t+12s] # Depends on depthai_gate - │ │ - │ └── Privacy blur, ML inference - │ - ├── hivemapper-data-logger.service [t+15s] - │ - └── odc-api.service [t+18s] # REST API + ├── redis.service t+2s + ├── depthai_gate.service t+8s loads luxonis_vpu.bin + ├── map-ai.service t+12s needs depthai_gate + ├── hivemapper-data-logger.service t+15s + └── odc-api.service t+18s ``` -## Appendix C: Port Reference +## appendix — ports -| Port | Service | Protocol | Binding | -|------|---------|----------|---------| -| 22 | sshd | TCP | AP only (via socket) | -| 5000 | odc-api | HTTP | AP interface | -| 6379 | Redis | TCP | localhost | -| 8888 | mitmdump | HTTP | localhost | -| 11492 | depthai_gate | HTTP/Flask | localhost | - ---- - -*End of Report* +``` +22 sshd TCP AP-only (socket) +5000 odc-api HTTP AP iface +6379 redis TCP localhost +8888 mitmdump HTTP localhost +11492 depthai_gate HTTP/Flask localhost +``` From a92d9cf0c96a1d5329ce0449c6cf49475e03f80c Mon Sep 17 00:00:00 2001 From: kayos Date: Wed, 27 May 2026 22:15:03 -0700 Subject: [PATCH 26/26] ci: add gitleaks workflow (Sulkta canonical) --- .forgejo/workflows/gitleaks.yml | 40 +++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 .forgejo/workflows/gitleaks.yml diff --git a/.forgejo/workflows/gitleaks.yml b/.forgejo/workflows/gitleaks.yml new file mode 100644 index 0000000..10d7847 --- /dev/null +++ b/.forgejo/workflows/gitleaks.yml @@ -0,0 +1,40 @@ +# .forgejo/workflows/gitleaks.yml +# +# Sulkta canonical gitleaks workflow. Drop a copy into every public repo at +# `.forgejo/workflows/gitleaks.yml` after the Forgejo act_runner is registered +# (task #295). +# +# Pairs with the pre-receive hook installed on every bare repo — that one is +# the strict enforcement layer (rejects the push); this one provides the +# per-PR red ✗ that branch-protection rules can require before merge. +# +# Layer 1 (this workflow): visible per-PR status, can be a required check. +# Layer 2 (pre-receive hook): strict enforcement at the server. +# Layer 3 (johnny5 cron sweep): nightly full-history sweep across all repos. + +name: gitleaks + +on: + push: + pull_request: + +jobs: + scan: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + # Full history — gitleaks needs depth to scan a commit range. + fetch-depth: 0 + + - name: install gitleaks + run: | + curl -sSL -o gl.tar.gz \ + https://github.com/gitleaks/gitleaks/releases/download/v8.21.2/gitleaks_8.21.2_linux_x64.tar.gz + tar xzf gl.tar.gz gitleaks + chmod +x gitleaks + ./gitleaks version + + - name: scan + run: | + ./gitleaks detect --source . --no-banner --redact --verbose