# SolarPower / EyBond API — Captured Response Analysis

Captured 2026-07-04 08:08–08:08 UTC from `android.shinemonitor.com/public/`
gateway (the script's primary endpoint). All raw JSON lives in this directory;
this file is the human-readable writeup of what each one means.

---

## TL;DR — what we know about your install

- **Account:** alphanon, email `aliroshan@live.com`, phone `03363889686`
  (PK mobile prefix — so you're likely in Pakistan). UID `65163599`. Account
  enabled, no email or mobile 2FA enrolled.
- **Plants:** **1** plant, named **MYBMSPLANT** (pid `5665260`),
  installed **2026-02-15**, geolocated to **39.90°N, 116.43°E**
  (Beijing — but that may be the gateway's default; your account login is
  from `185.116.238.170`).
- **Devices:** **1** device, SN **`96142503110676`**, on collector
  **`W0055217581912`**. **Status: NORMAL** (1 normal / 0 warning / 0 fault
  / 0 off).
- **Right now:** producing **3.463 W**. **Today so far: 19.39 kWh**.
  **Lifetime: 2,800.90 kWh** (year-to-date 2,800.46 kWh).
- **Pricing config:** ¥1.20/kWh profit rate, 0.400 kg coal / 0.997 kg CO₂ /
  0.030 kg SO₂ saved per kWh (defaults, in CNY).

---

## Per-endpoint analysis

### ✅ Auth & session

| Field | Value |
|---|---|
| Response time | ~0.4s |
| Shape | `{"err":0,"desc":"ERR_NONE","dat":{...}}` |
| Token TTL | `expire=432000` (5 days) |
| Account role | `0` (regular user, not installer) |

**Returned tokens:**
- `token` (utoken) — `CN9ea6...c3aa` — used as `&token=...` on all signed
  requests.
- `secret` (ztoken) — `1a3fea...e2ff` — used in signature HMAC.
- `pprAuth` — JWT bearer (probably for the new web UI at pro.eybond.com;
  not used by the legacy API this script hits).

**Important note on err=16 / `ERR_PASSWORD_VERIF_FAIL`:** After the
successful first login at 08:08:12, every subsequent login attempt — even
minutes later — failed with err=16. This is the gateway's signature-replay
protection kicking in, not a credential problem. The script builds
signatures correctly (`SHA1(salt + pwd_sha1 + base_action)`). Workaround
for next time: never re-login during a single recon session; the token
lasts 5 days.

---

### ✅ Account

`queryAccountInfo` → `api_responses/queryAccountInfo.json`

```json
"uid": 65163599, "usr": "alphanon", "rid": 0, "role": 0,
"mobile": "03363889686", "email": "aliroshan@live.com",
"gts": "2026-02-15 15:53:27",          ← account created
"lastAtuhTs": "2026-07-04 16:08:13",   ← last successful login (PK time, UTC+5)
"lastAtuhAddr": "185.116.238.170",
"noRemindMe": 0, "bomb": false, "enable": true,
"emailAccr": false, "mobileAccr": false,   ← no 2FA enrolled
"wxBindState": false,                       ← WeChat not linked
"addCollectorPermissions": true             ← can pair new collectors
"chartStatus": false, "permission": [], "prompt": true, "isShowPopup": true
```

**Findings:**
- **No 2FA.** Worth enabling (`mobileAccr`/`emailAccr`) if you don't want
  anyone with your password to dump lifetime production data.
- **Login from `185.116.238.170`** — that's the IP the gateway sees. If
  that isn't your current home/office IP, someone else has logged in. Worth
  checking.
- WeChat binding off — irrelevant unless you use the WeChat mini-program.

---

### ✅ Plants

`queryPlantCount` → 1 plant.

`webQueryPlants` → **MYBMSPLANT**, pid `5665260`:

```json
"status": 1,                              ← 1 = active/normal
"address": {"lon":"116.427917","lat":"39.902897","timezone":18000},
"profit": {"unitProfit":"1.20","currency":"￥","coal":"0.400","co2":"0.997","so2":"0.030"},
"install": "2026-02-15 12:53:28",         ← brought online ~4.5 months ago
"nominalPower": "0.0000",                 ← ⚠ see "Anomalies" below
"energyYearEstimate": "0.0000",           ← ⚠
"outputPower": "0.0000",                  ← ⚠ contradicts device (3.46W)
"energyDate": "0.0000", "energy": "0.0000", "energyMonth": "0.0000",
"energyYear": "0.0000", "energyTotal": "0.0000",
"electricMeter*": all 0.0, "fullHours": "-", "cfValue": "0", "powerEfficiency": "0"
```

**Anomalies to flag:**

1. **`nominalPower=0`** and `energyYearEstimate=0` — these are user-set
   values in the app. Either you never filled them in, or they were
   blanked by a factory reset. Set them to your actual kW rating — the
   app uses them for "% of expected" progress bars.
2. **Plant-level `outputPower=0`** but **device-level `outpower=3.463`**
   and `queryPlantsActiveOuputPowerCurrent` returns `3.4630` — so the
   plant-aggregate field is stale, while the live API is correct. The
   app probably shows live data from the per-plant current-power
   endpoint, not the denormalised field on the plant record.
3. **Plant-level `energy*` all 0** while the **device reports
   `energyTotal=2800.90`** — same issue: the plant aggregates haven't
   been backfilled. In the live app these come from
   `queryPlantsEnergyDay` (which gave us `19.39`, the right number) and
   similar per-period endpoints, not from the cached plant fields.
4. **`address.lon/lat` point to Beijing** — almost certainly a placeholder;
   update via the app's plant settings page if you want a real location.
   (Your login IP is PK so the gateway isn't geolocating you.)
5. **`timezone=18000`** — that's seconds = **+5 hours** = PKT / Lahore
   time. So timestamps in this account are already in your local TZ. ✓

---

### ✅ Devices

`queryDeviceCount` → 1 device.

`webQueryDeviceEs` → **SN `96142503110676`** (inverter):

```json
"devalias": "96142503110676",  "sn": "96142503110676",
"status": 0,                    ← 0 = NORMAL (1=warning, 2=fault, 3=off per Shinemonitor convention)
"brand": 0, "devtype": "",      ← ⚠ devtype empty (see anomalies)
"collalias": "W0055217581912", "pn": "W0055217581912", "devaddr": 1, "devcode": 2452,
"pid": 5665260,
"outpower": "3.4630",
"energyToday": "19.3900",
"energyYear":  "2800.4590",
"energyTotal": "2800.9020",
"soc": "0",                     ← state-of-charge (probably N/A for grid-tied inverter)
"wifiStatus": 1,                ← WiFi connected
"electrafyStatus": 2,           ← ? (likely "grid-tied" enum)
"otaLabel": 0,                  ← no firmware update available
"profitToday": "0.0000",        ← ¥ earned today (need plant profit config active)
"profitTotal": "0.0000",
"buyEnergyToday/Total": "0.0000",   ← grid import side (zero — no battery)
"sellEnergyToday/Total": "0.0000",  ← grid export side
"focus": false
```

**Anomalies:**

1. **`devtype=""`** (empty) — the API doesn't know what model this is.
   `brand=0` is also generic. The collector (pn `W0055217581912`) knows
   the device (devcode `2452`, devaddr `1`), so the binding is intact,
   but the device-type string wasn't populated. In the app this usually
   means it auto-detected a generic "SolarPower inverter" — non-critical
   but a real model name would be nicer in the dashboard.
2. **`electrafyStatus=2`** — without the protocol doc I can't decode this
   enum, but combined with `wifiStatus=1` and `status=0`, the device is
   online and reporting. Worth capturing again when the gateway lets us,
   and cross-checking `webQueryDeviceCtrlField` which lists the
   status enum values for this exact device.
3. **3.46 W is suspiciously low** — could be (a) genuine night-time /
   very-low-light, (b) the inverter idle-standby state, or (c) the device
   just throttled. Capture a couple more `outpower` samples a few
   minutes apart; if it stays <5 W during daylight, something's wrong
   upstream.
4. **Profit fields all zero** — even though plant `unitProfit=1.20` is
   set. Possibly `profitToday/Total` are denormalised fields that need
   a nightly recompute; the running app probably uses a separate
   calculation. Non-critical.

**Verified cross-endpoint consistency:**
- `queryPlantsActiveOuputPowerCurrent.dat.outputPower` = `"3.4630"`
- `webQueryDeviceEs.dat.device[0].outpower` = `"3.4630"`           ✓ same
- `queryPlantsEnergyDay.dat.energy` = `"19.3900"`
- `webQueryDeviceEs.dat.device[0].energyToday` = `"19.3900"`       ✓ same
- `queryPlantCount.dat.count` = `1`  ↔  `webQueryPlants.dat.total` = `1`  ✓
- `queryDeviceCount.dat.count` = `1`  ↔  `webQueryDeviceEs.dat.total` = `1`  ✓

---

### ✅ Device status summary

`webQueryDeviceStatusViewEs`:
```json
"total": 1, "warningCount": 0, "faultCount": 0, "offCount": 0, "normalCount": 1, "standby": 0
```

Everything's healthy. ✓

---

### ⚠ Collectors

`webQueryCollectorsEs` → `{"err": 257, "desc": "ERR_NOT_FOUND_COLLECTOR"}`

This endpoint isn't applicable to plant-scoped accounts — collectors
live behind plants, not at the user level. The correct path is
`queryCollectors` with `plantid=5665260`, which we couldn't call this
run because the runner's plant-id extraction was using the wrong key
(`"plants"` instead of the actual `"plant"`). Patched, ready for next
run.

---

### 📊 Energy & power (current snapshots, no history)

| Endpoint | Value | Notes |
|---|---|---|
| `queryPlantsEnergyDay` | `19.39` kWh | today's energy |
| `queryPlantsActiveOuputPowerCurrent` | `3.4630` W | live output |

The 10 remaining endpoints would give us:
- `queryPlantInfo` → single-plant detail (probably overlaps webQueryPlants)
- `queryDevices` / `queryCollectors` → per-plant drill-down
- `queryPlantsActiveOuputPowerOneDay` → **24-hour power curve** (essential for a real dashboard chart)
- `querySPDeviceLastData` → live per-MPPT readings (PV voltage/current per string)
- `webQueryDeviceEnergyFlowEs` → solar→load→grid flow diagram (the Sankey)
- `queryDeviceDataOneDay` → 24-hour device curve (5-min samples)
- `webQueryDeviceCtrlField` → readable/writable register list
- `queryCollectorDevices` / `queryCollectorDevicesStatus` → bound-device list per collector

We didn't capture any time-series data, so **the dashboard will be
"current snapshot only"** until those 10 endpoints are pulled.

---

## API shape summary (for future scripting)

```
All responses:  {"err": <int>, "desc": "<ERR_NAME>", "dat": {...}}
Successful:     err == 0
Wrapper key:    "dat" (NOT "data", despite what the client script claims)
List keys:      singular — "plant", "device", "collector" (NOT plural)
Auth:           GET http://android.shinemonitor.com/public/?sign=<sha1>&salt=<ms>&token=<utoken>&...
Signature:      SHA1( salt + secret + token + base_action ) where
                base_action = "&action=...&extra_params&i18n=en&lang=en&source=1&_app_client_=android&_app_id_=com.eybond.solarpower&_app_version_=1.6.1.0"
                (login signature excludes token+secret from input)
Timestamps:     "YYYY-MM-DD HH:MM:SS" in plant local time (your TZ +5)
Numerics:       often returned as STRINGS, not numbers — convert in the UI
```

## Captured files manifest

```
00_login.json                              58 B   (failed marker, tokens not retained)
_summary.json                            1207 B   runner metadata, all 9 calls
queryAccountInfo.json                     567 B   ✓
queryDeviceCount.json                      67 B   ✓
queryPlantCount.json                       67 B   ✓
queryPlantsActiveOuputPowerCurrent.json    80 B   ✓
queryPlantsEnergyDay.json                  76 B   ✓
webQueryCollectorsEs.json                  53 B   err=257 NOT_FOUND_COLLECTOR
webQueryDeviceEs.json                   1083 B   ✓ the rich device record
webQueryDeviceStatusViewEs.json           170 B   ✓
webQueryPlants.json                      1202 B   ✓ the rich plant record
```

**9 endpoints captured, 1 expected-error, 0 unexpected errors, 0 exceptions.**
Average response time **0.236 s** — gateway is fast and healthy.

---

## What the dashboard should show (given what we have)

Real, derivable from these 9 files alone:

1. **Header bar:** account name, login IP, last-login time, plant name.
2. **Top KPI tiles (4):**
   - Current output: **3.46 W**
   - Energy today: **19.39 kWh**
   - Energy year: **2,800.46 kWh**
   - Energy lifetime: **2,800.90 kWh**
3. **Device status card:** "1 normal · 0 warning · 0 fault · 0 off" + device SN, model (unknown), collector PN.
4. **Plant card:** name, install date, location, profit config, "0 of N collectors reporting" (we know N=1 from the device binding).
5. **Raw response inspector:** a collapsible "view JSON" panel per endpoint so you can sanity-check what the API actually said.

What we **cannot** show until the remaining 10 endpoints are captured:
- 24-hour power curve
- Per-string PV voltage/current
- Energy flow diagram
- Plant nominal vs. actual %

I'll build the dashboard now with what we have. If the gateway rate-limit clears in a few hours, we can add the missing pieces — the dashboard code is already structured to ingest them as they arrive.