A lightweight Windows system-tray app for real-time solar inverter monitoring
via the ShineMonitor / Eybond cloud API.
Tauri v2 · Rust + HTML · ~3 MB RAM · Runs 24/7
The app supports any solar monitoring portal built on the ShineMonitor / Eybond cloud backend. Adding a new company can be a one-line code change. Contributions welcome!
| Company | Status |
|---|---|
| Knox | ✅ Included |
| Your company? | Open an issue → |
See Adding a new company below for the 5-minute guide. Contributing →
- Tiny footprint at ~3-6 MB RAM and near-zero CPU. Won't impact your games or work.

- Lives in the system tray. No window unless you open one. Designed for 24/7 machines.
- Real-time dashboard with stat cards, animated energy flow diagram, and 22+ live readings refreshed every 5 minutes.
- Power outage alerts the moment your inverter switches to battery. Choose between a detailed popup or a compact toast.
- Restoration alerts when grid power returns, showing how long the outage lasted.
- History browser with a calendar date picker, per-reading timeline with LINE/BATTERY badges, outage count, peak power, and duration stats.
- Smart polling that syncs with the hardware's 5-minute data collector interval. No wasted API calls.
- Encrypted credentials on disk using AES-256-GCM, keyed to the machine hostname.
Dashboard & Live Readings
Stat cards (mode, grid, battery, load, solar), energy flow diagram, and 22+ live sensor readings.
Data History
Pick any date → scrollable reading timeline with LINE/BATTERY badges.
Stats bar shows average power, peak, load %, outage count, total duration, and longest outage.
Power breakdown by time of day, overall averages, peak wattage, and load percentages.
Power Outage Alerts
| Popup | Toast |
![]() |
![]() |
Shows battery %, load, grid voltage, outage time, and data delay.
"Don't Remind" mutes re-alerts until power restores.
Tray Icon States
| Line Mode (green) | Battery Mode (red) |
The tray icon changes color based on inverter status: green when on grid, red when on battery.
Download the latest .exe installer or .msi from Releases and run it. The app starts minimized to the system tray.
Prerequisites:
- Rust 1.70+ (stable)
- Tauri CLI v2:
cargo install tauri-cli --version "^2" - Windows 10/11 (WebView2 is bundled)
git clone https://github.com/BrAtUkA/knoxhybrid-monitor.git
cd knoxhybrid-monitor
cargo tauri buildThe compiled binary lands in src-tauri/target/release/knoxhybrid-monitor.exe.
Installers (.msi and .exe) are in src-tauri/target/release/bundle/.
- Launch the app. It appears as a tray icon in the system tray.
- Login on first run. Select your company, enter credentials, and sign in.
- Dashboard: right-click the tray icon → Dashboard.
- Alerts are automatic on outage. Configure style and behavior from the tray menu.
- History: Dashboard → History tab → pick any date.
| Option | Description |
|---|---|
| Dashboard | Open the monitoring dashboard |
| Pause Alerts | Mute alerts for 30 / 60 / 120 min |
| Resume Alerts | Un-pause alerts early |
| Alert Style | Switch between popup and toast |
| Restored Popup | Toggle the "power restored" notification |
| Test Alert | Simulate an outage → restore cycle |
| Quit | Exit the application |
The app is designed to keep the CPU asleep as much as possible. Instead of continuous polling, it calculates exactly when the next data point will arrive and sleeps until then. Between updates, the process does nothing and sits at 0% CPU and ~3 MB RAM.
flowchart TD
START["App Launch"] --> AUTH["Wait for login"]
AUTH --> INIT["Load cached device chain\nAuthenticate with API"]
INIT --> STEP1
subgraph STEP1_BOX["Step 1: Poll"]
STEP1["Lock poll mutex"] --> QS["get_quick_status()"]
QS --> SMART{"Data stale?\n(age > 300s)"}
SMART -- "Yes" --> TTL30["Cache TTL = 30s\n(check API sooner)"]
SMART -- "No" --> TTL240["Cache TTL = 240s\n(serve from cache)"]
TTL30 --> PARSE["Parse readings + timestamp"]
TTL240 --> PARSE
PARSE --> ALERT["process_alerts()"]
ALERT --> ICON["Update tray icon"]
ICON --> UNLOCK["Unlock mutex"]
end
UNLOCK --> CACHE_LIVE["Pre-build live data\nfor instant dashboard open"]
CACHE_LIVE --> ERR{"API error?"}
ERR -- "Yes" --> BACKOFF["Exponential backoff\n30s → 60s → 120s → 240s → 300s max"]
BACKOFF --> SLEEP_ERR["Sleep backoff duration"]
SLEEP_ERR --> STEP1
ERR -- "No" --> FRESH{"New data?\n(timestamp changed)"}
FRESH -- "No (same data)" --> RETRY_LOOP
FRESH -- "Yes" --> CALC["wait = 300s − data age"]
CALC --> WAIT{"wait > 0?"}
WAIT -- "Yes" --> SLEEP_MAIN["Sleep ~250-290s\n(CPU idle)"]
WAIT -- "No (overdue)" --> RETRY_LOOP
SLEEP_MAIN --> RETRY_LOOP
subgraph RETRY_BOX["Step 2: Retry until new data (max 20 × 30s)"]
RETRY_LOOP["iteration = 0"] --> SLEEP30["Sleep 30s"]
SLEEP30 --> POLL_RETRY["Lock → poll → alerts → icon → unlock"]
POLL_RETRY --> NEW{"Timestamp changed?"}
NEW -- "Yes" --> FOUND["Cache new data\nBreak"]
NEW -- "No" --> INC["iteration++"]
INC --> MAX{"iteration < 20?"}
MAX -- "Yes" --> SLEEP30
MAX -- "No (10 min)" --> TIMEOUT["Give up, restart loop"]
end
FOUND --> STEP1
TIMEOUT --> STEP1
style START fill:#22c55e,color:#fff
style BACKOFF fill:#ef4444,color:#fff
style FOUND fill:#22c55e,color:#fff
style TIMEOUT fill:#f59e0b,color:#fff
style SLEEP_MAIN fill:#1e3a5f,color:#8bb8e8
style SLEEP30 fill:#1e3a5f,color:#8bb8e8
style SLEEP_ERR fill:#1e3a5f,color:#8bb8e8
-
Authenticates against the ShineMonitor cloud API, using the same protocol as the official mobile app.
-
Resolves the device chain once (plant → data collector → inverter), then caches it on disk for 6 hours. No repeat lookups.
-
Polls and then sleeps. After getting fresh data, the app calculates exactly when the hardware will report next (300s interval minus data age) and sleeps the entire duration. No busy loops, no timers firing.
-
Uses adaptive cache TTLs. When data is fresh, API responses are cached 240s in-memory. When data is overdue, the TTL drops to 30s to catch updates faster. Either way, redundant network calls are avoided.
-
Retries efficiently. If the expected data doesn't arrive on time, it checks every 30s for up to 10 minutes, then resets. Errors use exponential backoff (30s → 300s) so a down API doesn't burn cycles.
-
Pre-builds dashboard data after each poll so opening the dashboard is instant from cache. No API call needed.
-
All sleep, no spin. The process spends 95%+ of its time in
tokio::time::sleep. The OS doesn't even schedule it, so CPU stays at 0% and RAM at ~3 MB.
All user data is stored in %APPDATA%\com.knoxhybrid.monitor\:
| File | Contents |
|---|---|
credentials.json |
AES-256-GCM encrypted login credentials |
preferences.json |
Alert style, popup toggles, UI preferences |
static_cache.json |
Cached device chain (6h TTL) |
KNOXX_tauri/
├── ui/ # Frontend (HTML/CSS/JS)
│ ├── index.html # Dashboard
│ ├── login.html # Login window
│ ├── alert.html # Alert popup/toast
│ ├── lucide.min.js # Icons (bundled)
│ └── fonts/ # Inter font (bundled)
├── src-tauri/ # Rust backend
│ ├── src/
│ │ ├── api.rs # API client, auth, caching, encryption
│ │ ├── tray.rs # Tray icon, menu, polling, alerts
│ │ ├── commands.rs # IPC commands
│ │ ├── lib.rs # App setup, startup, window events
│ │ └── main.rs # Entry point
│ ├── capabilities/ # Tauri permission config
│ ├── icons/ # App icons
│ ├── tauri.conf.json # Tauri config
│ └── Cargo.toml # Rust dependencies
├── imgs/ # Screenshots
└── .gitignore
| Component | Technology |
|---|---|
| Framework | Tauri v2 |
| Backend | Rust 2021 edition |
| Frontend | Vanilla HTML / CSS / JS |
| HTTP | reqwest (rustls-tls) |
| Icons | Lucide (bundled) |
| Font | Inter (bundled) |
| Encryption | AES-256-GCM via aes-gcm |
Any company using the ShineMonitor / Eybond platform can be added. Each has a company_key and app_id:
- Open
src-tauri/src/api.rs→company_lookup()function - Add one line:
"your-slug" => Some(("COMPANY_KEY", "APP_ID")),
- Add the slug to the
COMPANY_SLUGSarray - Add an
<option>inui/login.htmlinside the company dropdown - Rebuild, and the new company appears in the login screen
Don't know your company's key/app_id? Open an issue and we'll help figure it out.
Want to add your company? See Adding a new company above. It's a one-line change and a great first PR.
Found a bug or have an idea? Open an issue and let's talk about it.
Want to submit code?
- Fork the repo
- Create a feature branch (
git checkout -b feature/my-change) - Commit your changes
- Open a Pull Request
MIT. See LICENSE for details.







