commit 9aca623336b4a1a8898811997b339dbbc9034926 Author: noise Date: Fri Apr 24 16:34:10 2026 -0400 initialize diff --git a/51.2V-100Ah-LP4V2-Auto-Addressing-RS485-Firmware/51.2V 100Ah RS485 Firmware/51.2V 100Ah LP4V2 Auto-Addressing RS485 Z03T21 Firmware.bin b/51.2V-100Ah-LP4V2-Auto-Addressing-RS485-Firmware/51.2V 100Ah RS485 Firmware/51.2V 100Ah LP4V2 Auto-Addressing RS485 Z03T21 Firmware.bin new file mode 100644 index 0000000..54d336e Binary files /dev/null and b/51.2V-100Ah-LP4V2-Auto-Addressing-RS485-Firmware/51.2V 100Ah RS485 Firmware/51.2V 100Ah LP4V2 Auto-Addressing RS485 Z03T21 Firmware.bin differ diff --git a/51.2V-100Ah-LP4V2-Auto-Addressing-RS485-Firmware/51.2V 100Ah RS485 Updater/GetChipInformation.dll b/51.2V-100Ah-LP4V2-Auto-Addressing-RS485-Firmware/51.2V 100Ah RS485 Updater/GetChipInformation.dll new file mode 100644 index 0000000..3ae6ed2 Binary files /dev/null and b/51.2V-100Ah-LP4V2-Auto-Addressing-RS485-Firmware/51.2V 100Ah RS485 Updater/GetChipInformation.dll differ diff --git a/51.2V-100Ah-LP4V2-Auto-Addressing-RS485-Firmware/51.2V 100Ah RS485 Updater/NuvoISP.exe b/51.2V-100Ah-LP4V2-Auto-Addressing-RS485-Firmware/51.2V 100Ah RS485 Updater/NuvoISP.exe new file mode 100755 index 0000000..c1931d2 Binary files /dev/null and b/51.2V-100Ah-LP4V2-Auto-Addressing-RS485-Firmware/51.2V 100Ah RS485 Updater/NuvoISP.exe differ diff --git a/51.2V-100Ah-LP4V2-Auto-Addressing-RS485-Firmware/Cables Needed for Updating EG4 Electronics Batteries (Updated for AutoAddress).pdf b/51.2V-100Ah-LP4V2-Auto-Addressing-RS485-Firmware/Cables Needed for Updating EG4 Electronics Batteries (Updated for AutoAddress).pdf new file mode 100644 index 0000000..e104b82 Binary files /dev/null and b/51.2V-100Ah-LP4V2-Auto-Addressing-RS485-Firmware/Cables Needed for Updating EG4 Electronics Batteries (Updated for AutoAddress).pdf differ diff --git a/51.2V-100Ah-LP4V2-Auto-Addressing-RS485-Firmware/Change Log for 51.2V 100Ah Auto-Addressing RS485.pdf b/51.2V-100Ah-LP4V2-Auto-Addressing-RS485-Firmware/Change Log for 51.2V 100Ah Auto-Addressing RS485.pdf new file mode 100644 index 0000000..55fb10b Binary files /dev/null and b/51.2V-100Ah-LP4V2-Auto-Addressing-RS485-Firmware/Change Log for 51.2V 100Ah Auto-Addressing RS485.pdf differ diff --git a/51.2V-100Ah-LP4V2-Auto-Addressing-RS485-Firmware/EG4 51.2V 100Ah LP4V2 Auto-Addressing Windows Update Guide.pdf b/51.2V-100Ah-LP4V2-Auto-Addressing-RS485-Firmware/EG4 51.2V 100Ah LP4V2 Auto-Addressing Windows Update Guide.pdf new file mode 100644 index 0000000..c88c41b Binary files /dev/null and b/51.2V-100Ah-LP4V2-Auto-Addressing-RS485-Firmware/EG4 51.2V 100Ah LP4V2 Auto-Addressing Windows Update Guide.pdf differ diff --git a/LVX6048/Install.md b/LVX6048/Install.md new file mode 100644 index 0000000..d103fb6 --- /dev/null +++ b/LVX6048/Install.md @@ -0,0 +1,177 @@ +# LVX6048 → HA Monitoring Install + +Target: Debian-family Linux (developed on Raspberry Pi CM5), two LVX6048s over USB, HA MQTT broker on the LAN. + +> **Shortcut:** the top-level [`install.sh`](./install.sh) automates every section below. This document explains what it does and why, and how to do each step by hand if you need to diverge. + +Path conventions in the snippets below use `$BASE` to mean the root of this package (e.g. `~/solar/LVX6048`). Every canonical file lives under `$BASE/` mirroring its system destination path — e.g. `$BASE/etc/udev/rules.d/99-lvx6048.rules` deploys to `/etc/udev/rules.d/99-lvx6048.rules`. + +## 1. Install powermon + +```bash +uv tool install --with bleak powermon +powermon --listProtocols # confirm PI18 present +``` + +`mpp-solar` is yanked from PyPI; `powermon` replaces it for both adhoc and daemon use. + +## 2. udev rule + serial-keyed symlinks + +The LVX6048 reports no USB serial string in the USB descriptor, so vid:pid matching applies to both units equally. Rather than pin devices by USB hub port (which breaks the moment you move a cable), we use the PI18 `ID` query — which *does* return each inverter's real serial number — and build stable `/dev/lvx6048-{1,2}` symlinks from that. + +### 2a. udev rule — dialout access + +The rule grants `dialout` access to any LVX6048 hidraw; the logical "which unit is this?" decision happens in `lvx-resolve-links` (next step). + +Canonical: [`etc/udev/rules.d/99-lvx6048.rules`](./etc/udev/rules.d/99-lvx6048.rules). Deploy: + +```bash +sudo install -m 644 "$BASE/etc/udev/rules.d/99-lvx6048.rules" /etc/udev/rules.d/99-lvx6048.rules +sudo udevadm control --reload-rules && sudo udevadm trigger --subsystem-match=hidraw +ls -l /dev/hidraw* # both should be group=dialout, mode 0660 +``` + +### 2b. `lvx-resolve-links` — serial → symlink resolver + +[`bin/lvx-resolve-links`](./bin/lvx-resolve-links) runs once at boot, globs `/dev/hidraw*`, sends PI18 `ID` to each, and creates: + +``` +/dev/lvx6048-1 → /dev/hidrawX (where X matches SERIAL_UNIT_1) +/dev/lvx6048-2 → /dev/hidrawX (where X matches SERIAL_UNIT_2) +``` + +A single resolver pass, exclusive, before any powermon service starts — sidesteps the collision you get if each service probes independently (each probe open-read-close would race the sibling service already holding the hidraw fd). + +Deploy the script + systemd oneshot: + +```bash +sudo install -m 755 "$BASE/bin/lvx-resolve-links" /usr/local/sbin/lvx-resolve-links +# rewrite the shebang in the installed copy to point at powermon's venv python +sudo sed -i "1c#!$HOME/.local/share/uv/tools/powermon/bin/python" /usr/local/sbin/lvx-resolve-links +sudo install -m 644 "$BASE/etc/systemd/system/lvx-resolve-links.service" /etc/systemd/system/ +sudo systemctl daemon-reload +sudo systemctl enable --now lvx-resolve-links.service +ls -l /dev/lvx6048-* # both symlinks should now exist +``` + +### 2c. Capturing a unit's serial + +If you ever swap an inverter, capture the new serial and update `SERIAL_UNIT_*` in both `/usr/local/sbin/lvx-resolve-links` and [`lvx-flash/flash.py`](./lvx-flash/flash.py): + +```bash +sudo systemctl stop powermon.service powermon2.service lvx-resolve-links.service +TMP=$(mktemp --suffix=.yaml) +printf 'loop: once\ndevice:\n name: probe\n port: {type: usb, path: /dev/hidraw0, protocol: PI18}\ncommands:\n - {command: ID, trigger: {loops: 1}}\n' > "$TMP" +~/.local/bin/powermon -C "$TMP" 2>&1 | grep serial_number +rm -f "$TMP" +sudo systemctl start lvx-resolve-links.service powermon.service powermon2.service +``` + +The two current serials are hard-coded as `SERIAL_UNIT_1` / `SERIAL_UNIT_2` in both files. + +## 3. Smoke test + +```bash +powermon -C "$BASE/smoketest/console.yaml" # continuous console output (unit #1) +# or one-shot against either unit: +powermon --adhoc GS -C ~/.config/powermon/powermon.yaml +powermon --adhoc GS -C ~/.config/powermon/powermon2.yaml +``` + +## 4. powermon configs + +Powermon only supports **one `device:` per config file**, so each inverter needs its own config + its own systemd unit. + +Canonical (mode 600): [`config/powermon/powermon.yaml`](./config/powermon/powermon.yaml), [`config/powermon/powermon2.yaml`](./config/powermon/powermon2.yaml). The snapshots contain `` / `` / `` placeholders — fill them in after copying into place. Deploy: + +```bash +mkdir -p ~/.config/powermon +install -m 600 "$BASE/config/powermon/powermon.yaml" ~/.config/powermon/powermon.yaml +install -m 600 "$BASE/config/powermon/powermon2.yaml" ~/.config/powermon/powermon2.yaml +# edit the two files: replace , , placeholders +``` + +Key points (same for both, differ only in device name / path / topic prefix): + +- `loop: 1` at top level — without it, powermon defaults to `"once"` and exits after one pass +- `device.port.path: /dev/lvx6048-1` (or `-2`) — resolved via the resolver symlinks from §2b, not via powermon's own wildcard-+-serial probing (which collides when two services run simultaneously) +- `device.port.protocol: PI18` — PI30 returns CRC errors on this unit +- MQTT `username`/`password` are HA Mosquitto add-on creds (broker rejects anonymous by default) +- One `hass`-formatted MQTT output per command; ~28 entities auto-discovered per inverter, published at `homeassistant/sensor/lvx6048_{1,2}_/state` +- `entity_id_prefix` / topic = `lvx6048_1` for unit #1, `lvx6048_2` for unit #2 + +Poll cadence: `GS`/5s, `MOD`/10s, `ET`/60s, `PIRI`/300s. + +## 5. Local patches (pinned to powermon 1.0.18, survive until `uv tool upgrade`) + +The six patches are shipped as full file drop-ins under [`powermon-patches/`](./powermon-patches/) (see [its README](./powermon-patches/README.md) for per-file detail). To apply manually: + +```bash +POWERMON_SITE="$(ls -d ~/.local/share/uv/tools/powermon/lib/python*/site-packages/powermon)" +install -m 644 "$BASE/powermon-patches/pi18.py" "$POWERMON_SITE/protocols/pi18.py" +install -m 644 "$BASE/powermon-patches/usbport.py" "$POWERMON_SITE/ports/usbport.py" +install -m 644 "$BASE/powermon-patches/mqttbroker.py" "$POWERMON_SITE/libs/mqttbroker.py" +install -m 644 "$BASE/powermon-patches/port_config_model.py" "$POWERMON_SITE/configmodel/port_config_model.py" +install -m 644 "$BASE/powermon-patches/ports_init.py" "$POWERMON_SITE/ports/__init__.py" +``` + +What each patch fixes: + +**a.** `protocols/pi18.py` ~line 323: `"DC/AC power direction"` → `"DC-AC power direction"`. The slash creates a bogus extra MQTT topic level. + +**b.** `ports/usbport.py` `send_and_receive()`: (1) drain any leftover bytes from the hidraw fd before sending (non-blocking read loop swallowing `BlockingIOError`); (2) wrap the main `os.read` in the retry loop with `try: … except BlockingIOError: continue`. Without (1), a prior command's late HID bytes get parsed as the next command's reply → `KeyError` in response decoding. Without (2), an empty buffer on first read aborts the command. + +**c.** `libs/mqttbroker.py`: broaden `connect()`'s `except ConnectionRefusedError` to `except (ConnectionRefusedError, OSError)`, and narrow `publish()`'s bare `except Exception` to `except (OSError, RuntimeError, ValueError)` with a clearer log message. Without this, any network blip to the broker (e.g. HA restart, `Errno 113 No route to host`) crashes the daemon. + +**d.** `protocols/pi18.py`: append two new query commands to `QUERY_COMMANDS` — `FWS` (Fault and Warning Status, fault code + ~32 warning bits, cross-referenced with PI30 `QPGS` fault codes including `71 "Parallel version different"` and the rest of the `8x` parallel-comm family) and `PGS` (Parallel General Status, regex `PGS(\d+)$`, LVX6048-specific 30-field layout with only the high-confidence fields named and the rest exposed as raw strings). Bump `check_definitions_count(expected=24)` to `26`. These are used by `lvx-flash sync-check`. + +**e.** `configmodel/port_config_model.py`: add `serial_number: None | str | int = Field(default=None)` to `UsbPortConfig`. The model is `NoExtraBaseModel` (extra-forbidden), so powermon rejects the `serial_number:` config key without this even though `USBPort.resolve_path` already consumes it. + +**f.** `ports/__init__.py` `from_config()`: change `port_config['serial_number'] = serial_number` to be a fallback (`if port_config.get('serial_number') is None:`). The device-level `serial_number` is the logical HA identifier (`lvx6048_1`), the port-level one is the hardware PI18 serial (22-digit) — different fields, different purposes; they must not be conflated. + +Patches (e) and (f) enable powermon's native wildcard-path + serial-matching flow, which we *don't* currently use — two services probing independently at startup race each other over the same hidraw. Our current setup uses the external `lvx-resolve-links` resolver (§2b) instead, so (e) and (f) are dormant but kept applied in case we ever want to use powermon's native resolver for a single-service deployment. + +## 6. systemd services + +One powermon unit per inverter, both gated on `lvx-resolve-links.service`. Canonical: +[`etc/systemd/system/powermon.service`](./etc/systemd/system/powermon.service), +[`powermon2.service`](./etc/systemd/system/powermon2.service), +[`lvx-resolve-links.service`](./etc/systemd/system/lvx-resolve-links.service), +[`powermon.service.d/10-resolver.conf`](./etc/systemd/system/powermon.service.d/10-resolver.conf) (and its sibling under `powermon2.service.d/`). + +Deploy: + +```bash +sudo install -m 644 "$BASE/etc/systemd/system/powermon.service" /etc/systemd/system/ +sudo install -m 644 "$BASE/etc/systemd/system/powermon2.service" /etc/systemd/system/ +sudo mkdir -p /etc/systemd/system/powermon.service.d /etc/systemd/system/powermon2.service.d +sudo install -m 644 "$BASE/etc/systemd/system/powermon.service.d/10-resolver.conf" \ + /etc/systemd/system/powermon.service.d/10-resolver.conf +sudo install -m 644 "$BASE/etc/systemd/system/powermon2.service.d/10-resolver.conf" \ + /etc/systemd/system/powermon2.service.d/10-resolver.conf +sudo systemctl daemon-reload +sudo systemctl enable --now powermon.service powermon2.service +journalctl -u powermon.service -u powermon2.service -f +``` + +The drop-in adds `After=lvx-resolve-links.service` + `Requires=lvx-resolve-links.service` so both powermon services wait for the symlinks before starting. + +## 7. Verify + +```bash +# live sample of what both units are publishing (12 s = ~2 full GS cycles per unit) +timeout 15 mosquitto_sub -h 10.0.0.41 -u mqtt -P \ + -t 'homeassistant/sensor/+/state' -W 12 -v | grep lvx6048_ +``` + +Should show ~58 messages (~29 per unit per GS cycle) over that window, with both `lvx6048_1_*` and `lvx6048_2_*` entities. In HA: **Settings → Devices & Services → MQTT** → two devices `LVX6048` (and `LVX6048 #2`), each with ~29 auto-discovered sensors. + +## 8. Moving cables + +Because identification is PI18-serial-based, cable / USB hub moves don't require config changes. After any cable shuffle: + +```bash +sudo systemctl restart lvx-resolve-links.service powermon.service powermon2.service +``` + +The resolver re-probes, symlinks update, powermon services latch onto the correct device by symlink. No hardcoded hub-port references anywhere in the stack. diff --git a/LVX6048/Monitoring.md b/LVX6048/Monitoring.md new file mode 100644 index 0000000..4f10daf --- /dev/null +++ b/LVX6048/Monitoring.md @@ -0,0 +1,308 @@ +# LVX6048 Monitoring & Control via Home Assistant + +Integration plan for monitoring (and eventually controlling) 2x LVX6048 inverters from Home Assistant via a Raspberry Pi running a Python poller that publishes to MQTT. + +Continue dev from the Raspberry Pi. + +## Architecture + +``` + [LVX6048 #1] ──USB─┐ + ├──► [Raspberry Pi] ──MQTT──► [Home Assistant] + [LVX6048 #2] ──USB─┘ (mpp-solar/powermon (auto-discovers + Python daemon, ~40 entities + systemd service) per inverter) +``` + +### Why USB (not RS485) + +The LVX6048 is an MPP Solar / Voltronic-family unit. Its **documented, supported** comm path is USB-HID speaking the **PI18** protocol (some firmware revisions also accept PI30). The RS485 port exists on the hardware but is undocumented for external monitoring on this model — it's primarily intended for parallel/BMS comms. Use USB. + +### Why mpp-solar + +[`jblance/mpp-solar`](https://github.com/jblance/mpp-solar) is the canonical Python package for this inverter family. It ships: + +- PI18 / PI18LVX / PI30 / PI30MAX protocol drivers (all known query + set commands) +- A `powermon` daemon for continuous polling +- Built-in MQTT publisher with **Home Assistant auto-discovery** topic format — no HA YAML needed, entities appear automatically + +## Hardware Checklist + +- [ ] Raspberry Pi (Pi 4 or Pi 5 recommended; Pi 3B+ works) running Raspberry Pi OS 64-bit +- [ ] microSD card (32GB+) or USB SSD +- [ ] 2x USB-A to USB-B cables (one per inverter; ~3ft typical — **keep short**, USB-HID is not spec'd for long runs) +- [ ] Pi physically located near the inverter cabinet +- [ ] Ethernet (preferred) or WiFi to reach the HA MQTT broker +- [ ] Existing MQTT broker running on HA (confirmed operational) + +## Step 1 — Base Pi Setup + +```bash +sudo apt update && sudo apt upgrade -y +sudo apt install -y python3-pip python3-venv pipx git +pipx ensurepath +``` + +Reboot (or `exec $SHELL`) so `pipx` PATH takes effect. + +## Step 2 — Install mpp-solar + +```bash +pipx install mpp-solar +# verify +mpp-solar --version +mpp-solar --listProtocols +``` + +Expect `PI18`, `PI18LVX`, `PI18SV`, `PI30`, `PI30MAX` in the protocol list. + +## Step 3 — Plug in Inverters & Identify Them + +Connect inverter #1 first, alone, and run: + +```bash +ls -l /dev/hidraw* +lsusb +dmesg | tail -20 +``` + +Note the `/dev/hidraw*` device that appeared and the USB VID:PID (typical MPP Solar is `0665:5161` — confirm on your unit). + +Grab the serial number for a stable udev rule: + +```bash +udevadm info -a -n /dev/hidraw0 | grep -i serial | head -3 +``` + +Write down **SERIAL_A**. Unplug #1, plug in #2, repeat to get **SERIAL_B**. + +## Step 4 — Smoke Test + +With inverter #1 still plugged in, query it directly: + +```bash +mpp-solar -p /dev/hidraw0 -P PI18 -c GS +``` + +`GS` is the PI18 "General Status" query. Expected output: battery voltage, SoC, PV watts, load watts, grid voltage, inverter mode, etc. If `PI18` returns gibberish or CRC errors, try `PI30`: + +```bash +mpp-solar -p /dev/hidraw0 -P PI30 -c QPIGS +``` + +Whichever protocol returns clean data → that's the one to use in the config below. **Record which protocol worked.** + +Also grab rated info once: + +```bash +mpp-solar -p /dev/hidraw0 -P PI18 -c PIRI # PI18 rated info +# or +mpp-solar -p /dev/hidraw0 -P PI30 -c QPIRI # PI30 rated info +``` + +## Step 5 — Stable Device Names (udev) + +Without this, `/dev/hidraw0` and `/dev/hidraw1` can swap on reboot, and the wrong inverter's data ends up under the wrong entity in HA. + +> **As built on this CM5:** the LVX6048 returns no USB serial string, so serial-based matching below is aspirational. We ended up matching by USB hub port instead — see [`99-lvx6048.rules`](./99-lvx6048.rules) and `Install.md` §2. + +Create `/etc/udev/rules.d/99-lvx6048.rules`: + +``` +SUBSYSTEM=="hidraw", ATTRS{idVendor}=="0665", ATTRS{idProduct}=="5161", ATTRS{serial}=="SERIAL_A", SYMLINK+="lvx6048-1", MODE="0660", GROUP="dialout" +SUBSYSTEM=="hidraw", ATTRS{idVendor}=="0665", ATTRS{idProduct}=="5161", ATTRS{serial}=="SERIAL_B", SYMLINK+="lvx6048-2", MODE="0660", GROUP="dialout" +``` + +Replace `SERIAL_A` / `SERIAL_B` and the VID:PID with the real values from Step 3. Then: + +```bash +sudo usermod -aG dialout $USER +sudo udevadm control --reload-rules +sudo udevadm trigger +ls -l /dev/lvx6048-* +``` + +Both symlinks should be present. Log out / back in so the `dialout` group applies. + +## Step 6 — powermon Config + +> **As built:** the `ports:` (plural) schema shown below does **not** work with current powermon — its config model allows exactly one `device:` per file. We ship one config file per inverter ([`powermon.yaml`](./powermon.yaml), [`powermon2.yaml`](./powermon2.yaml)) and run two systemd units. See `Install.md` §4–6. + +Create `~/.config/powermon/powermon.yaml` (adjust MQTT creds and protocol name to match Step 4): + +```yaml +device: + name: lvx6048_pair + id: solar_lvx6048 + +mqttbroker: + name: + port: 1883 + username: + password: + adhoc_topic: powermon/adhoc + adhoc_result_topic: powermon/adhoc/results + +api: + enabled: false + +daemon: + type: systemd + keepalive: 60 + +ports: + - name: lvx1 + type: usb + path: /dev/lvx6048-1 + protocol: PI18 # confirmed in Step 4 + commands: + - command: GS + trigger: { every: 5 } + outputs: { name: mqtt_ha, type: hass, tag: lvx6048_1 } + - command: PIRI + trigger: { every: 300 } + outputs: { name: mqtt_ha, type: hass, tag: lvx6048_1 } + - command: MOD + trigger: { every: 10 } + outputs: { name: mqtt_ha, type: hass, tag: lvx6048_1 } + - command: FWS + trigger: { every: 30 } + outputs: { name: mqtt_ha, type: hass, tag: lvx6048_1 } + + - name: lvx2 + type: usb + path: /dev/lvx6048-2 + protocol: PI18 + commands: + - command: GS + trigger: { every: 5 } + outputs: { name: mqtt_ha, type: hass, tag: lvx6048_2 } + - command: PIRI + trigger: { every: 300 } + outputs: { name: mqtt_ha, type: hass, tag: lvx6048_2 } + - command: MOD + trigger: { every: 10 } + outputs: { name: mqtt_ha, type: hass, tag: lvx6048_2 } + - command: FWS + trigger: { every: 30 } + outputs: { name: mqtt_ha, type: hass, tag: lvx6048_2 } +``` + +**Command reference (PI18):** +- `GS` — General Status (voltages, currents, power, SoC, temps) → poll fast +- `PIRI` — Protocol / Rated Info (nameplate values) → poll slow +- `MOD` — Operating Mode (Grid / Battery / Fault / Standby) → poll medium +- `FWS` — Fault / Warning Status → poll medium +- `ET` — Total Energy (lifetime kWh) → optional, add with `every: 60` +- `ED` — Daily Energy → optional + +Run it in the foreground once to confirm MQTT is flowing: + +```bash +powermon -C ~/.config/powermon/powermon.yaml +``` + +In Home Assistant, go to **Settings → Devices & Services → MQTT** — two new devices (`lvx6048_1`, `lvx6048_2`) should appear with all their sensors auto-discovered. + +## Step 7 — systemd Service + +Create `/etc/systemd/system/powermon.service`: + +```ini +[Unit] +Description=powermon LVX6048 → MQTT bridge +After=network-online.target +Wants=network-online.target + +[Service] +Type=simple +User=pi +Group=dialout +ExecStart=/home/pi/.local/bin/powermon -C /home/pi/.config/powermon/powermon.yaml +Restart=on-failure +RestartSec=10 + +# Tolerate USB dropouts / HA broker restarts +StartLimitIntervalSec=300 +StartLimitBurst=10 + +[Install] +WantedBy=multi-user.target +``` + +Adjust `User=` / paths if not running as `pi`. Enable: + +```bash +sudo systemctl daemon-reload +sudo systemctl enable --now powermon.service +systemctl status powermon.service +journalctl -u powermon.service -f +``` + +## Step 8 — Home Assistant Dashboard + +Entities auto-appear. Minimum useful Lovelace card: + +```yaml +type: entities +title: Solar System +entities: + - sensor.lvx6048_1_battery_capacity # % + - sensor.lvx6048_1_battery_voltage # V + - sensor.lvx6048_1_pv_input_power # W + - sensor.lvx6048_1_ac_output_active_power # W + - sensor.lvx6048_1_grid_voltage # V + - sensor.lvx6048_1_inverter_heat_sink_temperature + - sensor.lvx6048_1_working_mode + - sensor.lvx6048_2_battery_capacity + - sensor.lvx6048_2_pv_input_power + - sensor.lvx6048_2_ac_output_active_power + - sensor.lvx6048_2_working_mode +``` + +Entity IDs will reflect the `tag:` values from the config. Exact names depend on protocol driver — check the MQTT integration page. + +Recommended extras: +- **Energy dashboard** — map `ET` (total lifetime energy) as a solar production source +- **Automation**: alert when `working_mode` changes to `Fault` on either unit +- **Utility meter** helpers on `ac_output_active_power` for daily/weekly kWh + +## Control (Deferred) + +Once monitoring is stable, control commands are available via the same library. PI18 set commands include: + +- `POP` — Output source priority (SUB / SBU / etc.) +- `PCP` — Charger priority (Solar first / Solar+Utility / only Solar) +- `MCHGC` — Max total charging current +- `MUCHGC` — Max utility charging current +- `PF` — Restore defaults (careful) + +Exposed in HA as `button`/`select` entities via powermon's MQTT command topic. Plan this as a separate phase after at least a week of stable monitoring data. + +## Troubleshooting + +| Symptom | Likely cause | Fix | +|---|---|---| +| `Permission denied` on `/dev/hidraw*` | User not in `dialout` | `sudo usermod -aG dialout $USER`, re-login | +| CRC errors / garbled output | Wrong protocol selected | Try `PI30` instead of `PI18` (or vice versa) | +| Device disappears after reboot | udev serial mismatch | Re-check `udevadm info -a`, verify serial string | +| Symlinks swap between units | Two units report same serial | Fall back to `KERNELS=="1-1.2"` USB-port matching | +| HA entities don't appear | MQTT discovery disabled or wrong prefix | Verify `discovery: true` in HA MQTT integration; default prefix is `homeassistant/` | +| Entities appear but never update | powermon service not actually running | `journalctl -u powermon.service -n 100` | +| One inverter works, other doesn't | Parallel-mode RS232 quirk | Query the *master* unit; the slave may not respond to USB queries while linked | + +### Parallel-mode caveat + +The 2x LVX6048s are wired in parallel (per `README.md`). In parallel mode, some MPP Solar firmware only responds to USB/PI commands on the **master** unit — the slave echoes the master's data or returns errors. If Step 4 smoke tests show one unit timing out, check the LCD to see which is master. If only the master responds, a single poller gives you combined system data; per-unit telemetry may require the parallel/sync RJ45 cable's side-channel (undocumented) or firmware that doesn't gate slave comms. + +**Recommendation:** do Step 4 on each inverter individually (unplug parallel comm cable, query via USB, replug) to confirm both respond. Then test again with parallel cable connected. + +## References + +- [jblance/mpp-solar](https://github.com/jblance/mpp-solar) — Python library +- [powermon](https://github.com/jblance/powermon) — daemon mode (split out of mpp-solar) +- [Set commands on LVX6048 — discussion #344](https://github.com/jblance/mpp-solar/discussions/344) +- [LVX6048WP user manual](https://watts247.com/manuals/mpp/PIP-LVX6048WP/LVX6048WP-manual.pdf) +- [MPP Solar LVX6048 product page](https://www.mppsolar.com/v3/lvx6048/) +- [HA community — LVX6048 integration thread](https://community.home-assistant.io/t/lvx6048-inverter/851522) +- [Connecting to LVX6048WP via USB with Raspberry Pi](https://diysolarforum.com/threads/connecting-to-lvx6048wp-via-usb-with-raspberry-pi.46774/) diff --git a/LVX6048/README.md b/LVX6048/README.md new file mode 100644 index 0000000..48374e2 --- /dev/null +++ b/LVX6048/README.md @@ -0,0 +1,154 @@ +# LVX6048 → Home Assistant + +Reproducible install package for monitoring 2× MPP Solar LVX6048 inverters over +USB-HID (PI18) via [`powermon`](https://github.com/jblance/powermon), publishing +to a Home Assistant MQTT broker with HA auto-discovery. + +## What's in the box + +``` +LVX6048/ +├── README.md ← start here +├── Install.md ← detailed walkthrough (what install.sh does) +├── Monitoring.md ← background / design notes +├── install.sh ← one-shot installer (idempotent, safe to re-run) +│ +├── etc/ mirror of target system paths +│ ├── udev/rules.d/99-lvx6048.rules +│ └── systemd/system/ +│ ├── lvx-resolve-links.service +│ ├── powermon.service +│ ├── powermon2.service +│ ├── powermon.service.d/10-resolver.conf +│ └── powermon2.service.d/10-resolver.conf +│ +├── config/powermon/ lands at ~/.config/powermon/ (mode 600) +│ ├── powermon.yaml ← unit #1 — edit MQTT creds before deploying +│ └── powermon2.yaml ← unit #2 — edit MQTT creds before deploying +│ +├── bin/ +│ └── lvx-resolve-links ← installed as /usr/local/sbin/lvx-resolve-links +│ (install.sh rewrites the shebang to the uv-installed python) +│ +├── powermon-patches/ ← drop-in files for the uv tool install +│ ├── README.md ← what each patch does + upgrade path +│ ├── pi18.py, usbport.py, mqttbroker.py, +│ └── port_config_model.py, ports_init.py +│ +├── lvx-flash/ ← settings-profile CLI +│ ├── flash.py ← dump / diff / apply / compare / sync-check +│ ├── README.md +│ └── profiles/current.yaml +│ +└── smoketest/ ← adhoc test configs (one-off powermon -C usage) + ├── console.yaml + └── smoketest.yaml +``` + +## Quick start (reproducing on a fresh machine) + +```bash +# 1. Clone / scp this folder into place (e.g. /home//solar/LVX6048) +cd ~/solar/LVX6048 + +# 2. Install uv if not already: https://docs.astral.sh/uv/ +# 3. Run the installer +./install.sh + +# 4. Edit the two things install.sh warns about: +# a. ~/.config/powermon/powermon{,2}.yaml — MQTT broker IP / user / password +# b. /usr/local/sbin/lvx-resolve-links — SERIAL_UNIT_1 / SERIAL_UNIT_2 (see below) +# lvx-flash/flash.py — same two constants + +# 5. Capture each inverter's PI18 serial (with services stopped): +sudo systemctl stop lvx-resolve-links.service powermon.service powermon2.service +for d in /dev/hidraw0 /dev/hidraw1; do + TMP=$(mktemp --suffix=.yaml) + printf 'loop: once\ndevice:\n name: probe\n port: {type: usb, path: %s, protocol: PI18}\ncommands:\n - {command: ID, trigger: {loops: 1}}\n' "$d" > "$TMP" + echo "=== $d ===" + ~/.local/bin/powermon -C "$TMP" 2>&1 | grep serial_number + rm -f "$TMP"; sleep 2 +done +# Edit SERIAL_UNIT_{1,2} in /usr/local/sbin/lvx-resolve-links and lvx-flash/flash.py, +# then: +sudo systemctl start lvx-resolve-links.service powermon.service powermon2.service +``` + +## How the pieces fit together + +``` +┌─────────────────────────┐ ┌─────────────────────────┐ +│ LVX6048 #1 (USB-HID) │ ──▶ │ /dev/hidraw{0|1} │ +│ LVX6048 #2 (USB-HID) │ ──▶ │ (vid:pid 0665:5161) │ +└─────────────────────────┘ └──────────┬──────────────┘ + │ + │ (99-lvx6048.rules → group=dialout) + │ + ▼ + ┌──────────────────────────────────┐ + │ lvx-resolve-links.service │ + │ (oneshot, runs before powermon) │ + │ │ + │ probes each hidraw w/ PI18 ID │ + │ creates /dev/lvx6048-{1,2} │ + │ symlinks keyed to serial │ + └──────────┬───────────────────────┘ + │ + │ After= / Requires= + ▼ + ┌──────────────────────────────────┐ + │ powermon.service (unit #1) │ + │ powermon2.service (unit #2) │ + │ │ + │ poll GS / MOD / PIRI / ET │ + │ publish to HA auto-discovery │ + │ topics under homeassistant/* │ + └──────────┬───────────────────────┘ + │ MQTT (port 1883) + ▼ + ┌──────────────────────────────────┐ + │ Home Assistant Mosquitto broker │ + │ ~29 auto-discovered sensors/unit │ + └──────────────────────────────────┘ +``` + +Separate from the monitoring pipeline, **`lvx-flash/`** is a manual settings +tool: dump the inverter's current config into a YAML profile, diff against a +target profile, apply changes safely (stops powermon, writes via PI18 setters, +verifies via PIRI readback). Also supports `compare` (diff live settings +between two inverters) and `sync-check` (verify parallel-stack health). + +## Cable moves + +Identification is PI18-serial-based, so moving USB cables between hub ports +never requires config edits. After any cable shuffle: + +```bash +sudo systemctl restart lvx-resolve-links.service powermon.service powermon2.service +``` + +## Replacing an inverter + +When a unit is swapped, capture its new PI18 serial (see step 5 of Quick start) +and update the two `SERIAL_UNIT_*` constants in: + +- `/usr/local/sbin/lvx-resolve-links` +- `~/solar/LVX6048/lvx-flash/flash.py` + +then restart the three services. + +## Next steps / not done + +- **Firmware parity:** on this dev stack, unit #1 is at main=06303/slave=06126 + and unit #2 is at 06440/06021. Parallel operation requires matching firmware + (fault code 71 "Parallel version different") — the sync kit's cables are + wired correctly, but the inverters won't phase-lock until both CPUs match. + Firmware upload is not part of this package (MPP Solar Windows-only tool). +- **PGS field layout:** the LVX6048-specific 30-field PGS response layout is + only partially decoded in `powermon-patches/pi18.py`. The key fields + (parallel_valid, fault_code, grid_hz, ac_output_voltage) are named; the rest + are exposed as raw strings. +- **Control / set commands via HA:** PI18 setters (POP, PCP, MCHGC, MUCHGC, PF) + are implemented in `lvx-flash/flash.py` for offline use, but aren't exposed + as HA button/select entities. Deferred until monitoring has been stable for + at least a week and firmware parity is restored. diff --git a/LVX6048/bin/lvx-resolve-links b/LVX6048/bin/lvx-resolve-links new file mode 100755 index 0000000..a0abeb8 --- /dev/null +++ b/LVX6048/bin/lvx-resolve-links @@ -0,0 +1,98 @@ +#!/usr/bin/env python3 +"""lvx-resolve-links — create /dev/lvx6048-N symlinks keyed to PI18 serial. + +Globs /dev/hidraw*, sends PI18 `ID` to each, and creates + /dev/lvx6048-1 -> /dev/hidrawX where X's serial matches SERIAL_UNIT_1 + /dev/lvx6048-2 -> /dev/hidrawX where X's serial matches SERIAL_UNIT_2 + +Must run as root. Intended as a systemd oneshot before powermon*.service. + +Runs a single discovery pass with exclusive access — unlike powermon's native +resolve_path which each service performs independently at startup, causing +collisions when a sibling service is already holding the target hidraw. + +Edit SERIAL_UNIT_1 / SERIAL_UNIT_2 when a unit is replaced. +""" +from __future__ import annotations + +import asyncio +import glob +import os +import sys + +SERIAL_UNIT_1 = "1496142109100037000000" +SERIAL_UNIT_2 = "1496142408100255000000" + +LINK_FOR_SERIAL = { + SERIAL_UNIT_1: "/dev/lvx6048-1", + SERIAL_UNIT_2: "/dev/lvx6048-2", +} + +sys.path.insert(0, "/home/noise/.local/share/uv/tools/powermon/lib/python3.11/site-packages") +from powermon.protocols import get_protocol_definition # noqa: E402 +from powermon.ports.usbport import USBPort # noqa: E402 + + +async def probe_serial(path: str) -> str | None: + proto = get_protocol_definition(protocol="PI18") + port = USBPort(path=path, protocol=proto) + port.path = path + try: + await port.connect() + if not port.is_connected(): + return None + cmd = proto.get_id_command() + res = await port.send_and_receive(command=cmd) + await port.disconnect() + except Exception as e: + print(f" probe {path}: {e.__class__.__name__}: {e}", file=sys.stderr) + return None + if res is None or not getattr(res, "is_valid", False) or not res.readings: + return None + return str(res.readings[0].data_value) + + +def _relink(link: str, target: str) -> None: + target_basename = os.path.basename(target) + try: + if os.path.islink(link) or os.path.exists(link): + os.unlink(link) + except FileNotFoundError: + pass + os.symlink(target_basename, link) + + +async def main() -> int: + candidates = sorted(glob.glob("/dev/hidraw*")) + if not candidates: + print("no /dev/hidraw* devices present", file=sys.stderr) + return 1 + + sn_to_path: dict[str, str] = {} + for p in candidates: + sn = await probe_serial(p) + if sn: + print(f"{p}: serial {sn}") + sn_to_path[sn] = p + else: + print(f"{p}: no PI18 response (probably not an LVX6048)") + + missing = [] + for sn, link in LINK_FOR_SERIAL.items(): + if sn in sn_to_path: + _relink(link, sn_to_path[sn]) + print(f"symlink {link} -> {os.path.basename(sn_to_path[sn])}") + else: + missing.append((link, sn)) + try: + if os.path.islink(link): + os.unlink(link) + except FileNotFoundError: + pass + print(f"WARNING: {link} serial {sn} not found on any /dev/hidraw*") + + return 0 if not missing else 2 + + +if __name__ == "__main__": + sys.exit(asyncio.run(main())) diff --git a/LVX6048/config/powermon/powermon.yaml b/LVX6048/config/powermon/powermon.yaml new file mode 100644 index 0000000..0c4f1b9 --- /dev/null +++ b/LVX6048/config/powermon/powermon.yaml @@ -0,0 +1,62 @@ +loop: 1 + +device: + name: LVX6048 #1 + serial_number: lvx6048_1 + manufacturer: MPP Solar + model: LVX6048 + port: + type: usb + path: /dev/lvx6048-1 + protocol: PI18 + +mqttbroker: + name: # e.g. 10.0.0.41 (HA Mosquitto broker) + port: 1883 + username: + password: + +commands: + - command: GS + trigger: + every: 5 + outputs: + - type: mqtt + topic: powermon/lvx6048_1/GS + format: + type: hass + discovery_prefix: homeassistant + entity_id_prefix: lvx6048_1 + + - command: MOD + trigger: + every: 10 + outputs: + - type: mqtt + topic: powermon/lvx6048_1/MOD + format: + type: hass + discovery_prefix: homeassistant + entity_id_prefix: lvx6048_1 + + - command: PIRI + trigger: + every: 300 + outputs: + - type: mqtt + topic: powermon/lvx6048_1/PIRI + format: + type: hass + discovery_prefix: homeassistant + entity_id_prefix: lvx6048_1 + + - command: ET + trigger: + every: 60 + outputs: + - type: mqtt + topic: powermon/lvx6048_1/ET + format: + type: hass + discovery_prefix: homeassistant + entity_id_prefix: lvx6048_1 diff --git a/LVX6048/config/powermon/powermon2.yaml b/LVX6048/config/powermon/powermon2.yaml new file mode 100644 index 0000000..83f422f --- /dev/null +++ b/LVX6048/config/powermon/powermon2.yaml @@ -0,0 +1,62 @@ +loop: 1 + +device: + name: LVX6048 #2 + serial_number: lvx6048_2 + manufacturer: MPP Solar + model: LVX6048 + port: + type: usb + path: /dev/lvx6048-2 + protocol: PI18 + +mqttbroker: + name: # e.g. 10.0.0.41 (HA Mosquitto broker) + port: 1883 + username: + password: + +commands: + - command: GS + trigger: + every: 5 + outputs: + - type: mqtt + topic: powermon/lvx6048_2/GS + format: + type: hass + discovery_prefix: homeassistant + entity_id_prefix: lvx6048_2 + + - command: MOD + trigger: + every: 10 + outputs: + - type: mqtt + topic: powermon/lvx6048_2/MOD + format: + type: hass + discovery_prefix: homeassistant + entity_id_prefix: lvx6048_2 + + - command: PIRI + trigger: + every: 300 + outputs: + - type: mqtt + topic: powermon/lvx6048_2/PIRI + format: + type: hass + discovery_prefix: homeassistant + entity_id_prefix: lvx6048_2 + + - command: ET + trigger: + every: 60 + outputs: + - type: mqtt + topic: powermon/lvx6048_2/ET + format: + type: hass + discovery_prefix: homeassistant + entity_id_prefix: lvx6048_2 diff --git a/LVX6048/etc/systemd/system/lvx-resolve-links.service b/LVX6048/etc/systemd/system/lvx-resolve-links.service new file mode 100644 index 0000000..41c1704 --- /dev/null +++ b/LVX6048/etc/systemd/system/lvx-resolve-links.service @@ -0,0 +1,13 @@ +[Unit] +Description=Resolve LVX6048 hidraw symlinks by PI18 serial +After=systemd-udev-settle.service +Wants=systemd-udev-settle.service +# powermon services Requires= this unit, so they won't start until it succeeds. + +[Service] +Type=oneshot +RemainAfterExit=yes +ExecStart=/usr/local/sbin/lvx-resolve-links + +[Install] +WantedBy=multi-user.target diff --git a/LVX6048/etc/systemd/system/powermon.service b/LVX6048/etc/systemd/system/powermon.service new file mode 100644 index 0000000..46cda8a --- /dev/null +++ b/LVX6048/etc/systemd/system/powermon.service @@ -0,0 +1,15 @@ +[Unit] +Description=powermon LVX6048 -> MQTT bridge +After=network-online.target +Wants=network-online.target + +[Service] +Type=simple +User=noise +Group=dialout +ExecStart=/home/noise/.local/bin/powermon -C /home/noise/.config/powermon/powermon.yaml +Restart=always +RestartSec=10 + +[Install] +WantedBy=multi-user.target diff --git a/LVX6048/etc/systemd/system/powermon.service.d/10-resolver.conf b/LVX6048/etc/systemd/system/powermon.service.d/10-resolver.conf new file mode 100644 index 0000000..1882c55 --- /dev/null +++ b/LVX6048/etc/systemd/system/powermon.service.d/10-resolver.conf @@ -0,0 +1,3 @@ +[Unit] +After=lvx-resolve-links.service +Requires=lvx-resolve-links.service diff --git a/LVX6048/etc/systemd/system/powermon2.service b/LVX6048/etc/systemd/system/powermon2.service new file mode 100644 index 0000000..c690a94 --- /dev/null +++ b/LVX6048/etc/systemd/system/powermon2.service @@ -0,0 +1,15 @@ +[Unit] +Description=powermon LVX6048 #2 -> MQTT bridge +After=network-online.target +Wants=network-online.target + +[Service] +Type=simple +User=noise +Group=dialout +ExecStart=/home/noise/.local/bin/powermon -C /home/noise/.config/powermon/powermon2.yaml +Restart=always +RestartSec=10 + +[Install] +WantedBy=multi-user.target diff --git a/LVX6048/etc/systemd/system/powermon2.service.d/10-resolver.conf b/LVX6048/etc/systemd/system/powermon2.service.d/10-resolver.conf new file mode 100644 index 0000000..1882c55 --- /dev/null +++ b/LVX6048/etc/systemd/system/powermon2.service.d/10-resolver.conf @@ -0,0 +1,3 @@ +[Unit] +After=lvx-resolve-links.service +Requires=lvx-resolve-links.service diff --git a/LVX6048/etc/udev/rules.d/99-lvx6048.rules b/LVX6048/etc/udev/rules.d/99-lvx6048.rules new file mode 100644 index 0000000..70b1fb8 --- /dev/null +++ b/LVX6048/etc/udev/rules.d/99-lvx6048.rules @@ -0,0 +1,6 @@ +# LVX6048 (MPP Solar / Voltronic) USB-HID — dialout access only. +# Logical unit identification is done via PI18 `ID` query at powermon startup +# (see powermon.yaml: path: /dev/hidraw*, serial_number: ). +# No SYMLINK here — powermon resolves the right /dev/hidrawN by inverter serial, +# which survives cable moves / hub port changes. +SUBSYSTEM=="hidraw", ATTRS{idVendor}=="0665", ATTRS{idProduct}=="5161", MODE="0660", GROUP="dialout" diff --git a/LVX6048/install.sh b/LVX6048/install.sh new file mode 100755 index 0000000..7a17af2 --- /dev/null +++ b/LVX6048/install.sh @@ -0,0 +1,109 @@ +#!/usr/bin/env bash +# LVX6048 → Home Assistant monitoring — reproducible installer. +# +# Installs powermon + our patches, drops in udev rule / systemd units / resolver +# script / powermon configs, and starts the services. Idempotent: safe to re-run. +# +# Assumptions: +# - Debian-family Linux (Raspberry Pi OS, Ubuntu) with systemd +# - `uv` is installed and on $PATH (https://docs.astral.sh/uv/) +# - User has sudo +# - MQTT broker reachable (edit config/powermon/*.yaml first if the broker IP / +# credentials aren't yet in place) +# +# After install, finish by editing: +# ~/.config/powermon/powermon{,2}.yaml — MQTT broker + credentials +# /usr/local/sbin/lvx-resolve-links — SERIAL_UNIT_1 / SERIAL_UNIT_2 +# lvx-flash/flash.py — SERIAL_UNIT_1 / SERIAL_UNIT_2 +# then: +# sudo systemctl restart lvx-resolve-links powermon.service powermon2.service + +set -euo pipefail + +PINNED_POWERMON="1.0.18" # version we developed/tested patches against +BASE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +msg() { printf '\n\033[1;36m== %s ==\033[0m\n' "$*"; } + +# --- 1. powermon ----------------------------------------------------------- +msg "Installing powermon ${PINNED_POWERMON} via uv" +command -v uv >/dev/null || { echo "uv not found — install from https://docs.astral.sh/uv/"; exit 1; } +uv tool install --with bleak "powermon==${PINNED_POWERMON}" + +POWERMON_VENV="${HOME}/.local/share/uv/tools/powermon" +POWERMON_SITE="$(ls -d "${POWERMON_VENV}"/lib/python*/site-packages/powermon)" +POWERMON_PY="${POWERMON_VENV}/bin/python" +[ -d "$POWERMON_SITE" ] || { echo "could not locate powermon site-packages under ${POWERMON_VENV}"; exit 1; } + +# --- 2. powermon patches --------------------------------------------------- +msg "Applying powermon patches into ${POWERMON_SITE}" +install -m 644 "${BASE}/powermon-patches/pi18.py" "${POWERMON_SITE}/protocols/pi18.py" +install -m 644 "${BASE}/powermon-patches/port_config_model.py" "${POWERMON_SITE}/configmodel/port_config_model.py" +install -m 644 "${BASE}/powermon-patches/ports_init.py" "${POWERMON_SITE}/ports/__init__.py" +install -m 644 "${BASE}/powermon-patches/usbport.py" "${POWERMON_SITE}/ports/usbport.py" +install -m 644 "${BASE}/powermon-patches/mqttbroker.py" "${POWERMON_SITE}/libs/mqttbroker.py" + +# --- 3. udev (LVX6048 hidraw → dialout perms) ------------------------------ +msg "Installing udev rule" +sudo install -m 644 "${BASE}/etc/udev/rules.d/99-lvx6048.rules" /etc/udev/rules.d/99-lvx6048.rules +sudo udevadm control --reload-rules +sudo udevadm trigger --subsystem-match=hidraw + +# --- 4. resolver script (serial → /dev/lvx6048-N symlinks) ----------------- +msg "Installing /usr/local/sbin/lvx-resolve-links (shebang → ${POWERMON_PY})" +TMP_RESOLVER="$(mktemp)" +trap 'rm -f "$TMP_RESOLVER"' EXIT +awk -v py="${POWERMON_PY}" 'NR==1 { print "#!" py; next } { print }' \ + "${BASE}/bin/lvx-resolve-links" > "$TMP_RESOLVER" +sudo install -m 755 "$TMP_RESOLVER" /usr/local/sbin/lvx-resolve-links + +# --- 5. systemd units + drop-ins ------------------------------------------- +msg "Installing systemd units" +sudo install -m 644 "${BASE}/etc/systemd/system/lvx-resolve-links.service" /etc/systemd/system/ +sudo install -m 644 "${BASE}/etc/systemd/system/powermon.service" /etc/systemd/system/ +sudo install -m 644 "${BASE}/etc/systemd/system/powermon2.service" /etc/systemd/system/ +sudo mkdir -p /etc/systemd/system/powermon.service.d /etc/systemd/system/powermon2.service.d +sudo install -m 644 "${BASE}/etc/systemd/system/powermon.service.d/10-resolver.conf" \ + /etc/systemd/system/powermon.service.d/10-resolver.conf +sudo install -m 644 "${BASE}/etc/systemd/system/powermon2.service.d/10-resolver.conf" \ + /etc/systemd/system/powermon2.service.d/10-resolver.conf +sudo systemctl daemon-reload + +# --- 6. user configs (only if not already deployed; don't clobber creds) --- +msg "Installing powermon configs (skipping existing files — edit creds manually if new)" +mkdir -p "${HOME}/.config/powermon" +for f in powermon.yaml powermon2.yaml; do + dest="${HOME}/.config/powermon/${f}" + if [ -e "$dest" ]; then + echo " $dest already exists — not overwriting" + else + install -m 600 "${BASE}/config/powermon/${f}" "$dest" + echo " wrote $dest (mode 600) — edit MQTT credentials before enabling services" + fi +done + +# --- 7. enable services ---------------------------------------------------- +msg "Enabling services" +sudo systemctl enable --now lvx-resolve-links.service +# Only auto-start powermon services if credentials look real (no placeholder present) +if grep -q '' "${HOME}/.config/powermon/powermon.yaml" 2>/dev/null; then + cat <= float_voltage). Only honored when battery_type=USER.", + "float_voltage": "V 48.0..58.4 — held while on grid (pair: bulk_voltage; must be <= bulk_voltage). Only honored when battery_type=USER.", + "max_charging_current": "A 10,20,30,40,50,60,70,80 — combined solar+AC cap", + "max_utility_charging_current": "A 2,10,20,30,40,50,60,70,80 — grid-side cap only", + "output_source_priority": "enum: solar_utility_battery | solar_battery_utility", + "charger_priority": "enum: solar_first | solar_and_utility | solar_only", + "solar_power_priority": "enum: battery_load_utility_ac | load_battery_utility", + "grid_tie": "enum: enabled | disabled (PEI/PDI)", +} + +POP_MAP = {"solar_utility_battery": "0", "solar_battery_utility": "01"} +PCP_MAP = {"solar_first": "0", "solar_and_utility": "1", "solar_only": "2"} +PSP_MAP = {"battery_load_utility_ac": "0", "load_battery_utility": "1"} +PBT_MAP = {"AGM": "0", "FLOODED": "1", "USER": "2"} + +# enum indices in PIRI (for readback verification) +POP_PIRI = {"0": "solar_utility_battery", "1": "solar_battery_utility"} +PCP_PIRI = {"0": "solar_first", "1": "solar_and_utility", "2": "solar_only"} +PSP_PIRI = {"0": "battery_load_utility_ac", "1": "load_battery_utility"} +PBT_PIRI = {"0": "AGM", "1": "FLOODED", "2": "USER"} +MACHINE_PIRI = {"0": "off_grid", "1": "grid_tie"} + + +@dataclass +class Setting: + key: str + encoder: Callable[[Any], str] # profile value -> PI18 raw command (no prefix/CRC) + decoder: Callable[[str], Any] # PIRI raw field -> profile value + piri_field: int + pair_keys: tuple[str, ...] = () # if set, these keys are encoded together in one command + + +def _v_to_tenths(v: float | int) -> str: + return f"{int(round(float(v) * 10)):03d}" + + +def _tenths_to_v(raw: str) -> float: + return int(raw) / 10.0 + + +def _amps_to_str(a: int) -> str: + return f"{int(a):03d}" + + +def _amps_from_str(raw: str) -> int: + return int(raw) + + +# Applied in this order. Safe: set low-range protections before high-range; currents before priorities. +SCHEDULE: list[Setting] = [ + Setting("battery_type", + encoder=lambda v: f"PBT{PBT_MAP[v]}", + decoder=lambda r: PBT_PIRI.get(r.lstrip("0") or "0", r), + piri_field=PIRI["battery_type"]), + Setting("cutoff_voltage", + encoder=lambda v: f"PSDV{_v_to_tenths(v)}", + decoder=_tenths_to_v, + piri_field=PIRI["cutoff_voltage"]), + # BUCD sets stop-discharge and stop-charge together + Setting("stop_discharge_voltage", + encoder=lambda pair: f"BUCD{_v_to_tenths(pair[0])},{'000' if pair[1] in (0,0.0) else _v_to_tenths(pair[1])}", + decoder=_tenths_to_v, + piri_field=PIRI["stop_discharge_voltage"], + pair_keys=("stop_discharge_voltage", "stop_charge_voltage")), + # MCHGV sets bulk and float together + Setting("bulk_voltage", + encoder=lambda pair: f"MCHGV{_v_to_tenths(pair[0])},{_v_to_tenths(pair[1])}", + decoder=_tenths_to_v, + piri_field=PIRI["bulk_voltage"], + pair_keys=("bulk_voltage", "float_voltage")), + Setting("max_utility_charging_current", + encoder=lambda a: f"MUCHGC{UNIT_INDEX},{_amps_to_str(a)}", + decoder=_amps_from_str, + piri_field=PIRI["max_utility_charging_current"]), + Setting("max_charging_current", + encoder=lambda a: f"MCHGC{UNIT_INDEX},{_amps_to_str(a)}", + decoder=_amps_from_str, + piri_field=PIRI["max_charging_current"]), + Setting("output_source_priority", + encoder=lambda v: f"POP{POP_MAP[v]}", + decoder=lambda r: POP_PIRI.get(r.lstrip("0") or "0", r), + piri_field=PIRI["output_source_priority"]), + Setting("charger_priority", + encoder=lambda v: f"PCP{UNIT_INDEX},{PCP_MAP[v]}", + decoder=lambda r: PCP_PIRI.get(r.lstrip("0") or "0", r), + piri_field=PIRI["charger_priority"]), + Setting("solar_power_priority", + encoder=lambda v: f"PSP{PSP_MAP[v]}", + decoder=lambda r: PSP_PIRI.get(r.lstrip("0") or "0", r), + piri_field=PIRI["solar_power_priority"]), + Setting("grid_tie", + encoder=lambda v: "PEI" if v == "enabled" else "PDI", + decoder=lambda r: "grid_tie" if r.lstrip("0") == "1" else "off_grid", + piri_field=PIRI["machine_type"]), +] + +# -------- range / consistency validation -------- + +def _validate(profile: dict) -> list[str]: + errs: list[str] = [] + def rng(key, lo, hi): + if key in profile and not (lo <= profile[key] <= hi): + errs.append(f"{key}={profile[key]} out of range [{lo}, {hi}]") + + rng("cutoff_voltage", 40.0, 48.0) + rng("stop_discharge_voltage", 44.0, 51.0) + if "stop_charge_voltage" in profile and profile["stop_charge_voltage"] != 0: + rng("stop_charge_voltage", 48.0, 58.0) + rng("bulk_voltage", 48.0, 58.4) + rng("float_voltage", 48.0, 58.4) + rng("max_charging_current", 10, 80) + rng("max_utility_charging_current", 2, 80) + + if "cutoff_voltage" in profile and "stop_discharge_voltage" in profile: + if profile["cutoff_voltage"] >= profile["stop_discharge_voltage"]: + errs.append("cutoff_voltage must be < stop_discharge_voltage") + if "float_voltage" in profile and "bulk_voltage" in profile: + if profile["float_voltage"] > profile["bulk_voltage"]: + errs.append("float_voltage must be <= bulk_voltage") + if "max_charging_current" in profile and profile["max_charging_current"] not in (10,20,30,40,50,60,70,80): + errs.append("max_charging_current must be one of 10,20,...,80") + if "max_utility_charging_current" in profile and profile["max_utility_charging_current"] not in (2,10,20,30,40,50,60,70,80): + errs.append("max_utility_charging_current must be one of 2,10,20,...,80") + + for k, allowed in [("output_source_priority", POP_MAP), ("charger_priority", PCP_MAP), + ("solar_power_priority", PSP_MAP), ("battery_type", PBT_MAP)]: + if k in profile and profile[k] not in allowed: + errs.append(f"{k}={profile[k]!r} not in {list(allowed)}") + if "grid_tie" in profile and profile["grid_tie"] not in ("enabled", "disabled"): + errs.append("grid_tie must be 'enabled' or 'disabled'") + return errs + +# -------- powermon glue -------- + +async def _open_port(path: str) -> USBPort: + protocol = get_protocol_definition(protocol=PROTOCOL) + port = USBPort(path=path, protocol=protocol) + port.path = path + await port.connect() + if not port.is_connected(): + raise RuntimeError(f"could not open {path}") + return port + + +async def _resolve_path(device: str | None, serial: str | None) -> str: + """Return a concrete device path. + + If `serial` is given, glob /dev/hidraw* and probe each candidate via PI18 ID + until one matches. Otherwise return `device` verbatim (typically the + resolver-maintained /dev/lvx6048-N symlink). --serial takes precedence for + cases where the resolver hasn't run (e.g. early boot, bare hidraw probing). + """ + import glob as _glob + if serial: + pass # fall through to probe + elif device: + return device + else: + raise RuntimeError("must supply --device or --serial") + candidates = sorted(_glob.glob("/dev/hidraw*")) + if not candidates: + raise RuntimeError("no /dev/hidraw* devices present") + for path in candidates: + try: + port = await _open_port(path) + except Exception: + continue + try: + parts = await _read_raw_parts(port, "ID") + # ID returns one field, so parts[0] is the inverter serial + if parts and parts[0] == str(serial): + return path + except Exception: + pass + finally: + await port.disconnect() + raise RuntimeError(f"no device at /dev/hidraw* responds with serial {serial!r}") + + +def _build_command(code: str, proto) -> Command: + cd = proto.get_command_definition(code) + if cd is None: + raise RuntimeError(f"unknown PI18 command: {code!r}") + cmd = Command(code=code, commandtype="basic", outputs=[], trigger=None) + cmd.command_definition = cd + cmd.full_command = proto.get_full_command(code) + return cmd + + +async def _send(port: USBPort, code: str): + cmd = _build_command(code, port.protocol) + result = await port.send_and_receive(cmd) + return result + + +async def _read_piri(port: USBPort, retries: int = 3) -> dict[str, str]: + """Return {name: raw_string} for each PIRI field. + + Retries on transient decode failures — powermon's parser can IndexError when + the hidraw fd still holds leftover bytes from a prior command (e.g. running + multiple queries back-to-back in sync-check). + """ + cmd = _build_command("PIRI", port.protocol) + last_err: Any = None + for _ in range(retries): + try: + result = await port.send_and_receive(cmd) + except (IndexError, KeyError, ValueError) as e: + last_err = e + await asyncio.sleep(0.3) + continue + if result is not None and getattr(result, "is_valid", True): + raw = result.raw_response + if raw.startswith(b"^D"): + raw = raw[5:] # ^D + 3-digit length + if raw.endswith(b"\r"): + raw = raw[:-3] # 2-byte CRC + \r + parts = raw.decode("ascii", errors="replace").split(",") + return {i: p for i, p in enumerate(parts)} | {"_parts": parts} + last_err = getattr(result, "error_messages", None) or result + await asyncio.sleep(0.3) + raise RuntimeError(f"PIRI read failed after {retries} attempts: {last_err}") + + +def _piri_raw(piri: dict, idx: int) -> str: + return piri["_parts"][idx] + + +async def _wait_ack(port: USBPort, code: str) -> bool: + """Send a setter. Return True on ^1, False on ^0 / unknown.""" + cmd = _build_command(code, port.protocol) + result = await port.send_and_receive(cmd) + raw = getattr(result, "raw_response", b"") or b"" + # setter response framed as ^Dlll^1... or similar. Just look for ^1 / ^0 anywhere. + if b"^1" in raw: + return True + if b"^0" in raw: + return False + # fallback: inspect parsed readings + for r in (result.readings or []): + v = str(r.data_value).lower() + if "succeed" in v: + return True + if "fail" in v: + return False + return False + +# -------- dump / diff / apply -------- + +def _dump_profile(piri_raw: list[str]) -> dict: + """Convert PIRI raw fields into a profile dict, using the SCHEDULE decoders.""" + out: dict[str, Any] = {} + # walk SCHEDULE once; pairs already have both halves + for s in SCHEDULE: + if s.key == "grid_tie": + mt = piri_raw[PIRI["machine_type"]] + out["grid_tie"] = "enabled" if mt.lstrip("0") == "1" else "disabled" + continue + out[s.key] = s.decoder(piri_raw[s.piri_field]) + # fill pair's partner keys too (bulk/float already via bulk_voltage row; pair_keys picks up the second) + # BUCD pair + out["stop_discharge_voltage"] = _tenths_to_v(piri_raw[PIRI["stop_discharge_voltage"]]) + out["stop_charge_voltage"] = _tenths_to_v(piri_raw[PIRI["stop_charge_voltage"]]) + out["bulk_voltage"] = _tenths_to_v(piri_raw[PIRI["bulk_voltage"]]) + out["float_voltage"] = _tenths_to_v(piri_raw[PIRI["float_voltage"]]) + return out + + +def _diff(want: dict, have: dict) -> list[tuple[str, Any, Any]]: + diffs = [] + for k, v in want.items(): + if k not in have: + continue + hv = have[k] + if isinstance(v, float) or isinstance(hv, float): + if abs(float(v) - float(hv)) > 0.05: + diffs.append((k, hv, v)) + else: + if v != hv: + diffs.append((k, hv, v)) + return diffs + + +def _systemctl(action: str) -> None: + # Stop both units so neither holds the hidraw fd for the one we're writing to. + subprocess.run(["sudo", "systemctl", action, "powermon.service", "powermon2.service"], check=True) + + +async def cmd_dump(args) -> int: + path = await _resolve_path(args.device, args.serial) + port = await _open_port(path) + try: + piri = await _read_piri(port) + finally: + await port.disconnect() + prof = _dump_profile(piri["_parts"]) + # Drop any keys whose values aren't round-trippable via their PI18 setter. + errs = _validate(prof) + skipped: list[str] = [] + for e in errs: + for k in list(prof): + if e.startswith(k + "=") or e.startswith(k + " "): + if k in prof: + skipped.append(f"{k}={prof.pop(k)!r} ({e})") + break + out_path = Path(args.out) + out_path.parent.mkdir(parents=True, exist_ok=True) + with out_path.open("w") as f: + f.write("# LVX6048 settings profile. Edit freely; all keys are optional.\n") + f.write("# `flash.py diff` previews changes; `flash.py apply --confirm` writes.\n") + if skipped: + f.write("#\n# skipped (read-only or out of settable range):\n") + for s in skipped: + f.write(f"# {s}\n") + f.write("\n") + for k, v in prof.items(): + doc = KEY_DOCS.get(k) + if doc: + f.write(f"# {doc}\n") + # one-key dump preserves native YAML formatting for scalars + f.write(yaml.safe_dump({k: v}, sort_keys=False, default_flow_style=False)) + f.write("\n") + print(f"wrote {out_path} with {len(prof)} keys" + (f" ({len(skipped)} skipped)" if skipped else "")) + return 0 + + +async def cmd_diff(args) -> int: + with open(args.profile) as f: + want = yaml.safe_load(f) or {} + errs = _validate(want) + if errs: + for e in errs: + print(f"INVALID: {e}") + return 2 + path = await _resolve_path(args.device, args.serial) + port = await _open_port(path) + try: + piri = await _read_piri(port) + finally: + await port.disconnect() + have = _dump_profile(piri["_parts"]) + diffs = _diff(want, have) + if not diffs: + print("no diff — profile matches device") + return 0 + print(f"{len(diffs)} setting(s) would change:") + for k, hv, wv in diffs: + print(f" {k}: {hv!r} -> {wv!r}") + return 0 + + +async def cmd_apply(args) -> int: + with open(args.profile) as f: + want = yaml.safe_load(f) or {} + errs = _validate(want) + if errs: + for e in errs: + print(f"INVALID: {e}") + return 2 + if not args.confirm: + print("refusing to write without --confirm (use `diff` to preview)") + return 2 + + path = await _resolve_path(args.device, args.serial) + print("stopping powermon.service...") + _systemctl("stop") + try: + port = await _open_port(path) + try: + piri = await _read_piri(port) + have = _dump_profile(piri["_parts"]) + diffs = _diff(want, have) + if not diffs: + print("nothing to do") + return 0 + + pending = {k for k, _, _ in diffs} + applied: list[str] = [] + for s in SCHEDULE: + # handle pair settings once + if s.pair_keys: + a, b = s.pair_keys + if a not in pending and b not in pending: + continue + if a not in want or b not in want: + print(f"SKIP {a}/{b}: pair requires both keys in profile") + continue + code = s.encoder((want[a], want[b])) + print(f"-> {code} ({a}={want[a]}, {b}={want[b]})") + ok = await _wait_ack(port, code) + if not ok: + print(f"FAIL: inverter NAK on {code}") + return 3 + # readback + piri2 = await _read_piri(port) + new_a = _tenths_to_v(piri2["_parts"][PIRI[a]]) + new_b = _tenths_to_v(piri2["_parts"][PIRI[b]]) + if abs(new_a - float(want[a])) > 0.05 or abs(new_b - float(want[b])) > 0.05: + print(f"FAIL: readback mismatch: {a}={new_a}, {b}={new_b}") + return 3 + applied += [a, b] + continue + if s.key not in pending: + continue + code = s.encoder(want[s.key]) + print(f"-> {code} ({s.key}={want[s.key]})") + ok = await _wait_ack(port, code) + if not ok: + print(f"FAIL: inverter NAK on {code}") + return 3 + piri2 = await _read_piri(port) + actual = s.decoder(piri2["_parts"][s.piri_field]) + expected = want[s.key] + if isinstance(actual, float): + match = abs(float(actual) - float(expected)) <= 0.05 + else: + match = actual == expected or (s.key == "grid_tie" and actual == ("grid_tie" if expected == "enabled" else "off_grid")) + if not match: + print(f"FAIL: readback mismatch on {s.key}: got {actual!r}, want {expected!r}") + return 3 + applied.append(s.key) + + print(f"OK — applied {len(applied)} setting(s): {', '.join(applied)}") + return 0 + finally: + await port.disconnect() + finally: + print("restarting powermon.service...") + _systemctl("start") + + +# -------- sync-check: are the two units in a valid parallel state? -------- + +# PI18 MOD codes +MOD_NAMES = { + "00": "Power on", "01": "Standby", "02": "Bypass", + "03": "Battery", "04": "Fault", "05": "Hybrid", +} + +# PI18 FWS fault-code map (cross-referenced with PI30 QPGS fault codes) +FAULT_NAMES = { + "00": "No fault", + "01": "Fan is locked", + "02": "Over temperature", + "03": "Battery voltage is too high", + "04": "Battery voltage is too low", + "05": "Output short circuited or Over temperature", + "06": "Output voltage is too high", + "07": "Over load time out", + "08": "Bus voltage is too high", + "09": "Bus soft start failed", + "11": "Main relay failed", + "51": "Over current inverter", + "52": "Bus soft start failed", + "53": "Inverter soft start failed", + "54": "Self-test failed", + "55": "Over DC voltage on output of inverter", + "56": "Battery connection is open", + "57": "Current sensor failed", + "58": "Output voltage is too low", + "60": "Inverter negative power", + "71": "Parallel version different", + "72": "Output circuit failed", + "80": "CAN communication failed", + "81": "Parallel host line lost", + "82": "Parallel synchronized signal lost", + "83": "Parallel battery voltage detect different", + "84": "Parallel Line voltage or frequency detect different", + "85": "Parallel Line input current unbalanced", + "86": "Parallel output setting different", +} + +# GS field indices (see powermon/protocols/pi18.py :: QUERY_COMMANDS["GS"]) +GS_AC_OUTPUT_V = 2 +GS_AC_OUTPUT_HZ = 3 +GS_PARALLEL_VALID = 27 + + +async def _read_raw_parts(port: USBPort, code: str, retries: int = 3) -> list[str]: + cmd = _build_command(code, port.protocol) + last_err: Any = None + for _ in range(retries): + result = await port.send_and_receive(cmd) + if result is not None and getattr(result, "is_valid", False): + raw = result.raw_response + if raw.startswith(b"^D"): + raw = raw[5:] # ^D + 3-digit length + if raw.endswith(b"\r"): + raw = raw[:-3] # 2-byte CRC + \r + return raw.decode("ascii", errors="replace").split(",") + last_err = getattr(result, "error_messages", None) or result + await asyncio.sleep(0.3) + raise RuntimeError(f"{code} read failed after {retries} attempts: {last_err}") + + +async def _snapshot_sync(path: str) -> dict[str, Any]: + port = await _open_port(path) + try: + gs = await _read_raw_parts(port, "GS") + fws = await _read_raw_parts(port, "FWS") + mod = await _read_raw_parts(port, "MOD") + vfw = await _read_raw_parts(port, "VFW") + finally: + await port.disconnect() + return { + "parallel_valid": gs[GS_PARALLEL_VALID] == "1", + "ac_output_v": _tenths_to_v(gs[GS_AC_OUTPUT_V]), + "ac_output_hz": int(gs[GS_AC_OUTPUT_HZ]) / 10.0, + "fault_code": fws[0], + "fault_name": FAULT_NAMES.get(fws[0], f"unknown ({fws[0]})"), + "mode_raw": mod[0], + "mode": MOD_NAMES.get(mod[0], mod[0]), + "main_cpu": vfw[0], + "slave_cpu": vfw[1], + } + + +async def cmd_sync_check(args) -> int: + path_a = await _resolve_path(args.device_a, args.serial_a) + path_b = await _resolve_path(args.device_b, args.serial_b) + a = await _snapshot_sync(path_a) + b = await _snapshot_sync(path_b) + + def _row(label: str, s: dict) -> str: + valid = "valid" if s["parallel_valid"] else "NOT VALID" + return (f"{label}: fw={s['main_cpu']}/{s['slave_cpu']} mode={s['mode']} " + f"parallel={valid} fault={s['fault_name']} " + f"vac={s['ac_output_v']}V fac={s['ac_output_hz']}Hz") + + print(_row(path_a, a)) + print(_row(path_b, b)) + + issues: list[str] = [] + if a["main_cpu"] != b["main_cpu"] or a["slave_cpu"] != b["slave_cpu"]: + issues.append(f"firmware mismatch: {a['main_cpu']}/{a['slave_cpu']} vs {b['main_cpu']}/{b['slave_cpu']} — parallel requires matching firmware on both units") + if not a["parallel_valid"]: + issues.append(f"{path_a}: GS parallel_instance_number = Not valid") + if not b["parallel_valid"]: + issues.append(f"{path_b}: GS parallel_instance_number = Not valid") + if a["fault_code"] != "00": + issues.append(f"{path_a}: active fault {a['fault_code']} ({a['fault_name']})") + if b["fault_code"] != "00": + issues.append(f"{path_b}: active fault {b['fault_code']} ({b['fault_name']})") + if a["mode_raw"] != b["mode_raw"]: + issues.append(f"mode differs: {a['mode']} vs {b['mode']}") + if abs(a["ac_output_hz"] - b["ac_output_hz"]) > 0.1 and a["ac_output_hz"] > 0 and b["ac_output_hz"] > 0: + issues.append(f"AC output frequency diverges: {a['ac_output_hz']}Hz vs {b['ac_output_hz']}Hz (>0.1Hz = not phase-locked)") + if abs(a["ac_output_v"] - b["ac_output_v"]) > 2.0 and a["ac_output_v"] > 0 and b["ac_output_v"] > 0: + issues.append(f"AC output voltage diverges: {a['ac_output_v']}V vs {b['ac_output_v']}V (>2V gap)") + if a["ac_output_v"] == 0 and b["ac_output_v"] == 0: + issues.append("both units AC output = 0 V (idle / not producing); frequency/voltage sync cannot be verified until at least one is inverting") + + print() + if not issues: + print("SYNC OK") + return 0 + print(f"{len(issues)} issue(s):") + for i in issues: + print(f" - {i}") + return 1 + + +async def cmd_compare(args) -> int: + async def _snapshot(path: str) -> dict: + port = await _open_port(path) + try: + piri = await _read_piri(port) + finally: + await port.disconnect() + return _dump_profile(piri["_parts"]) + + path_a = await _resolve_path(args.device_a, args.serial_a) + path_b = await _resolve_path(args.device_b, args.serial_b) + a = await _snapshot(path_a) + b = await _snapshot(path_b) + + shared = sorted(set(a) & set(b)) + diffs: list[tuple[str, Any, Any]] = [] + for k in shared: + av, bv = a[k], b[k] + if isinstance(av, float) or isinstance(bv, float): + if abs(float(av) - float(bv)) > 0.05: + diffs.append((k, av, bv)) + elif av != bv: + diffs.append((k, av, bv)) + + a_only = sorted(set(a) - set(b)) + b_only = sorted(set(b) - set(a)) + + if not diffs and not a_only and not b_only: + print(f"MATCH — {len(shared)} settings identical on {path_a} and {path_b}") + return 0 + + if diffs: + kw = max(len(k) for k, _, _ in diffs) + print(f"{len(diffs)} setting(s) differ (of {len(shared)} shared):") + for k, av, bv in diffs: + print(f" {k:<{kw}} {path_a}={av!r} {path_b}={bv!r}") + if a_only: + print(f"only on {path_a}: {', '.join(a_only)}") + if b_only: + print(f"only on {path_b}: {', '.join(b_only)}") + return 1 + + +def main() -> int: + ap = argparse.ArgumentParser(description="Flash LVX6048 settings profiles via PI18.") + sub = ap.add_subparsers(dest="cmd", required=True) + + def _add_common(p): + # --device uses the resolver-maintained symlink by default. --serial is + # an explicit fallback that probes /dev/hidraw* via PI18 ID — only use + # when the resolver hasn't run (e.g. early boot / debugging). + p.add_argument("--device", default="/dev/lvx6048-1", + help="device path (default: /dev/lvx6048-1 symlink)") + p.add_argument("--serial", default=None, + help="override --device by probing /dev/hidraw* for this PI18 serial") + + d = sub.add_parser("dump", help="read current settings into a YAML profile") + _add_common(d) + d.add_argument("--out", required=True) + + df = sub.add_parser("diff", help="show what would change if a profile were applied") + _add_common(df) + df.add_argument("--profile", required=True) + + ap_ = sub.add_parser("apply", help="apply a profile (requires --confirm)") + _add_common(ap_) + ap_.add_argument("--profile", required=True) + ap_.add_argument("--confirm", action="store_true") + + def _add_pair(p): + p.add_argument("--device-a", default="/dev/lvx6048-1", help="device path for unit A") + p.add_argument("--device-b", default="/dev/lvx6048-2", help="device path for unit B") + p.add_argument("--serial-a", default=None, help="override --device-a by probing PI18 serial") + p.add_argument("--serial-b", default=None, help="override --device-b by probing PI18 serial") + + cp = sub.add_parser("compare", help="diff live settings between two inverters") + _add_pair(cp) + + sc = sub.add_parser("sync-check", help="verify two paralleled inverters are in sync") + _add_pair(sc) + + args = ap.parse_args() + handler = { + "dump": cmd_dump, "diff": cmd_diff, "apply": cmd_apply, + "compare": cmd_compare, "sync-check": cmd_sync_check, + }[args.cmd] + return asyncio.run(handler(args)) + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/LVX6048/lvx-flash/profiles/current.yaml b/LVX6048/lvx-flash/profiles/current.yaml new file mode 100644 index 0000000..583315d --- /dev/null +++ b/LVX6048/lvx-flash/profiles/current.yaml @@ -0,0 +1,39 @@ +# LVX6048 settings profile. Edit freely; all keys are optional. +# `flash.py diff` previews changes; `flash.py apply --confirm` writes. +# +# skipped (read-only or out of settable range): +# max_charging_current=100 (max_charging_current=100 out of range [10, 80]) + +# enum: AGM | FLOODED | USER +battery_type: AGM + +# V 40.0..48.0 — hard shutdown; must be < stop_discharge_voltage. Only honored when battery_type=USER. +cutoff_voltage: 40.8 + +# V 44.0..51.0 — switch battery→grid below this (pair: stop_charge_voltage). Only honored when battery_type=USER. +stop_discharge_voltage: 46.0 + +# V 48.0..58.4 — CC→CV transition (pair: float_voltage; must be >= float_voltage). Only honored when battery_type=USER. +bulk_voltage: 56.4 + +# A 2,10,20,30,40,50,60,70,80 — grid-side cap only +max_utility_charging_current: 30 + +# enum: solar_utility_battery | solar_battery_utility +output_source_priority: solar_battery_utility + +# enum: solar_first | solar_and_utility | solar_only +charger_priority: solar_first + +# enum: battery_load_utility_ac | load_battery_utility +solar_power_priority: battery_load_utility_ac + +# enum: enabled | disabled (PEI/PDI) +grid_tie: disabled + +# V 0 (=Full) or 48.0..58.0 — switch grid→battery above this (pair: stop_discharge_voltage). Only honored when battery_type=USER. +stop_charge_voltage: 54.0 + +# V 48.0..58.4 — held while on grid (pair: bulk_voltage; must be <= bulk_voltage). Only honored when battery_type=USER. +float_voltage: 54.0 + diff --git a/LVX6048/powermon-patches/README.md b/LVX6048/powermon-patches/README.md new file mode 100644 index 0000000..12aa14c --- /dev/null +++ b/LVX6048/powermon-patches/README.md @@ -0,0 +1,28 @@ +# powermon patches + +Drop-in replacements for files inside a `uv tool install powermon==1.0.18` tree. +Each file below lands at the indicated path — the top-level `install.sh` does the copy. + +| Snapshot here | Target (inside `$POWERMON_SITE`) | Purpose | +|--------------------------|---------------------------------------|---------| +| `pi18.py` | `protocols/pi18.py` | (a) rename `"DC/AC power direction"` → `"DC-AC power direction"` so the slash doesn't create a bogus MQTT topic level. (d) add `FWS` (fault + warning status) and `PGS` (parallel general status) query commands; bump `check_definitions_count(expected=24)` → `26`. | +| `usbport.py` | `ports/usbport.py` | (b) drain leftover bytes from the hidraw fd before sending (non-blocking read loop swallowing `BlockingIOError`); wrap the main `os.read` in the retry loop so an empty first read doesn't abort. Otherwise late HID bytes from a prior command get parsed as the next reply → `KeyError`. | +| `mqttbroker.py` | `libs/mqttbroker.py` | (c) broaden `connect()`'s `except ConnectionRefusedError` to `(ConnectionRefusedError, OSError)` and narrow `publish()`'s bare `except Exception` to `(OSError, RuntimeError, ValueError)`. Otherwise any broker blip (HA restart, `Errno 113 No route to host`) crashes the daemon. | +| `port_config_model.py` | `configmodel/port_config_model.py` | (e) add `serial_number: None \| str \| int = Field(default=None)` to `UsbPortConfig`. The model is `NoExtraBaseModel`, so powermon rejects `serial_number:` at the port level without this. | +| `ports_init.py` | `ports/__init__.py` | (f) in `from_config()`, make `port_config['serial_number'] = serial_number` a fallback (`if port_config.get('serial_number') is None:`). Device-level `serial_number` is the HA identifier (e.g. `lvx6048_1`); the port-level one is the hardware PI18 serial — they must not be conflated. | + +Patches (a)–(d) are load-bearing for the live setup. Patches (e) and (f) enable +powermon's native wildcard-path + serial-matching flow for a single-daemon +setup; we don't currently exercise that because two services probing +independently at startup race each other — the external `lvx-resolve-links` +oneshot handles identification instead. (e)/(f) are kept applied for future +flexibility. + +## Upgrade path + +These patches are pinned against **powermon 1.0.18**. Before bumping powermon: + +1. Install the new version in a scratch location: `uv tool install --prefix /tmp/pm-next 'powermon==X.Y.Z'` +2. Diff each of the five files against the pristine upstream copy. +3. Re-apply each patch by hand into the new files (they're short — see descriptions above). +4. Drop the new files into this folder and re-run `./install.sh` on the target. diff --git a/LVX6048/powermon-patches/mqttbroker.py b/LVX6048/powermon-patches/mqttbroker.py new file mode 100644 index 0000000..b71b105 --- /dev/null +++ b/LVX6048/powermon-patches/mqttbroker.py @@ -0,0 +1,220 @@ +""" powermon / libs / mqttbroker.py """ +import logging +from time import sleep + +import paho.mqtt.client as mqtt_client + +from powermon.libs.config import safe_config + +# Set-up logger +log = logging.getLogger("mqttbroker") + + +class MqttBroker: + """ Wrapper for mqtt broker connectivity and message proccessing """ + def __str__(self): + if self.disabled: + return "MqttBroker DISABLED" + else: + return f"MqttBroker name: {self.name}, port: {self.port}, user: {self.username}" + + @classmethod + def from_config(cls, config=None) -> 'MqttBroker': + """ build the mqtt broker object from a config dict """ + log.debug("mqttbroker config: %s", safe_config(config)) + + if config: + name = config.get("name") + port = config.get("port", 1883) + username = config.get("username") + password = config.get("password") + mqtt_broker = cls(name=name, port=port, username=username, password=password) + mqtt_broker.adhoc_topic = config.get("adhoc_topic") + mqtt_broker.adhoc_result_topic = config.get("adhoc_result_topic") + return mqtt_broker + else: + return cls(name=None) + + def __init__(self, name, port=None, username=None, password=None): + self.name = name + self.port = port + self.username = username + self.password = password + self.is_connected = False + + if self.name is None: + self.disabled = True + else: + self.disabled = False + self.mqttc = mqtt_client.Client() + + @property + def adhoc_topic(self) -> str: + """ return the adhoc command topic """ + return getattr(self, "_adhoc_topic", None) + + @adhoc_topic.setter + def adhoc_topic(self, value): + log.debug("setting adhoc topic to: %s", value) + self._adhoc_topic = value + + @property + def adhoc_result_topic(self) -> str: + """ return the adhoc result topic """ + return getattr(self, "_adhoc_result_topic", None) + + @adhoc_result_topic.setter + def adhoc_result_topic(self, value): + log.debug("setting adhoc result topic to: %s", value) + self._adhoc_result_topic = value + + def on_connect(self, client, userdata, flags, rc): + """ callback for connect """ + # 0: Connection successful + # 1: Connection refused - incorrect protocol version + # 2: Connection refused - invalid client identifier + # 3: Connection refused - server unavailable + # 4: Connection refused - bad username or password + # 5: Connection refused - not authorised + # 6-255: Currently unused. + log.debug("on_connect called - client: %s, userdata: %s, flags: %s", client, userdata, flags) + connection_result = [ + "Connection successful", + "Connection refused - incorrect protocol version", + "Connection refused - invalid client identifier", + "Connection refused - server unavailable", + "Connection refused - bad username or password", + "Connection refused - not authorised", + ] + log.debug("MqttBroker connection returned result: %s %s", rc, connection_result[rc]) + if rc == 0: + self.is_connected = True + return + self.is_connected = False + + def on_disconnect(self, client, userdata, rc): + """ callback for disconnect """ + log.debug("on_disconnect called - client: %s, userdata: %s, rc: %s", client, userdata, rc) + self.is_connected = False + + def connect(self): + """ connect to mqtt broker """ + if self.disabled: + log.info("MQTT broker not enabled, was a broker name defined? '%s'", self.name) + return + if not self.name: + log.info("MQTT could not connect as no broker name") + return + self.mqttc.on_connect = self.on_connect + self.mqttc.on_disconnect = self.on_disconnect + # if name is screen just return without connecting + if self.name == "screen": + # allows checking of message formats + return + try: + log.debug("Connecting to %s on port %s", self.name, self.port) + if self.username: + # auth = {"username": mqtt_user, "password": mqtt_pass} + _password = "********" if self.password is not None else "None" + log.info("Using mqtt authentication, username: %s, password: %s", self.username, _password) + self.mqttc.username_pw_set(self.username, password=self.password) + else: + log.debug("No mqtt authentication used") + # auth = None + self.mqttc.connect(self.name, port=self.port, keepalive=60) + self.mqttc.loop_start() + sleep(1) + except (ConnectionRefusedError, OSError) as ex: + log.warning("%s connection failed: '%s'", self.name, ex) + + def start(self): + """ start mqtt broker """ + if self.disabled: + return + if self.is_connected: + self.mqttc.loop_start() + + def stop(self): + """ stop mqtt broker """ + log.debug("Stopping mqttbroker connection") + if self.disabled: + return + self.mqttc.loop_stop() + + # def set(self, variable, value): + # setattr(self, variable, value) + + # def update(self, variable, value): + # # only override if value is not None + # if value is None: + # return + # setattr(self, variable, value) + + def subscribe(self, topic, callback): + """ subscribe to a mqtt topic """ + if not self.name: + return + if self.disabled: + return + # check if connected, connect if not + if not self.is_connected: + log.debug("Not connected, connecting") + self.connect() + # Register callback + self.mqttc.on_message = callback + if self.is_connected: + # Subscribe to command topic + log.debug("Subscribing to topic: %s", topic) + self.mqttc.subscribe(topic, qos=0) + else: + log.warning("Did not subscribe to topic: %s as not connected to broker", topic) + + def post_adhoc_command(self, command_code): + """ shortcut function to publish an adhoc command """ + self.publish(topic=self.adhoc_topic, payload=command_code) + + def post_adhoc_result(self, payload): + """ shortcut function to publish the results of an adhoc command """ + self.publish(topic=self.adhoc_result_topic, payload=payload) + + + def publish(self, topic: str = None, payload: str = None): + """ function to publish messages to mqtt broker """ + if self.disabled: + log.debug("Cannot publish msg as mqttbroker disabled") + return + # log.debug("Publishing '%s' to '%s'", payload, topic) + if self.name == "screen": + print(f"mqtt debug - topic: '{topic}', payload: '{payload}'") + return + # check if connected, connect if not + if not self.is_connected: + log.debug("Not connected, connecting") + self.connect() + sleep(1) + if not self.is_connected: + log.warning("mqtt broker did not connect") + return + if isinstance(topic, bytes): + topic = topic.decode('utf-8') + if isinstance(payload, bytes): + payload = payload.decode('utf-8') + try: + infot = self.mqttc.publish(topic, payload) + infot.wait_for_publish(5) + except (OSError, RuntimeError, ValueError) as e: + log.warning("mqtt publish failed: %s", e) + + # def setAdhocCommands(self, config={}, callback=None): + # if not config: + # return + # if self.disabled: + # log.debug("Cannot setAdhocCommands as mqttbroker disabled") + # return + + # adhoc_commands = config.get("adhoc_commands") + # # sub to command topic if defined + # adhoc_commands_topic = adhoc_commands.get("topic") + # if adhoc_commands_topic is not None: + # log.info("Setting adhoc commands topic to %s", adhoc_commands_topic) + # self.subscribe(adhoc_commands_topic, callback) diff --git a/LVX6048/powermon-patches/pi18.py b/LVX6048/powermon-patches/pi18.py new file mode 100644 index 0000000..59ff7fc --- /dev/null +++ b/LVX6048/powermon-patches/pi18.py @@ -0,0 +1,651 @@ +""" powermon / protocols / pi18.py """ +import logging + +from powermon.commands.command import CommandType +from powermon.commands.command_definition import CommandDefinition +from powermon.commands.reading_definition import ReadingType, ResponseType +from powermon.commands.result import ResultType +from powermon.libs.errors import CommandDefinitionMissing, InvalidCRC, InvalidResponse +from powermon.ports import PortType +from powermon.protocols.abstractprotocol import AbstractProtocol +from powermon.protocols.helpers import crc_pi30 as crc +from powermon.protocols.pi30 import BATTERY_TYPE_LIST, OUTPUT_MODE_LIST + +log = logging.getLogger("pi18") + +SETTER_COMMANDS = { + "POP": { + "name": "POP", + "command_type": CommandType.PI18_SETTER, + "description": "Set Device Output Source Priority", + "help": " -- examples: POP0 (set utility first), POP01 (set solar first)", + "regex": "POP([01])$", + }, + "PSP": { + "name": "PSP", + "command_type": CommandType.PI18_SETTER, + "description": "Set Solar Power priority", + "help": " -- examples: PSP0 (Battery-Load-Utility +AC Charge), PSP1 (Load-Battery-Utility)", + "regex": "PSP([01])$", + }, + "PEI": { + "name": "PEI", + "command_type": CommandType.PI18_SETTER, + "description": "Set Machine type, enable: Grid-Tie", + "help": " -- examples: PEI (enable Grid-Tie)", + }, + "PDI": { + "name": "PDI", + "command_type": CommandType.PI18_SETTER, + "description": "Set Machine type, disable: Grid-Tie", + "help": " -- examples: PDI (disable Grid-Tie)", + }, + "PCP": { + "name": "PCP", + "command_type": CommandType.PI18_SETTER, + "description": "Set Device Charger Priority", + "help": " -- examples: PCP0,1 (set unit 0 [0-9] to Solar and Utility) PCP0,0 (set unit 0 to Solar first), PCP0,1 (set unit 0 to Solar and Utility), PCP0,2 (set unit 0 to solar only charging)", + "regex": "PCP([0-9],[012])$", + }, + "MCHGC": { + "name": "MCHGC", + "command_type": CommandType.PI18_SETTER, + "description": "Set Battery Max Charging Current Solar + AC", + "help": " -- examples: MCHGC0,040 (set unit 0 to max charging current of 40A), MCHGC1,060 (set unit 1 to max charging current of 60A) [010 020 030 040 050 060 070 080]", + "regex": "MCHGC([0-9],0[1-8]0)$", + }, + "MUCHGC": { + "name": "MUCHGC", + "command_type": CommandType.PI18_SETTER, + "description": "Set Battery Max AC Charging Current", + "help": " -- examples: MUCHGC0,040 (set unit 0 to max charging current of 40A), MUCHGC1,060 (set unit 1 to max charging current of 60A) [002 010 020 030 040 050 060 070 080]", + "regex": "MUCHGC([0-9]),(002|0[1-8]0)$", + }, + "PBT": { + "name": "PBT", + "command_type": CommandType.PI18_SETTER, + "description": "Set Battery Type", + "help": " -- examples: PBT0 (set battery as AGM), PBT1 (set battery as FLOODED), PBT2 (set battery as USER)", + "regex": "PBT([012])$", + }, + "MCHGV": { + "name": "MCHGV", + "command_type": CommandType.PI18_SETTER, + "description": "Set Battery Bulk,Float Charging Voltages", + "help": " -- example MCHGV552,540 - set battery charging voltage Bulk to 52.2V, float 54V (set Bulk Voltage [480~584] in 0.1V xxx, Float Voltage [480~584] in 0.1V yyy)", + # Regex 48.0 - 58.4 Volt + "regex": "MCHGV(4[8-9][0-9]|5[0-7][0-9]|58[0-5]),(4[8-9][0-9]|5[0-7][0-9]|58[0-4])$", + }, + "PSDV": { + "name": "PSDV", + "command_type": CommandType.PI18_SETTER, + "description": "Set Battery Cut-off Voltage", + "help": " -- example PSDV400 - set battery cut-off voltage to 40V [400~480V] for 48V unit)", + # Regex 40 to 48V + "regex": "PSDV(4[0-7][0-9]|480)$", + }, + "BUCD": { + "name": "BUCD", + "command_type": CommandType.PI18_SETTER, + "description": "Set Battery Stop dis,charging when Grid is available", + "help": " -- example BUCD440,480 - set Stop discharge Voltage [440~510] in 0.1V xxx, Stop Charge Voltage [000(Full) or 480~580] in 0.1V yyy", + # Regex 44 to 51V, Full|48V to 58V + "regex": "BUCD((4[4-9]0|5[0-1]0),(000|4[8-9]0|5[0-8]0))$", + }, +} + +QUERY_COMMANDS = { + "PI": { + "name": "PI", + "command_type": CommandType.PI18_QUERY, + "description": "Protocol ID inquiry", + "help": " -- queries the protocol ID", + "result_type": ResultType.SINGLE, + "reading_definitions": [ + {"description": "Protocol ID"}, + ], + "test_responses": [ + b"^D00518m\xae\r" + ] + }, + "ID": { + "name": "ID", + "aliases": ["default", "get_id"], + "command_type": CommandType.PI18_QUERY, + "description": "Device Serial Number inquiry", + "help": " -- queries the device serial number", + "result_type": ResultType.SINGLE, + "reading_definitions": [{"description": "Serial Number"}], + "test_responses": [ + b"^D02514012345678901234567\r", + ], + }, + "ET": { + "name": "ET", + "command_type": CommandType.PI18_QUERY, + "description": "Total PV Generated Energy Inquiry", + "result_type": ResultType.SINGLE, + "reading_definitions": [ + {"description": "Total PV Generated Energy", "reading_type": ReadingType.WATT_HOURS, + "response_type": ResponseType.INT, "icon": "mdi:solar-power", "device_class": "energy", "state_class": "total"}, + ], + "test_responses": [ + b"" + ], + }, + "EY": { + "name": "EY", + "command_type": CommandType.PI18_QUERY, + "description": "Yearly PV Generated Energy Inquiry", + "result_type": ResultType.SINGLE, + "reading_definitions": [ + {"description": "PV Generated Energy for Year", "reading_type": ReadingType.WATT_HOURS, + "response_type": ResponseType.INT, "icon": "mdi:counter", "device_class": "energy", "state_class": "total"}, + {"description": "Year", "reading_type": ReadingType.YEAR, + "response_type": ResponseType.INFO_FROM_COMMAND, "format_template": "int(cn[3:])"}, + ], + "test_responses": [ + b"^D01105580051\x0b\x9f\r", + ], + "regex": "EY(\\d\\d\\d\\d)$", + }, + "EM": { + "name": "EM", + "command_type": CommandType.PI18_QUERY, + "description": "Monthly PV Generated Energy Inquiry", + "result_type": ResultType.SINGLE, + "reading_definitions": [ + {"description": "PV Generated Energy for Month", "reading_type": ReadingType.WATT_HOURS, + "response_type": ResponseType.INT, "icon": "mdi:solar-power", "device_class": "energy", "state_class": "total"}, + {"description": "Year", "reading_type": ReadingType.YEAR, + "response_type": ResponseType.INFO_FROM_COMMAND, "format_template": "int(cn[3:7])"}, + {"description": "Month", "reading_type": ReadingType.MONTH, + "response_type": ResponseType.INFO_FROM_COMMAND, "format_template": "calendar.month_name[int(cn[7:])]"}, + ], + "test_responses": [ + b"", + ], + "regex": "EM(\\d\\d\\d\\d\\d\\d)$", + }, + "ED": { + "name": "ED", + "command_type": CommandType.PI18_QUERY, + "description": "Daily PV Generated Energy Inquiry", + "help": " -- display daily generated energy, format is QEDyyyymmdd", + "result_type": ResultType.SINGLE, + "reading_definitions": [ + {"description": "PV Generated Energy for Day", "reading_type": ReadingType.WATT_HOURS, + "response_type": ResponseType.INT, "icon": "mdi:solar-power", "device_class": "energy", "state_class": "total"}, + {"description": "Year", "reading_type": ReadingType.YEAR, + "response_type": ResponseType.INFO_FROM_COMMAND, "format_template": "int(cn[3:7])"}, + {"description": "Month", "reading_type": ReadingType.MONTH, + "response_type": ResponseType.INFO_FROM_COMMAND, "format_template": "calendar.month_name[int(cn[7:9])]"}, + {"description": "Day", "reading_type": ReadingType.DAY, + "response_type": ResponseType.INFO_FROM_COMMAND, "format_template": "int(cn[9:11])"}, + ], + "test_responses": [ + b"(00238800!J\r", + ], + "regex": "ED(\\d\\d\\d\\d\\d\\d\\d\\d)$", + }, + "PIRI": { + "name": "PIRI", + "command_type": CommandType.PI18_QUERY, + "description": "Current Settings inquiry", + "help": " -- queries the current settings from the Inverter", + "result_type": ResultType.COMMA_DELIMITED, + "reading_definitions": [ + {"description": "AC Input Voltage", "reading_type": ReadingType.VOLTS, + "response_type": ResponseType.TEMPLATE_INT, "format_template": "r/10"}, + {"description": "AC Input Current", "reading_type": ReadingType.CURRENT, + "response_type": ResponseType.TEMPLATE_INT, "format_template": "r/10"}, + {"description": "AC Output Voltage", "reading_type": ReadingType.VOLTS, + "response_type": ResponseType.TEMPLATE_INT, "format_template": "r/10"}, + {"description": "AC Output Frequency", "reading_type": ReadingType.FREQUENCY, + "response_type": ResponseType.TEMPLATE_INT, "format_template": "r/10"}, + {"description": "AC Output Current", "reading_type": ReadingType.CURRENT, + "response_type": ResponseType.TEMPLATE_INT, "format_template": "r/10"}, + {"description": "AC Output Apparent Power", "reading_type": ReadingType.APPARENT_POWER}, + {"description": "AC Output Active Power", "reading_type": ReadingType.WATTS}, + {"description": "Battery Voltage", "reading_type": ReadingType.VOLTS, + "response_type": ResponseType.TEMPLATE_INT, "format_template": "r/10"}, + {"description": "Battery re-charge Voltage", "reading_type": ReadingType.VOLTS, + "response_type": ResponseType.TEMPLATE_INT, "format_template": "r/10"}, + {"description": "Battery re-discharge Voltage", "reading_type": ReadingType.VOLTS, + "response_type": ResponseType.TEMPLATE_INT, "format_template": "r/10"}, + {"description": "Battery Under Voltage", "reading_type": ReadingType.VOLTS, "response_type": ResponseType.TEMPLATE_INT, "format_template": "r/10"}, + {"description": "Battery Bulk Charge Voltage", "reading_type": ReadingType.VOLTS, "response_type": ResponseType.TEMPLATE_INT, "format_template": "r/10"}, + {"description": "Battery Float Charge Voltage", "reading_type": ReadingType.VOLTS, "response_type": ResponseType.TEMPLATE_INT, "format_template": "r/10"}, + {"description": "Battery Type", "reading_type": ReadingType.MESSAGE, "response_type": ResponseType.LIST, "options": BATTERY_TYPE_LIST}, + {"description": "Max AC Charging Current", "reading_type": ReadingType.CURRENT}, + {"description": "Max Charging Current", "reading_type": ReadingType.CURRENT}, + {"description": "Input Voltage Range", "response_type": ResponseType.LIST, "options": ["Appliance", "UPS"]}, + {"description": "Output Source Priority", + "response_type": ResponseType.LIST, "options": ["Solar - Utility - Battery", "Solar - Battery - Utility"]}, + {"description": "Charger Source Priority", + "response_type": ResponseType.LIST, "options": ["Solar First", "Solar + Utility", "Only solar charging permitted"]}, + {"description": "Max Parallel Units"}, + {"description": "Machine Type", "response_type": ResponseType.LIST, "options": ["Off Grid", "Grid Tie"]}, + {"description": "Topology", "response_type": ResponseType.LIST, "options": ["transformerless", "transformer"]}, + {"description": "Output Mode", "reading_type": ReadingType.MESSAGE, "response_type": ResponseType.LIST, "options": OUTPUT_MODE_LIST}, + {"description": "Solar power priority", "response_type": ResponseType.LIST, "options": ["Battery-Load-Utiliy + AC Charger", "Load-Battery-Utiliy"]}, + {"description": "MPPT strings"}, + {"description": "Unknown flags?", "response_type": ResponseType.STRING}, + ], + "test_responses": [ + b"^D0882300,217,2300,500,217,5000,5000,480,480,530,440,570,570,2,10,070,1,1,1,9,0,0,0,0,1,00\xe1k\r", + ] + }, + "GS": { + "name": "GS", + "command_type": CommandType.PI18_QUERY, + "description": "General Status Parameters inquiry", + "result_type": ResultType.COMMA_DELIMITED, + "reading_definitions": [ + {"description": "AC Input Voltage", "reading_type": ReadingType.VOLTS, + "response_type": ResponseType.TEMPLATE_INT, "format_template": "r/10", "icon": "mdi:transmission-tower-export", "device_class": "voltage"}, + {"description": "AC Input Frequency", "reading_type": ReadingType.FREQUENCY, + "response_type": ResponseType.TEMPLATE_INT, "format_template": "r/10", "icon": "mdi:current-ac", "device_class": "frequency"}, + {"description": "AC Output Voltage", "reading_type": ReadingType.VOLTS, + "response_type": ResponseType.TEMPLATE_INT, "format_template": "r/10", "icon": "mdi:transmission-tower-export", "device_class": "voltage"}, + {"description": "AC Output Frequency", "reading_type": ReadingType.FREQUENCY, + "response_type": ResponseType.TEMPLATE_INT, "format_template": "r/10", "icon": "mdi:current-ac", "device_class": "frequency"}, + {"description": "AC Output Apparent Power", "reading_type": ReadingType.APPARENT_POWER, + "response_type": ResponseType.INT, "icon": "mdi:power-plug", "device_class": "apparent_power"}, + {"description": "AC Output Active Power", "reading_type": ReadingType.WATTS, + "response_type": ResponseType.INT, "icon": "mdi:power-plug", "device_class": "power", "state_class": "measurement"}, + {"description": "AC Output Load", "reading_type": ReadingType.PERCENTAGE, + "response_type": ResponseType.INT, "icon": "mdi:brightness-percent"}, + {"description": "Battery Voltage", "reading_type": ReadingType.VOLTS, + "response_type": ResponseType.TEMPLATE_INT, "format_template": "r/10", "icon": "mdi:battery-outline", "device_class": "voltage"}, + {"description": "Battery Voltage from SCC", "reading_type": ReadingType.VOLTS, + "response_type": ResponseType.TEMPLATE_INT, "format_template": "r/10", "icon": "mdi:battery-outline", "device_class": "voltage"}, + {"description": "Battery Voltage from SCC2", "reading_type": ReadingType.VOLTS, + "response_type": ResponseType.TEMPLATE_INT, "format_template": "r/10", "icon": "mdi:battery-outline", "device_class": "voltage"}, + {"description": "Battery Discharge Current", "reading_type": ReadingType.CURRENT, + "response_type": ResponseType.INT, "icon": "mdi:battery-negative", "device_class": "current"}, + {"description": "Battery Charging Current", "reading_type": ReadingType.CURRENT, + "response_type": ResponseType.INT, "icon": "mdi:current-dc", "device_class": "current"}, + {"description": "Battery Capacity", "reading_type": ReadingType.PERCENTAGE, + "response_type": ResponseType.INT, "icon": "mdi:brightness-percent", "device_class": "battery"}, + {"description": "Inverter heat sink temperature", "reading_type": ReadingType.TEMPERATURE, + "response_type": ResponseType.INT, "icon": "mdi:details", "device_class": "temperature"}, + {"description": "MPPT1 charger temperature", "reading_type": ReadingType.TEMPERATURE, + "response_type": ResponseType.INT, "icon": "mdi:details", "device_class": "temperature"}, + {"description": "MPPT2 charger temperature", "reading_type": ReadingType.TEMPERATURE, + "response_type": ResponseType.INT, "icon": "mdi:details", "device_class": "temperature"}, + {"description": "MPPT1 Input Power", "reading_type": ReadingType.WATTS, + "response_type": ResponseType.INT, "icon": "mdi:solar-power", "device_class": "power", "state_class": "measurement"}, + {"description": "MPPT2 Input Power", "reading_type": ReadingType.WATTS, + "response_type": ResponseType.INT, "icon": "mdi:solar-power", "device_class": "power", "state_class": "measurement"}, + {"description": "MPPT1 Input Voltage", "reading_type": ReadingType.VOLTS, + "response_type": ResponseType.TEMPLATE_INT, "format_template": "r/10", "icon": "mdi:solar-power", "device_class": "voltage"}, + {"description": "MPPT2 Input Voltage", "reading_type": ReadingType.VOLTS, + "response_type": ResponseType.TEMPLATE_INT, "format_template": "r/10", "icon": "mdi:solar-power", "device_class": "voltage"}, + {"description": "Setting value configuration state", "reading_type": ReadingType.MESSAGE, + "response_type": ResponseType.OPTION, + "options": { + "0": "Nothing changed", + "1": "Something changed", + }, + }, + {"description": "MPPT1 charger status", "reading_type": ReadingType.MESSAGE, + "response_type": ResponseType.OPTION, + "options": { + "0": "abnormal", + "1": "normal but not charged", + "2": "charging", + }, + }, + {"description": "MPPT2 charger status", "reading_type": ReadingType.MESSAGE, + "response_type": ResponseType.OPTION, + "options": { + "0": "abnormal", + "1": "normal but not charged", + "2": "charging", + }, + }, + {"description": "Load connection", "reading_type": ReadingType.MESSAGE, + "response_type": ResponseType.OPTION, + "options": { + "0": "disconnect", + "1": "connect", + }, + }, + {"description": "Battery power direction", "reading_type": ReadingType.MESSAGE, + "response_type": ResponseType.OPTION, + "options": { + "0": "donothing", + "1": "charge", + "2": "discharge", + }, + }, + {"description": "DC-AC power direction", "reading_type": ReadingType.MESSAGE, + "response_type": ResponseType.OPTION, + "options": { + "0": "donothing", + "1": "AC-DC", + "2": "DC-AC", + }, + }, + {"description": "Line power direction", "reading_type": ReadingType.MESSAGE, + "response_type": ResponseType.OPTION, + "options": { + "0": "donothing", + "1": "input", + "2": "output", + }, + }, + {"description": "Parallel instance number", "reading_type": ReadingType.MESSAGE, + "response_type": ResponseType.LIST, + "options": ["Not valid", "valid"], + }, + + ], + "test_responses": [ + b"D1062232,499,2232,499,0971,0710,019,008,000,000,000,000,000,044,000,000,0520,0000,1941,0000,0,2,0,1,0,2,1,0\x09\x7b\r", + b"^D1062232,499,2232,499,1406,1376,028,549,000,000,000,010,095,060,000,000,0082,0000,1604,0000,0,2,0,1,1,1,1,0D\x12\r", + ], + }, + "MOD": { + "name": "MOD", + "command_type": CommandType.PI18_QUERY, + "description": "Mode inquiry", + "result_type": ResultType.SINGLE, + "reading_definitions": [ + {"description": "Device Mode", "reading_type": ReadingType.MESSAGE, + "response_type": ResponseType.OPTION, + "options": { + "00": "Power on", + "01": "Standby", + "02": "Bypass", + "03": "Battery", + "04": "Fault", + "05": "Hybrid mode(Line mode, Grid mode)", + } + }, + ], + "test_responses": [ + b"^D00505\xd9\x9f\r", + ], + }, + "MCHGCR": { + "name": "MCHGCR", + "command_type": CommandType.PI18_QUERY, + "description": "Max Charging Current Options inquiry", + "help": " -- queries the maximum charging current setting of the Inverter", + "result_type": ResultType.MULTIVALUED, + "reading_definitions": [ + {"description": "Max Charging Current Options", "reading_type": ReadingType.MESSAGE_AMPS, + "response_type": ResponseType.STRING + } + ], + "test_responses": [ + b"^D034010,020,030,040,050,060,070,080\x161\r", + ], + }, + "MUCHGCR": { + "name": "MUCHGCR", + "command_type": CommandType.PI18_QUERY, + "description": "Max Utility Charging Current Options inquiry", + "help": " -- queries the maximum utility charging current setting of the Inverter", + "result_type": ResultType.MULTIVALUED, + "reading_definitions": [ + {"reading_type": ReadingType.MESSAGE_AMPS, "description": "Max Utility Charging Current", "response_type": ResponseType.STRING} + ], + "test_responses": [ + b"^D038002,010,020,030,040,050,060,070,080\xd01\r" + ], + }, + "FLAG": { + "name": "FLAG", + "command_type": CommandType.PI18_QUERY, + "description": "Query enable/disable flag status", + "result_type": ResultType.COMMA_DELIMITED, + "reading_definitions": [ + {"description": "Buzzer beep", "reading_type": ReadingType.MESSAGE, "response_type": ResponseType.ENABLED_BOOL}, + {"description": "Overload bypass function", "reading_type": ReadingType.MESSAGE, "response_type": ResponseType.ENABLED_BOOL}, + {"description": "Display back to default page", "reading_type": ReadingType.MESSAGE, "response_type": ResponseType.ENABLED_BOOL}, + {"description": "Overload restart", "reading_type": ReadingType.MESSAGE, "response_type": ResponseType.ENABLED_BOOL}, + {"description": "Over temperature restart", "reading_type": ReadingType.MESSAGE, "response_type": ResponseType.ENABLED_BOOL}, + {"description": "Backlight on", "reading_type": ReadingType.MESSAGE, "response_type": ResponseType.ENABLED_BOOL}, + {"description": "Alarm primary source interrupt", "reading_type": ReadingType.MESSAGE, "response_type": ResponseType.ENABLED_BOOL}, + {"description": "Fault code record", "reading_type": ReadingType.MESSAGE, "response_type": ResponseType.ENABLED_BOOL}, + {"description": "Reserved", "reading_type": ReadingType.MESSAGE}, + ], + "test_responses": [ + b"^D0200,0,0,0,0,1,0,0,12\xc2\x39\r", + ], + }, + "VFW": { + "name": "VFW", + "description": "Device CPU version inquiry", + "command_type": CommandType.PI18_QUERY, + "result_type": ResultType.COMMA_DELIMITED, + "reading_definitions": [ + {"description": "Main CPU Version", "reading_type": ReadingType.MESSAGE}, + {"description": "Slave 1 CPU Version", "reading_type": ReadingType.MESSAGE}, + {"description": "Slave 2 CPU Version", "reading_type": ReadingType.MESSAGE}, + ], + "test_responses": [ + b"^D02005220,00000,00000\x3e\xf8\r", + ], + }, + # Fault + warning bitmap. 2-digit fault code followed by ~32 0/1 warning bits. + # Fault-code list cross-referenced with PI30 QPGS (same firmware family). + "FWS": { + "name": "FWS", + "description": "Fault and warning status inquiry", + "command_type": CommandType.PI18_QUERY, + "result_type": ResultType.COMMA_DELIMITED, + "reading_definitions": [ + {"description": "Fault code", "reading_type": ReadingType.MESSAGE, + "response_type": ResponseType.OPTION, + "options": { + "00": "No fault", + "01": "Fan is locked", + "02": "Over temperature", + "03": "Battery voltage is too high", + "04": "Battery voltage is too low", + "05": "Output short circuited or Over temperature", + "06": "Output voltage is too high", + "07": "Over load time out", + "08": "Bus voltage is too high", + "09": "Bus soft start failed", + "11": "Main relay failed", + "51": "Over current inverter", + "52": "Bus soft start failed", + "53": "Inverter soft start failed", + "54": "Self-test failed", + "55": "Over DC voltage on output of inverter", + "56": "Battery connection is open", + "57": "Current sensor failed", + "58": "Output voltage is too low", + "60": "Inverter negative power", + "71": "Parallel version different", + "72": "Output circuit failed", + "80": "CAN communication failed", + "81": "Parallel host line lost", + "82": "Parallel synchronized signal lost", + "83": "Parallel battery voltage detect different", + "84": "Parallel Line voltage or frequency detect different", + "85": "Parallel Line input current unbalanced", + "86": "Parallel output setting different", + }}, + {"description": "PV loss warning", "response_type": ResponseType.ENABLED_BOOL}, + {"description": "Inverter fault", "response_type": ResponseType.ENABLED_BOOL}, + {"description": "Bus over", "response_type": ResponseType.ENABLED_BOOL}, + {"description": "Bus under", "response_type": ResponseType.ENABLED_BOOL}, + {"description": "Bus soft fail", "response_type": ResponseType.ENABLED_BOOL}, + {"description": "Line fail", "response_type": ResponseType.ENABLED_BOOL}, + {"description": "OPV short", "response_type": ResponseType.ENABLED_BOOL}, + {"description": "Inverter voltage too low", "response_type": ResponseType.ENABLED_BOOL}, + {"description": "Inverter voltage too high", "response_type": ResponseType.ENABLED_BOOL}, + {"description": "Over temperature", "response_type": ResponseType.ENABLED_BOOL}, + {"description": "Fan locked", "response_type": ResponseType.ENABLED_BOOL}, + {"description": "Battery voltage high", "response_type": ResponseType.ENABLED_BOOL}, + {"description": "Battery low alarm", "response_type": ResponseType.ENABLED_BOOL}, + {"description": "Battery under shutdown", "response_type": ResponseType.ENABLED_BOOL}, + {"description": "Battery derating", "response_type": ResponseType.ENABLED_BOOL}, + {"description": "Overload", "response_type": ResponseType.ENABLED_BOOL}, + {"description": "EEPROM fault", "response_type": ResponseType.ENABLED_BOOL}, + {"description": "Inverter over current", "response_type": ResponseType.ENABLED_BOOL}, + {"description": "Inverter soft fail", "response_type": ResponseType.ENABLED_BOOL}, + {"description": "Self test fail", "response_type": ResponseType.ENABLED_BOOL}, + {"description": "OP DC voltage over", "response_type": ResponseType.ENABLED_BOOL}, + {"description": "Battery open", "response_type": ResponseType.ENABLED_BOOL}, + {"description": "Current sensor fail", "response_type": ResponseType.ENABLED_BOOL}, + {"description": "Battery short", "response_type": ResponseType.ENABLED_BOOL}, + {"description": "Power limit", "response_type": ResponseType.ENABLED_BOOL}, + {"description": "PV voltage high", "response_type": ResponseType.ENABLED_BOOL}, + {"description": "MPPT overload fault", "response_type": ResponseType.ENABLED_BOOL}, + {"description": "MPPT overload warning", "response_type": ResponseType.ENABLED_BOOL}, + {"description": "Battery too low to charge", "response_type": ResponseType.ENABLED_BOOL}, + {"description": "Battery weak", "response_type": ResponseType.ENABLED_BOOL}, + {"description": "Battery equalization", "response_type": ResponseType.ENABLED_BOOL}, + ], + "test_responses": [ + b"^D07100,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0\xaa\xaa\r", + ], + }, + # Per-unit parallel view. PGS, n = 0..N-1 (0 is master). + # LVX6048 emits a 30-field layout that differs from the PI30 QPGS layout; + # only fields confirmed against live responses are semantically named here. + # The rest are exposed as raw strings so the command doesn't error out, and + # can be tightened later as more firmware rev responses are confirmed. + # Observed unit-1 response (Not valid + fault 71 "Parallel version different"): + # 0,4,71,2453,599,0000,000,0000,0000,00000,00000,000,211,005,000,000,000, + # 000,0008,0000,2925,0000,1,0,0,0,0,0,016 + "PGS": { + "name": "PGS", + "description": "Parallel general status inquiry", + "help": " -- example: PGS0 queries parallel status for instance 0 (master)", + "command_type": CommandType.PI18_QUERY, + "result_type": ResultType.COMMA_DELIMITED, + "reading_definitions": [ + {"description": "Parallel instance number", "reading_type": ReadingType.MESSAGE, + "response_type": ResponseType.LIST, "options": ["Not valid", "valid"]}, + {"description": "Parallel unit count", "reading_type": ReadingType.MESSAGE}, + {"description": "Fault code", "reading_type": ReadingType.MESSAGE}, + {"description": "Field 4 (raw)", "reading_type": ReadingType.MESSAGE}, + {"description": "Grid frequency", "reading_type": ReadingType.FREQUENCY, + "response_type": ResponseType.TEMPLATE_INT, "format_template": "r/10", "device_class": "frequency"}, + {"description": "AC output voltage", "reading_type": ReadingType.VOLTS, + "response_type": ResponseType.TEMPLATE_INT, "format_template": "r/10", "device_class": "voltage"}, + {"description": "AC output frequency (raw)", "reading_type": ReadingType.MESSAGE}, + {"description": "AC output apparent power", "reading_type": ReadingType.APPARENT_POWER, + "response_type": ResponseType.INT, "device_class": "apparent_power"}, + {"description": "AC output active power", "reading_type": ReadingType.WATTS, + "response_type": ResponseType.INT, "device_class": "power"}, + {"description": "Total AC output apparent power", "reading_type": ReadingType.APPARENT_POWER, "response_type": ResponseType.INT}, + {"description": "Total AC output active power", "reading_type": ReadingType.WATTS, "response_type": ResponseType.INT}, + {"description": "Load percentage", "reading_type": ReadingType.PERCENTAGE, "response_type": ResponseType.INT}, + {"description": "Field 13 (raw)", "reading_type": ReadingType.MESSAGE}, + {"description": "Field 14 (raw)", "reading_type": ReadingType.MESSAGE}, + {"description": "Field 15 (raw)", "reading_type": ReadingType.MESSAGE}, + {"description": "Field 16 (raw)", "reading_type": ReadingType.MESSAGE}, + {"description": "Field 17 (raw)", "reading_type": ReadingType.MESSAGE}, + {"description": "Field 18 (raw)", "reading_type": ReadingType.MESSAGE}, + {"description": "Field 19 (raw)", "reading_type": ReadingType.MESSAGE}, + {"description": "Field 20 (raw)", "reading_type": ReadingType.MESSAGE}, + {"description": "Field 21 (raw)", "reading_type": ReadingType.MESSAGE}, + {"description": "Field 22 (raw)", "reading_type": ReadingType.MESSAGE}, + {"description": "Flag 23 (raw)", "reading_type": ReadingType.MESSAGE}, + {"description": "Flag 24 (raw)", "reading_type": ReadingType.MESSAGE}, + {"description": "Flag 25 (raw)", "reading_type": ReadingType.MESSAGE}, + {"description": "Flag 26 (raw)", "reading_type": ReadingType.MESSAGE}, + {"description": "Flag 27 (raw)", "reading_type": ReadingType.MESSAGE}, + {"description": "Flag 28 (raw)", "reading_type": ReadingType.MESSAGE}, + {"description": "Field 30 (raw)", "reading_type": ReadingType.MESSAGE}, + ], + "test_responses": [ + b"^D1130,4,71,2453,599,0000,000,0000,0000,00000,00000,000,211,005,000,000,000,000,0008,0000,2925,0000,1,0,0,0,0,0,016\x8f\xad\r", + ], + "regex": "PGS(\\d+)$", + }, +} + +COMMANDS_TO_REMOVE = [] + + +class PI18(AbstractProtocol): + """ pi18 protocol handler """ + def __str__(self): + return "PI18 protocol handler" + + def __init__(self) -> None: + super().__init__() + self.protocol_id = b"PI18" + self.add_command_definitions(QUERY_COMMANDS) + self.add_command_definitions(SETTER_COMMANDS, result_type=ResultType.PI18_ACK) + self.remove_command_definitions(COMMANDS_TO_REMOVE) + self.check_definitions_count(expected=26) # Count of all Commands + self.add_supported_ports([PortType.SERIAL, PortType.USB]) + + def check_crc(self, response: str, command_definition: CommandDefinition = None): + """ crc check, override for now """ + log.debug("check crc for %s in pi18", response) + if response.startswith(b"^D") or response.startswith(b"^1") or response.startswith(b"^0"): + # get response CRC + data_to_check = response[:-3] + crc_high, crc_low = crc(data_to_check) + # print(crc_high, crc_low) + # print(response[-3], response[-2]) + if (crc_high, crc_low) == (response[-3], response[-2]): + return True + else: + log.info("PI18 response check_crc doesnt match calc (%x, %x), got (%x, %x)", crc_high, crc_low, response[-3], response[-2]) + raise InvalidCRC(f"PI18 response check_crc doesnt match calc ({crc_high:02x}, {crc_low:02x}), got ({response[-3]:02x}, {response[-2]:02x})") + else: + log.info("PI18 response doesnt start with ^D - check_crc fails") + raise InvalidResponse("PI18 response starts with invalid character - crc check fails") + + log.info("PI18 response check_crc fall through") + return False + + def trim_response(self, response: str, command_definition: CommandDefinition = None) -> str: + """ Remove extra characters from response """ + log.debug("trim %s, definition: %s", response, command_definition) + if response.startswith(b"^D"): + # trim ^Dxxx where xxx is data length + response = response[5:] + if response.endswith(b'\r'): + # has checksum, so trim last 3 chars + response = response[:-3] + if response.startswith(b'('): + # pi30 style response + response = response[1:] + # if response.startswith(b'^1') or response.startswith(b'^0'): + # # ACK / NACK response + # response = response[1:] + return response + + def get_full_command(self, command: str) -> bytes: + """ generate the full command including prefix, crc and \n as needed """ + log.info("Using protocol: %s with %i commands", self.protocol_id, len(self.command_definitions)) + command_defn = self.get_command_definition(command) + + # raise exception if no command definition is found + if command_defn is None: + raise CommandDefinitionMissing(f"No definition found in PI18 for {command}") + + # full command is ^PlllCCCcrc\n or ^SlllCCCcrc\n + # lll = length of all except ^Dlll + # CCC = command + # crc = 2 bytes + length = len(command) + 3 + # Determine prefix + match command_defn.command_type: + case CommandType.PI18_QUERY: + prefix = "^P" + case CommandType.PI18_SETTER: + prefix = "^S" + case _: + # edge case / default PI30 command / maybe this should raise an error + prefix = "(" + full_command = bytes(f"{prefix}{length:#03d}{command}", "utf-8") + crc_high, crc_low = crc(full_command) + full_command += bytes([crc_high, crc_low, 13]) + + log.debug("full command: %s", full_command) + return full_command diff --git a/LVX6048/powermon-patches/port_config_model.py b/LVX6048/powermon-patches/port_config_model.py new file mode 100644 index 0000000..3acf7f9 --- /dev/null +++ b/LVX6048/powermon-patches/port_config_model.py @@ -0,0 +1,38 @@ +""" pydantic definitions for the powermon port config model +""" +from typing import Literal + +from pydantic import Field + +from powermon.configmodel import NoExtraBaseModel + + +class BlePortConfig(NoExtraBaseModel): + """ model/allowed elements for ble port config """ + type: Literal["ble"] + mac: str + protocol: None | str + victron_key: None | str = Field(default=None, repr=False) + + +class SerialPortConfig(NoExtraBaseModel): + """ model/allowed elements for serial port config """ + type: Literal["serial"] + path: str + baud: None | int = Field(default=None) + protocol: None | str + + +class UsbPortConfig(NoExtraBaseModel): + """ model/allowed elements for usb port config """ + type: Literal["usb"] + path: None | str + protocol: None | str + serial_number: None | str | int = Field(default=None) + + +class TestPortConfig(NoExtraBaseModel): + """ model/allowed elements for test port config """ + type: Literal["test"] + response_number: None | int = Field(default=None) + protocol: None | str = Field(default=None) diff --git a/LVX6048/powermon-patches/ports_init.py b/LVX6048/powermon-patches/ports_init.py new file mode 100644 index 0000000..51cc457 --- /dev/null +++ b/LVX6048/powermon-patches/ports_init.py @@ -0,0 +1,76 @@ +""" powermon / ports / __init__.py """ +import logging +from enum import StrEnum, auto + +from pydantic import BaseModel + +from powermon.libs.errors import ConfigError + + +# Set-up logger +log = logging.getLogger("ports") + + +class PortType(StrEnum): + """ enumeration of supported / known port types """ + UNKNOWN = auto() + TEST = auto() + SERIAL = auto() + USB = auto() + BLE = auto() + + JKBLE = auto() + MQTT = auto() + VSERIAL = auto() + DALYSERIAL = auto() + ESP32 = auto() + + +class PortTypeDTO(BaseModel): + """ data transfer model for PortType class """ + port_type: PortType + +def from_config(port_config, serial_number=None): + """ get a port object from config data """ + log.debug("port_config: %s", port_config) + + port_object = None + if not port_config: + raise ConfigError("no port config supplied") + + # port type is mandatory + port_type = port_config.get("type") + log.debug("portType: %s", port_type) + + # return None if port type is not defined + if port_type is None: + return None + + # add serial_number to config — but only if the port config didn't already + # specify one. Port-level serial_number is the hardware serial used by + # USBPort.resolve_path for wildcard matching; device-level serial_number is + # the logical HA identifier and is unrelated. + if port_config.get('serial_number') is None: + port_config['serial_number'] = serial_number + + # build port object + match port_type: + case PortType.TEST: + from powermon.ports.testport import TestPort + port_object = TestPort.from_config(config=port_config) + case PortType.SERIAL: + from powermon.ports.serialport import SerialPort + port_object = SerialPort.from_config(config=port_config) + case PortType.USB: + from powermon.ports.usbport import USBPort + port_object = USBPort.from_config(config=port_config) + # Pattern for port types that cause problems when imported + case PortType.BLE: + log.debug("port_type BLE found") + from powermon.ports.bleport import BlePort + port_object = BlePort.from_config(config=port_config) + case _: + log.info("port type object not found for %s", port_type) + raise ConfigError(f"Invalid port type: '{port_type}'") + + return port_object diff --git a/LVX6048/powermon-patches/usbport.py b/LVX6048/powermon-patches/usbport.py new file mode 100644 index 0000000..3c6361d --- /dev/null +++ b/LVX6048/powermon-patches/usbport.py @@ -0,0 +1,169 @@ +""" powermon / ports / usbport.py """ +# import asyncio +import logging +import os +import time +from glob import glob + +from powermon.commands.command import Command +from powermon.commands.result import Result, ResultType +from powermon.libs.errors import ConfigError, PowermonProtocolError +from powermon.ports import PortType +from powermon.ports.abstractport import AbstractPort, _AbstractPortDTO +from powermon.protocols import get_protocol_definition + +log = logging.getLogger("USBPort") + + +class UsbPortDTO(_AbstractPortDTO): + """ data transfer model for SerialPort class """ + path: str + serial_number: None | int | str + + +class USBPort(AbstractPort): + """ usb port object """ + @classmethod + async def from_config(cls, config=None): + log.debug("building usb port. config:%s", config) + path = config.get("path", "/dev/hidraw0") + serial_number = config.get("serial_number") + # get protocol handler, default to PI30 if not supplied + protocol = get_protocol_definition(protocol=config.get("protocol", "PI30")) + # instantiate class + _class = cls(path=path, protocol=protocol) + # deal with wildcard path resolution + _class.path = await _class.resolve_path(path, serial_number) + return _class + + def __init__(self, path, protocol) -> None: + self.port_type = PortType.USB + super().__init__(protocol=protocol) + + self.path = None + self.port = None + + + async def resolve_path(self, path, serial_number): + """Async method to resolve a valid path by testing each one.""" + # expand 'wildcard' + paths = glob(path) + if not paths: + raise ConfigError(f"No matching paths found on this system for {path}") + + if len(paths) == 1: + return paths[0] # only one valid result + + # More than one valid path + # check we have something to look for + if serial_number is None: + raise ConfigError("Wildcard paths require a serial_number in config.") + # check we have get_id in this protocol + try: + command = self.protocol.get_id_command() + except PowermonProtocolError as ex: + raise ConfigError(f"No get_id in protocol: {self.protocol.protocol_id}") from ex + + for _path in paths: + log.debug("Checking path: %s for serial_number: %s", _path, serial_number) + self.path = _path + await self.connect() + res = await self.send_and_receive(command=command) + await self.disconnect() + + if res.is_valid and str(res.readings[0].data_value) == str(serial_number): + log.info("SUCCESS: path: %s matches serial_number: %s", _path, serial_number) + return _path # return the matching path + raise ConfigError(f"None of the paths match serial_number: {serial_number}") + + + def to_dto(self): + dto = UsbPortDTO(port_type="usb", path=self.path, protocol=self.protocol.to_dto()) + return dto + + def is_connected(self) -> bool: + return self.port is not None + + async def connect(self) -> bool: + if self.is_connected(): + log.debug("USBPort already connected") + return True + log.debug("USBPort connecting. path:%s, protocol:%s", self.path, self.protocol) + try: + self.port = os.open(self.path, os.O_RDWR | os.O_NONBLOCK) + log.debug("USBPort port number $%s", self.port) + except Exception as e: + log.warning("Error openning usb port: %s", e) + self.port = None + self.error_message = e + return self.is_connected() + + async def disconnect(self) -> None: + log.debug("USBPort disconnecting: %i", self.port) + if self.port is not None: + os.close(self.port) + self.port = None + + async def send_and_receive(self, command: Command) -> Result: + if not self.is_connected(): + log.warning("USBPort not connected") + return command.build_result(result_type=ResultType.ERROR, raw_response=b"USBPort not connected", protocol=self.protocol) + response_line = bytes() + + # Drain any leftover bytes from previous command's response so we + # don't parse them as this command's reply. + try: + while True: + stale = os.read(self.port, 256) + if not stale: + break + log.debug("drained %i stale bytes: %s", len(stale), stale) + except BlockingIOError: + pass + + # Send the command to the open usb connection + full_command = command.full_command + cmd_len = len(full_command) + log.debug("length of to_send: %i", cmd_len) + # for command of len < 8 it ok just to send + # otherwise need to pack to a multiple of 8 bytes and send 8 at a time + try: + if cmd_len <= 8: + # Send all at once + log.debug("sending full_command in on shot") + time.sleep(0.05) + os.write(self.port, full_command) + else: + log.debug("multiple chunk send") + chunks = [full_command[i:i + 8] for i in range(0, cmd_len, 8)] + for chunk in chunks: + # pad chunk to 8 bytes + if len(chunk) < 8: + padding = 8 - len(chunk) + chunk += b'\x00' * padding + log.debug("sending chunk: %s", chunk) + time.sleep(0.05) + os.write(self.port, chunk) + time.sleep(0.25) + # Read from the usb connection + # try to a max of 100 times + for _ in range(100): + # attempt to deal with resource busy and other failures to read + time.sleep(0.15) + try: + r = os.read(self.port, 256) + except BlockingIOError: + continue + response_line += r + # Finished is \r is in byte_response + if bytes([13]) in response_line: + # remove anything after the \r + response_line = response_line[: response_line.find(bytes([13])) + 1] + break + except BrokenPipeError as e: + log.debug("USB read error: %s", e) + log.debug("usb response was: %s", response_line) + # response = self.get_protocol().check_response_and_trim(response_line) + result = command.build_result(raw_response=response_line, protocol=self.protocol) + + return result diff --git a/LVX6048/smoketest/console.yaml b/LVX6048/smoketest/console.yaml new file mode 100644 index 0000000..f7630e0 --- /dev/null +++ b/LVX6048/smoketest/console.yaml @@ -0,0 +1,26 @@ +device: + name: lvx6048_console + serial_number: lvx6048_console + port: + type: usb + path: /dev/lvx6048-1 + protocol: PI18 +commands: + - command: GS + trigger: + every: 5 + outputs: + - type: screen + format: table + - command: MOD + trigger: + every: 10 + outputs: + - type: screen + format: table + - command: PIRI + trigger: + every: 300 + outputs: + - type: screen + format: table diff --git a/LVX6048/smoketest/smoketest.yaml b/LVX6048/smoketest/smoketest.yaml new file mode 100644 index 0000000..300490b --- /dev/null +++ b/LVX6048/smoketest/smoketest.yaml @@ -0,0 +1,12 @@ +device: + name: lvx6048_smoketest + serial_number: lvx6048_smoketest + port: + type: usb + path: /dev/lvx6048-1 + protocol: PI18 +commands: + - command: GS + outputs: + - type: screen + format: table diff --git a/battery/.claude/settings.local.json b/battery/.claude/settings.local.json new file mode 100644 index 0000000..218ae71 --- /dev/null +++ b/battery/.claude/settings.local.json @@ -0,0 +1,13 @@ +{ + "permissions": { + "allow": [ + "Bash(strings -n 4 \"51.2V 100Ah LP4V2 Auto-Addressing RS485 Z03T21 Firmware.bin\")", + "Read(//tmp/**)", + "WebSearch", + "WebFetch(domain:eg4electronics.com)", + "WebFetch(domain:diysolarforum.com)", + "WebFetch(domain:github.com)", + "WebFetch(domain:raw.githubusercontent.com)" + ] + } +} diff --git a/battery/eg4_lifepower.py b/battery/eg4_lifepower.py new file mode 100644 index 0000000..9c0ddd9 --- /dev/null +++ b/battery/eg4_lifepower.py @@ -0,0 +1,134 @@ +#!/usr/bin/env python3 +"""Standalone EG4 LifePower4 (V1/V2) decoder over USB-RS485. + +Adapted from Louisvdw/dbus-serialbattery (bms/lifepower.py) stripped of its +Victron dbus dependencies. Pure pyserial + stdlib. + +Frame format (observed on wire, not standard Modbus): + request : 7E 00 0D (6 bytes) + reply : 7E ... 0D (variable, status payload + contains 10 groups of + 16-bit unsigned ints) + +Verified commands: general status (01), firmware ver (33), hardware ver (42). +Address byte is typically 0x01 for a single battery; multipack setups have the +master at 0x01 and only the master answers external RS485. + +Usage: + python3 eg4_lifepower.py # auto-detect port + python3 eg4_lifepower.py /dev/tty.usbserial-XXX # explicit port +""" +from __future__ import annotations + +import glob +import sys +import time +from dataclasses import dataclass, field +from struct import unpack_from + +import serial # pip install pyserial + +BAUD = 9600 + +CMD_GENERAL = bytes([0x7E, 0x01, 0x01, 0x00, 0xFE, 0x0D]) +CMD_HW_VER = bytes([0x7E, 0x01, 0x42, 0x00, 0xFC, 0x0D]) +CMD_FW_VER = bytes([0x7E, 0x01, 0x33, 0x00, 0xFE, 0x0D]) + + +@dataclass +class Status: + cell_voltages: list[float] = field(default_factory=list) # volts + current: float = 0.0 # amps (signed, +charge) + soc: float = 0.0 # percent + capacity_ah: float = 0.0 + temps_c: list[int] = field(default_factory=list) # up to 6 + cycles: int = 0 + pack_voltage: float = 0.0 + alarms: dict = field(default_factory=dict) + + def summary(self) -> str: + cells = self.cell_voltages + cmin, cmax = (min(cells), max(cells)) if cells else (0, 0) + return ( + f"pack={self.pack_voltage:6.2f}V I={self.current:+7.2f}A " + f"SoC={self.soc:5.1f}% cap={self.capacity_ah:6.2f}Ah " + f"cells={len(cells)} ({cmin:.3f}..{cmax:.3f}V Δ={cmax-cmin:.3f}) " + f"temps={self.temps_c} cycles={self.cycles} alarms={self.alarms}" + ) + + +def send(port: serial.Serial, cmd: bytes, read_n: int = 256) -> bytes: + port.reset_input_buffer() + port.write(cmd) + time.sleep(0.2) + return port.read(read_n) + + +def parse_status(data: bytes) -> Status: + """Parse the general-status reply. Raises ValueError on bad framing.""" + if not data or data[0] != 0x7E or data[-1] != 0x0D: + raise ValueError(f"bad framing: {data.hex(' ')}") + + groups: list[list[int]] = [] + i = 4 # skip 7E — payload starts at byte 4 + for _ in range(10): + if i + 2 > len(data): + raise ValueError("truncated payload") + group_len = data[i + 1] + end = i + 2 + (group_len * 2) + payload = data[i + 2 : end] + values = [unpack_from(">H", payload, k)[0] for k in range(0, len(payload), 2)] + groups.append(values) + i = end + + s = Status() + s.cell_voltages = [(v & 0x7FFF) / 1000 for v in groups[0]] + s.current = (30000 - groups[1][0]) / 100 + s.soc = groups[2][0] / 100 + s.capacity_ah = groups[3][0] / 100 + s.temps_c = [(t & 0xFF) - 50 for t in groups[4][:6]] + flags = groups[5][1] if len(groups[5]) > 1 else 0 + s.alarms = { + "current_over": bool(flags & 0b00001000), + "voltage_high": bool(flags & 0b00010000), + "voltage_low": bool(flags & 0b00100000), + "temp_high_chg": bool(flags & 0b01000000), + "temp_low_chg": bool(flags & 0b10000000), + } + s.cycles = groups[6][0] + s.pack_voltage = groups[7][0] / 100 + return s + + +def decode_ascii(data: bytes) -> str: + return data.decode("ascii", errors="ignore").strip() + + +def autodetect() -> str | None: + for pat in ("/dev/tty.usbserial*", "/dev/tty.usbmodem*", "/dev/ttyUSB*", "/dev/ttyACM*"): + hits = glob.glob(pat) + if hits: + return hits[0] + return None + + +def main() -> None: + port_path = sys.argv[1] if len(sys.argv) > 1 else autodetect() + if not port_path: + sys.exit("no serial port found; pass one explicitly") + print(f"opening {port_path} @ {BAUD} 8N1") + with serial.Serial(port_path, BAUD, bytesize=8, parity="N", stopbits=1, timeout=1.5) as p: + hw = send(p, CMD_HW_VER) + fw = send(p, CMD_FW_VER) + if hw: + print(f"hw: {decode_ascii(hw)}") + if fw: + print(f"fw: {decode_ascii(fw)}") + raw = send(p, CMD_GENERAL) + print(f"raw ({len(raw)}B): {raw.hex(' ')}") + if raw: + print(parse_status(raw).summary()) + + +if __name__ == "__main__": + main() diff --git a/battery/probe.py b/battery/probe.py new file mode 100644 index 0000000..b4dfe74 --- /dev/null +++ b/battery/probe.py @@ -0,0 +1,64 @@ +#!/usr/bin/env python3 +"""Sanity-check byte-level probe for EG4 LifePower4 V2 over USB-RS485. + +Sends the 'general status' frame and prints the raw reply. If you see bytes +starting with 0x7E coming back, the protocol is confirmed and eg4_lifepower.py +will decode them. + +Usage: + python3 probe.py # auto-detect likely USB-serial port + python3 probe.py /dev/tty.usbserial-XXX # explicit port + python3 probe.py COM5 # Windows +""" +import sys +import time +import glob +import serial # pip install pyserial + +BAUD = 9600 +CMDS = { + "general": bytes.fromhex("7E01010000FE0D"[:-2] + "0D"), # 7E 01 01 00 FE 0D + "hw_ver": bytes.fromhex("7E01420000FC0D"[:-2] + "0D"), # 7E 01 42 00 FC 0D + "fw_ver": bytes.fromhex("7E01330000FE0D"[:-2] + "0D"), # 7E 01 33 00 FE 0D +} +# (the [:-2]+"0D" dance above is just to keep the literal aligned with the +# canonical 6-byte frames documented upstream; simpler form below) +CMDS = { + "general": bytes([0x7E, 0x01, 0x01, 0x00, 0xFE, 0x0D]), + "hw_ver": bytes([0x7E, 0x01, 0x42, 0x00, 0xFC, 0x0D]), + "fw_ver": bytes([0x7E, 0x01, 0x33, 0x00, 0xFE, 0x0D]), +} + + +def autodetect() -> str | None: + candidates = ( + glob.glob("/dev/tty.usbserial*") # macOS FTDI/CH340 + + glob.glob("/dev/tty.usbmodem*") # macOS CDC-ACM + + glob.glob("/dev/ttyUSB*") # Linux FTDI/CH340 + + glob.glob("/dev/ttyACM*") # Linux CDC-ACM + ) + return candidates[0] if candidates else None + + +def probe(port: str) -> None: + print(f"opening {port} @ {BAUD} 8N1") + with serial.Serial(port, BAUD, bytesize=8, parity="N", stopbits=1, timeout=1.5) as s: + for name, cmd in CMDS.items(): + s.reset_input_buffer() + s.write(cmd) + time.sleep(0.2) + reply = s.read(256) + print(f"\n[{name}] sent {cmd.hex(' ')}") + if reply: + print(f" got ({len(reply)} bytes) {reply.hex(' ')}") + if reply[0] == 0x7E and reply[-1] == 0x0D: + print(" -> frame looks valid (7E ... 0D)") + else: + print(" got (nothing — timeout)") + + +if __name__ == "__main__": + port = sys.argv[1] if len(sys.argv) > 1 else autodetect() + if not port: + sys.exit("no serial port found; pass one explicitly: python3 probe.py /dev/tty.usbserial-XXXX") + probe(port) diff --git a/battery/requirements.txt b/battery/requirements.txt new file mode 100644 index 0000000..7ad05ef --- /dev/null +++ b/battery/requirements.txt @@ -0,0 +1 @@ +pyserial>=3.5 diff --git a/battery/sweep.py b/battery/sweep.py new file mode 100644 index 0000000..9a7f4e6 --- /dev/null +++ b/battery/sweep.py @@ -0,0 +1,208 @@ +#!/usr/bin/env python3 +"""Address sweep + protocol probe for EG4 LifePower4 over USB-RS485. + +Tries three candidate protocols across a range of unit addresses: + 1. Legacy 7E/0D binary framing — addresses 0x00..0x10 + 2. PACE BMS V25 (ASCII-hex inside 7E/0D) — addresses 0x01..0x10 + Used by Pylontech, Solark, Deye, Lux, Growatt, MegaRev, etc. + 3. Standard Modbus RTU (fn 0x03) — addresses 1..16, reads 39 holding regs + +Any non-empty reply is printed raw. First byte tells you which protocol: + - 7E + ASCII-hex payload = PACE V25 + - 7E + binary payload = legacy + - matching addr byte + 0x03 = Modbus + +Usage: + python3 sweep.py # auto-detect port, 9600 + python3 sweep.py /dev/ttyUSB0 # explicit port + python3 sweep.py /dev/ttyUSB0 19200 # explicit port + baud +""" +import glob +import sys +import time + +import serial # pip install pyserial + +DEFAULT_BAUD = 9600 + + +def autodetect() -> str | None: + for pat in ("/dev/tty.usbserial*", "/dev/tty.usbmodem*", "/dev/ttyUSB*", "/dev/ttyACM*"): + hits = glob.glob(pat) + if hits: + return hits[0] + return None + + +def frame_7e(addr: int, cmd: int = 0x01, length: int = 0x00) -> bytes: + chk = (0x100 - (addr + cmd + length)) & 0xFF + return bytes([0x7E, addr, cmd, length, chk, 0x0D]) + + +def crc16_modbus(data: bytes) -> int: + crc = 0xFFFF + for b in data: + crc ^= b + for _ in range(8): + crc = (crc >> 1) ^ 0xA001 if crc & 1 else crc >> 1 + return crc + + +def frame_modbus(addr: int, func: int = 0x03, start: int = 0x0000, count: int = 39) -> bytes: + body = bytes([addr, func, start >> 8, start & 0xFF, count >> 8, count & 0xFF]) + crc = crc16_modbus(body) + return body + bytes([crc & 0xFF, crc >> 8]) # Modbus CRC is LSB-first on wire + + +def _pace_lenid(n: int) -> int: + """V25 LENID = (lchecksum << 12) | (n & 0x0FFF); lchecksum per PACE spec.""" + s = ((n >> 8) & 0x0F) + ((n >> 4) & 0x0F) + (n & 0x0F) + lchk = ((~s) + 1) & 0x0F + return (lchk << 12) | (n & 0x0FFF) + + +def frame_pace(addr: int, ver: int = 0x20, cid1: int = 0x4A, cid2: int = 0x42, info: bytes = b"") -> bytes: + """Build a PACE BMS request frame (ASCII-hex inside ~…\\r). + + Defaults reproduce the EG4 LifePower4 V20 read-analog frame verified against + nkinnan/esphome-pace-bms: ver=0x20 (V20), cid1=0x4A (EG4/Narada family), + cid2=0x42 (read analog), empty INFO payload (EG4 variant — busId is *not* + repeated in the payload, unlike generic PACE). Known-good at addr 1: + ~20014A420000FDA2\\r + """ + info_ascii = info.hex().upper().encode() + lenid = _pace_lenid(len(info_ascii)) + body = f"{ver:02X}{addr:02X}{cid1:02X}{cid2:02X}{lenid:04X}".encode() + info_ascii + chksum = ((-sum(body)) & 0xFFFF) + return b"~" + body + f"{chksum:04X}".encode() + b"\r" + + +def exchange(port: serial.Serial, tx: bytes, wait: float = 0.25, read_n: int = 256) -> bytes: + port.reset_input_buffer() + port.write(tx) + time.sleep(wait) + return port.read(read_n) + + +def classify_7e(rx: bytes) -> str: + if not rx: + return "" + if rx[0] == 0x7E and rx.endswith(b"\x0D"): + return " <- 7E framed reply" + return " <- unexpected bytes" + + +def classify_pace(rx: bytes) -> str: + if not rx: + return "" + if rx[0] == 0x7E and rx.endswith(b"\r"): + # V25 payload is ASCII-hex between ~ and \r + mid = rx[1:-1] + if mid.isascii() and all(c in b"0123456789ABCDEFabcdef" for c in mid): + return " <- PACE V25 reply" + return " <- 7E-framed but non-ASCII (likely legacy, not V25)" + return " <- unexpected bytes" + + +def classify_modbus(rx: bytes, addr: int) -> str: + if not rx: + return "" + if len(rx) >= 3 and rx[0] == addr and rx[1] == 0x03: + return " <- Modbus reply" + if len(rx) >= 3 and rx[0] == addr and rx[1] == 0x83: + return f" <- Modbus exception (code {rx[2]:#04x})" + return " <- unexpected bytes" + + +COMMON_BAUDS = (9600, 19200, 38400, 57600, 115200) + + +def passive_listen(port_path: str, seconds: int = 15) -> None: + """Open at each baud for N seconds and dump whatever the pack transmits + unsolicited. If the pack is in broadcast mode (Pylontech CAN-style, + Victron, some Growatt modes), we'll see frames arrive without asking. + """ + print(f"passive listen on {port_path} — {seconds}s per baud") + for baud in COMMON_BAUDS: + try: + with serial.Serial(port_path, baud, bytesize=8, parity="N", stopbits=1, timeout=0.25) as p: + p.reset_input_buffer() + deadline = time.monotonic() + seconds + buf = bytearray() + while time.monotonic() < deadline: + chunk = p.read(256) + if chunk: + buf.extend(chunk) + ascii_preview = buf.decode("ascii", errors="replace") if buf else "" + tag = " <- traffic!" if buf else "" + print(f" @ {baud:6d} 8N1: {len(buf):4d}B{tag}") + if buf: + print(f" hex: {buf.hex(' ')}") + print(f" ascii: {ascii_preview!r}") + except serial.SerialException as e: + print(f" @ {baud}: {e}") + + +def quick_baud_scan(port_path: str) -> None: + """At addr 0x01, send one legacy + one V25 + one Modbus frame at each baud. + Any non-empty reply means we've found the right baud + a protocol that the + pack is willing to answer — then rerun the full sweep at that baud. + """ + print(f"opening {port_path} — scanning baud rates {COMMON_BAUDS}") + for baud in COMMON_BAUDS: + try: + with serial.Serial(port_path, baud, bytesize=8, parity="N", stopbits=1, timeout=2.0) as p: + print(f"\n @ {baud} 8N1:") + for label, tx in ( + ("legacy", frame_7e(0x01)), + ("pace", frame_pace(0x01)), + ("modbus", frame_modbus(0x01)), + ): + rx = exchange(p, tx, wait=0.3) + tag = "" + if rx: + tag = " <- REPLY!" + print(f" {label:7s}: tx {tx.hex(' ')} rx ({len(rx):3d}B) {rx.hex(' ')}{tag}") + except serial.SerialException as e: + print(f" @ {baud}: {e}") + + +def sweep(port_path: str, baud: int) -> None: + print(f"opening {port_path} @ {baud} 8N1 (2s timeout)") + with serial.Serial(port_path, baud, bytesize=8, parity="N", stopbits=1, timeout=2.0) as p: + print("\n[7E protocol — general-status sweep]") + for addr in range(0x00, 0x11): + tx = frame_7e(addr) + rx = exchange(p, tx) + print(f" addr 0x{addr:02X}: tx {tx.hex(' ')} rx ({len(rx):3d}B) {rx.hex(' ')}{classify_7e(rx)}") + + print("\n[PACE V20 (EG4/Narada variant) — read-analog-data sweep]") + for addr in list(range(0x01, 0x11)) + [0xFF]: # include broadcast 0xFF + tx = frame_pace(addr) + rx = exchange(p, tx, wait=0.35) + print(f" addr 0x{addr:02X}: tx {tx.decode('ascii', errors='replace').strip()!r} rx ({len(rx):3d}B) {rx.hex(' ')}{classify_pace(rx)}") + + print("\n[Modbus RTU — read 39 holding regs sweep]") + for addr in range(1, 17): + tx = frame_modbus(addr) + rx = exchange(p, tx) + print(f" addr 0x{addr:02X}: tx {tx.hex(' ')} rx ({len(rx):3d}B) {rx.hex(' ')}{classify_modbus(rx, addr)}") + + +if __name__ == "__main__": + args = sys.argv[1:] + scan_only = "--scan" in args + listen_only = "--listen" in args + args = [a for a in args if a not in ("--scan", "--listen")] + port = args[0] if args else autodetect() + if not port: + sys.exit("no serial port found; pass one explicitly") + if listen_only: + passive_listen(port) + sys.exit(0) + if scan_only or len(args) < 2: + quick_baud_scan(port) + if scan_only: + sys.exit(0) + baud = int(args[1]) if len(args) > 1 else DEFAULT_BAUD + sweep(port, baud) diff --git a/eg4battery/Install.md b/eg4battery/Install.md new file mode 100644 index 0000000..436e0c6 --- /dev/null +++ b/eg4battery/Install.md @@ -0,0 +1,157 @@ +# EG4 LifePower4 v2 → HA Monitoring Install + +Target: Debian-family Linux (developed on Raspberry Pi CM5), one USB-to-RS-485 +adapter per pack's **RS485** socket, HA MQTT broker on the LAN. + +> **Shortcut:** [`install.sh`](./install.sh) automates §3–§7 and supports +> `--dry-run` for a no-hardware smoke test. This doc explains what it does +> and how to do it by hand. + +Path conventions: `$BASE` = root of this package (e.g. `~/solar/eg4battery`). + +## 1. Prerequisites + +- `uv` on `$PATH` ([docs](https://docs.astral.sh/uv/)) +- `sudo` +- **One USB-to-RS-485 adapter per pack**. FTDI-based is what we've tested + (FT232R + MAX485 combo, identified by the Linux kernel as `FT232R USB UART` + with a unique serial-number suffix). CH340 / CP210x also fine — adjust + the udev rule's vendor/product ID. + +## 2. Cabling — read this before wiring + +LP4V2 back panel has four RJ45 sockets: `CAN`, `RS485`, `Comm1`, `Comm2`. +Only `RS485` is relevant for our daemon: + +| Socket | Use for monitoring? | +|--------|---------------------------------------------------------------------| +| CAN | No — separate bus, CAN signaling, for inverter BMS comms | +| RS485 | **Yes.** External monitor port. Pin 1-2 = B/A. Modbus RTU at 9600. | +| Comm1 | No — inter-pack hub bus (19200 Modbus). Leave for pack daisy-chain. | +| Comm2 | No — same internal bus as Comm1. | + +The stock EG4 USB-RS-485 cable (included with each pack) is already wired +correctly for the RS485 socket (pins 1-2 / A-B). + +**Topology**: each pack gets its own adapter plugged into its **RS485** socket. +No daisy chain required for monitoring — each pack is a dedicated bus. The +Comm1↔Comm2 daisy chain between packs is separate and carries the inter-pack +hub bus (not our concern). + +## 3. udev rule + +Grants `dialout` group access to FTDI USB-serial adapters. + +```bash +sudo install -m 644 "$BASE/etc/udev/rules.d/99-eg4-rs485.rules" \ + /etc/udev/rules.d/99-eg4-rs485.rules +sudo udevadm control --reload-rules +sudo udevadm trigger --subsystem-match=tty +ls -l /dev/serial/by-id/ # expect one symlink per adapter +``` + +## 4. Daemon binary + +```bash +sudo install -m 755 "$BASE/bin/eg4-battery" /usr/local/bin/eg4-battery +``` + +uv handles deps; no venv work on your side. + +## 5. Config + +Canonical template: [`config/eg4-battery.yaml.example`](./config/eg4-battery.yaml.example). + +```bash +mkdir -p ~/.config/eg4-battery +install -m 600 "$BASE/config/eg4-battery.yaml.example" \ + ~/.config/eg4-battery/eg4-battery.yaml +# Edit — see "mode selection" below +``` + +### 5a. mode selection + +- **`modbus_per_pack`** (default / recommended). Each pack listed with its own + `port:`, `address:` and `baud:` — the daemon opens one serial port per pack + and polls each independently. + ```yaml + bus: + mode: modbus_per_pack + timeout_s: 1.0 + poll_interval_s: 10.0 + packs: + - name: lifepower4_1 + address: 0x40 + port: /dev/serial/by-id/usb-FTDI_FT232R_USB_UART_-if00-port0 + baud: 9600 + - name: lifepower4_2 + address: 0x40 + port: /dev/serial/by-id/usb-FTDI_FT232R_USB_UART_-if00-port0 + baud: 9600 + ``` + +- **`active`** (legacy, V1 hardware only) — single shared bus, EG4 7E/0D + protocol at 9600. Not used on V2 Auto-Addressing hardware. + +- **`passive`** (diagnostic) — listen-only Modbus sniff at 19200. See + [`NOTES.md`](./NOTES.md) "Modes" for details. + +### 5b. MQTT creds + +Replace ``, ``, ``. `install.sh` +will not auto-start the service while those placeholders remain. + +## 6. Smoke test without hardware + +```bash +eg4-battery -C ~/.config/eg4-battery/eg4-battery.yaml --dry-run +``` + +Mock transport, one cycle per pack, prints every discovery-config and +state-topic / payload to stdout. Confirms the pipeline end-to-end before +hardware is involved. + +## 7. systemd + +```bash +sudo install -m 644 "$BASE/etc/systemd/system/eg4-battery.service" \ + /etc/systemd/system/ +sudo systemctl daemon-reload +sudo systemctl enable --now eg4-battery.service +journalctl -u eg4-battery.service -f +``` + +Service includes `Environment=PATH=…` so uv is found under systemd. + +## 8. Bring up additional packs + +When a new pack comes online: + +1. Plug its adapter into the pack's **RS485** socket and power the pack. +2. `ls -l /dev/serial/by-id/` — note the new symlink. +3. Add / update an entry in `~/.config/eg4-battery/eg4-battery.yaml`: + ```yaml + - name: lifepower4_N + address: 0x40 + port: /dev/serial/by-id/usb-FTDI_FT232R_USB_UART_-if00-port0 + baud: 9600 + ``` +4. `sudo systemctl restart eg4-battery.service`. The journal shows + `pack lifepower4_N: recovered after N failed cycle(s)` within ~10 s when + the pack starts responding, and HA auto-discovers ~65 entities. + +## 9. Verify MQTT flow + +```bash +# modbus_per_pack: all named + raw register entities per pack +mosquitto_sub -h -u mqtt -P \ + -t 'homeassistant/sensor/lifepower4_+_pack_voltage/state' \ + -t 'homeassistant/sensor/lifepower4_+_soc/state' \ + -t 'homeassistant/sensor/lifepower4_+_cell_voltage_delta_mv/state' \ + -v +``` + +## 10. Swapping modes later + +Change `bus.mode` in the config, restart the service. Config reshape varies +per mode — see §5a. No binary redeploy needed. diff --git a/eg4battery/NOTES.md b/eg4battery/NOTES.md new file mode 100644 index 0000000..618e880 --- /dev/null +++ b/eg4battery/NOTES.md @@ -0,0 +1,160 @@ +# EG4 LifePower4 v2 — architecture & protocol notes + +## Modes — choose per deployment + +`bus.mode` in the config picks one of three daemon modes: + +| Mode | Wire protocol | Baud | Role | Status | +|------------------|----------------------|-------|----------------|------------------------------------------------------------| +| `modbus_per_pack`| Modbus RTU, fn 0x03 | 9600 | master (per pack)| **Primary path for LP4V2 Auto-Addressing.** One FTDI per pack's RS485 port, polls once per cycle, fully decodes into named HA entities. | +| `active` | EG4 7E/0D legacy | 9600 | master (shared) | **Legacy.** V1 firmware only. V2 packs don't respond to this protocol — kept for reference. | +| `passive` | Modbus RTU sniff | 19200 | listener | **Diagnostic.** Originally intended to listen on an LVX6048 BMS bus; the LVX6048 doesn't poll EG4 packs that way, so has no production use here. | + +## How we got here (summary) + +1. **Port matrix test** (2026-04-24). Established that the LP4V2 back panel's four RJ45s (CAN / RS485 / Comm1 / Comm2) carry three distinct electrical buses: + - Comm1 + Comm2 = inter-pack hub bus (19200 Modbus, master-to-slave coordination). Pin 1-2 and pin 7-8 both tap the same bus. + - RS485 = external monitor bus. Inactive until an external master drives it. + - CAN = separate bus, not in scope. +2. **EG4 BMS Tool capture.** User confirmed the stock Windows/macOS BMS Tool connects to the RS485 port at 9600 baud, Modbus slave ID **0x40**. Our first canonical Modbus probe at that address returned a clean 99-byte reply. +3. **`lv_host.app` reverse-engineering.** The BMS Tool is a Qt app. Its Mach-O binary contains: + - SQLite schema for the `total` table → complete list of 39 fields the BMS Tool stores per cycle. + - String-table names for warning / protection bit flags. + - C++ symbols `BmsDatalog::allFunctionModbusAnalysis`, `BmsMonitoring::allFunctionModbusAnalysis`, `usModbusAskRegBase` etc. → confirms Modbus RTU fn 0x03 read-holding-regs. +4. **Register map construction.** Correlated live register values (cell voltages, pack V) against the SQL field list to derive the 47-reg map below. High-confidence fields promoted to named HA entities; unknowns still emitted as `register_NN` for future correlation. + +## Register map (Modbus fn 0x03, start 0x0000, count 47) + +From live observation + lv_host.app schema: + +| Reg | Observed | Field | Scale | Unit | HA entity suffix | +|-------|--------------|---------------------|---------|------|----------------------| +| 00 | 5256 | Total_Voltage | × 0.01 | V | `pack_voltage` | +| 01 | 0 (signed) | Current_I | × 0.01 | A | `pack_current` | +| 02-17 | ~3285 each | Vol_Cell01..16 | × 0.001 | V | `cell_01_voltage`..`cell_16_voltage` + `cell_voltage_min/max/delta_mv/lowest/highest` | +| 18 | 21 | Temp_01 | × 1 | °C | `temperature_01` | +| 19 | 21 | Temp_02 | × 1 | °C | `temperature_02` | +| 20 | 20 | Temp_03 | × 1 | °C | `temperature_03` | +| 21 | 54 | Temp_04 | × 1 | °C | `temperature_04` | +| 22 | 100 | SOC | × 1 | % | `soc` | +| 23 | 100 | SOH | × 1 | % | `soh` | +| 24 | 55 | Temp_PCB | × 1 | °C | `temperature_pcb` | +| 25-29 | 0 | reserved | | | (register_NN only) | +| 30 | 1 | Heater (bit 0) | | | `heater` (on/off) | +| 31 | 5493 | MAX_Curren | × 0.01 | A | `max_current_limit` | +| 32 | 10752 | *?* | | | (register_32) | +| 33 | ? | Warning bitfield | | | `warning_*` (14 bits)| +| 34 | ? | Protection bitfield | | | `protection_*` (14 bits)| +| 35 | 0 | Error_Code | | | `error_code` | +| 36 | 16 | Cell_Num | | | `cell_count` | +| 37 | 1000 | Capacity | × 0.1 | Ah | `capacity_ah` | +| 38 | 0 | Remaining | × 0.01 | Ah | `remaining_ah` | +| 39 | 0 | CycleNum | | | `cycle_count` | +| 40 | 7 | Battery_Mode (enum) | | | `battery_mode` | +| 41 | 0x0fff | BMS_Version (hi) | | | `bms_version_hi` | +| 42 | 0x07ff | BMS_Version (lo) | | | `bms_version_lo` | +| 43-45 | — | *?* | | | (register_NN only) | +| 46 | +1.25 Hz | runtime counter | × 0.1 s?| | `uptime_ds` | + +**Confidence levels**: Bold-worthy certain (confirmed by live values + UI labels): pack_voltage, cells 01-16, SOC, SOH, cell_count, capacity_ah. Medium (fits data, unverified): temps, current, bitfields, Battery_Mode. Unknown: regs 32, 35 (probably Error_Code but value always 0 so far), 38-40, 43-45. + +## Warning / protection bit maps + +From the UI labels in lv_host.app (bit 0 = first listed): + +| Bit | Warning (reg 33) | Protection (reg 34) | +|-----|-------------------|---------------------| +| 0 | pack_ov | pack_ov | +| 1 | cell_ov | cell_ov | +| 2 | pack_uv | pack_uv | +| 3 | cell_uv | cell_uv | +| 4 | charge_oc | charge_oc | +| 5 | discharge_oc | discharge_oc | +| 6 | temp_anomaly | temp_anomaly | +| 7 | mos_ot | mos_ot | +| 8 | charge_ot | charge_ot | +| 9 | discharge_ot | discharge_ot | +| 10 | charge_ut | charge_ut | +| 11 | discharge_ut | discharge_ut | +| 12 | low_capacity | float_stopped | +| 13 | other_error | discharge_sc | + +Each bit becomes an HA sensor reporting `on` / `off`. Exact bit ordering is guessed from UI display order — adjust if the EG4 tool ever shows a flag we don't match. + +## Hardware topology notes + +### Critical: RS485 port only works when the pack is standalone + +**Empirical finding** (2026-04-24): the LP4V2's external `RS485` port +only answers Modbus queries when the pack is **not** daisy-chained to +other packs via `Comm1`/`Comm2`. Specifically: + +- With daisy chains intact (bat1 Comm2 → bat2 Comm1 etc.), one pack + elects as master and polls slaves over the internal hub bus + (19200 Modbus on Comm1/Comm2). Slave packs' RS485 ports go silent — + only the master responds externally. +- Remove the inter-pack Comm jumpers and each pack becomes a + self-contained master: its Comm1/Comm2 LEDs flash (it's trying to + poll slaves that aren't there), and its own RS485 port becomes fully + live for external queries. + +**Implication**: `modbus_per_pack` mode requires **each pack standalone** +— one FTDI adapter per pack's RS485 port, no inter-pack Comm jumpers. +This is how we got bat1 responding cleanly. If the batteries later +need to be daisy-chained to an inverter, only the master pack's RS485 +port will answer external queries; slave per-pack data would need to +come from decoding the Comm1/Comm2 hub bus instead (a future mode). + +### Port roles on the LP4V2 pack (from the port matrix test) + +| Port | Role | Pins carrying signal | Protocol / baud | +|-------|--------------------------------|-------------------------|-----------------------| +| CAN | Inverter CAN comms | (CAN-specific pinout) | CANbus (not RS-485) | +| RS485 | External monitor | 1-2 | Modbus RTU @ 9600 | +| Comm1 | Inter-pack hub bus (in/out) | 1-2 and 7-8 both tap it | Modbus RTU @ 19200 | +| Comm2 | Inter-pack hub bus (in/out) | 1-2 and 7-8 both tap it | Modbus RTU @ 19200 | + +- The **stock USB-RS485 cable** ships wired to pins 1-2 — usable on either Comm or RS485. +- The **pin 7-8 modified cable** only gains us the Comm/Comm2 hub-bus tap; since pins 1-2 reach the same bus, it's not strictly necessary. Kept in the toolkit for diagnostic purposes. +- Factory inter-pack jumpers (between packs) are 8-conductor CAT5 — they carry both pin pairs. + +### Adapters + +On this host, three USB-FTDI adapters are plugged into the three packs' RS485 ports: + +| Adapter ID | Pack | `/dev/serial/by-id/...` | +|------------------|----------------|--------------------------------------------------------| +| A994XMVK | bat1 (RS485) | `usb-FTDI_FT232R_USB_UART_A994XMVK-if00-port0` | +| A994XGUY | bat2 (RS485) | `usb-FTDI_FT232R_USB_UART_A994XGUY-if00-port0` | +| A994XMBR | bat3 (RS485) | `usb-FTDI_FT232R_USB_UART_A994XMBR-if00-port0` | + +Each pack gets polled on its own bus → no shared-bus arbitration, no master/slave coordination needed, pack Modbus address is 0x40 for all of them. + +## LVX6048 compatibility (still true) + +LVX6048 BMS port protocols: `PYL` (Pylontech), `LIb` (MPP LIO), `WEC` (WECO), `SOL` (Soltaro), `VSC` (Pylontech-CAN), `USE` (voltage-only). **No native EG4 LP4V2 support.** For inverter↔battery comms, set `P05/P14 = USE` and manage charge profile via `lvx-flash`. See DIY Solar Forum threads 67496 & 96019, LVX6048WP manual §9-2. + +## Bring-up checklist (when a new pack goes live) + +1. Wire: plug USB-FTDI adapter (stock pin-1-2 cable) into the pack's **RS485** port. +2. Confirm the pack's BMS is powered (LEDs steady on Comm1 + Comm2, not dark). +3. Verify the `/dev/serial/by-id/...` symlink exists for the adapter. +4. Add a pack entry to the config: + ```yaml + packs: + - name: lifepower4_N + address: 0x40 + port: /dev/serial/by-id/usb-FTDI_...-if00-port0 + baud: 9600 + ``` +5. `sudo systemctl restart eg4-battery.service`. Watch journal — within ~10 s you should see the first MQTT publish, or `WARNING: no/bad response` if the pack isn't answering. +6. In HA: `EG4 LifePower4 lifepower4_N` device appears with ~65 auto-discovered entities. + +## Sources / references + +- `lv_host.app` (Qt) — Contents/MacOS/lv_host Mach-O binary + my1.db/my2.db SQLite schemas +- `../battery/eg4_lifepower.py` — V1 7E/0D decoder (Louisvdw/dbus-serialbattery port), historical reference +- `../battery/sweep.py` — protocol + baud scanner used for initial triage +- EG4 "Cables Needed for Updating" PDF +- EG4 Community Forum: "Specs for LifePower4 V2 BAT-COM ports" +- LVX6048WP manual §9-2 (BMS pinout), §Programs P03/P05 diff --git a/eg4battery/README.md b/eg4battery/README.md new file mode 100644 index 0000000..df64eaa --- /dev/null +++ b/eg4battery/README.md @@ -0,0 +1,95 @@ +# EG4 LifePower4 v2 → Home Assistant + +Daemon that polls EG4 LifePower4 48V 100Ah v2 (Auto-Addressing) packs over +RS-485 and publishes per-pack telemetry to MQTT with HA auto-discovery. + +## Status: live + +As of 2026-04-24, `bat1` is live via `modbus_per_pack` mode on its RS485 port, +reporting all ~65 entities into HA: + +``` +lifepower4_1_pack_voltage 52.56 V (16 cells × 3.285 V) +lifepower4_1_cell_01_voltage 3.285 V +lifepower4_1_cell_16_voltage 3.285 V +lifepower4_1_cell_voltage_delta_mv 2 (outstanding balance) +lifepower4_1_soc 100 % +lifepower4_1_capacity_ah 100.0 Ah +lifepower4_1_temperature_01 21 °C +lifepower4_1_temperature_pcb 55 °C +... plus 14 warning bits, 14 protection bits, all 47 raw registers +``` + +`bat2` and `bat3` are wired but unpowered — the daemon logs one warning per +unreachable pack per startup and keeps retrying silently. They'll come online +automatically when the user powers them up. + +## Modes + +Set by `bus.mode` in `~/.config/eg4-battery/eg4-battery.yaml`: + +| Mode | When to use | +|-------------------|---------------------------------------------------------| +| `modbus_per_pack` | **Default.** One FTDI per pack's RS485 port. Fully decoded HA entities. | +| `active` | Legacy 7E/0D (V1 firmware only). Not used on V2 hardware. | +| `passive` | Listen-only Modbus sniff (19200). Diagnostic use. | + +See [`NOTES.md`](./NOTES.md) for architecture, register map, LVX6048 +compatibility findings, and bring-up checklist. + +## What's in the box + +``` +eg4battery/ +├── README.md ← start here +├── Install.md ← detailed walkthrough + mode-switch howto +├── NOTES.md ← architecture, register map, port matrix +├── install.sh ← idempotent installer (supports --dry-run) +│ +├── bin/eg4-battery ← single-file daemon (uv PEP-723 inline deps) +│ +├── config/eg4-battery.yaml.example ← template, multiple-pack config +│ +├── etc/ mirror of target paths (Pi side) +│ ├── udev/rules.d/99-eg4-rs485.rules +│ └── systemd/system/eg4-battery.service +│ +├── homeassistant/ ← drop into your HA config dir +│ ├── README.md (what goes where + retention tiers) +│ ├── recorder.yaml (exclude noisy / diagnostic entities) +│ ├── template_sensors.yaml (derived: power, temp_max, cell_imbalance, stack rollups) +│ └── lovelace_overview.yaml (3-pack stack dashboard) +│ +└── tmp/ ad-hoc diagnostics + ├── port-probe (single-cycle 9600/19200/7E probe) + ├── eg4-snapshot (47-reg dump for BMS Tool cross-check) + └── bms-tool-ref/ (unpacked vendor BMS Tool for RE reference) +``` + +## Quick start on a fresh host + +```bash +cd ~/solar/eg4battery +./install.sh --dry-run # mock cycle, prints MQTT payloads, exits + +# Edit ~/.config/eg4-battery/eg4-battery.yaml: +# - For modbus_per_pack: one 'packs:' entry per pack, each with port + address + baud +# - mqtt.host / username / password + +./install.sh # real deploy; auto-starts once creds are filled +journalctl -u eg4-battery.service -f +``` + +## Related packages + +- [`../LVX6048/`](../LVX6048/) — inverter-side monitoring via PI18 over USB-HID. + Same MQTT broker. Between the two packages, HA sees every useful number from + the stack. + +## Acknowledgements + +- `battery/eg4_lifepower.py` — V1 protocol decoder adapted from + [`Louisvdw/dbus-serialbattery`](https://github.com/Louisvdw/dbus-serialbattery). + Historical; V2 firmware moved to Modbus on a different port. +- EG4 Electronics `lv_host.app` — the vendor BMS Tool; its Qt binary's SQLite + schema and strings gave us the register-to-field mapping. diff --git a/eg4battery/bin/eg4-battery b/eg4battery/bin/eg4-battery new file mode 100755 index 0000000..bee3160 --- /dev/null +++ b/eg4battery/bin/eg4-battery @@ -0,0 +1,969 @@ +#!/usr/bin/env -S uv run --script +# /// script +# requires-python = ">=3.11" +# dependencies = [ +# "pyserial>=3.5", +# "paho-mqtt>=2.0", +# "pyyaml>=6.0", +# ] +# /// +""" +eg4-battery — telemetry bridge from EG4 LifePower4 v2 BMSes to MQTT/HA. + +Three modes, selected via `bus.mode` in the config: + + modbus_per_pack — RECOMMENDED. One FTDI RS-485 adapter per pack. Each pack + has its own (port, address, baud) in the `packs:` list. + Uses Modbus RTU fn=0x03 read-47-regs at 0x0000. Decoder + extracts named fields (pack V, 16 cell voltages, temps, + SoC, SoH, Capacity, warnings, protections) — register + map reverse-engineered from the EG4 `lv_host.app` BMS + Tool's SQLite schema + UI labels. + + active — LEGACY. Single FTDI adapter on a dedicated bus, EG4 + 7E/0D protocol at 9600 baud. Was ported from the V1 + firmware via `battery/eg4_lifepower.py`; V2 hardware + doesn't speak this protocol in practice. Kept for + reference / possible V1 deployments. + + passive — LEGACY. Listen-only Modbus-RTU sniffer at 19200 baud. + Originally targeted the LVX6048 BMS bus; LVX6048 doesn't + poll EG4 packs that way, so the mode is diagnostic only. + +Usage: + eg4-battery -C + eg4-battery -C --dry-run # mock bus, print, exit + eg4-battery -C --trace # log every frame +""" +from __future__ import annotations + +import argparse +import asyncio +import dataclasses +import json +import logging +import random +import struct +import sys +import time +from pathlib import Path +from struct import unpack_from +from typing import Any, Iterator + +import paho.mqtt.client as mqtt +import serial +import yaml + +log = logging.getLogger("eg4-battery") + + +# ============================================================================= +# === config ================================================================== +# ============================================================================= + + +@dataclasses.dataclass +class PackConfig: + name: str # HA entity prefix / device identifier (e.g. "lifepower4_1") + address: int # protocol-level address (Modbus slave ID, or EG4 7E address) + port: str | None = None # per-pack port (modbus_per_pack mode only) + baud: int | None = None # per-pack baud override (modbus_per_pack mode only) + + +@dataclasses.dataclass +class BusConfig: + mode: str # "modbus_per_pack" | "active" | "passive" + transport: str = "serial" # "serial" | "mock" + port: str = "" # shared port (active / passive modes) + baud: int = 9600 + read_chunk: int = 512 + timeout_s: float = 1.5 # per-query timeout + poll_interval_s: float = 10.0 # full round-robin cycle target + + +@dataclasses.dataclass +class MQTTConfig: + host: str + port: int + username: str + password: str + discovery_prefix: str = "homeassistant" + + +@dataclasses.dataclass +class AppConfig: + bus: BusConfig + mqtt: MQTTConfig + packs: list[PackConfig] + cell_count: int = 16 # active mode only + + +def load_config(path: Path) -> AppConfig: + raw = yaml.safe_load(path.read_text()) + return AppConfig( + bus=BusConfig(**raw["bus"]), + mqtt=MQTTConfig(**raw["mqtt"]), + packs=[PackConfig(**p) for p in raw["packs"]], + cell_count=raw.get("cell_count", 16), + ) + + +# ============================================================================= +# === active mode: EG4 7E/0D protocol ========================================= +# ============================================================================= +# Verified against `battery/eg4_lifepower.py`. Frame: +# request (6 bytes): 7E 00 0D +# chk = (0x100 - (addr + cmd + len)) & 0xFF +# reply (variable): 7E [10 groups] 0D +# each group: + + +CMD_GENERAL_STATUS = 0x01 # cells, V, I, SoC, cap, temps, cycles, alarms +CMD_FW_VER = 0x33 +CMD_HW_VER = 0x42 + + +def encode_eg4_request(address: int, cmd: int, length: int = 0) -> bytes: + chk = (0x100 - (address + cmd + length)) & 0xFF + return bytes([0x7E, address, cmd, length, chk, 0x0D]) + + +def decode_eg4_general_status(data: bytes, cell_count: int) -> dict[str, Any]: + """Decode a fn=0x01 reply into a flat dict keyed for HA. Mirrors + `battery/eg4_lifepower.py::parse_status`. Permissive framing check + (header/footer); upstream doesn't validate the reply CRC and neither + do we until we know the algorithm.""" + if not data or len(data) < 6 or data[0] != 0x7E or data[-1] != 0x0D: + raise ValueError(f"bad framing: {data.hex(' ')[:120]}") + + groups: list[list[int]] = [] + i = 4 # skip 7E + for _ in range(10): + if i + 2 > len(data): + raise ValueError(f"truncated payload at group {len(groups)}") + group_len = data[i + 1] + end = i + 2 + group_len * 2 + if end > len(data): + raise ValueError(f"group {len(groups)} overruns frame (end={end}, len={len(data)})") + payload = data[i + 2:end] + groups.append([unpack_from(">H", payload, k)[0] for k in range(0, len(payload), 2)]) + i = end + + out: dict[str, Any] = {} + + # group 0 — cell voltages (mV; mask 0x7FFF per upstream — top bit is some flag) + cells = [(v & 0x7FFF) / 1000.0 for v in groups[0][:cell_count]] + for idx, cv in enumerate(cells, start=1): + out[f"cell_{idx:02d}_voltage"] = round(cv, 3) + if cells: + vmin, vmax = min(cells), max(cells) + out["cell_voltage_min"] = round(vmin, 3) + out["cell_voltage_max"] = round(vmax, 3) + out["cell_voltage_delta_mv"] = round((vmax - vmin) * 1000) + out["cell_lowest"] = cells.index(vmin) + 1 + out["cell_highest"] = cells.index(vmax) + 1 + + # group 1 — current (signed; encoded as 30000 - A×100; positive = charge) + if groups[1]: + out["current"] = round((30000 - groups[1][0]) / 100.0, 2) + # group 2 — SoC × 100 + if groups[2]: + out["soc"] = round(groups[2][0] / 100.0, 1) + # group 3 — capacity (Ah × 100) + if groups[3]: + out["capacity_ah"] = round(groups[3][0] / 100.0, 2) + # group 4 — temperatures (low byte − 50 °C) + for idx, raw in enumerate(groups[4][:6], start=1): + out[f"temperature_{idx}"] = (raw & 0xFF) - 50 + # group 5 — alarm bitfield (second word per upstream) + flags = groups[5][1] if len(groups[5]) > 1 else 0 + out["alarm_current_over"] = "on" if flags & 0b00001000 else "off" + out["alarm_voltage_high"] = "on" if flags & 0b00010000 else "off" + out["alarm_voltage_low"] = "on" if flags & 0b00100000 else "off" + out["alarm_temp_high_chg"] = "on" if flags & 0b01000000 else "off" + out["alarm_temp_low_chg"] = "on" if flags & 0b10000000 else "off" + # group 6 — cycle count + if groups[6]: + out["cycle_count"] = groups[6][0] + # group 7 — pack voltage (V × 100) + if groups[7]: + out["pack_voltage"] = round(groups[7][0] / 100.0, 2) + # groups 8-9 — undecoded; leave as future work + return out + + +# ============================================================================= +# === passive mode: Modbus RTU framing ======================================== +# ============================================================================= + + +def crc16_modbus(data: bytes) -> int: + crc = 0xFFFF + for b in data: + crc ^= b + for _ in range(8): + if crc & 1: + crc = (crc >> 1) ^ 0xA001 + else: + crc >>= 1 + return crc + + +def _crc_ok(buf: bytes, start: int, length: int) -> bool: + if start + length > len(buf): + return False + body = buf[start:start + length - 2] + expected = buf[start + length - 2] | (buf[start + length - 1] << 8) + return crc16_modbus(body) == expected + + +_MODBUS_FUNCS = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x0F, 0x10, 0x16, 0x17} + + +def parse_modbus_frame_at(buf: bytes, start: int) -> tuple[int, str] | None: + if start + 4 > len(buf): + return None + func = buf[start + 1] + # exception response (5 bytes), only for legitimate function codes + if func >= 0x80 and (func & 0x7F) in _MODBUS_FUNCS \ + and start + 5 <= len(buf) and 1 <= buf[start + 2] <= 11 \ + and _crc_ok(buf, start, 5): + return (5, "exception") + if func == 0x03: + # query: 8 bytes + if _crc_ok(buf, start, 8): + return (8, "query") + # response: 1 + 1 + 1 + byte_count + 2 + if start + 3 <= len(buf): + byte_count = buf[start + 2] + if 2 <= byte_count <= 250 and byte_count % 2 == 0: + total = 3 + byte_count + 2 + if _crc_ok(buf, start, total): + return (total, "response") + return None + + +@dataclasses.dataclass +class ModbusFrame: + address: int + function: int + kind: str + raw: bytes + + @property + def registers(self) -> list[int]: + if self.kind != "response" or self.function != 0x03: + return [] + bc = self.raw[2] + d = self.raw[3:3 + bc] + return [(d[i] << 8) | d[i + 1] for i in range(0, len(d), 2)] + + +def decode_modbus_response(frame: ModbusFrame) -> dict[str, Any]: + """Raw-register dump; promote to named fields once we know the layout.""" + return {f"register_{i:02d}": v for i, v in enumerate(frame.registers)} + + +# ---- modbus_per_pack active-poll decoder (EG4 LP4V2) ----------------------- +# Register map derived from lv_host.app BMS Tool SQLite schema + UI labels + +# live probing of a single pack. See ../NOTES.md "Register map" section. +# High-confidence fields promoted to named entities; unknowns (reg 32, 35, +# 38-40, 43-45) still emitted as register_NN for correlation. + +_WARNING_BITS = [ + "pack_ov", "cell_ov", "pack_uv", "cell_uv", + "charge_oc", "discharge_oc", "temp_anomaly", "mos_ot", + "charge_ot", "discharge_ot", "charge_ut", "discharge_ut", + "low_capacity", "other_error", +] +_PROTECTION_BITS = [ + "pack_ov", "cell_ov", "pack_uv", "cell_uv", + "charge_oc", "discharge_oc", "temp_anomaly", "mos_ot", + "charge_ot", "discharge_ot", "charge_ut", "discharge_ut", + "float_stopped", "discharge_sc", +] + + +def _signed16(v: int) -> int: + return v - 0x10000 if v & 0x8000 else v + + +def decode_eg4_modbus_regs(regs: list[int]) -> dict[str, Any]: + """Decode the 47-reg read-holding-regs response from an LP4V2 BMS. + Emits named HA entities where meaning is known; raw register_NN + passthrough for the rest.""" + out: dict[str, Any] = {} + # always emit raw registers — invaluable for future refinement + for i, v in enumerate(regs): + out[f"register_{i:02d}"] = v + + if len(regs) < 47: + return out + + # --- pack-level V / I (regs 0, 1) --- + out["pack_voltage"] = round(regs[0] / 100.0, 2) + out["pack_current"] = round(_signed16(regs[1]) / 100.0, 2) + + # --- 16 cell voltages (regs 2-17), mV --- + cells_v = [regs[2 + i] / 1000.0 for i in range(16)] + for i, cv in enumerate(cells_v, start=1): + out[f"cell_{i:02d}_voltage"] = round(cv, 3) + vmin, vmax = min(cells_v), max(cells_v) + out["cell_voltage_min"] = round(vmin, 3) + out["cell_voltage_max"] = round(vmax, 3) + out["cell_voltage_delta_mv"] = round((vmax - vmin) * 1000) + out["cell_lowest"] = cells_v.index(vmin) + 1 + out["cell_highest"] = cells_v.index(vmax) + 1 + + # --- temperatures (regs 18-21 = Temp_01..04, reg 24 = Temp_PCB) --- + out["temperature_01"] = regs[18] + out["temperature_02"] = regs[19] + out["temperature_03"] = regs[20] + out["temperature_04"] = regs[21] + out["temperature_pcb"] = regs[24] + + # --- SoC / SoH (regs 22, 23) --- + out["soc"] = regs[22] + out["soh"] = regs[23] + + # --- heater / status (regs 25-30) --- + # reg 30 has been observed = 1 on a healthy pack; treat as binary + out["heater"] = "on" if regs[30] & 0x01 else "off" + + # --- max charge/discharge current limit (reg 31), A --- + out["max_current_limit"] = round(regs[31] / 100.0, 2) + + # --- bitfields: warnings (reg 33), protections (reg 34), error code (reg 35) --- + warn = regs[33] + for i, name in enumerate(_WARNING_BITS): + out[f"warning_{name}"] = "on" if (warn >> i) & 1 else "off" + prot = regs[34] + for i, name in enumerate(_PROTECTION_BITS): + out[f"protection_{name}"] = "on" if (prot >> i) & 1 else "off" + out["error_code"] = regs[35] + + # --- static-ish (regs 36, 37) --- + out["cell_count"] = regs[36] + out["capacity_ah"] = round(regs[37] / 10.0, 1) + out["remaining_ah"] = round(regs[38] / 100.0, 2) + out["cycle_count"] = regs[39] + out["battery_mode"] = regs[40] + + # BMS firmware version — regs 41 & 42 appear to hold version codes; emit + # the raw u16s alongside a decimal representation for easier HA display + out["bms_version_hi"] = regs[41] + out["bms_version_lo"] = regs[42] + + # reg 46 increments ~1.25 Hz on live bus — likely uptime in deciseconds + out["uptime_ds"] = regs[46] + return out + + +class ModbusActivePoller: + """One instance per pack. Opens its own serial port, issues a single + read-holding-regs fn=0x03 on every `poll()` call, returns raw registers + (or raises). Graceful: a pack whose port doesn't exist or whose BMS is + off will raise on poll, and main loop catches + rate-limits the noise.""" + + READ_START = 0x0000 + READ_COUNT = 47 + + def __init__(self, port: str, baud: int, address: int, timeout_s: float = 1.0): + self._port_path = port + self._baud = baud + self._address = address + self._timeout_s = timeout_s + self._ser: serial.Serial | None = None + + def _open(self) -> None: + if self._ser is None or not self._ser.is_open: + self._ser = serial.Serial( + port=self._port_path, baudrate=self._baud, timeout=0.2, + bytesize=8, parity="N", stopbits=1, + ) + + def poll(self) -> list[int]: + self._open() + body = bytes([self._address, 0x03, + self.READ_START >> 8, self.READ_START & 0xFF, + self.READ_COUNT >> 8, self.READ_COUNT & 0xFF]) + crc = crc16_modbus(body) + frame = body + bytes([crc & 0xFF, crc >> 8]) + + assert self._ser is not None + self._ser.reset_input_buffer() + self._ser.write(frame) + + expected = 3 + self.READ_COUNT * 2 + 2 # addr + func + bc + data + crc + buf = bytearray() + deadline = time.monotonic() + self._timeout_s + while time.monotonic() < deadline and len(buf) < expected: + chunk = self._ser.read(expected - len(buf)) + if chunk: + buf.extend(chunk) + raw = bytes(buf) + log.debug("pack 0x%02x tx=%s rx=%s", self._address, frame.hex(" "), raw.hex(" ")) + + if len(raw) < 5 or raw[0] != self._address or raw[1] != 0x03: + raise RuntimeError(f"no/bad response ({len(raw)} B)") + bc = raw[2] + if len(raw) < 3 + bc + 2: + raise RuntimeError(f"truncated response ({len(raw)} B, expected {3 + bc + 2})") + if not _crc_ok(raw, 0, 3 + bc + 2): + raise RuntimeError("CRC mismatch") + data = raw[3:3 + bc] + return [(data[i] << 8) | data[i + 1] for i in range(0, len(data), 2)] + + def close(self) -> None: + if self._ser is not None and self._ser.is_open: + self._ser.close() + + +# ============================================================================= +# === transports ============================================================== +# ============================================================================= +# Two abstractions; main loop picks the right one based on bus.mode. + + +class ActiveTransport: + """Request-response transport for active mode.""" + + def query_general(self, address: int) -> bytes: + raise NotImplementedError + + def close(self) -> None: + pass + + +class PassiveListener: + """Continuous frame-iterator for passive mode.""" + + def frames(self) -> Iterator[ModbusFrame]: + raise NotImplementedError + + def close(self) -> None: + pass + + +# --- active: serial + mock -------------------------------------------------- + + +class SerialActiveTransport(ActiveTransport): + def __init__(self, port: str, baud: int, timeout_s: float): + self._timeout_s = timeout_s + self._ser = serial.Serial(port=port, baudrate=baud, timeout=0.25, + bytesize=8, parity="N", stopbits=1) + + def query_general(self, address: int) -> bytes: + frame = encode_eg4_request(address, CMD_GENERAL_STATUS) + log.debug("TX addr=0x%02x: %s", address, frame.hex()) + self._ser.reset_input_buffer() + self._ser.write(frame) + buf = bytearray() + deadline = time.monotonic() + self._timeout_s + while time.monotonic() < deadline: + chunk = self._ser.read(256) + if chunk: + buf.extend(chunk) + if buf[0:1] == b"\x7E" and buf.endswith(b"\x0D"): + break + log.debug("RX addr=0x%02x: %s", address, bytes(buf).hex()) + return bytes(buf) + + def close(self) -> None: + self._ser.close() + + +class MockActiveTransport(ActiveTransport): + """Synthesise EG4 7E/0D replies. Values drift per call so HA dashboards + look alive in dry-run mode.""" + + def __init__(self, cell_count: int = 16): + self._cell_count = cell_count + self._call = 0 + + def query_general(self, address: int) -> bytes: + self._call += 1 + rng = random.Random(address * 1000 + self._call) + base_mv = 3280 + rng.randint(-5, 5) + cells_mv = [max(0, min(0x7FFF, base_mv + rng.randint(-8, 8))) + for _ in range(self._cell_count)] + current_x100 = rng.randint(-500, 2000) + current_raw = 30000 - current_x100 + soc_x100 = (50 + rng.randint(-2, 2)) * 100 + cap_ah_x100 = 5000 + rng.randint(-10, 10) + temps_raw = [50 + 25 + rng.randint(-3, 3) for _ in range(4)] + cycles = 42 + address + pack_v_x100 = round(sum(cells_mv) / 10) + + def grp(gid: int, values: list[int]) -> bytes: + return bytes([gid, len(values)]) + b"".join( + struct.pack(">H", v & 0xFFFF) for v in values + ) + + body = b"".join([ + grp(0x01, cells_mv), + grp(0x02, [current_raw]), + grp(0x03, [soc_x100]), + grp(0x04, [cap_ah_x100]), + grp(0x05, temps_raw), + grp(0x06, [0, 0]), # alarms = clear + grp(0x07, [cycles]), + grp(0x08, [pack_v_x100]), + grp(0x09, []), + grp(0x0A, []), + ]) + # checksum byte tolerated as 0x00 by the upstream parser + return bytes([0x7E, address, CMD_GENERAL_STATUS, len(body) & 0xFF]) \ + + body + bytes([0x00, 0x0D]) + + +# --- passive: serial + mock ------------------------------------------------- + + +class SerialPassiveListener(PassiveListener): + _BUF_MAX = 4096 + + def __init__(self, port: str, baud: int, read_chunk: int = 512): + self._read_chunk = read_chunk + self._ser = serial.Serial(port=port, baudrate=baud, timeout=0.1, + bytesize=8, parity="N", stopbits=1) + self._buf = bytearray() + + def frames(self) -> Iterator[ModbusFrame]: + while True: + chunk = self._ser.read(self._read_chunk) + if chunk: + self._buf.extend(chunk) + if len(self._buf) > self._BUF_MAX: + del self._buf[:self._BUF_MAX // 2] + yield from self._extract() + + def _extract(self) -> Iterator[ModbusFrame]: + i = 0 + while i < len(self._buf) - 4: + r = parse_modbus_frame_at(self._buf, i) + if r is None: + i += 1 + continue + length, kind = r + raw = bytes(self._buf[i:i + length]) + yield ModbusFrame(address=raw[0], function=raw[1], kind=kind, raw=raw) + del self._buf[:i + length] + i = 0 + + def close(self) -> None: + self._ser.close() + + +class MockPassiveListener(PassiveListener): + def __init__(self, packs: list[PackConfig], gap_s: float = 0.5): + self._packs = packs + self._gap_s = gap_s + self._tick = 0 + + def frames(self) -> Iterator[ModbusFrame]: + while True: + for pack in self._packs: + self._tick += 1 + q = self._build_query(pack.address) + yield ModbusFrame(address=pack.address, function=0x03, kind="query", raw=q) + time.sleep(0.05) + r = self._build_response(pack.address) + yield ModbusFrame(address=pack.address, function=0x03, kind="response", raw=r) + time.sleep(self._gap_s) + + def _build_query(self, addr: int) -> bytes: + body = bytes([addr, 0x03, 0x00, 0x00, 0x00, 0x2F]) + crc = crc16_modbus(body) + return body + bytes([crc & 0xFF, crc >> 8]) + + def _build_response(self, addr: int) -> bytes: + rng = random.Random(addr * 1000 + self._tick) + regs = [3280 + rng.randint(-5, 5) for _ in range(16)] + regs += [round(52.48 * 100), 50_00, rng.randint(0, 100)] + while len(regs) < 47: + regs.append(rng.randint(0, 100)) + body = bytes([addr, 0x03, len(regs) * 2]) + b"".join( + struct.pack(">H", r & 0xFFFF) for r in regs + ) + crc = crc16_modbus(body) + return body + bytes([crc & 0xFF, crc >> 8]) + + +# ============================================================================= +# === MQTT publisher (HA auto-discovery) ====================================== +# ============================================================================= + + +# Field metadata. Active and passive modes emit different keys; both sets +# coexist here without overlap. +_FIELD_META: dict[str, tuple[str | None, str | None, str | None, str | None]] = { + # active mode (EG4 7E/0D decoded) + "pack_voltage": ("V", "voltage", "measurement", "mdi:battery-outline"), + "current": ("A", "current", "measurement", "mdi:current-dc"), + "soc": ("%", "battery", "measurement", "mdi:battery-70"), + "capacity_ah": ("Ah", None, "measurement", "mdi:battery-clock"), + "cycle_count": (None, None, "total", "mdi:counter"), + "cell_voltage_min": ("V", "voltage", "measurement", "mdi:arrow-down-bold"), + "cell_voltage_max": ("V", "voltage", "measurement", "mdi:arrow-up-bold"), + "cell_voltage_delta_mv": ("mV", None, "measurement", "mdi:sine-wave"), + "cell_lowest": (None, None, "measurement", "mdi:numeric"), + "cell_highest": (None, None, "measurement", "mdi:numeric"), + "alarm_current_over": (None, None, None, "mdi:alert-octagon"), + "alarm_voltage_high": (None, None, None, "mdi:alert"), + "alarm_voltage_low": (None, None, None, "mdi:alert"), + "alarm_temp_high_chg": (None, None, None, "mdi:thermometer-alert"), + "alarm_temp_low_chg": (None, None, None, "mdi:thermometer-alert"), +} +for _i in range(1, 33): + _FIELD_META[f"cell_{_i:02d}_voltage"] = ("V", "voltage", "measurement", "mdi:battery-outline") +for _i in range(1, 7): + _FIELD_META[f"temperature_{_i}"] = ("°C", "temperature", "measurement", "mdi:thermometer") +# modbus_per_pack named fields (EG4 register map) +_FIELD_META.update({ + "pack_current": ("A", "current", "measurement", "mdi:current-dc"), + "temperature_01": ("°C", "temperature", "measurement", "mdi:thermometer"), + "temperature_02": ("°C", "temperature", "measurement", "mdi:thermometer"), + "temperature_03": ("°C", "temperature", "measurement", "mdi:thermometer"), + "temperature_04": ("°C", "temperature", "measurement", "mdi:thermometer"), + "temperature_pcb": ("°C", "temperature", "measurement", "mdi:chip"), + "heater": (None, None, None, "mdi:heating-coil"), + "max_current_limit": ("A", "current", "measurement", "mdi:current-dc"), + "error_code": (None, None, None, "mdi:alert-octagon"), + "cell_count": (None, None, "measurement", "mdi:numeric"), + "remaining_ah": ("Ah", None, "measurement", "mdi:battery-clock"), + "battery_mode": (None, None, None, "mdi:state-machine"), + "bms_version_hi": (None, None, None, "mdi:chip"), + "bms_version_lo": (None, None, None, "mdi:chip"), + "uptime_ds": (None, None, "total_increasing", "mdi:timer-outline"), +}) +for _name in _WARNING_BITS: + _FIELD_META[f"warning_{_name}"] = (None, None, None, "mdi:alert") +for _name in _PROTECTION_BITS: + _FIELD_META[f"protection_{_name}"] = (None, None, None, "mdi:shield-alert") + + +def field_meta(key: str) -> tuple[str | None, str | None, str | None, str | None]: + if key.startswith("register_"): + return (None, None, "measurement", "mdi:numeric") + return _FIELD_META.get(key, (None, None, None, None)) + + +class MQTTPublisher: + def __init__(self, cfg: MQTTConfig, dry_run: bool = False): + self._cfg = cfg + self._dry_run = dry_run + self._client: mqtt.Client | None = None + self._discovered: set[tuple[str, str]] = set() + if not dry_run: + c = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id="eg4-battery") + c.username_pw_set(cfg.username, cfg.password) + c.connect(cfg.host, cfg.port, keepalive=60) + c.loop_start() + self._client = c + log.info("connected to MQTT %s:%d", cfg.host, cfg.port) + + def publish_pack(self, pack_name: str, readings: dict[str, Any]) -> None: + for key, value in readings.items(): + self._publish_one(pack_name, key, value) + + def _publish_one(self, pack_name: str, key: str, value: Any) -> None: + entity_id = f"{pack_name}_{key}" + state_topic = f"{self._cfg.discovery_prefix}/sensor/{entity_id}/state" + disco_key = (pack_name, key) + if disco_key not in self._discovered: + self._publish_discovery(pack_name, key, state_topic) + self._discovered.add(disco_key) + payload = json.dumps(value) if isinstance(value, (dict, list)) else str(value) + if self._dry_run: + print(f" {state_topic} {payload}") + else: + self._client.publish(state_topic, payload, qos=0, retain=False) + + def _publish_discovery(self, pack_name: str, key: str, state_topic: str) -> None: + unit, device_class, state_class, icon = field_meta(key) + cfg = { + "name": f"{pack_name} {key}", + "state_topic": state_topic, + "unique_id": f"{pack_name}_{key}_eg4", + "device": { + "name": f"EG4 LifePower4 {pack_name}", + "identifiers": [pack_name], + "model": "LifePower4 48V 100Ah v2 Auto-Addressing", + "manufacturer": "EG4 Electronics", + }, + } + if unit is not None: cfg["unit_of_measurement"] = unit + if device_class is not None: cfg["device_class"] = device_class + if state_class is not None: cfg["state_class"] = state_class + if icon is not None: cfg["icon"] = icon + topic = f"{self._cfg.discovery_prefix}/sensor/{pack_name}_{key}/config" + payload = json.dumps(cfg) + if self._dry_run: + print(f" [discovery] {topic} {payload}") + else: + self._client.publish(topic, payload, qos=0, retain=True) + + def close(self) -> None: + if self._client is not None: + self._client.loop_stop() + self._client.disconnect() + + +# ============================================================================= +# === per-pack state & rate-limited logging =================================== +# ============================================================================= + + +@dataclasses.dataclass +class _PackState: + ok: bool = False + last_error_category: str = "" + consecutive_errors: int = 0 + response_count: int = 0 + first_seen_logged: bool = False + + +_FAIL_HEARTBEAT_CYCLES = 360 # re-log a stuck failure every ~hour at 10 s cadence + + +def _resolve_pack_name(addr: int, packs: list[PackConfig]) -> str: + for p in packs: + if p.address == addr: + return p.name + return f"lifepower4_addr_{addr:02x}" + + +# ============================================================================= +# === main loops ============================================================== +# ============================================================================= + + +def run_active(transport: ActiveTransport, publisher: MQTTPublisher, cfg: AppConfig, + states: dict[str, _PackState], one_cycle: bool = False) -> None: + """Round-robin poll every configured pack; rate-limit error noise.""" + while True: + cycle_start = time.monotonic() + for pack in cfg.packs: + st = states.setdefault(pack.name, _PackState()) + try: + raw = transport.query_general(pack.address) + if not raw: + raise RuntimeError(f"empty response from addr=0x{pack.address:02x}") + readings = decode_eg4_general_status(raw, cell_count=cfg.cell_count) + publisher.publish_pack(pack.name, readings) + st.response_count += 1 + if not st.ok and st.consecutive_errors > 0: + log.info("pack %s (0x%02x): recovered after %d failed cycle(s)", + pack.name, pack.address, st.consecutive_errors) + st.ok = True + st.consecutive_errors = 0 + except Exception as e: + category = f"{type(e).__name__}:{str(e).split(':', 1)[0]}" + if st.ok or category != st.last_error_category: + log.warning("pack %s (0x%02x): %s", pack.name, pack.address, e) + elif st.consecutive_errors > 0 and st.consecutive_errors % _FAIL_HEARTBEAT_CYCLES == 0: + log.warning("pack %s (0x%02x): still failing (%d cycles): %s", + pack.name, pack.address, st.consecutive_errors, e) + st.ok = False + st.last_error_category = category + st.consecutive_errors += 1 + if one_cycle: + return + elapsed = time.monotonic() - cycle_start + time.sleep(max(0.0, cfg.bus.poll_interval_s - elapsed)) + + +def run_passive(listener: PassiveListener, publisher: MQTTPublisher, cfg: AppConfig, + trace: bool, max_frames: int | None = None) -> None: + """Consume frames as they arrive; publish on every fn=0x03 response.""" + states: dict[int, _PackState] = {} + seen_unconfigured: set[int] = set() + configured = {p.address for p in cfg.packs} + n = 0 + for frame in listener.frames(): + n += 1 + if trace: + log.debug("%r raw=%s", frame, frame.raw.hex(" ")) + if frame.kind != "response" or frame.function != 0x03: + if max_frames is not None and n >= max_frames: + return + continue + + st = states.setdefault(frame.address, _PackState()) + st.response_count += 1 + if not st.first_seen_logged: + if frame.address in configured: + log.info("first response from configured pack 0x%02x (%s)", + frame.address, _resolve_pack_name(frame.address, cfg.packs)) + elif frame.address not in seen_unconfigured: + log.warning("response from unconfigured slave 0x%02x — auto-naming as %s", + frame.address, _resolve_pack_name(frame.address, cfg.packs)) + seen_unconfigured.add(frame.address) + st.first_seen_logged = True + try: + readings = decode_modbus_response(frame) + except Exception as e: + log.warning("decode failed for addr 0x%02x: %s (raw=%s)", + frame.address, e, frame.raw.hex(" ")) + continue + publisher.publish_pack(_resolve_pack_name(frame.address, cfg.packs), readings) + if max_frames is not None and n >= max_frames: + return + + +def run_modbus_per_pack(cfg: AppConfig, publisher: MQTTPublisher, + states: dict[str, _PackState], one_cycle: bool = False, + dry_run: bool = False) -> None: + """One adapter per pack. Each `PackConfig` must have `port` and `baud` + set. Round-robin poll every pack on its own serial port; decode + Modbus response into named HA entities + raw register_NN dump.""" + pollers: dict[str, ModbusActivePoller] = {} + mock_regs_call: dict[str, int] = {} + + def make_poller(p: PackConfig) -> ModbusActivePoller | None: + if dry_run: + return None # mock path, no real poller + if not p.port: + log.warning("pack %s: no `port` set in config; skipping", p.name) + return None + baud = p.baud or cfg.bus.baud + try: + return ModbusActivePoller(p.port, baud, p.address, cfg.bus.timeout_s) + except Exception as e: + log.warning("pack %s: could not open %s: %s", p.name, p.port, e) + return None + + for p in cfg.packs: + pl = make_poller(p) + if pl is not None: + pollers[p.name] = pl + + try: + while True: + cycle_start = time.monotonic() + for p in cfg.packs: + st = states.setdefault(p.name, _PackState()) + try: + if dry_run: + mock_regs_call[p.name] = mock_regs_call.get(p.name, 0) + 1 + regs = _mock_modbus_regs(p.address, mock_regs_call[p.name]) + else: + if p.name not in pollers: + raise RuntimeError(f"no poller configured for {p.name}") + regs = pollers[p.name].poll() + readings = decode_eg4_modbus_regs(regs) + publisher.publish_pack(p.name, readings) + st.response_count += 1 + if not st.ok and st.consecutive_errors > 0: + log.info("pack %s: recovered after %d failed cycle(s)", + p.name, st.consecutive_errors) + st.ok = True + st.consecutive_errors = 0 + except Exception as e: + category = f"{type(e).__name__}:{str(e).split(':', 1)[0]}" + if st.ok or category != st.last_error_category: + log.warning("pack %s (0x%02x): %s", p.name, p.address, e) + elif st.consecutive_errors > 0 \ + and st.consecutive_errors % _FAIL_HEARTBEAT_CYCLES == 0: + log.warning("pack %s (0x%02x): still failing (%d cycles): %s", + p.name, p.address, st.consecutive_errors, e) + st.ok = False + st.last_error_category = category + st.consecutive_errors += 1 + if one_cycle: + return + elapsed = time.monotonic() - cycle_start + time.sleep(max(0.0, cfg.bus.poll_interval_s - elapsed)) + finally: + for pl in pollers.values(): + pl.close() + + +def _mock_modbus_regs(address: int, tick: int) -> list[int]: + """Synthesise 47 realistic-looking registers for dry-run mode.""" + rng = random.Random(address * 1000 + tick) + base_mv = 3280 + rng.randint(-3, 3) + cells_mv = [base_mv + rng.randint(-8, 8) for _ in range(16)] + regs: list[int] = [0] * 47 + regs[0] = sum(cells_mv) // 10 # pack voltage × 100 + regs[1] = (30000 - rng.randint(-500, 2000)) & 0xFFFF # current (×100 biased) + for i, mv in enumerate(cells_mv, start=2): + regs[i] = mv + regs[18] = 21 + rng.randint(-1, 1) + regs[19] = 21 + rng.randint(-1, 1) + regs[20] = 20 + rng.randint(-1, 1) + regs[21] = 54 + rng.randint(-1, 1) + regs[22] = 100 + regs[23] = 100 + regs[24] = 55 + regs[30] = 1 + regs[31] = 5493 + regs[32] = 10752 + regs[33] = 0 # no warnings + regs[34] = 0 # no protections + regs[35] = 0 # error code + regs[36] = 16 # cell count + regs[37] = 1000 # 100.0 Ah + regs[46] = (tick * 5) & 0xFFFF # runtime counter + return regs + + +def main() -> int: + ap = argparse.ArgumentParser( + description="EG4 LifePower4 v2 → MQTT bridge.") + ap.add_argument("-C", "--config", required=True, type=Path) + ap.add_argument("--dry-run", action="store_true", + help="Mock-bus smoke test — one cycle, print, exit.") + ap.add_argument("--trace", action="store_true", help="Log every frame.") + args = ap.parse_args() + + logging.basicConfig( + level=logging.DEBUG if args.trace else logging.INFO, + format="%(asctime)s %(levelname)s %(name)s: %(message)s", + ) + + cfg = load_config(args.config) + valid_modes = {"modbus_per_pack", "active", "passive"} + if cfg.bus.mode not in valid_modes: + raise SystemExit(f"bus.mode must be one of {valid_modes}, got {cfg.bus.mode!r}") + if cfg.bus.transport not in {"serial", "mock"}: + raise SystemExit(f"bus.transport must be 'serial' or 'mock', got {cfg.bus.transport!r}") + + publisher = MQTTPublisher(cfg.mqtt, dry_run=args.dry_run) + log.info("eg4-battery starting: mode=%s %d configured pack(s)", + cfg.bus.mode, len(cfg.packs)) + + use_mock = args.dry_run or cfg.bus.transport == "mock" + + try: + if cfg.bus.mode == "modbus_per_pack": + run_modbus_per_pack(cfg, publisher, states={}, + one_cycle=args.dry_run, dry_run=args.dry_run) + elif cfg.bus.mode == "active": + transport: ActiveTransport + transport = (MockActiveTransport(cell_count=cfg.cell_count) if use_mock + else SerialActiveTransport(cfg.bus.port, cfg.bus.baud, cfg.bus.timeout_s)) + try: + run_active(transport, publisher, cfg, states={}, one_cycle=args.dry_run) + finally: + transport.close() + else: # passive + listener: PassiveListener + listener = (MockPassiveListener(cfg.packs) if use_mock + else SerialPassiveListener(cfg.bus.port, cfg.bus.baud, cfg.bus.read_chunk)) + try: + run_passive(listener, publisher, cfg, trace=args.trace, + max_frames=(2 * len(cfg.packs) if args.dry_run else None)) + finally: + listener.close() + return 0 + except KeyboardInterrupt: + return 0 + finally: + publisher.close() + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/eg4battery/config/eg4-battery.yaml.example b/eg4battery/config/eg4-battery.yaml.example new file mode 100644 index 0000000..e5276b0 --- /dev/null +++ b/eg4battery/config/eg4-battery.yaml.example @@ -0,0 +1,48 @@ +# eg4-battery config — deploys to ~/.config/eg4-battery/eg4-battery.yaml (mode 600) + +bus: + # ---- mode: pick one ---- + # active ← RECOMMENDED. We are the master on a dedicated bus to the + # battery's pin-1/2 external monitor port. Speaks the EG4 + # 7E/0D protocol at 9600 baud. Returns named, decoded HA + # entities (pack V, cell voltages, SoC, temps, alarms). + # + # passive ← Listen-only Modbus-RTU sniffer at 19200 baud. Use only when + # the FTDI is on a bus that already has a Modbus master + # (e.g. the LVX6048 parallel-comm bus — although see NOTES.md: + # the LVX6048 doesn't poll EG4 packs that way, so this mode + # is mostly diagnostic). Publishes raw `register_NN` per slave. + mode: active + + transport: serial # serial | mock (--dry-run on the CLI forces mock) + + # Stable /dev/serial/by-id symlink survives USB reshuffles. Find yours with + # ls -l /dev/serial/by-id/ + port: /dev/serial/by-id/usb-FTDI_-if00-port0 + + # Default 9600 (active EG4 protocol). Set 19200 for passive Modbus mode. + baud: 9600 + + # Active mode only: + timeout_s: 1.5 # per-query response wait + poll_interval_s: 10.0 # round-robin cycle target + +mqtt: + host: # e.g. 10.0.0.41 (HA Mosquitto broker) + port: 1883 + username: + password: + discovery_prefix: homeassistant + +# One entry per pack. `name` is the HA entity prefix and device identifier. +# `address` is the EG4 7E protocol address in active mode (master = 1, slaves +# typically 2, 3, ...) or the Modbus slave ID in passive mode. +packs: + - name: lifepower4_1 + address: 1 + - name: lifepower4_2 + address: 2 + - name: lifepower4_3 + address: 3 + +cell_count: 16 # 16S for 48V LFP — active mode only diff --git a/eg4battery/etc/systemd/system/eg4-battery.service b/eg4battery/etc/systemd/system/eg4-battery.service new file mode 100644 index 0000000..b853f7e --- /dev/null +++ b/eg4battery/etc/systemd/system/eg4-battery.service @@ -0,0 +1,19 @@ +[Unit] +Description=EG4 LifePower4 RS485 -> MQTT bridge +After=network-online.target +Wants=network-online.target + +[Service] +Type=simple +User=noise +Group=dialout +# Systemd's default PATH does not include ~/.local/bin where `uv` is installed. +# The script's `#!/usr/bin/env -S uv run --script` shebang needs to find uv. +Environment=PATH=/home/noise/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin +# uv manages the ephemeral venv per the PEP-723 inline deps in the script +ExecStart=/usr/local/bin/eg4-battery -C /home/noise/.config/eg4-battery/eg4-battery.yaml +Restart=always +RestartSec=10 + +[Install] +WantedBy=multi-user.target diff --git a/eg4battery/etc/udev/rules.d/99-eg4-rs485.rules b/eg4battery/etc/udev/rules.d/99-eg4-rs485.rules new file mode 100644 index 0000000..0128b6b --- /dev/null +++ b/eg4battery/etc/udev/rules.d/99-eg4-rs485.rules @@ -0,0 +1,5 @@ +# EG4 LifePower4 RS485 bus via USB-serial adapter. +# Grants dialout group access; /dev/serial/by-id/ symlink survives USB moves. +# Adjust idVendor/idProduct if you use a CP210x (0x10c4:0xea60) or CH340 +# (0x1a86:0x7523) adapter instead of the reference FTDI FT232R. +SUBSYSTEM=="tty", ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6001", MODE="0660", GROUP="dialout" diff --git a/eg4battery/homeassistant/README.md b/eg4battery/homeassistant/README.md new file mode 100644 index 0000000..f71ede8 --- /dev/null +++ b/eg4battery/homeassistant/README.md @@ -0,0 +1,58 @@ +# HA-side configuration for the eg4-battery daemon + +Reference configs that go into your Home Assistant instance — they aren't +installed by `install.sh` (HA typically lives on a different host / in an +HA OS appliance), but they're tracked here so the full stack is reproducible. + +## What's in here + +| File | Where it goes in HA | +|---------------------------|-----------------------------------------------------| +| `recorder.yaml` | `configuration.yaml` → under a `recorder:` key, or merge into existing | +| `template_sensors.yaml` | `configuration.yaml` → under a top-level `template:` list, or include via `!include` | +| `lovelace_overview.yaml` | Raw Lovelace card config — paste into a new dashboard view | + +All of it assumes pack names `lifepower4_1`, `lifepower4_2`, `lifepower4_3` +matching the daemon's default config. If you renamed your packs, do a +`sed -i 's/lifepower4_/your_prefix_/' *.yaml` first. + +## Recommended retention tiers + +Full rationale in [`../NOTES.md`](../NOTES.md) and the architecture thread, +but the short version: + +- **Tier 1 — keep forever**: `pack_voltage`, `pack_current`, `soc`, `soh`, + `cycle_count`, `cell_voltage_min/max/delta_mv`, `capacity_ah`. +- **Tier 2 — keep short**: all 14 `warning_*` + 14 `protection_*`, + `error_code`, `remaining_ah`, `heater`, the derived `temperature_max` + and `pack_power`. +- **Tier 3 — exclude** (the `recorder.yaml` here does this): all 47 raw + `register_NN` entities, the 16 individual `cell_NN_voltage` series, + static metadata (`bms_version_*`, `battery_mode`, `cell_count`, etc.), + and the `uptime_ds` counter that increments every second. + +## Enabling in HA + +Easiest path: + +```yaml +# configuration.yaml + +# merge our recorder exclusions with your existing recorder config +recorder: !include eg4_battery/recorder.yaml + +# include the template sensors (creates a new `template:` list block) +template: !include eg4_battery/template_sensors.yaml +``` + +And drop the two YAMLs into `~/homeassistant/eg4_battery/`. + +If you already have `recorder:` or `template:` keys elsewhere, merge by +hand — HA doesn't allow two definitions of the same top-level key. + +## Energy dashboard wiring (optional) + +Once the derived `pack_power` template sensors exist, add them to the +Energy dashboard via **Settings → Dashboards → Energy → Home battery +storage** — each pack's `pack_power` integrates to `pack_energy_in_kwh` +and `pack_energy_out_kwh` automatically, with per-pack bars. diff --git a/eg4battery/homeassistant/lovelace_overview.yaml b/eg4battery/homeassistant/lovelace_overview.yaml new file mode 100644 index 0000000..49bfce9 --- /dev/null +++ b/eg4battery/homeassistant/lovelace_overview.yaml @@ -0,0 +1,116 @@ +# Lovelace card config for a 3-pack LifePower4 stack overview. +# Paste into a dashboard view's raw-config editor, or drop in as a YAML-mode +# dashboard. Assumes the template sensors in template_sensors.yaml exist. + +views: + - title: Battery Stack + icon: mdi:battery + path: batteries + cards: + # ---- stack summary row ---- + - type: horizontal-stack + cards: + - type: gauge + name: Stack SoC + entity: sensor.lifepower4_stack_soc_avg + min: 0 + max: 100 + severity: + green: 40 + yellow: 20 + red: 0 + - type: entity + name: Stack Power + entity: sensor.lifepower4_stack_pack_power_total + icon: mdi:flash + - type: entity + name: Hottest Point + entity: sensor.lifepower4_stack_temperature_max + icon: mdi:thermometer + + # ---- per-pack summary cards ---- + - type: horizontal-stack + cards: + - !include_named pack_summary_pack1.yaml + - !include_named pack_summary_pack2.yaml + - !include_named pack_summary_pack3.yaml + + # ---- pack voltage time series ---- + - type: history-graph + title: Pack voltage (24 h) + hours_to_show: 24 + entities: + - sensor.lifepower4_1_pack_voltage + - sensor.lifepower4_2_pack_voltage + - sensor.lifepower4_3_pack_voltage + + # ---- SoC + SoH trend ---- + - type: history-graph + title: SoC / SoH + hours_to_show: 168 # 1 week + entities: + - sensor.lifepower4_1_soc + - sensor.lifepower4_2_soc + - sensor.lifepower4_3_soc + - sensor.lifepower4_1_soh + - sensor.lifepower4_2_soh + - sensor.lifepower4_3_soh + + # ---- cell balance health (the crucial long-term metric) ---- + - type: history-graph + title: Cell voltage delta (mV) — rising = balance degrading + hours_to_show: 168 + entities: + - sensor.lifepower4_1_cell_voltage_delta_mv + - sensor.lifepower4_2_cell_voltage_delta_mv + - sensor.lifepower4_3_cell_voltage_delta_mv + + # ---- any active warnings/protections — glance card, visible red when on ---- + - type: entities + title: Alarms (should all be "off" on a healthy stack) + show_header_toggle: false + entities: + - type: section + label: Pack 1 + - entity: sensor.lifepower4_1_warning_cell_ov + name: Cell OV + - entity: sensor.lifepower4_1_warning_cell_uv + name: Cell UV + - entity: sensor.lifepower4_1_warning_charge_oc + name: Charge OC + - entity: sensor.lifepower4_1_warning_discharge_oc + name: Discharge OC + - entity: sensor.lifepower4_1_warning_mos_ot + name: MOSFET OT + - entity: sensor.lifepower4_1_warning_low_capacity + name: Low Capacity + - entity: sensor.lifepower4_1_protection_cell_ov + name: PROT Cell OV + - entity: sensor.lifepower4_1_protection_cell_uv + name: PROT Cell UV + # (repeat structure for pack_2 and pack_3, omitted for brevity) + +# Per-pack summary card template — save as three copies named +# pack_summary_pack1.yaml, pack_summary_pack2.yaml, pack_summary_pack3.yaml +# with only the N suffix different. +# +# type: entities +# title: Pack 1 +# show_header_toggle: false +# entities: +# - entity: sensor.lifepower4_1_pack_voltage +# name: Voltage +# - entity: sensor.lifepower4_1_pack_current +# name: Current +# - entity: sensor.lifepower4_1_pack_power +# name: Power +# - entity: sensor.lifepower4_1_soc +# name: SoC +# - entity: sensor.lifepower4_1_soh +# name: SoH +# - entity: sensor.lifepower4_1_temperature_max +# name: Hottest +# - entity: sensor.lifepower4_1_cell_voltage_delta_mv +# name: Cell Δ +# - entity: sensor.lifepower4_1_cycle_count +# name: Cycles diff --git a/eg4battery/homeassistant/recorder.yaml b/eg4battery/homeassistant/recorder.yaml new file mode 100644 index 0000000..13230dd --- /dev/null +++ b/eg4battery/homeassistant/recorder.yaml @@ -0,0 +1,51 @@ +# HA recorder exclusions for the eg4-battery daemon's MQTT entities. +# +# Merge with your existing recorder config; if you don't have one, this whole +# file can be referenced as `recorder: !include eg4_battery/recorder.yaml`. +# +# Rationale: +# - register_NN entities are raw Modbus registers, diagnostic only +# - individual cell voltages are redundant once you have min/max/delta +# - uptime / version / static config values are pure noise in a timeseries +# +# Everything NOT in `entity_globs` below keeps recording normally, including +# the Tier-1 (pack_voltage / soc / soh / cycle_count / cell_voltage_min/max/ +# delta_mv / capacity_ah) and Tier-2 (warnings / protections / error_code) +# entities. See ../NOTES.md for the retention-tier breakdown. + +exclude: + entity_globs: + # raw Modbus register dump — diagnostic only + - sensor.lifepower4_*_register_* + + # 16 individual cells per pack = 48 noisy series. + # cell_voltage_min / _max / _delta_mv already capture 95% of the info. + # Comment this out if you're debugging a specific drifting cell. + - sensor.lifepower4_*_cell_01_voltage + - sensor.lifepower4_*_cell_02_voltage + - sensor.lifepower4_*_cell_03_voltage + - sensor.lifepower4_*_cell_04_voltage + - sensor.lifepower4_*_cell_05_voltage + - sensor.lifepower4_*_cell_06_voltage + - sensor.lifepower4_*_cell_07_voltage + - sensor.lifepower4_*_cell_08_voltage + - sensor.lifepower4_*_cell_09_voltage + - sensor.lifepower4_*_cell_10_voltage + - sensor.lifepower4_*_cell_11_voltage + - sensor.lifepower4_*_cell_12_voltage + - sensor.lifepower4_*_cell_13_voltage + - sensor.lifepower4_*_cell_14_voltage + - sensor.lifepower4_*_cell_15_voltage + - sensor.lifepower4_*_cell_16_voltage + + # static metadata (doesn't change, no reason to keep history) + - sensor.lifepower4_*_bms_version_hi + - sensor.lifepower4_*_bms_version_lo + - sensor.lifepower4_*_cell_count + - sensor.lifepower4_*_cell_highest + - sensor.lifepower4_*_cell_lowest + - sensor.lifepower4_*_battery_mode + - sensor.lifepower4_*_max_current_limit + + # uptime counter — increments every second, kills the recorder's write cache + - sensor.lifepower4_*_uptime_ds diff --git a/eg4battery/homeassistant/template_sensors.yaml b/eg4battery/homeassistant/template_sensors.yaml new file mode 100644 index 0000000..a9dfb74 --- /dev/null +++ b/eg4battery/homeassistant/template_sensors.yaml @@ -0,0 +1,174 @@ +# Derived template sensors for the eg4-battery daemon's 3-pack stack. +# Include into configuration.yaml as: +# template: !include eg4_battery/template_sensors.yaml +# +# Per-pack entities created: +# sensor.lifepower4_N_pack_power W (V × I, signed; + = charging) +# sensor.lifepower4_N_temperature_max °C (max of 5 temp sensors) +# sensor.lifepower4_N_cell_imbalance_pct % (delta / min_cell) × 100 +# +# Stack-wide rollups: +# sensor.lifepower4_stack_pack_power_total W (sum of all 3 pack_powers) +# sensor.lifepower4_stack_soc_avg % (average SoC across packs) +# sensor.lifepower4_stack_temperature_max °C (hottest point anywhere) + +- sensor: + # ---- pack 1 ---- + - name: "lifepower4_1 pack_power" + unique_id: lifepower4_1_pack_power + unit_of_measurement: "W" + device_class: power + state_class: measurement + state: > + {% set v = states('sensor.lifepower4_1_pack_voltage') | float(0) %} + {% set i = states('sensor.lifepower4_1_pack_current') | float(0) %} + {{ (v * i) | round(1) }} + + - name: "lifepower4_1 temperature_max" + unique_id: lifepower4_1_temperature_max + unit_of_measurement: "°C" + device_class: temperature + state_class: measurement + state: > + {% set t = [ + states('sensor.lifepower4_1_temperature_01') | int(0), + states('sensor.lifepower4_1_temperature_02') | int(0), + states('sensor.lifepower4_1_temperature_03') | int(0), + states('sensor.lifepower4_1_temperature_04') | int(0), + states('sensor.lifepower4_1_temperature_pcb') | int(0), + ] %} + {{ t | max }} + + - name: "lifepower4_1 cell_imbalance_pct" + unique_id: lifepower4_1_cell_imbalance_pct + unit_of_measurement: "%" + state_class: measurement + state: > + {% set d = states('sensor.lifepower4_1_cell_voltage_delta_mv') | float(0) %} + {% set mn = states('sensor.lifepower4_1_cell_voltage_min') | float(0) %} + {{ (d / (mn * 1000) * 100) | round(3) if mn > 0 else 0 }} + + # ---- pack 2 ---- + - name: "lifepower4_2 pack_power" + unique_id: lifepower4_2_pack_power + unit_of_measurement: "W" + device_class: power + state_class: measurement + state: > + {% set v = states('sensor.lifepower4_2_pack_voltage') | float(0) %} + {% set i = states('sensor.lifepower4_2_pack_current') | float(0) %} + {{ (v * i) | round(1) }} + + - name: "lifepower4_2 temperature_max" + unique_id: lifepower4_2_temperature_max + unit_of_measurement: "°C" + device_class: temperature + state_class: measurement + state: > + {% set t = [ + states('sensor.lifepower4_2_temperature_01') | int(0), + states('sensor.lifepower4_2_temperature_02') | int(0), + states('sensor.lifepower4_2_temperature_03') | int(0), + states('sensor.lifepower4_2_temperature_04') | int(0), + states('sensor.lifepower4_2_temperature_pcb') | int(0), + ] %} + {{ t | max }} + + - name: "lifepower4_2 cell_imbalance_pct" + unique_id: lifepower4_2_cell_imbalance_pct + unit_of_measurement: "%" + state_class: measurement + state: > + {% set d = states('sensor.lifepower4_2_cell_voltage_delta_mv') | float(0) %} + {% set mn = states('sensor.lifepower4_2_cell_voltage_min') | float(0) %} + {{ (d / (mn * 1000) * 100) | round(3) if mn > 0 else 0 }} + + # ---- pack 3 ---- + - name: "lifepower4_3 pack_power" + unique_id: lifepower4_3_pack_power + unit_of_measurement: "W" + device_class: power + state_class: measurement + state: > + {% set v = states('sensor.lifepower4_3_pack_voltage') | float(0) %} + {% set i = states('sensor.lifepower4_3_pack_current') | float(0) %} + {{ (v * i) | round(1) }} + + - name: "lifepower4_3 temperature_max" + unique_id: lifepower4_3_temperature_max + unit_of_measurement: "°C" + device_class: temperature + state_class: measurement + state: > + {% set t = [ + states('sensor.lifepower4_3_temperature_01') | int(0), + states('sensor.lifepower4_3_temperature_02') | int(0), + states('sensor.lifepower4_3_temperature_03') | int(0), + states('sensor.lifepower4_3_temperature_04') | int(0), + states('sensor.lifepower4_3_temperature_pcb') | int(0), + ] %} + {{ t | max }} + + - name: "lifepower4_3 cell_imbalance_pct" + unique_id: lifepower4_3_cell_imbalance_pct + unit_of_measurement: "%" + state_class: measurement + state: > + {% set d = states('sensor.lifepower4_3_cell_voltage_delta_mv') | float(0) %} + {% set mn = states('sensor.lifepower4_3_cell_voltage_min') | float(0) %} + {{ (d / (mn * 1000) * 100) | round(3) if mn > 0 else 0 }} + + # ---- stack-wide rollups ---- + - name: "lifepower4_stack pack_power_total" + unique_id: lifepower4_stack_pack_power_total + unit_of_measurement: "W" + device_class: power + state_class: measurement + state: > + {% set p = [ + states('sensor.lifepower4_1_pack_power') | float(0), + states('sensor.lifepower4_2_pack_power') | float(0), + states('sensor.lifepower4_3_pack_power') | float(0), + ] %} + {{ p | sum | round(1) }} + + - name: "lifepower4_stack soc_avg" + unique_id: lifepower4_stack_soc_avg + unit_of_measurement: "%" + device_class: battery + state_class: measurement + state: > + {% set s = [ + states('sensor.lifepower4_1_soc') | float(0), + states('sensor.lifepower4_2_soc') | float(0), + states('sensor.lifepower4_3_soc') | float(0), + ] %} + {{ (s | sum / s | length) | round(1) }} + + - name: "lifepower4_stack temperature_max" + unique_id: lifepower4_stack_temperature_max + unit_of_measurement: "°C" + device_class: temperature + state_class: measurement + state: > + {% set t = [ + states('sensor.lifepower4_1_temperature_max') | int(0), + states('sensor.lifepower4_2_temperature_max') | int(0), + states('sensor.lifepower4_3_temperature_max') | int(0), + ] %} + {{ t | max }} + +# --- integration → energy (pack_power integrated to Wh) --- +# Paste this at the top level of configuration.yaml, NOT inside `template:`: +# +# sensor: +# - platform: integration +# source: sensor.lifepower4_1_pack_power +# name: lifepower4_1_pack_energy +# unit_prefix: k +# round: 3 +# method: left +# # ... repeat for pack 2 and 3 ... +# +# Then wire the resulting sensor.lifepower4_N_pack_energy into the HA +# Energy dashboard → Home battery storage → one entry per pack. diff --git a/eg4battery/install.sh b/eg4battery/install.sh new file mode 100755 index 0000000..2d619d7 --- /dev/null +++ b/eg4battery/install.sh @@ -0,0 +1,93 @@ +#!/usr/bin/env bash +# EG4 LifePower4 → Home Assistant monitoring — reproducible installer. +# +# Installs the eg4-battery daemon onto /usr/local/bin (uv handles deps via the +# script's PEP-723 inline metadata), drops in the udev rule + systemd unit, +# and seeds the config template. Idempotent: safe to re-run. +# +# Assumptions: +# - Debian-family Linux with systemd +# - `uv` on $PATH (https://docs.astral.sh/uv/) +# - User has sudo +# +# After install, finish by editing: +# ~/.config/eg4-battery/eg4-battery.yaml — bus.port, MQTT creds, pack addresses +# then: +# ./install.sh --dry-run — end-to-end smoke test (no HW needed) +# sudo systemctl enable --now eg4-battery.service +# journalctl -u eg4-battery.service -f + +set -euo pipefail + +BASE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +DRY_RUN=0 +for arg in "$@"; do + [ "$arg" = "--dry-run" ] && DRY_RUN=1 +done + +msg() { printf '\n\033[1;36m== %s ==\033[0m\n' "$*"; } + +# --- 1. dependency check --------------------------------------------------- +msg "Checking prerequisites" +command -v uv >/dev/null || { echo "uv not found — install from https://docs.astral.sh/uv/"; exit 1; } + +# --- 2. entry point -------------------------------------------------------- +msg "Installing /usr/local/bin/eg4-battery" +sudo install -m 755 "${BASE}/bin/eg4-battery" /usr/local/bin/eg4-battery + +# --- 3. udev rule ---------------------------------------------------------- +msg "Installing udev rule (FTDI/CH340/CP210x → dialout)" +sudo install -m 644 "${BASE}/etc/udev/rules.d/99-eg4-rs485.rules" /etc/udev/rules.d/99-eg4-rs485.rules +sudo udevadm control --reload-rules +sudo udevadm trigger --subsystem-match=tty + +# --- 4. systemd unit ------------------------------------------------------- +msg "Installing systemd unit" +sudo install -m 644 "${BASE}/etc/systemd/system/eg4-battery.service" /etc/systemd/system/eg4-battery.service +sudo systemctl daemon-reload + +# --- 5. user config (only if not already present) -------------------------- +msg "Installing config template" +mkdir -p "${HOME}/.config/eg4-battery" +dest="${HOME}/.config/eg4-battery/eg4-battery.yaml" +if [ -e "$dest" ]; then + echo " $dest already exists — not overwriting" +else + install -m 600 "${BASE}/config/eg4-battery.yaml.example" "$dest" + echo " wrote $dest (mode 600) — edit bus.port, MQTT creds, pack addresses" +fi + +# --- 6. smoke test --------------------------------------------------------- +if [ "$DRY_RUN" = "1" ]; then + msg "Dry-run: running one mock-bus cycle (no MQTT publish)" + /usr/local/bin/eg4-battery -C "$dest" --dry-run + echo + echo "(above output is what would be published to MQTT under real run)" +fi + +# --- 7. enable ------------------------------------------------------------- +if grep -q '' "$dest" 2>/dev/null; then + cat < + + + + CFBundleExecutable + QtCore + CFBundleIdentifier + org.qt-project.QtCore + CFBundlePackageType + FMWK + CFBundleShortVersionString + 5.14 + CFBundleSignature + ???? + CFBundleVersion + 5.14.2 + NOTE + Please, do NOT change this file -- It was generated by Qt/QMake. + + diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtCore.framework/Resources/QtCore.prl b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtCore.framework/Resources/QtCore.prl new file mode 100644 index 0000000..94d9770 --- /dev/null +++ b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtCore.framework/Resources/QtCore.prl @@ -0,0 +1,7 @@ + +QMAKE_PRO_INPUT = corelib.pro +QMAKE_PRL_TARGET = QtCore +QMAKE_PRL_CONFIG = lex yacc exceptions depend_includepath testcase_targets import_plugins import_qpa_plugin asset_catalogs rez prepare_docs qt_docs_targets qt_build_extra file_copies qmake_use qt warn_on release link_prl app_bundle incremental global_init_link_order lib_version_first sdk clang_pch_style shared shared qt_framework release macos osx macx mac darwin unix posix gcc clang llvm sse2 aesni sse3 ssse3 sse4_1 sse4_2 avx avx2 avx512f avx512bw avx512cd avx512dq avx512er avx512ifma avx512pf avx512vbmi avx512vl compile_examples f16c force_debug_info largefile precompile_header rdrnd shani x86SimdAlways prefix_build force_independent no_warn_empty_obj_files utf8_source create_prl link_prl no_private_qt_headers_warning QTDIR_build qt_example_installs testcase_exceptions explicitlib testcase_no_bundle warning_clean exceptions qt_tracepoints moc resources simd optimize_full pcre2 generated_privates module_frameworks lib_bundle relative_qt_rpath app_extension_api_only git_build target_qt c++11 strict_c++ c++14 c99 c11 hide_symbols separate_debug_info need_fwd_pri qt_install_module create_cmake sliced_bundle compiler_supports_fpmath create_pc have_target dll debug_info objective_c any_bundle arch_haswell avx512common avx512core thread +QMAKE_PRL_VERSION = 5.14.2 +QMAKE_PRL_LIBS = -framework DiskArbitration -framework IOKit +QMAKE_PRL_LIBS_FOR_CMAKE = -framework;DiskArbitration;-framework;IOKit; diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtCore.framework/Versions/5/QtCore b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtCore.framework/Versions/5/QtCore new file mode 100755 index 0000000..2e03cad Binary files /dev/null and b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtCore.framework/Versions/5/QtCore differ diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtCore.framework/Versions/5/Resources/Info.plist b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtCore.framework/Versions/5/Resources/Info.plist new file mode 100644 index 0000000..789cbc8 --- /dev/null +++ b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtCore.framework/Versions/5/Resources/Info.plist @@ -0,0 +1,20 @@ + + + + + CFBundleExecutable + QtCore + CFBundleIdentifier + org.qt-project.QtCore + CFBundlePackageType + FMWK + CFBundleShortVersionString + 5.14 + CFBundleSignature + ???? + CFBundleVersion + 5.14.2 + NOTE + Please, do NOT change this file -- It was generated by Qt/QMake. + + diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtCore.framework/Versions/5/Resources/QtCore.prl b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtCore.framework/Versions/5/Resources/QtCore.prl new file mode 100644 index 0000000..94d9770 --- /dev/null +++ b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtCore.framework/Versions/5/Resources/QtCore.prl @@ -0,0 +1,7 @@ + +QMAKE_PRO_INPUT = corelib.pro +QMAKE_PRL_TARGET = QtCore +QMAKE_PRL_CONFIG = lex yacc exceptions depend_includepath testcase_targets import_plugins import_qpa_plugin asset_catalogs rez prepare_docs qt_docs_targets qt_build_extra file_copies qmake_use qt warn_on release link_prl app_bundle incremental global_init_link_order lib_version_first sdk clang_pch_style shared shared qt_framework release macos osx macx mac darwin unix posix gcc clang llvm sse2 aesni sse3 ssse3 sse4_1 sse4_2 avx avx2 avx512f avx512bw avx512cd avx512dq avx512er avx512ifma avx512pf avx512vbmi avx512vl compile_examples f16c force_debug_info largefile precompile_header rdrnd shani x86SimdAlways prefix_build force_independent no_warn_empty_obj_files utf8_source create_prl link_prl no_private_qt_headers_warning QTDIR_build qt_example_installs testcase_exceptions explicitlib testcase_no_bundle warning_clean exceptions qt_tracepoints moc resources simd optimize_full pcre2 generated_privates module_frameworks lib_bundle relative_qt_rpath app_extension_api_only git_build target_qt c++11 strict_c++ c++14 c99 c11 hide_symbols separate_debug_info need_fwd_pri qt_install_module create_cmake sliced_bundle compiler_supports_fpmath create_pc have_target dll debug_info objective_c any_bundle arch_haswell avx512common avx512core thread +QMAKE_PRL_VERSION = 5.14.2 +QMAKE_PRL_LIBS = -framework DiskArbitration -framework IOKit +QMAKE_PRL_LIBS_FOR_CMAKE = -framework;DiskArbitration;-framework;IOKit; diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtCore.framework/Versions/Current/QtCore b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtCore.framework/Versions/Current/QtCore new file mode 100755 index 0000000..2e03cad Binary files /dev/null and b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtCore.framework/Versions/Current/QtCore differ diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtCore.framework/Versions/Current/Resources/Info.plist b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtCore.framework/Versions/Current/Resources/Info.plist new file mode 100644 index 0000000..789cbc8 --- /dev/null +++ b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtCore.framework/Versions/Current/Resources/Info.plist @@ -0,0 +1,20 @@ + + + + + CFBundleExecutable + QtCore + CFBundleIdentifier + org.qt-project.QtCore + CFBundlePackageType + FMWK + CFBundleShortVersionString + 5.14 + CFBundleSignature + ???? + CFBundleVersion + 5.14.2 + NOTE + Please, do NOT change this file -- It was generated by Qt/QMake. + + diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtCore.framework/Versions/Current/Resources/QtCore.prl b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtCore.framework/Versions/Current/Resources/QtCore.prl new file mode 100644 index 0000000..94d9770 --- /dev/null +++ b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtCore.framework/Versions/Current/Resources/QtCore.prl @@ -0,0 +1,7 @@ + +QMAKE_PRO_INPUT = corelib.pro +QMAKE_PRL_TARGET = QtCore +QMAKE_PRL_CONFIG = lex yacc exceptions depend_includepath testcase_targets import_plugins import_qpa_plugin asset_catalogs rez prepare_docs qt_docs_targets qt_build_extra file_copies qmake_use qt warn_on release link_prl app_bundle incremental global_init_link_order lib_version_first sdk clang_pch_style shared shared qt_framework release macos osx macx mac darwin unix posix gcc clang llvm sse2 aesni sse3 ssse3 sse4_1 sse4_2 avx avx2 avx512f avx512bw avx512cd avx512dq avx512er avx512ifma avx512pf avx512vbmi avx512vl compile_examples f16c force_debug_info largefile precompile_header rdrnd shani x86SimdAlways prefix_build force_independent no_warn_empty_obj_files utf8_source create_prl link_prl no_private_qt_headers_warning QTDIR_build qt_example_installs testcase_exceptions explicitlib testcase_no_bundle warning_clean exceptions qt_tracepoints moc resources simd optimize_full pcre2 generated_privates module_frameworks lib_bundle relative_qt_rpath app_extension_api_only git_build target_qt c++11 strict_c++ c++14 c99 c11 hide_symbols separate_debug_info need_fwd_pri qt_install_module create_cmake sliced_bundle compiler_supports_fpmath create_pc have_target dll debug_info objective_c any_bundle arch_haswell avx512common avx512core thread +QMAKE_PRL_VERSION = 5.14.2 +QMAKE_PRL_LIBS = -framework DiskArbitration -framework IOKit +QMAKE_PRL_LIBS_FOR_CMAKE = -framework;DiskArbitration;-framework;IOKit; diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtDBus.framework/QtDBus b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtDBus.framework/QtDBus new file mode 100755 index 0000000..ee74946 Binary files /dev/null and b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtDBus.framework/QtDBus differ diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtDBus.framework/Resources/Info.plist b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtDBus.framework/Resources/Info.plist new file mode 100644 index 0000000..e61fdc2 --- /dev/null +++ b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtDBus.framework/Resources/Info.plist @@ -0,0 +1,20 @@ + + + + + CFBundleExecutable + QtDBus + CFBundleIdentifier + org.qt-project.QtDBus + CFBundlePackageType + FMWK + CFBundleShortVersionString + 5.14 + CFBundleSignature + ???? + CFBundleVersion + 5.14.2 + NOTE + Please, do NOT change this file -- It was generated by Qt/QMake. + + diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtDBus.framework/Resources/QtDBus.prl b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtDBus.framework/Resources/QtDBus.prl new file mode 100644 index 0000000..872947b --- /dev/null +++ b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtDBus.framework/Resources/QtDBus.prl @@ -0,0 +1,7 @@ + +QMAKE_PRO_INPUT = dbus.pro +QMAKE_PRL_TARGET = QtDBus +QMAKE_PRL_CONFIG = lex yacc depend_includepath testcase_targets import_plugins import_qpa_plugin asset_catalogs rez prepare_docs qt_docs_targets qt_build_extra file_copies qmake_use qt warn_on release link_prl app_bundle incremental global_init_link_order lib_version_first sdk clang_pch_style shared shared qt_framework release macos osx macx mac darwin unix posix gcc clang llvm sse2 aesni sse3 ssse3 sse4_1 sse4_2 avx avx2 avx512f avx512bw avx512cd avx512dq avx512er avx512ifma avx512pf avx512vbmi avx512vl compile_examples f16c force_debug_info largefile precompile_header rdrnd shani x86SimdAlways prefix_build force_independent no_warn_empty_obj_files utf8_source create_prl link_prl no_private_qt_headers_warning QTDIR_build qt_example_installs exceptions_off testcase_exceptions explicitlib testcase_no_bundle warning_clean module_frameworks lib_bundle relative_qt_rpath app_extension_api_only git_build target_qt c++11 strict_c++ c++14 c99 c11 hide_symbols separate_debug_info need_fwd_pri qt_install_module create_cmake sliced_bundle compiler_supports_fpmath create_pc have_target dll debug_info objective_c any_bundle thread moc resources +QMAKE_PRL_VERSION = 5.14.2 +QMAKE_PRL_LIBS = -F$$[QT_INSTALL_LIBS] -framework QtCore -framework DiskArbitration -framework IOKit +QMAKE_PRL_LIBS_FOR_CMAKE = -F$$[QT_INSTALL_LIBS];-framework;QtCore;-framework;DiskArbitration;-framework;IOKit; diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtDBus.framework/Versions/5/QtDBus b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtDBus.framework/Versions/5/QtDBus new file mode 100755 index 0000000..ee74946 Binary files /dev/null and b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtDBus.framework/Versions/5/QtDBus differ diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtDBus.framework/Versions/5/Resources/Info.plist b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtDBus.framework/Versions/5/Resources/Info.plist new file mode 100644 index 0000000..e61fdc2 --- /dev/null +++ b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtDBus.framework/Versions/5/Resources/Info.plist @@ -0,0 +1,20 @@ + + + + + CFBundleExecutable + QtDBus + CFBundleIdentifier + org.qt-project.QtDBus + CFBundlePackageType + FMWK + CFBundleShortVersionString + 5.14 + CFBundleSignature + ???? + CFBundleVersion + 5.14.2 + NOTE + Please, do NOT change this file -- It was generated by Qt/QMake. + + diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtDBus.framework/Versions/5/Resources/QtDBus.prl b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtDBus.framework/Versions/5/Resources/QtDBus.prl new file mode 100644 index 0000000..872947b --- /dev/null +++ b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtDBus.framework/Versions/5/Resources/QtDBus.prl @@ -0,0 +1,7 @@ + +QMAKE_PRO_INPUT = dbus.pro +QMAKE_PRL_TARGET = QtDBus +QMAKE_PRL_CONFIG = lex yacc depend_includepath testcase_targets import_plugins import_qpa_plugin asset_catalogs rez prepare_docs qt_docs_targets qt_build_extra file_copies qmake_use qt warn_on release link_prl app_bundle incremental global_init_link_order lib_version_first sdk clang_pch_style shared shared qt_framework release macos osx macx mac darwin unix posix gcc clang llvm sse2 aesni sse3 ssse3 sse4_1 sse4_2 avx avx2 avx512f avx512bw avx512cd avx512dq avx512er avx512ifma avx512pf avx512vbmi avx512vl compile_examples f16c force_debug_info largefile precompile_header rdrnd shani x86SimdAlways prefix_build force_independent no_warn_empty_obj_files utf8_source create_prl link_prl no_private_qt_headers_warning QTDIR_build qt_example_installs exceptions_off testcase_exceptions explicitlib testcase_no_bundle warning_clean module_frameworks lib_bundle relative_qt_rpath app_extension_api_only git_build target_qt c++11 strict_c++ c++14 c99 c11 hide_symbols separate_debug_info need_fwd_pri qt_install_module create_cmake sliced_bundle compiler_supports_fpmath create_pc have_target dll debug_info objective_c any_bundle thread moc resources +QMAKE_PRL_VERSION = 5.14.2 +QMAKE_PRL_LIBS = -F$$[QT_INSTALL_LIBS] -framework QtCore -framework DiskArbitration -framework IOKit +QMAKE_PRL_LIBS_FOR_CMAKE = -F$$[QT_INSTALL_LIBS];-framework;QtCore;-framework;DiskArbitration;-framework;IOKit; diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtDBus.framework/Versions/Current/QtDBus b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtDBus.framework/Versions/Current/QtDBus new file mode 100755 index 0000000..ee74946 Binary files /dev/null and b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtDBus.framework/Versions/Current/QtDBus differ diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtDBus.framework/Versions/Current/Resources/Info.plist b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtDBus.framework/Versions/Current/Resources/Info.plist new file mode 100644 index 0000000..e61fdc2 --- /dev/null +++ b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtDBus.framework/Versions/Current/Resources/Info.plist @@ -0,0 +1,20 @@ + + + + + CFBundleExecutable + QtDBus + CFBundleIdentifier + org.qt-project.QtDBus + CFBundlePackageType + FMWK + CFBundleShortVersionString + 5.14 + CFBundleSignature + ???? + CFBundleVersion + 5.14.2 + NOTE + Please, do NOT change this file -- It was generated by Qt/QMake. + + diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtDBus.framework/Versions/Current/Resources/QtDBus.prl b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtDBus.framework/Versions/Current/Resources/QtDBus.prl new file mode 100644 index 0000000..872947b --- /dev/null +++ b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtDBus.framework/Versions/Current/Resources/QtDBus.prl @@ -0,0 +1,7 @@ + +QMAKE_PRO_INPUT = dbus.pro +QMAKE_PRL_TARGET = QtDBus +QMAKE_PRL_CONFIG = lex yacc depend_includepath testcase_targets import_plugins import_qpa_plugin asset_catalogs rez prepare_docs qt_docs_targets qt_build_extra file_copies qmake_use qt warn_on release link_prl app_bundle incremental global_init_link_order lib_version_first sdk clang_pch_style shared shared qt_framework release macos osx macx mac darwin unix posix gcc clang llvm sse2 aesni sse3 ssse3 sse4_1 sse4_2 avx avx2 avx512f avx512bw avx512cd avx512dq avx512er avx512ifma avx512pf avx512vbmi avx512vl compile_examples f16c force_debug_info largefile precompile_header rdrnd shani x86SimdAlways prefix_build force_independent no_warn_empty_obj_files utf8_source create_prl link_prl no_private_qt_headers_warning QTDIR_build qt_example_installs exceptions_off testcase_exceptions explicitlib testcase_no_bundle warning_clean module_frameworks lib_bundle relative_qt_rpath app_extension_api_only git_build target_qt c++11 strict_c++ c++14 c99 c11 hide_symbols separate_debug_info need_fwd_pri qt_install_module create_cmake sliced_bundle compiler_supports_fpmath create_pc have_target dll debug_info objective_c any_bundle thread moc resources +QMAKE_PRL_VERSION = 5.14.2 +QMAKE_PRL_LIBS = -F$$[QT_INSTALL_LIBS] -framework QtCore -framework DiskArbitration -framework IOKit +QMAKE_PRL_LIBS_FOR_CMAKE = -F$$[QT_INSTALL_LIBS];-framework;QtCore;-framework;DiskArbitration;-framework;IOKit; diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtGui.framework/QtGui b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtGui.framework/QtGui new file mode 100755 index 0000000..9757640 Binary files /dev/null and b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtGui.framework/QtGui differ diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtGui.framework/Resources/Info.plist b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtGui.framework/Resources/Info.plist new file mode 100644 index 0000000..7962073 --- /dev/null +++ b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtGui.framework/Resources/Info.plist @@ -0,0 +1,20 @@ + + + + + CFBundleExecutable + QtGui + CFBundleIdentifier + org.qt-project.QtGui + CFBundlePackageType + FMWK + CFBundleShortVersionString + 5.14 + CFBundleSignature + ???? + CFBundleVersion + 5.14.2 + NOTE + Please, do NOT change this file -- It was generated by Qt/QMake. + + diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtGui.framework/Resources/QtGui.prl b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtGui.framework/Resources/QtGui.prl new file mode 100644 index 0000000..8cd9fd0 --- /dev/null +++ b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtGui.framework/Resources/QtGui.prl @@ -0,0 +1,7 @@ + +QMAKE_PRO_INPUT = gui.pro +QMAKE_PRL_TARGET = QtGui +QMAKE_PRL_CONFIG = lex yacc depend_includepath testcase_targets import_plugins import_qpa_plugin asset_catalogs rez prepare_docs qt_docs_targets qt_build_extra file_copies qmake_use qt warn_on release link_prl app_bundle incremental global_init_link_order lib_version_first sdk clang_pch_style shared shared qt_framework release macos osx macx mac darwin unix posix gcc clang llvm sse2 aesni sse3 ssse3 sse4_1 sse4_2 avx avx2 avx512f avx512bw avx512cd avx512dq avx512er avx512ifma avx512pf avx512vbmi avx512vl compile_examples f16c force_debug_info largefile precompile_header rdrnd shani x86SimdAlways prefix_build force_independent no_warn_empty_obj_files utf8_source create_prl link_prl no_private_qt_headers_warning QTDIR_build qt_example_installs exceptions_off testcase_exceptions explicitlib testcase_no_bundle warning_clean simd optimize_full opengl generated_privates module_frameworks lib_bundle relative_qt_rpath app_extension_api_only git_build target_qt c++11 strict_c++ c++14 c99 c11 hide_symbols separate_debug_info need_fwd_pri qt_install_module create_cmake sliced_bundle compiler_supports_fpmath create_pc qt_tracepoints have_target dll debug_info objective_c any_bundle arch_haswell avx512common avx512core thread moc resources +QMAKE_PRL_VERSION = 5.14.2 +QMAKE_PRL_LIBS = -F$$[QT_INSTALL_LIBS] -framework AppKit -framework Metal -framework QtCore -framework DiskArbitration -framework IOKit +QMAKE_PRL_LIBS_FOR_CMAKE = -F$$[QT_INSTALL_LIBS];-framework;AppKit;-framework;Metal;-framework;QtCore;-framework;DiskArbitration;-framework;IOKit; diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtGui.framework/Versions/5/QtGui b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtGui.framework/Versions/5/QtGui new file mode 100755 index 0000000..9757640 Binary files /dev/null and b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtGui.framework/Versions/5/QtGui differ diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtGui.framework/Versions/5/Resources/Info.plist b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtGui.framework/Versions/5/Resources/Info.plist new file mode 100644 index 0000000..7962073 --- /dev/null +++ b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtGui.framework/Versions/5/Resources/Info.plist @@ -0,0 +1,20 @@ + + + + + CFBundleExecutable + QtGui + CFBundleIdentifier + org.qt-project.QtGui + CFBundlePackageType + FMWK + CFBundleShortVersionString + 5.14 + CFBundleSignature + ???? + CFBundleVersion + 5.14.2 + NOTE + Please, do NOT change this file -- It was generated by Qt/QMake. + + diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtGui.framework/Versions/5/Resources/QtGui.prl b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtGui.framework/Versions/5/Resources/QtGui.prl new file mode 100644 index 0000000..8cd9fd0 --- /dev/null +++ b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtGui.framework/Versions/5/Resources/QtGui.prl @@ -0,0 +1,7 @@ + +QMAKE_PRO_INPUT = gui.pro +QMAKE_PRL_TARGET = QtGui +QMAKE_PRL_CONFIG = lex yacc depend_includepath testcase_targets import_plugins import_qpa_plugin asset_catalogs rez prepare_docs qt_docs_targets qt_build_extra file_copies qmake_use qt warn_on release link_prl app_bundle incremental global_init_link_order lib_version_first sdk clang_pch_style shared shared qt_framework release macos osx macx mac darwin unix posix gcc clang llvm sse2 aesni sse3 ssse3 sse4_1 sse4_2 avx avx2 avx512f avx512bw avx512cd avx512dq avx512er avx512ifma avx512pf avx512vbmi avx512vl compile_examples f16c force_debug_info largefile precompile_header rdrnd shani x86SimdAlways prefix_build force_independent no_warn_empty_obj_files utf8_source create_prl link_prl no_private_qt_headers_warning QTDIR_build qt_example_installs exceptions_off testcase_exceptions explicitlib testcase_no_bundle warning_clean simd optimize_full opengl generated_privates module_frameworks lib_bundle relative_qt_rpath app_extension_api_only git_build target_qt c++11 strict_c++ c++14 c99 c11 hide_symbols separate_debug_info need_fwd_pri qt_install_module create_cmake sliced_bundle compiler_supports_fpmath create_pc qt_tracepoints have_target dll debug_info objective_c any_bundle arch_haswell avx512common avx512core thread moc resources +QMAKE_PRL_VERSION = 5.14.2 +QMAKE_PRL_LIBS = -F$$[QT_INSTALL_LIBS] -framework AppKit -framework Metal -framework QtCore -framework DiskArbitration -framework IOKit +QMAKE_PRL_LIBS_FOR_CMAKE = -F$$[QT_INSTALL_LIBS];-framework;AppKit;-framework;Metal;-framework;QtCore;-framework;DiskArbitration;-framework;IOKit; diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtGui.framework/Versions/Current/QtGui b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtGui.framework/Versions/Current/QtGui new file mode 100755 index 0000000..9757640 Binary files /dev/null and b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtGui.framework/Versions/Current/QtGui differ diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtGui.framework/Versions/Current/Resources/Info.plist b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtGui.framework/Versions/Current/Resources/Info.plist new file mode 100644 index 0000000..7962073 --- /dev/null +++ b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtGui.framework/Versions/Current/Resources/Info.plist @@ -0,0 +1,20 @@ + + + + + CFBundleExecutable + QtGui + CFBundleIdentifier + org.qt-project.QtGui + CFBundlePackageType + FMWK + CFBundleShortVersionString + 5.14 + CFBundleSignature + ???? + CFBundleVersion + 5.14.2 + NOTE + Please, do NOT change this file -- It was generated by Qt/QMake. + + diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtGui.framework/Versions/Current/Resources/QtGui.prl b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtGui.framework/Versions/Current/Resources/QtGui.prl new file mode 100644 index 0000000..8cd9fd0 --- /dev/null +++ b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtGui.framework/Versions/Current/Resources/QtGui.prl @@ -0,0 +1,7 @@ + +QMAKE_PRO_INPUT = gui.pro +QMAKE_PRL_TARGET = QtGui +QMAKE_PRL_CONFIG = lex yacc depend_includepath testcase_targets import_plugins import_qpa_plugin asset_catalogs rez prepare_docs qt_docs_targets qt_build_extra file_copies qmake_use qt warn_on release link_prl app_bundle incremental global_init_link_order lib_version_first sdk clang_pch_style shared shared qt_framework release macos osx macx mac darwin unix posix gcc clang llvm sse2 aesni sse3 ssse3 sse4_1 sse4_2 avx avx2 avx512f avx512bw avx512cd avx512dq avx512er avx512ifma avx512pf avx512vbmi avx512vl compile_examples f16c force_debug_info largefile precompile_header rdrnd shani x86SimdAlways prefix_build force_independent no_warn_empty_obj_files utf8_source create_prl link_prl no_private_qt_headers_warning QTDIR_build qt_example_installs exceptions_off testcase_exceptions explicitlib testcase_no_bundle warning_clean simd optimize_full opengl generated_privates module_frameworks lib_bundle relative_qt_rpath app_extension_api_only git_build target_qt c++11 strict_c++ c++14 c99 c11 hide_symbols separate_debug_info need_fwd_pri qt_install_module create_cmake sliced_bundle compiler_supports_fpmath create_pc qt_tracepoints have_target dll debug_info objective_c any_bundle arch_haswell avx512common avx512core thread moc resources +QMAKE_PRL_VERSION = 5.14.2 +QMAKE_PRL_LIBS = -F$$[QT_INSTALL_LIBS] -framework AppKit -framework Metal -framework QtCore -framework DiskArbitration -framework IOKit +QMAKE_PRL_LIBS_FOR_CMAKE = -F$$[QT_INSTALL_LIBS];-framework;AppKit;-framework;Metal;-framework;QtCore;-framework;DiskArbitration;-framework;IOKit; diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtNetwork.framework/QtNetwork b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtNetwork.framework/QtNetwork new file mode 100755 index 0000000..2cc7419 Binary files /dev/null and b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtNetwork.framework/QtNetwork differ diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtNetwork.framework/Resources/Info.plist b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtNetwork.framework/Resources/Info.plist new file mode 100644 index 0000000..18b6f20 --- /dev/null +++ b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtNetwork.framework/Resources/Info.plist @@ -0,0 +1,20 @@ + + + + + CFBundleExecutable + QtNetwork + CFBundleIdentifier + org.qt-project.QtNetwork + CFBundlePackageType + FMWK + CFBundleShortVersionString + 5.14 + CFBundleSignature + ???? + CFBundleVersion + 5.14.2 + NOTE + Please, do NOT change this file -- It was generated by Qt/QMake. + + diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtNetwork.framework/Resources/QtNetwork.prl b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtNetwork.framework/Resources/QtNetwork.prl new file mode 100644 index 0000000..df2e28e --- /dev/null +++ b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtNetwork.framework/Resources/QtNetwork.prl @@ -0,0 +1,7 @@ + +QMAKE_PRO_INPUT = network.pro +QMAKE_PRL_TARGET = QtNetwork +QMAKE_PRL_CONFIG = lex yacc depend_includepath testcase_targets import_plugins import_qpa_plugin asset_catalogs rez prepare_docs qt_docs_targets qt_build_extra file_copies qmake_use qt warn_on release link_prl app_bundle incremental global_init_link_order lib_version_first sdk clang_pch_style shared shared qt_framework release macos osx macx mac darwin unix posix gcc clang llvm sse2 aesni sse3 ssse3 sse4_1 sse4_2 avx avx2 avx512f avx512bw avx512cd avx512dq avx512er avx512ifma avx512pf avx512vbmi avx512vl compile_examples f16c force_debug_info largefile precompile_header rdrnd shani x86SimdAlways prefix_build force_independent no_warn_empty_obj_files utf8_source create_prl link_prl no_private_qt_headers_warning QTDIR_build qt_example_installs exceptions_off testcase_exceptions explicitlib testcase_no_bundle warning_clean generated_privates module_frameworks lib_bundle relative_qt_rpath app_extension_api_only git_build target_qt c++11 strict_c++ c++14 c99 c11 hide_symbols separate_debug_info need_fwd_pri qt_install_module create_cmake sliced_bundle compiler_supports_fpmath create_pc have_target dll debug_info objective_c any_bundle thread moc resources +QMAKE_PRL_VERSION = 5.14.2 +QMAKE_PRL_LIBS = -F$$[QT_INSTALL_LIBS] -framework QtCore -framework DiskArbitration -framework IOKit +QMAKE_PRL_LIBS_FOR_CMAKE = -F$$[QT_INSTALL_LIBS];-framework;QtCore;-framework;DiskArbitration;-framework;IOKit; diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtNetwork.framework/Versions/5/QtNetwork b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtNetwork.framework/Versions/5/QtNetwork new file mode 100755 index 0000000..2cc7419 Binary files /dev/null and b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtNetwork.framework/Versions/5/QtNetwork differ diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtNetwork.framework/Versions/5/Resources/Info.plist b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtNetwork.framework/Versions/5/Resources/Info.plist new file mode 100644 index 0000000..18b6f20 --- /dev/null +++ b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtNetwork.framework/Versions/5/Resources/Info.plist @@ -0,0 +1,20 @@ + + + + + CFBundleExecutable + QtNetwork + CFBundleIdentifier + org.qt-project.QtNetwork + CFBundlePackageType + FMWK + CFBundleShortVersionString + 5.14 + CFBundleSignature + ???? + CFBundleVersion + 5.14.2 + NOTE + Please, do NOT change this file -- It was generated by Qt/QMake. + + diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtNetwork.framework/Versions/5/Resources/QtNetwork.prl b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtNetwork.framework/Versions/5/Resources/QtNetwork.prl new file mode 100644 index 0000000..df2e28e --- /dev/null +++ b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtNetwork.framework/Versions/5/Resources/QtNetwork.prl @@ -0,0 +1,7 @@ + +QMAKE_PRO_INPUT = network.pro +QMAKE_PRL_TARGET = QtNetwork +QMAKE_PRL_CONFIG = lex yacc depend_includepath testcase_targets import_plugins import_qpa_plugin asset_catalogs rez prepare_docs qt_docs_targets qt_build_extra file_copies qmake_use qt warn_on release link_prl app_bundle incremental global_init_link_order lib_version_first sdk clang_pch_style shared shared qt_framework release macos osx macx mac darwin unix posix gcc clang llvm sse2 aesni sse3 ssse3 sse4_1 sse4_2 avx avx2 avx512f avx512bw avx512cd avx512dq avx512er avx512ifma avx512pf avx512vbmi avx512vl compile_examples f16c force_debug_info largefile precompile_header rdrnd shani x86SimdAlways prefix_build force_independent no_warn_empty_obj_files utf8_source create_prl link_prl no_private_qt_headers_warning QTDIR_build qt_example_installs exceptions_off testcase_exceptions explicitlib testcase_no_bundle warning_clean generated_privates module_frameworks lib_bundle relative_qt_rpath app_extension_api_only git_build target_qt c++11 strict_c++ c++14 c99 c11 hide_symbols separate_debug_info need_fwd_pri qt_install_module create_cmake sliced_bundle compiler_supports_fpmath create_pc have_target dll debug_info objective_c any_bundle thread moc resources +QMAKE_PRL_VERSION = 5.14.2 +QMAKE_PRL_LIBS = -F$$[QT_INSTALL_LIBS] -framework QtCore -framework DiskArbitration -framework IOKit +QMAKE_PRL_LIBS_FOR_CMAKE = -F$$[QT_INSTALL_LIBS];-framework;QtCore;-framework;DiskArbitration;-framework;IOKit; diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtNetwork.framework/Versions/Current/QtNetwork b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtNetwork.framework/Versions/Current/QtNetwork new file mode 100755 index 0000000..2cc7419 Binary files /dev/null and b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtNetwork.framework/Versions/Current/QtNetwork differ diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtNetwork.framework/Versions/Current/Resources/Info.plist b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtNetwork.framework/Versions/Current/Resources/Info.plist new file mode 100644 index 0000000..18b6f20 --- /dev/null +++ b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtNetwork.framework/Versions/Current/Resources/Info.plist @@ -0,0 +1,20 @@ + + + + + CFBundleExecutable + QtNetwork + CFBundleIdentifier + org.qt-project.QtNetwork + CFBundlePackageType + FMWK + CFBundleShortVersionString + 5.14 + CFBundleSignature + ???? + CFBundleVersion + 5.14.2 + NOTE + Please, do NOT change this file -- It was generated by Qt/QMake. + + diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtNetwork.framework/Versions/Current/Resources/QtNetwork.prl b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtNetwork.framework/Versions/Current/Resources/QtNetwork.prl new file mode 100644 index 0000000..df2e28e --- /dev/null +++ b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtNetwork.framework/Versions/Current/Resources/QtNetwork.prl @@ -0,0 +1,7 @@ + +QMAKE_PRO_INPUT = network.pro +QMAKE_PRL_TARGET = QtNetwork +QMAKE_PRL_CONFIG = lex yacc depend_includepath testcase_targets import_plugins import_qpa_plugin asset_catalogs rez prepare_docs qt_docs_targets qt_build_extra file_copies qmake_use qt warn_on release link_prl app_bundle incremental global_init_link_order lib_version_first sdk clang_pch_style shared shared qt_framework release macos osx macx mac darwin unix posix gcc clang llvm sse2 aesni sse3 ssse3 sse4_1 sse4_2 avx avx2 avx512f avx512bw avx512cd avx512dq avx512er avx512ifma avx512pf avx512vbmi avx512vl compile_examples f16c force_debug_info largefile precompile_header rdrnd shani x86SimdAlways prefix_build force_independent no_warn_empty_obj_files utf8_source create_prl link_prl no_private_qt_headers_warning QTDIR_build qt_example_installs exceptions_off testcase_exceptions explicitlib testcase_no_bundle warning_clean generated_privates module_frameworks lib_bundle relative_qt_rpath app_extension_api_only git_build target_qt c++11 strict_c++ c++14 c99 c11 hide_symbols separate_debug_info need_fwd_pri qt_install_module create_cmake sliced_bundle compiler_supports_fpmath create_pc have_target dll debug_info objective_c any_bundle thread moc resources +QMAKE_PRL_VERSION = 5.14.2 +QMAKE_PRL_LIBS = -F$$[QT_INSTALL_LIBS] -framework QtCore -framework DiskArbitration -framework IOKit +QMAKE_PRL_LIBS_FOR_CMAKE = -F$$[QT_INSTALL_LIBS];-framework;QtCore;-framework;DiskArbitration;-framework;IOKit; diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtPrintSupport.framework/QtPrintSupport b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtPrintSupport.framework/QtPrintSupport new file mode 100755 index 0000000..95303e9 Binary files /dev/null and b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtPrintSupport.framework/QtPrintSupport differ diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtPrintSupport.framework/Resources/Info.plist b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtPrintSupport.framework/Resources/Info.plist new file mode 100644 index 0000000..0ed097f --- /dev/null +++ b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtPrintSupport.framework/Resources/Info.plist @@ -0,0 +1,20 @@ + + + + + CFBundleExecutable + QtPrintSupport + CFBundleIdentifier + org.qt-project.QtPrintSupport + CFBundlePackageType + FMWK + CFBundleShortVersionString + 5.14 + CFBundleSignature + ???? + CFBundleVersion + 5.14.2 + NOTE + Please, do NOT change this file -- It was generated by Qt/QMake. + + diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtPrintSupport.framework/Resources/QtPrintSupport.prl b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtPrintSupport.framework/Resources/QtPrintSupport.prl new file mode 100644 index 0000000..855fee0 --- /dev/null +++ b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtPrintSupport.framework/Resources/QtPrintSupport.prl @@ -0,0 +1,7 @@ + +QMAKE_PRO_INPUT = printsupport.pro +QMAKE_PRL_TARGET = QtPrintSupport +QMAKE_PRL_CONFIG = lex yacc depend_includepath testcase_targets import_plugins import_qpa_plugin asset_catalogs rez prepare_docs qt_docs_targets qt_build_extra file_copies qmake_use qt warn_on release link_prl app_bundle incremental global_init_link_order lib_version_first sdk clang_pch_style shared shared qt_framework release macos osx macx mac darwin unix posix gcc clang llvm sse2 aesni sse3 ssse3 sse4_1 sse4_2 avx avx2 avx512f avx512bw avx512cd avx512dq avx512er avx512ifma avx512pf avx512vbmi avx512vl compile_examples f16c force_debug_info largefile precompile_header rdrnd shani x86SimdAlways prefix_build force_independent no_warn_empty_obj_files utf8_source create_prl link_prl no_private_qt_headers_warning QTDIR_build qt_example_installs exceptions_off testcase_exceptions explicitlib testcase_no_bundle warning_clean generated_privates module_frameworks lib_bundle relative_qt_rpath app_extension_api_only git_build target_qt c++11 strict_c++ c++14 c99 c11 hide_symbols separate_debug_info need_fwd_pri qt_install_module create_cmake sliced_bundle compiler_supports_fpmath create_pc have_target dll debug_info objective_c any_bundle thread uic opengl moc resources +QMAKE_PRL_VERSION = 5.14.2 +QMAKE_PRL_LIBS = -F$$[QT_INSTALL_LIBS] -framework QtWidgets -framework QtGui -framework AppKit -framework Metal -framework QtCore -framework DiskArbitration -framework IOKit +QMAKE_PRL_LIBS_FOR_CMAKE = -F$$[QT_INSTALL_LIBS];-framework;QtWidgets;-framework;QtGui;-framework;AppKit;-framework;Metal;-framework;QtCore;-framework;DiskArbitration;-framework;IOKit; diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtPrintSupport.framework/Versions/5/QtPrintSupport b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtPrintSupport.framework/Versions/5/QtPrintSupport new file mode 100755 index 0000000..95303e9 Binary files /dev/null and b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtPrintSupport.framework/Versions/5/QtPrintSupport differ diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtPrintSupport.framework/Versions/5/Resources/Info.plist b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtPrintSupport.framework/Versions/5/Resources/Info.plist new file mode 100644 index 0000000..0ed097f --- /dev/null +++ b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtPrintSupport.framework/Versions/5/Resources/Info.plist @@ -0,0 +1,20 @@ + + + + + CFBundleExecutable + QtPrintSupport + CFBundleIdentifier + org.qt-project.QtPrintSupport + CFBundlePackageType + FMWK + CFBundleShortVersionString + 5.14 + CFBundleSignature + ???? + CFBundleVersion + 5.14.2 + NOTE + Please, do NOT change this file -- It was generated by Qt/QMake. + + diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtPrintSupport.framework/Versions/5/Resources/QtPrintSupport.prl b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtPrintSupport.framework/Versions/5/Resources/QtPrintSupport.prl new file mode 100644 index 0000000..855fee0 --- /dev/null +++ b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtPrintSupport.framework/Versions/5/Resources/QtPrintSupport.prl @@ -0,0 +1,7 @@ + +QMAKE_PRO_INPUT = printsupport.pro +QMAKE_PRL_TARGET = QtPrintSupport +QMAKE_PRL_CONFIG = lex yacc depend_includepath testcase_targets import_plugins import_qpa_plugin asset_catalogs rez prepare_docs qt_docs_targets qt_build_extra file_copies qmake_use qt warn_on release link_prl app_bundle incremental global_init_link_order lib_version_first sdk clang_pch_style shared shared qt_framework release macos osx macx mac darwin unix posix gcc clang llvm sse2 aesni sse3 ssse3 sse4_1 sse4_2 avx avx2 avx512f avx512bw avx512cd avx512dq avx512er avx512ifma avx512pf avx512vbmi avx512vl compile_examples f16c force_debug_info largefile precompile_header rdrnd shani x86SimdAlways prefix_build force_independent no_warn_empty_obj_files utf8_source create_prl link_prl no_private_qt_headers_warning QTDIR_build qt_example_installs exceptions_off testcase_exceptions explicitlib testcase_no_bundle warning_clean generated_privates module_frameworks lib_bundle relative_qt_rpath app_extension_api_only git_build target_qt c++11 strict_c++ c++14 c99 c11 hide_symbols separate_debug_info need_fwd_pri qt_install_module create_cmake sliced_bundle compiler_supports_fpmath create_pc have_target dll debug_info objective_c any_bundle thread uic opengl moc resources +QMAKE_PRL_VERSION = 5.14.2 +QMAKE_PRL_LIBS = -F$$[QT_INSTALL_LIBS] -framework QtWidgets -framework QtGui -framework AppKit -framework Metal -framework QtCore -framework DiskArbitration -framework IOKit +QMAKE_PRL_LIBS_FOR_CMAKE = -F$$[QT_INSTALL_LIBS];-framework;QtWidgets;-framework;QtGui;-framework;AppKit;-framework;Metal;-framework;QtCore;-framework;DiskArbitration;-framework;IOKit; diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtPrintSupport.framework/Versions/Current/QtPrintSupport b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtPrintSupport.framework/Versions/Current/QtPrintSupport new file mode 100755 index 0000000..95303e9 Binary files /dev/null and b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtPrintSupport.framework/Versions/Current/QtPrintSupport differ diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtPrintSupport.framework/Versions/Current/Resources/Info.plist b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtPrintSupport.framework/Versions/Current/Resources/Info.plist new file mode 100644 index 0000000..0ed097f --- /dev/null +++ b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtPrintSupport.framework/Versions/Current/Resources/Info.plist @@ -0,0 +1,20 @@ + + + + + CFBundleExecutable + QtPrintSupport + CFBundleIdentifier + org.qt-project.QtPrintSupport + CFBundlePackageType + FMWK + CFBundleShortVersionString + 5.14 + CFBundleSignature + ???? + CFBundleVersion + 5.14.2 + NOTE + Please, do NOT change this file -- It was generated by Qt/QMake. + + diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtPrintSupport.framework/Versions/Current/Resources/QtPrintSupport.prl b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtPrintSupport.framework/Versions/Current/Resources/QtPrintSupport.prl new file mode 100644 index 0000000..855fee0 --- /dev/null +++ b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtPrintSupport.framework/Versions/Current/Resources/QtPrintSupport.prl @@ -0,0 +1,7 @@ + +QMAKE_PRO_INPUT = printsupport.pro +QMAKE_PRL_TARGET = QtPrintSupport +QMAKE_PRL_CONFIG = lex yacc depend_includepath testcase_targets import_plugins import_qpa_plugin asset_catalogs rez prepare_docs qt_docs_targets qt_build_extra file_copies qmake_use qt warn_on release link_prl app_bundle incremental global_init_link_order lib_version_first sdk clang_pch_style shared shared qt_framework release macos osx macx mac darwin unix posix gcc clang llvm sse2 aesni sse3 ssse3 sse4_1 sse4_2 avx avx2 avx512f avx512bw avx512cd avx512dq avx512er avx512ifma avx512pf avx512vbmi avx512vl compile_examples f16c force_debug_info largefile precompile_header rdrnd shani x86SimdAlways prefix_build force_independent no_warn_empty_obj_files utf8_source create_prl link_prl no_private_qt_headers_warning QTDIR_build qt_example_installs exceptions_off testcase_exceptions explicitlib testcase_no_bundle warning_clean generated_privates module_frameworks lib_bundle relative_qt_rpath app_extension_api_only git_build target_qt c++11 strict_c++ c++14 c99 c11 hide_symbols separate_debug_info need_fwd_pri qt_install_module create_cmake sliced_bundle compiler_supports_fpmath create_pc have_target dll debug_info objective_c any_bundle thread uic opengl moc resources +QMAKE_PRL_VERSION = 5.14.2 +QMAKE_PRL_LIBS = -F$$[QT_INSTALL_LIBS] -framework QtWidgets -framework QtGui -framework AppKit -framework Metal -framework QtCore -framework DiskArbitration -framework IOKit +QMAKE_PRL_LIBS_FOR_CMAKE = -F$$[QT_INSTALL_LIBS];-framework;QtWidgets;-framework;QtGui;-framework;AppKit;-framework;Metal;-framework;QtCore;-framework;DiskArbitration;-framework;IOKit; diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtQml.framework/QtQml b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtQml.framework/QtQml new file mode 100755 index 0000000..9389479 Binary files /dev/null and b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtQml.framework/QtQml differ diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtQml.framework/Resources/Info.plist b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtQml.framework/Resources/Info.plist new file mode 100644 index 0000000..1c221b3 --- /dev/null +++ b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtQml.framework/Resources/Info.plist @@ -0,0 +1,20 @@ + + + + + CFBundleExecutable + QtQml + CFBundleIdentifier + org.qt-project.QtQml + CFBundlePackageType + FMWK + CFBundleShortVersionString + 5.14 + CFBundleSignature + ???? + CFBundleVersion + 5.14.2 + NOTE + Please, do NOT change this file -- It was generated by Qt/QMake. + + diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtQml.framework/Resources/QtQml.prl b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtQml.framework/Resources/QtQml.prl new file mode 100644 index 0000000..3409f33 --- /dev/null +++ b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtQml.framework/Resources/QtQml.prl @@ -0,0 +1,7 @@ + +QMAKE_PRO_INPUT = qml.pro +QMAKE_PRL_TARGET = QtQml +QMAKE_PRL_CONFIG = lex yacc depend_includepath testcase_targets import_plugins import_qpa_plugin asset_catalogs rez prepare_docs qt_docs_targets qt_build_extra file_copies qmake_use qt warn_on release link_prl app_bundle incremental global_init_link_order lib_version_first sdk clang_pch_style shared shared qt_framework release macos osx macx mac darwin unix posix gcc clang llvm sse2 aesni sse3 ssse3 sse4_1 sse4_2 avx avx2 avx512f avx512bw avx512cd avx512dq avx512er avx512ifma avx512pf avx512vbmi avx512vl compile_examples f16c force_debug_info largefile precompile_header rdrnd shani x86SimdAlways prefix_build force_independent no_warn_empty_obj_files utf8_source create_prl link_prl no_private_qt_headers_warning QTDIR_build qt_example_installs exceptions_off testcase_exceptions explicitlib testcase_no_bundle warning_clean python_available qt_tracepoints qlalr generated_privates module_frameworks lib_bundle relative_qt_rpath app_extension_api_only git_build target_qt c++11 strict_c++ c++14 c99 c11 hide_symbols separate_debug_info need_fwd_pri qt_install_module create_cmake sliced_bundle compiler_supports_fpmath create_pc have_target dll debug_info objective_c any_bundle thread moc resources +QMAKE_PRL_VERSION = 5.14.2 +QMAKE_PRL_LIBS = -F$$[QT_INSTALL_LIBS] -framework QtNetwork -framework QtCore -framework DiskArbitration -framework IOKit +QMAKE_PRL_LIBS_FOR_CMAKE = -F$$[QT_INSTALL_LIBS];-framework;QtNetwork;-framework;QtCore;-framework;DiskArbitration;-framework;IOKit; diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtQml.framework/Versions/5/QtQml b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtQml.framework/Versions/5/QtQml new file mode 100755 index 0000000..9389479 Binary files /dev/null and b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtQml.framework/Versions/5/QtQml differ diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtQml.framework/Versions/5/Resources/Info.plist b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtQml.framework/Versions/5/Resources/Info.plist new file mode 100644 index 0000000..1c221b3 --- /dev/null +++ b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtQml.framework/Versions/5/Resources/Info.plist @@ -0,0 +1,20 @@ + + + + + CFBundleExecutable + QtQml + CFBundleIdentifier + org.qt-project.QtQml + CFBundlePackageType + FMWK + CFBundleShortVersionString + 5.14 + CFBundleSignature + ???? + CFBundleVersion + 5.14.2 + NOTE + Please, do NOT change this file -- It was generated by Qt/QMake. + + diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtQml.framework/Versions/5/Resources/QtQml.prl b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtQml.framework/Versions/5/Resources/QtQml.prl new file mode 100644 index 0000000..3409f33 --- /dev/null +++ b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtQml.framework/Versions/5/Resources/QtQml.prl @@ -0,0 +1,7 @@ + +QMAKE_PRO_INPUT = qml.pro +QMAKE_PRL_TARGET = QtQml +QMAKE_PRL_CONFIG = lex yacc depend_includepath testcase_targets import_plugins import_qpa_plugin asset_catalogs rez prepare_docs qt_docs_targets qt_build_extra file_copies qmake_use qt warn_on release link_prl app_bundle incremental global_init_link_order lib_version_first sdk clang_pch_style shared shared qt_framework release macos osx macx mac darwin unix posix gcc clang llvm sse2 aesni sse3 ssse3 sse4_1 sse4_2 avx avx2 avx512f avx512bw avx512cd avx512dq avx512er avx512ifma avx512pf avx512vbmi avx512vl compile_examples f16c force_debug_info largefile precompile_header rdrnd shani x86SimdAlways prefix_build force_independent no_warn_empty_obj_files utf8_source create_prl link_prl no_private_qt_headers_warning QTDIR_build qt_example_installs exceptions_off testcase_exceptions explicitlib testcase_no_bundle warning_clean python_available qt_tracepoints qlalr generated_privates module_frameworks lib_bundle relative_qt_rpath app_extension_api_only git_build target_qt c++11 strict_c++ c++14 c99 c11 hide_symbols separate_debug_info need_fwd_pri qt_install_module create_cmake sliced_bundle compiler_supports_fpmath create_pc have_target dll debug_info objective_c any_bundle thread moc resources +QMAKE_PRL_VERSION = 5.14.2 +QMAKE_PRL_LIBS = -F$$[QT_INSTALL_LIBS] -framework QtNetwork -framework QtCore -framework DiskArbitration -framework IOKit +QMAKE_PRL_LIBS_FOR_CMAKE = -F$$[QT_INSTALL_LIBS];-framework;QtNetwork;-framework;QtCore;-framework;DiskArbitration;-framework;IOKit; diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtQml.framework/Versions/Current/QtQml b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtQml.framework/Versions/Current/QtQml new file mode 100755 index 0000000..9389479 Binary files /dev/null and b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtQml.framework/Versions/Current/QtQml differ diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtQml.framework/Versions/Current/Resources/Info.plist b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtQml.framework/Versions/Current/Resources/Info.plist new file mode 100644 index 0000000..1c221b3 --- /dev/null +++ b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtQml.framework/Versions/Current/Resources/Info.plist @@ -0,0 +1,20 @@ + + + + + CFBundleExecutable + QtQml + CFBundleIdentifier + org.qt-project.QtQml + CFBundlePackageType + FMWK + CFBundleShortVersionString + 5.14 + CFBundleSignature + ???? + CFBundleVersion + 5.14.2 + NOTE + Please, do NOT change this file -- It was generated by Qt/QMake. + + diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtQml.framework/Versions/Current/Resources/QtQml.prl b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtQml.framework/Versions/Current/Resources/QtQml.prl new file mode 100644 index 0000000..3409f33 --- /dev/null +++ b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtQml.framework/Versions/Current/Resources/QtQml.prl @@ -0,0 +1,7 @@ + +QMAKE_PRO_INPUT = qml.pro +QMAKE_PRL_TARGET = QtQml +QMAKE_PRL_CONFIG = lex yacc depend_includepath testcase_targets import_plugins import_qpa_plugin asset_catalogs rez prepare_docs qt_docs_targets qt_build_extra file_copies qmake_use qt warn_on release link_prl app_bundle incremental global_init_link_order lib_version_first sdk clang_pch_style shared shared qt_framework release macos osx macx mac darwin unix posix gcc clang llvm sse2 aesni sse3 ssse3 sse4_1 sse4_2 avx avx2 avx512f avx512bw avx512cd avx512dq avx512er avx512ifma avx512pf avx512vbmi avx512vl compile_examples f16c force_debug_info largefile precompile_header rdrnd shani x86SimdAlways prefix_build force_independent no_warn_empty_obj_files utf8_source create_prl link_prl no_private_qt_headers_warning QTDIR_build qt_example_installs exceptions_off testcase_exceptions explicitlib testcase_no_bundle warning_clean python_available qt_tracepoints qlalr generated_privates module_frameworks lib_bundle relative_qt_rpath app_extension_api_only git_build target_qt c++11 strict_c++ c++14 c99 c11 hide_symbols separate_debug_info need_fwd_pri qt_install_module create_cmake sliced_bundle compiler_supports_fpmath create_pc have_target dll debug_info objective_c any_bundle thread moc resources +QMAKE_PRL_VERSION = 5.14.2 +QMAKE_PRL_LIBS = -F$$[QT_INSTALL_LIBS] -framework QtNetwork -framework QtCore -framework DiskArbitration -framework IOKit +QMAKE_PRL_LIBS_FOR_CMAKE = -F$$[QT_INSTALL_LIBS];-framework;QtNetwork;-framework;QtCore;-framework;DiskArbitration;-framework;IOKit; diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtQmlModels.framework/QtQmlModels b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtQmlModels.framework/QtQmlModels new file mode 100755 index 0000000..40bae2d Binary files /dev/null and b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtQmlModels.framework/QtQmlModels differ diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtQmlModels.framework/Resources/Info.plist b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtQmlModels.framework/Resources/Info.plist new file mode 100644 index 0000000..de94f4f --- /dev/null +++ b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtQmlModels.framework/Resources/Info.plist @@ -0,0 +1,20 @@ + + + + + CFBundleExecutable + QtQmlModels + CFBundleIdentifier + org.qt-project.QtQmlModels + CFBundlePackageType + FMWK + CFBundleShortVersionString + 5.14 + CFBundleSignature + ???? + CFBundleVersion + 5.14.2 + NOTE + Please, do NOT change this file -- It was generated by Qt/QMake. + + diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtQmlModels.framework/Resources/QtQmlModels.prl b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtQmlModels.framework/Resources/QtQmlModels.prl new file mode 100644 index 0000000..e962dbf --- /dev/null +++ b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtQmlModels.framework/Resources/QtQmlModels.prl @@ -0,0 +1,7 @@ + +QMAKE_PRO_INPUT = qmlmodels.pro +QMAKE_PRL_TARGET = QtQmlModels +QMAKE_PRL_CONFIG = lex yacc depend_includepath testcase_targets import_plugins import_qpa_plugin asset_catalogs rez prepare_docs qt_docs_targets qt_build_extra file_copies qmake_use qt warn_on release link_prl app_bundle incremental global_init_link_order lib_version_first sdk clang_pch_style shared shared qt_framework release macos osx macx mac darwin unix posix gcc clang llvm sse2 aesni sse3 ssse3 sse4_1 sse4_2 avx avx2 avx512f avx512bw avx512cd avx512dq avx512er avx512ifma avx512pf avx512vbmi avx512vl compile_examples f16c force_debug_info largefile precompile_header rdrnd shani x86SimdAlways prefix_build force_independent no_warn_empty_obj_files utf8_source create_prl link_prl no_private_qt_headers_warning QTDIR_build qt_example_installs exceptions_off testcase_exceptions explicitlib testcase_no_bundle warning_clean python_available generated_privates module_frameworks lib_bundle relative_qt_rpath app_extension_api_only git_build target_qt c++11 strict_c++ c++14 c99 c11 hide_symbols separate_debug_info need_fwd_pri qt_install_module create_cmake sliced_bundle compiler_supports_fpmath create_pc have_target dll debug_info objective_c any_bundle thread moc resources +QMAKE_PRL_VERSION = 5.14.2 +QMAKE_PRL_LIBS = -F$$[QT_INSTALL_LIBS] -F$$[QT_INSTALL_LIBS] -framework QtQml -framework QtNetwork -framework QtCore -framework DiskArbitration -framework IOKit +QMAKE_PRL_LIBS_FOR_CMAKE = -F$$[QT_INSTALL_LIBS];-F$$[QT_INSTALL_LIBS];-framework;QtQml;-framework;QtNetwork;-framework;QtCore;-framework;DiskArbitration;-framework;IOKit; diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtQmlModels.framework/Versions/5/QtQmlModels b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtQmlModels.framework/Versions/5/QtQmlModels new file mode 100755 index 0000000..40bae2d Binary files /dev/null and b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtQmlModels.framework/Versions/5/QtQmlModels differ diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtQmlModels.framework/Versions/5/Resources/Info.plist b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtQmlModels.framework/Versions/5/Resources/Info.plist new file mode 100644 index 0000000..de94f4f --- /dev/null +++ b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtQmlModels.framework/Versions/5/Resources/Info.plist @@ -0,0 +1,20 @@ + + + + + CFBundleExecutable + QtQmlModels + CFBundleIdentifier + org.qt-project.QtQmlModels + CFBundlePackageType + FMWK + CFBundleShortVersionString + 5.14 + CFBundleSignature + ???? + CFBundleVersion + 5.14.2 + NOTE + Please, do NOT change this file -- It was generated by Qt/QMake. + + diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtQmlModels.framework/Versions/5/Resources/QtQmlModels.prl b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtQmlModels.framework/Versions/5/Resources/QtQmlModels.prl new file mode 100644 index 0000000..e962dbf --- /dev/null +++ b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtQmlModels.framework/Versions/5/Resources/QtQmlModels.prl @@ -0,0 +1,7 @@ + +QMAKE_PRO_INPUT = qmlmodels.pro +QMAKE_PRL_TARGET = QtQmlModels +QMAKE_PRL_CONFIG = lex yacc depend_includepath testcase_targets import_plugins import_qpa_plugin asset_catalogs rez prepare_docs qt_docs_targets qt_build_extra file_copies qmake_use qt warn_on release link_prl app_bundle incremental global_init_link_order lib_version_first sdk clang_pch_style shared shared qt_framework release macos osx macx mac darwin unix posix gcc clang llvm sse2 aesni sse3 ssse3 sse4_1 sse4_2 avx avx2 avx512f avx512bw avx512cd avx512dq avx512er avx512ifma avx512pf avx512vbmi avx512vl compile_examples f16c force_debug_info largefile precompile_header rdrnd shani x86SimdAlways prefix_build force_independent no_warn_empty_obj_files utf8_source create_prl link_prl no_private_qt_headers_warning QTDIR_build qt_example_installs exceptions_off testcase_exceptions explicitlib testcase_no_bundle warning_clean python_available generated_privates module_frameworks lib_bundle relative_qt_rpath app_extension_api_only git_build target_qt c++11 strict_c++ c++14 c99 c11 hide_symbols separate_debug_info need_fwd_pri qt_install_module create_cmake sliced_bundle compiler_supports_fpmath create_pc have_target dll debug_info objective_c any_bundle thread moc resources +QMAKE_PRL_VERSION = 5.14.2 +QMAKE_PRL_LIBS = -F$$[QT_INSTALL_LIBS] -F$$[QT_INSTALL_LIBS] -framework QtQml -framework QtNetwork -framework QtCore -framework DiskArbitration -framework IOKit +QMAKE_PRL_LIBS_FOR_CMAKE = -F$$[QT_INSTALL_LIBS];-F$$[QT_INSTALL_LIBS];-framework;QtQml;-framework;QtNetwork;-framework;QtCore;-framework;DiskArbitration;-framework;IOKit; diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtQmlModels.framework/Versions/Current/QtQmlModels b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtQmlModels.framework/Versions/Current/QtQmlModels new file mode 100755 index 0000000..40bae2d Binary files /dev/null and b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtQmlModels.framework/Versions/Current/QtQmlModels differ diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtQmlModels.framework/Versions/Current/Resources/Info.plist b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtQmlModels.framework/Versions/Current/Resources/Info.plist new file mode 100644 index 0000000..de94f4f --- /dev/null +++ b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtQmlModels.framework/Versions/Current/Resources/Info.plist @@ -0,0 +1,20 @@ + + + + + CFBundleExecutable + QtQmlModels + CFBundleIdentifier + org.qt-project.QtQmlModels + CFBundlePackageType + FMWK + CFBundleShortVersionString + 5.14 + CFBundleSignature + ???? + CFBundleVersion + 5.14.2 + NOTE + Please, do NOT change this file -- It was generated by Qt/QMake. + + diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtQmlModels.framework/Versions/Current/Resources/QtQmlModels.prl b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtQmlModels.framework/Versions/Current/Resources/QtQmlModels.prl new file mode 100644 index 0000000..e962dbf --- /dev/null +++ b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtQmlModels.framework/Versions/Current/Resources/QtQmlModels.prl @@ -0,0 +1,7 @@ + +QMAKE_PRO_INPUT = qmlmodels.pro +QMAKE_PRL_TARGET = QtQmlModels +QMAKE_PRL_CONFIG = lex yacc depend_includepath testcase_targets import_plugins import_qpa_plugin asset_catalogs rez prepare_docs qt_docs_targets qt_build_extra file_copies qmake_use qt warn_on release link_prl app_bundle incremental global_init_link_order lib_version_first sdk clang_pch_style shared shared qt_framework release macos osx macx mac darwin unix posix gcc clang llvm sse2 aesni sse3 ssse3 sse4_1 sse4_2 avx avx2 avx512f avx512bw avx512cd avx512dq avx512er avx512ifma avx512pf avx512vbmi avx512vl compile_examples f16c force_debug_info largefile precompile_header rdrnd shani x86SimdAlways prefix_build force_independent no_warn_empty_obj_files utf8_source create_prl link_prl no_private_qt_headers_warning QTDIR_build qt_example_installs exceptions_off testcase_exceptions explicitlib testcase_no_bundle warning_clean python_available generated_privates module_frameworks lib_bundle relative_qt_rpath app_extension_api_only git_build target_qt c++11 strict_c++ c++14 c99 c11 hide_symbols separate_debug_info need_fwd_pri qt_install_module create_cmake sliced_bundle compiler_supports_fpmath create_pc have_target dll debug_info objective_c any_bundle thread moc resources +QMAKE_PRL_VERSION = 5.14.2 +QMAKE_PRL_LIBS = -F$$[QT_INSTALL_LIBS] -F$$[QT_INSTALL_LIBS] -framework QtQml -framework QtNetwork -framework QtCore -framework DiskArbitration -framework IOKit +QMAKE_PRL_LIBS_FOR_CMAKE = -F$$[QT_INSTALL_LIBS];-F$$[QT_INSTALL_LIBS];-framework;QtQml;-framework;QtNetwork;-framework;QtCore;-framework;DiskArbitration;-framework;IOKit; diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtQuick.framework/QtQuick b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtQuick.framework/QtQuick new file mode 100755 index 0000000..05fbfcc Binary files /dev/null and b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtQuick.framework/QtQuick differ diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtQuick.framework/Resources/Info.plist b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtQuick.framework/Resources/Info.plist new file mode 100644 index 0000000..08b6fdb --- /dev/null +++ b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtQuick.framework/Resources/Info.plist @@ -0,0 +1,20 @@ + + + + + CFBundleExecutable + QtQuick + CFBundleIdentifier + org.qt-project.QtQuick + CFBundlePackageType + FMWK + CFBundleShortVersionString + 5.14 + CFBundleSignature + ???? + CFBundleVersion + 5.14.2 + NOTE + Please, do NOT change this file -- It was generated by Qt/QMake. + + diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtQuick.framework/Resources/QtQuick.prl b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtQuick.framework/Resources/QtQuick.prl new file mode 100644 index 0000000..93549dd --- /dev/null +++ b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtQuick.framework/Resources/QtQuick.prl @@ -0,0 +1,7 @@ + +QMAKE_PRO_INPUT = quick.pro +QMAKE_PRL_TARGET = QtQuick +QMAKE_PRL_CONFIG = lex yacc depend_includepath testcase_targets import_plugins import_qpa_plugin asset_catalogs rez prepare_docs qt_docs_targets qt_build_extra file_copies qmake_use qt warn_on release link_prl app_bundle incremental global_init_link_order lib_version_first sdk clang_pch_style shared shared qt_framework release macos osx macx mac darwin unix posix gcc clang llvm sse2 aesni sse3 ssse3 sse4_1 sse4_2 avx avx2 avx512f avx512bw avx512cd avx512dq avx512er avx512ifma avx512pf avx512vbmi avx512vl compile_examples f16c force_debug_info largefile precompile_header rdrnd shani x86SimdAlways prefix_build force_independent no_warn_empty_obj_files utf8_source create_prl link_prl no_private_qt_headers_warning QTDIR_build qt_example_installs exceptions_off testcase_exceptions explicitlib testcase_no_bundle warning_clean python_available generated_privates module_frameworks lib_bundle relative_qt_rpath app_extension_api_only git_build target_qt c++11 strict_c++ c++14 c99 c11 hide_symbols separate_debug_info need_fwd_pri qt_install_module create_cmake sliced_bundle compiler_supports_fpmath create_pc have_target dll debug_info objective_c any_bundle thread opengl moc resources moc resources +QMAKE_PRL_VERSION = 5.14.2 +QMAKE_PRL_LIBS = -F$$[QT_INSTALL_LIBS] -F$$[QT_INSTALL_LIBS] -framework QtQmlModels -framework QtQml -framework QtGui -framework AppKit -framework Metal -framework QtNetwork -framework QtCore -framework DiskArbitration -framework IOKit +QMAKE_PRL_LIBS_FOR_CMAKE = -F$$[QT_INSTALL_LIBS];-F$$[QT_INSTALL_LIBS];-framework;QtQmlModels;-framework;QtQml;-framework;QtGui;-framework;AppKit;-framework;Metal;-framework;QtNetwork;-framework;QtCore;-framework;DiskArbitration;-framework;IOKit; diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtQuick.framework/Versions/5/QtQuick b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtQuick.framework/Versions/5/QtQuick new file mode 100755 index 0000000..05fbfcc Binary files /dev/null and b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtQuick.framework/Versions/5/QtQuick differ diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtQuick.framework/Versions/5/Resources/Info.plist b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtQuick.framework/Versions/5/Resources/Info.plist new file mode 100644 index 0000000..08b6fdb --- /dev/null +++ b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtQuick.framework/Versions/5/Resources/Info.plist @@ -0,0 +1,20 @@ + + + + + CFBundleExecutable + QtQuick + CFBundleIdentifier + org.qt-project.QtQuick + CFBundlePackageType + FMWK + CFBundleShortVersionString + 5.14 + CFBundleSignature + ???? + CFBundleVersion + 5.14.2 + NOTE + Please, do NOT change this file -- It was generated by Qt/QMake. + + diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtQuick.framework/Versions/5/Resources/QtQuick.prl b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtQuick.framework/Versions/5/Resources/QtQuick.prl new file mode 100644 index 0000000..93549dd --- /dev/null +++ b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtQuick.framework/Versions/5/Resources/QtQuick.prl @@ -0,0 +1,7 @@ + +QMAKE_PRO_INPUT = quick.pro +QMAKE_PRL_TARGET = QtQuick +QMAKE_PRL_CONFIG = lex yacc depend_includepath testcase_targets import_plugins import_qpa_plugin asset_catalogs rez prepare_docs qt_docs_targets qt_build_extra file_copies qmake_use qt warn_on release link_prl app_bundle incremental global_init_link_order lib_version_first sdk clang_pch_style shared shared qt_framework release macos osx macx mac darwin unix posix gcc clang llvm sse2 aesni sse3 ssse3 sse4_1 sse4_2 avx avx2 avx512f avx512bw avx512cd avx512dq avx512er avx512ifma avx512pf avx512vbmi avx512vl compile_examples f16c force_debug_info largefile precompile_header rdrnd shani x86SimdAlways prefix_build force_independent no_warn_empty_obj_files utf8_source create_prl link_prl no_private_qt_headers_warning QTDIR_build qt_example_installs exceptions_off testcase_exceptions explicitlib testcase_no_bundle warning_clean python_available generated_privates module_frameworks lib_bundle relative_qt_rpath app_extension_api_only git_build target_qt c++11 strict_c++ c++14 c99 c11 hide_symbols separate_debug_info need_fwd_pri qt_install_module create_cmake sliced_bundle compiler_supports_fpmath create_pc have_target dll debug_info objective_c any_bundle thread opengl moc resources moc resources +QMAKE_PRL_VERSION = 5.14.2 +QMAKE_PRL_LIBS = -F$$[QT_INSTALL_LIBS] -F$$[QT_INSTALL_LIBS] -framework QtQmlModels -framework QtQml -framework QtGui -framework AppKit -framework Metal -framework QtNetwork -framework QtCore -framework DiskArbitration -framework IOKit +QMAKE_PRL_LIBS_FOR_CMAKE = -F$$[QT_INSTALL_LIBS];-F$$[QT_INSTALL_LIBS];-framework;QtQmlModels;-framework;QtQml;-framework;QtGui;-framework;AppKit;-framework;Metal;-framework;QtNetwork;-framework;QtCore;-framework;DiskArbitration;-framework;IOKit; diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtQuick.framework/Versions/Current/QtQuick b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtQuick.framework/Versions/Current/QtQuick new file mode 100755 index 0000000..05fbfcc Binary files /dev/null and b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtQuick.framework/Versions/Current/QtQuick differ diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtQuick.framework/Versions/Current/Resources/Info.plist b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtQuick.framework/Versions/Current/Resources/Info.plist new file mode 100644 index 0000000..08b6fdb --- /dev/null +++ b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtQuick.framework/Versions/Current/Resources/Info.plist @@ -0,0 +1,20 @@ + + + + + CFBundleExecutable + QtQuick + CFBundleIdentifier + org.qt-project.QtQuick + CFBundlePackageType + FMWK + CFBundleShortVersionString + 5.14 + CFBundleSignature + ???? + CFBundleVersion + 5.14.2 + NOTE + Please, do NOT change this file -- It was generated by Qt/QMake. + + diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtQuick.framework/Versions/Current/Resources/QtQuick.prl b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtQuick.framework/Versions/Current/Resources/QtQuick.prl new file mode 100644 index 0000000..93549dd --- /dev/null +++ b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtQuick.framework/Versions/Current/Resources/QtQuick.prl @@ -0,0 +1,7 @@ + +QMAKE_PRO_INPUT = quick.pro +QMAKE_PRL_TARGET = QtQuick +QMAKE_PRL_CONFIG = lex yacc depend_includepath testcase_targets import_plugins import_qpa_plugin asset_catalogs rez prepare_docs qt_docs_targets qt_build_extra file_copies qmake_use qt warn_on release link_prl app_bundle incremental global_init_link_order lib_version_first sdk clang_pch_style shared shared qt_framework release macos osx macx mac darwin unix posix gcc clang llvm sse2 aesni sse3 ssse3 sse4_1 sse4_2 avx avx2 avx512f avx512bw avx512cd avx512dq avx512er avx512ifma avx512pf avx512vbmi avx512vl compile_examples f16c force_debug_info largefile precompile_header rdrnd shani x86SimdAlways prefix_build force_independent no_warn_empty_obj_files utf8_source create_prl link_prl no_private_qt_headers_warning QTDIR_build qt_example_installs exceptions_off testcase_exceptions explicitlib testcase_no_bundle warning_clean python_available generated_privates module_frameworks lib_bundle relative_qt_rpath app_extension_api_only git_build target_qt c++11 strict_c++ c++14 c99 c11 hide_symbols separate_debug_info need_fwd_pri qt_install_module create_cmake sliced_bundle compiler_supports_fpmath create_pc have_target dll debug_info objective_c any_bundle thread opengl moc resources moc resources +QMAKE_PRL_VERSION = 5.14.2 +QMAKE_PRL_LIBS = -F$$[QT_INSTALL_LIBS] -F$$[QT_INSTALL_LIBS] -framework QtQmlModels -framework QtQml -framework QtGui -framework AppKit -framework Metal -framework QtNetwork -framework QtCore -framework DiskArbitration -framework IOKit +QMAKE_PRL_LIBS_FOR_CMAKE = -F$$[QT_INSTALL_LIBS];-F$$[QT_INSTALL_LIBS];-framework;QtQmlModels;-framework;QtQml;-framework;QtGui;-framework;AppKit;-framework;Metal;-framework;QtNetwork;-framework;QtCore;-framework;DiskArbitration;-framework;IOKit; diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtSerialPort.framework/QtSerialPort b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtSerialPort.framework/QtSerialPort new file mode 100755 index 0000000..0c15e95 Binary files /dev/null and b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtSerialPort.framework/QtSerialPort differ diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtSerialPort.framework/Resources/Info.plist b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtSerialPort.framework/Resources/Info.plist new file mode 100644 index 0000000..bc252a8 --- /dev/null +++ b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtSerialPort.framework/Resources/Info.plist @@ -0,0 +1,20 @@ + + + + + CFBundleExecutable + QtSerialPort + CFBundleIdentifier + org.qt-project.QtSerialPort + CFBundlePackageType + FMWK + CFBundleShortVersionString + 5.14 + CFBundleSignature + ???? + CFBundleVersion + 5.14.2 + NOTE + Please, do NOT change this file -- It was generated by Qt/QMake. + + diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtSerialPort.framework/Resources/QtSerialPort.prl b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtSerialPort.framework/Resources/QtSerialPort.prl new file mode 100644 index 0000000..f50bf99 --- /dev/null +++ b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtSerialPort.framework/Resources/QtSerialPort.prl @@ -0,0 +1,7 @@ + +QMAKE_PRO_INPUT = serialport.pro +QMAKE_PRL_TARGET = QtSerialPort +QMAKE_PRL_CONFIG = lex yacc depend_includepath testcase_targets import_plugins import_qpa_plugin asset_catalogs rez prepare_docs qt_docs_targets qt_build_extra file_copies qmake_use qt warn_on release link_prl app_bundle incremental global_init_link_order lib_version_first sdk clang_pch_style shared shared qt_framework release macos osx macx mac darwin unix posix gcc clang llvm sse2 aesni sse3 ssse3 sse4_1 sse4_2 avx avx2 avx512f avx512bw avx512cd avx512dq avx512er avx512ifma avx512pf avx512vbmi avx512vl compile_examples f16c force_debug_info largefile precompile_header rdrnd shani x86SimdAlways prefix_build force_independent no_warn_empty_obj_files utf8_source create_prl link_prl no_private_qt_headers_warning QTDIR_build qt_example_installs exceptions_off testcase_exceptions explicitlib testcase_no_bundle done_config_ntddmodm generated_privates module_frameworks lib_bundle relative_qt_rpath app_extension_api_only git_build target_qt c++11 strict_c++ c++14 c99 c11 hide_symbols separate_debug_info need_fwd_pri qt_install_module create_cmake sliced_bundle compiler_supports_fpmath create_pc have_target dll debug_info objective_c any_bundle thread moc resources +QMAKE_PRL_VERSION = 5.14.2 +QMAKE_PRL_LIBS = -F$$[QT_INSTALL_LIBS] -framework QtCore -framework DiskArbitration -framework IOKit +QMAKE_PRL_LIBS_FOR_CMAKE = -F$$[QT_INSTALL_LIBS];-framework;QtCore;-framework;DiskArbitration;-framework;IOKit; diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtSerialPort.framework/Versions/5/QtSerialPort b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtSerialPort.framework/Versions/5/QtSerialPort new file mode 100755 index 0000000..0c15e95 Binary files /dev/null and b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtSerialPort.framework/Versions/5/QtSerialPort differ diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtSerialPort.framework/Versions/5/Resources/Info.plist b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtSerialPort.framework/Versions/5/Resources/Info.plist new file mode 100644 index 0000000..bc252a8 --- /dev/null +++ b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtSerialPort.framework/Versions/5/Resources/Info.plist @@ -0,0 +1,20 @@ + + + + + CFBundleExecutable + QtSerialPort + CFBundleIdentifier + org.qt-project.QtSerialPort + CFBundlePackageType + FMWK + CFBundleShortVersionString + 5.14 + CFBundleSignature + ???? + CFBundleVersion + 5.14.2 + NOTE + Please, do NOT change this file -- It was generated by Qt/QMake. + + diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtSerialPort.framework/Versions/5/Resources/QtSerialPort.prl b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtSerialPort.framework/Versions/5/Resources/QtSerialPort.prl new file mode 100644 index 0000000..f50bf99 --- /dev/null +++ b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtSerialPort.framework/Versions/5/Resources/QtSerialPort.prl @@ -0,0 +1,7 @@ + +QMAKE_PRO_INPUT = serialport.pro +QMAKE_PRL_TARGET = QtSerialPort +QMAKE_PRL_CONFIG = lex yacc depend_includepath testcase_targets import_plugins import_qpa_plugin asset_catalogs rez prepare_docs qt_docs_targets qt_build_extra file_copies qmake_use qt warn_on release link_prl app_bundle incremental global_init_link_order lib_version_first sdk clang_pch_style shared shared qt_framework release macos osx macx mac darwin unix posix gcc clang llvm sse2 aesni sse3 ssse3 sse4_1 sse4_2 avx avx2 avx512f avx512bw avx512cd avx512dq avx512er avx512ifma avx512pf avx512vbmi avx512vl compile_examples f16c force_debug_info largefile precompile_header rdrnd shani x86SimdAlways prefix_build force_independent no_warn_empty_obj_files utf8_source create_prl link_prl no_private_qt_headers_warning QTDIR_build qt_example_installs exceptions_off testcase_exceptions explicitlib testcase_no_bundle done_config_ntddmodm generated_privates module_frameworks lib_bundle relative_qt_rpath app_extension_api_only git_build target_qt c++11 strict_c++ c++14 c99 c11 hide_symbols separate_debug_info need_fwd_pri qt_install_module create_cmake sliced_bundle compiler_supports_fpmath create_pc have_target dll debug_info objective_c any_bundle thread moc resources +QMAKE_PRL_VERSION = 5.14.2 +QMAKE_PRL_LIBS = -F$$[QT_INSTALL_LIBS] -framework QtCore -framework DiskArbitration -framework IOKit +QMAKE_PRL_LIBS_FOR_CMAKE = -F$$[QT_INSTALL_LIBS];-framework;QtCore;-framework;DiskArbitration;-framework;IOKit; diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtSerialPort.framework/Versions/Current/QtSerialPort b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtSerialPort.framework/Versions/Current/QtSerialPort new file mode 100755 index 0000000..0c15e95 Binary files /dev/null and b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtSerialPort.framework/Versions/Current/QtSerialPort differ diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtSerialPort.framework/Versions/Current/Resources/Info.plist b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtSerialPort.framework/Versions/Current/Resources/Info.plist new file mode 100644 index 0000000..bc252a8 --- /dev/null +++ b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtSerialPort.framework/Versions/Current/Resources/Info.plist @@ -0,0 +1,20 @@ + + + + + CFBundleExecutable + QtSerialPort + CFBundleIdentifier + org.qt-project.QtSerialPort + CFBundlePackageType + FMWK + CFBundleShortVersionString + 5.14 + CFBundleSignature + ???? + CFBundleVersion + 5.14.2 + NOTE + Please, do NOT change this file -- It was generated by Qt/QMake. + + diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtSerialPort.framework/Versions/Current/Resources/QtSerialPort.prl b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtSerialPort.framework/Versions/Current/Resources/QtSerialPort.prl new file mode 100644 index 0000000..f50bf99 --- /dev/null +++ b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtSerialPort.framework/Versions/Current/Resources/QtSerialPort.prl @@ -0,0 +1,7 @@ + +QMAKE_PRO_INPUT = serialport.pro +QMAKE_PRL_TARGET = QtSerialPort +QMAKE_PRL_CONFIG = lex yacc depend_includepath testcase_targets import_plugins import_qpa_plugin asset_catalogs rez prepare_docs qt_docs_targets qt_build_extra file_copies qmake_use qt warn_on release link_prl app_bundle incremental global_init_link_order lib_version_first sdk clang_pch_style shared shared qt_framework release macos osx macx mac darwin unix posix gcc clang llvm sse2 aesni sse3 ssse3 sse4_1 sse4_2 avx avx2 avx512f avx512bw avx512cd avx512dq avx512er avx512ifma avx512pf avx512vbmi avx512vl compile_examples f16c force_debug_info largefile precompile_header rdrnd shani x86SimdAlways prefix_build force_independent no_warn_empty_obj_files utf8_source create_prl link_prl no_private_qt_headers_warning QTDIR_build qt_example_installs exceptions_off testcase_exceptions explicitlib testcase_no_bundle done_config_ntddmodm generated_privates module_frameworks lib_bundle relative_qt_rpath app_extension_api_only git_build target_qt c++11 strict_c++ c++14 c99 c11 hide_symbols separate_debug_info need_fwd_pri qt_install_module create_cmake sliced_bundle compiler_supports_fpmath create_pc have_target dll debug_info objective_c any_bundle thread moc resources +QMAKE_PRL_VERSION = 5.14.2 +QMAKE_PRL_LIBS = -F$$[QT_INSTALL_LIBS] -framework QtCore -framework DiskArbitration -framework IOKit +QMAKE_PRL_LIBS_FOR_CMAKE = -F$$[QT_INSTALL_LIBS];-framework;QtCore;-framework;DiskArbitration;-framework;IOKit; diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtSql.framework/QtSql b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtSql.framework/QtSql new file mode 100755 index 0000000..fb92407 Binary files /dev/null and b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtSql.framework/QtSql differ diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtSql.framework/Resources/Info.plist b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtSql.framework/Resources/Info.plist new file mode 100644 index 0000000..a3dc5e5 --- /dev/null +++ b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtSql.framework/Resources/Info.plist @@ -0,0 +1,20 @@ + + + + + CFBundleExecutable + QtSql + CFBundleIdentifier + org.qt-project.QtSql + CFBundlePackageType + FMWK + CFBundleShortVersionString + 5.14 + CFBundleSignature + ???? + CFBundleVersion + 5.14.2 + NOTE + Please, do NOT change this file -- It was generated by Qt/QMake. + + diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtSql.framework/Resources/QtSql.prl b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtSql.framework/Resources/QtSql.prl new file mode 100644 index 0000000..3026e6e --- /dev/null +++ b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtSql.framework/Resources/QtSql.prl @@ -0,0 +1,7 @@ + +QMAKE_PRO_INPUT = sql.pro +QMAKE_PRL_TARGET = QtSql +QMAKE_PRL_CONFIG = lex yacc depend_includepath testcase_targets import_plugins import_qpa_plugin asset_catalogs rez prepare_docs qt_docs_targets qt_build_extra file_copies qmake_use qt warn_on release link_prl app_bundle incremental global_init_link_order lib_version_first sdk clang_pch_style shared shared qt_framework release macos osx macx mac darwin unix posix gcc clang llvm sse2 aesni sse3 ssse3 sse4_1 sse4_2 avx avx2 avx512f avx512bw avx512cd avx512dq avx512er avx512ifma avx512pf avx512vbmi avx512vl compile_examples f16c force_debug_info largefile precompile_header rdrnd shani x86SimdAlways prefix_build force_independent no_warn_empty_obj_files utf8_source create_prl link_prl no_private_qt_headers_warning QTDIR_build qt_example_installs exceptions_off testcase_exceptions explicitlib testcase_no_bundle warning_clean generated_privates module_frameworks lib_bundle relative_qt_rpath app_extension_api_only git_build target_qt c++11 strict_c++ c++14 c99 c11 hide_symbols separate_debug_info need_fwd_pri qt_install_module create_cmake sliced_bundle compiler_supports_fpmath create_pc have_target dll debug_info objective_c any_bundle thread moc resources +QMAKE_PRL_VERSION = 5.14.2 +QMAKE_PRL_LIBS = -F$$[QT_INSTALL_LIBS] -framework QtCore -framework DiskArbitration -framework IOKit +QMAKE_PRL_LIBS_FOR_CMAKE = -F$$[QT_INSTALL_LIBS];-framework;QtCore;-framework;DiskArbitration;-framework;IOKit; diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtSql.framework/Versions/5/QtSql b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtSql.framework/Versions/5/QtSql new file mode 100755 index 0000000..fb92407 Binary files /dev/null and b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtSql.framework/Versions/5/QtSql differ diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtSql.framework/Versions/5/Resources/Info.plist b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtSql.framework/Versions/5/Resources/Info.plist new file mode 100644 index 0000000..a3dc5e5 --- /dev/null +++ b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtSql.framework/Versions/5/Resources/Info.plist @@ -0,0 +1,20 @@ + + + + + CFBundleExecutable + QtSql + CFBundleIdentifier + org.qt-project.QtSql + CFBundlePackageType + FMWK + CFBundleShortVersionString + 5.14 + CFBundleSignature + ???? + CFBundleVersion + 5.14.2 + NOTE + Please, do NOT change this file -- It was generated by Qt/QMake. + + diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtSql.framework/Versions/5/Resources/QtSql.prl b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtSql.framework/Versions/5/Resources/QtSql.prl new file mode 100644 index 0000000..3026e6e --- /dev/null +++ b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtSql.framework/Versions/5/Resources/QtSql.prl @@ -0,0 +1,7 @@ + +QMAKE_PRO_INPUT = sql.pro +QMAKE_PRL_TARGET = QtSql +QMAKE_PRL_CONFIG = lex yacc depend_includepath testcase_targets import_plugins import_qpa_plugin asset_catalogs rez prepare_docs qt_docs_targets qt_build_extra file_copies qmake_use qt warn_on release link_prl app_bundle incremental global_init_link_order lib_version_first sdk clang_pch_style shared shared qt_framework release macos osx macx mac darwin unix posix gcc clang llvm sse2 aesni sse3 ssse3 sse4_1 sse4_2 avx avx2 avx512f avx512bw avx512cd avx512dq avx512er avx512ifma avx512pf avx512vbmi avx512vl compile_examples f16c force_debug_info largefile precompile_header rdrnd shani x86SimdAlways prefix_build force_independent no_warn_empty_obj_files utf8_source create_prl link_prl no_private_qt_headers_warning QTDIR_build qt_example_installs exceptions_off testcase_exceptions explicitlib testcase_no_bundle warning_clean generated_privates module_frameworks lib_bundle relative_qt_rpath app_extension_api_only git_build target_qt c++11 strict_c++ c++14 c99 c11 hide_symbols separate_debug_info need_fwd_pri qt_install_module create_cmake sliced_bundle compiler_supports_fpmath create_pc have_target dll debug_info objective_c any_bundle thread moc resources +QMAKE_PRL_VERSION = 5.14.2 +QMAKE_PRL_LIBS = -F$$[QT_INSTALL_LIBS] -framework QtCore -framework DiskArbitration -framework IOKit +QMAKE_PRL_LIBS_FOR_CMAKE = -F$$[QT_INSTALL_LIBS];-framework;QtCore;-framework;DiskArbitration;-framework;IOKit; diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtSql.framework/Versions/Current/QtSql b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtSql.framework/Versions/Current/QtSql new file mode 100755 index 0000000..fb92407 Binary files /dev/null and b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtSql.framework/Versions/Current/QtSql differ diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtSql.framework/Versions/Current/Resources/Info.plist b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtSql.framework/Versions/Current/Resources/Info.plist new file mode 100644 index 0000000..a3dc5e5 --- /dev/null +++ b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtSql.framework/Versions/Current/Resources/Info.plist @@ -0,0 +1,20 @@ + + + + + CFBundleExecutable + QtSql + CFBundleIdentifier + org.qt-project.QtSql + CFBundlePackageType + FMWK + CFBundleShortVersionString + 5.14 + CFBundleSignature + ???? + CFBundleVersion + 5.14.2 + NOTE + Please, do NOT change this file -- It was generated by Qt/QMake. + + diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtSql.framework/Versions/Current/Resources/QtSql.prl b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtSql.framework/Versions/Current/Resources/QtSql.prl new file mode 100644 index 0000000..3026e6e --- /dev/null +++ b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtSql.framework/Versions/Current/Resources/QtSql.prl @@ -0,0 +1,7 @@ + +QMAKE_PRO_INPUT = sql.pro +QMAKE_PRL_TARGET = QtSql +QMAKE_PRL_CONFIG = lex yacc depend_includepath testcase_targets import_plugins import_qpa_plugin asset_catalogs rez prepare_docs qt_docs_targets qt_build_extra file_copies qmake_use qt warn_on release link_prl app_bundle incremental global_init_link_order lib_version_first sdk clang_pch_style shared shared qt_framework release macos osx macx mac darwin unix posix gcc clang llvm sse2 aesni sse3 ssse3 sse4_1 sse4_2 avx avx2 avx512f avx512bw avx512cd avx512dq avx512er avx512ifma avx512pf avx512vbmi avx512vl compile_examples f16c force_debug_info largefile precompile_header rdrnd shani x86SimdAlways prefix_build force_independent no_warn_empty_obj_files utf8_source create_prl link_prl no_private_qt_headers_warning QTDIR_build qt_example_installs exceptions_off testcase_exceptions explicitlib testcase_no_bundle warning_clean generated_privates module_frameworks lib_bundle relative_qt_rpath app_extension_api_only git_build target_qt c++11 strict_c++ c++14 c99 c11 hide_symbols separate_debug_info need_fwd_pri qt_install_module create_cmake sliced_bundle compiler_supports_fpmath create_pc have_target dll debug_info objective_c any_bundle thread moc resources +QMAKE_PRL_VERSION = 5.14.2 +QMAKE_PRL_LIBS = -F$$[QT_INSTALL_LIBS] -framework QtCore -framework DiskArbitration -framework IOKit +QMAKE_PRL_LIBS_FOR_CMAKE = -F$$[QT_INSTALL_LIBS];-framework;QtCore;-framework;DiskArbitration;-framework;IOKit; diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtSvg.framework/QtSvg b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtSvg.framework/QtSvg new file mode 100755 index 0000000..35e4765 Binary files /dev/null and b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtSvg.framework/QtSvg differ diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtSvg.framework/Resources/Info.plist b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtSvg.framework/Resources/Info.plist new file mode 100644 index 0000000..2c8268a --- /dev/null +++ b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtSvg.framework/Resources/Info.plist @@ -0,0 +1,20 @@ + + + + + CFBundleExecutable + QtSvg + CFBundleIdentifier + org.qt-project.QtSvg + CFBundlePackageType + FMWK + CFBundleShortVersionString + 5.14 + CFBundleSignature + ???? + CFBundleVersion + 5.14.2 + NOTE + Please, do NOT change this file -- It was generated by Qt/QMake. + + diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtSvg.framework/Resources/QtSvg.prl b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtSvg.framework/Resources/QtSvg.prl new file mode 100644 index 0000000..6922d61 --- /dev/null +++ b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtSvg.framework/Resources/QtSvg.prl @@ -0,0 +1,7 @@ + +QMAKE_PRO_INPUT = svg.pro +QMAKE_PRL_TARGET = QtSvg +QMAKE_PRL_CONFIG = lex yacc depend_includepath testcase_targets import_plugins import_qpa_plugin asset_catalogs rez prepare_docs qt_docs_targets qt_build_extra file_copies qmake_use qt warn_on release link_prl app_bundle incremental global_init_link_order lib_version_first sdk clang_pch_style shared shared qt_framework release macos osx macx mac darwin unix posix gcc clang llvm sse2 aesni sse3 ssse3 sse4_1 sse4_2 avx avx2 avx512f avx512bw avx512cd avx512dq avx512er avx512ifma avx512pf avx512vbmi avx512vl compile_examples f16c force_debug_info largefile precompile_header rdrnd shani x86SimdAlways prefix_build force_independent no_warn_empty_obj_files utf8_source create_prl link_prl no_private_qt_headers_warning QTDIR_build qt_example_installs exceptions_off testcase_exceptions explicitlib testcase_no_bundle warning_clean module_frameworks lib_bundle relative_qt_rpath app_extension_api_only git_build target_qt c++11 strict_c++ c++14 c99 c11 hide_symbols separate_debug_info need_fwd_pri qt_install_module create_cmake sliced_bundle compiler_supports_fpmath create_pc have_target dll debug_info objective_c any_bundle thread uic opengl moc resources +QMAKE_PRL_VERSION = 5.14.2 +QMAKE_PRL_LIBS = -F$$[QT_INSTALL_LIBS] -framework QtWidgets -framework QtGui -framework AppKit -framework Metal -framework QtCore -framework DiskArbitration -framework IOKit +QMAKE_PRL_LIBS_FOR_CMAKE = -F$$[QT_INSTALL_LIBS];-framework;QtWidgets;-framework;QtGui;-framework;AppKit;-framework;Metal;-framework;QtCore;-framework;DiskArbitration;-framework;IOKit; diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtSvg.framework/Versions/5/QtSvg b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtSvg.framework/Versions/5/QtSvg new file mode 100755 index 0000000..35e4765 Binary files /dev/null and b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtSvg.framework/Versions/5/QtSvg differ diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtSvg.framework/Versions/5/Resources/Info.plist b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtSvg.framework/Versions/5/Resources/Info.plist new file mode 100644 index 0000000..2c8268a --- /dev/null +++ b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtSvg.framework/Versions/5/Resources/Info.plist @@ -0,0 +1,20 @@ + + + + + CFBundleExecutable + QtSvg + CFBundleIdentifier + org.qt-project.QtSvg + CFBundlePackageType + FMWK + CFBundleShortVersionString + 5.14 + CFBundleSignature + ???? + CFBundleVersion + 5.14.2 + NOTE + Please, do NOT change this file -- It was generated by Qt/QMake. + + diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtSvg.framework/Versions/5/Resources/QtSvg.prl b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtSvg.framework/Versions/5/Resources/QtSvg.prl new file mode 100644 index 0000000..6922d61 --- /dev/null +++ b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtSvg.framework/Versions/5/Resources/QtSvg.prl @@ -0,0 +1,7 @@ + +QMAKE_PRO_INPUT = svg.pro +QMAKE_PRL_TARGET = QtSvg +QMAKE_PRL_CONFIG = lex yacc depend_includepath testcase_targets import_plugins import_qpa_plugin asset_catalogs rez prepare_docs qt_docs_targets qt_build_extra file_copies qmake_use qt warn_on release link_prl app_bundle incremental global_init_link_order lib_version_first sdk clang_pch_style shared shared qt_framework release macos osx macx mac darwin unix posix gcc clang llvm sse2 aesni sse3 ssse3 sse4_1 sse4_2 avx avx2 avx512f avx512bw avx512cd avx512dq avx512er avx512ifma avx512pf avx512vbmi avx512vl compile_examples f16c force_debug_info largefile precompile_header rdrnd shani x86SimdAlways prefix_build force_independent no_warn_empty_obj_files utf8_source create_prl link_prl no_private_qt_headers_warning QTDIR_build qt_example_installs exceptions_off testcase_exceptions explicitlib testcase_no_bundle warning_clean module_frameworks lib_bundle relative_qt_rpath app_extension_api_only git_build target_qt c++11 strict_c++ c++14 c99 c11 hide_symbols separate_debug_info need_fwd_pri qt_install_module create_cmake sliced_bundle compiler_supports_fpmath create_pc have_target dll debug_info objective_c any_bundle thread uic opengl moc resources +QMAKE_PRL_VERSION = 5.14.2 +QMAKE_PRL_LIBS = -F$$[QT_INSTALL_LIBS] -framework QtWidgets -framework QtGui -framework AppKit -framework Metal -framework QtCore -framework DiskArbitration -framework IOKit +QMAKE_PRL_LIBS_FOR_CMAKE = -F$$[QT_INSTALL_LIBS];-framework;QtWidgets;-framework;QtGui;-framework;AppKit;-framework;Metal;-framework;QtCore;-framework;DiskArbitration;-framework;IOKit; diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtSvg.framework/Versions/Current/QtSvg b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtSvg.framework/Versions/Current/QtSvg new file mode 100755 index 0000000..35e4765 Binary files /dev/null and b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtSvg.framework/Versions/Current/QtSvg differ diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtSvg.framework/Versions/Current/Resources/Info.plist b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtSvg.framework/Versions/Current/Resources/Info.plist new file mode 100644 index 0000000..2c8268a --- /dev/null +++ b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtSvg.framework/Versions/Current/Resources/Info.plist @@ -0,0 +1,20 @@ + + + + + CFBundleExecutable + QtSvg + CFBundleIdentifier + org.qt-project.QtSvg + CFBundlePackageType + FMWK + CFBundleShortVersionString + 5.14 + CFBundleSignature + ???? + CFBundleVersion + 5.14.2 + NOTE + Please, do NOT change this file -- It was generated by Qt/QMake. + + diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtSvg.framework/Versions/Current/Resources/QtSvg.prl b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtSvg.framework/Versions/Current/Resources/QtSvg.prl new file mode 100644 index 0000000..6922d61 --- /dev/null +++ b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtSvg.framework/Versions/Current/Resources/QtSvg.prl @@ -0,0 +1,7 @@ + +QMAKE_PRO_INPUT = svg.pro +QMAKE_PRL_TARGET = QtSvg +QMAKE_PRL_CONFIG = lex yacc depend_includepath testcase_targets import_plugins import_qpa_plugin asset_catalogs rez prepare_docs qt_docs_targets qt_build_extra file_copies qmake_use qt warn_on release link_prl app_bundle incremental global_init_link_order lib_version_first sdk clang_pch_style shared shared qt_framework release macos osx macx mac darwin unix posix gcc clang llvm sse2 aesni sse3 ssse3 sse4_1 sse4_2 avx avx2 avx512f avx512bw avx512cd avx512dq avx512er avx512ifma avx512pf avx512vbmi avx512vl compile_examples f16c force_debug_info largefile precompile_header rdrnd shani x86SimdAlways prefix_build force_independent no_warn_empty_obj_files utf8_source create_prl link_prl no_private_qt_headers_warning QTDIR_build qt_example_installs exceptions_off testcase_exceptions explicitlib testcase_no_bundle warning_clean module_frameworks lib_bundle relative_qt_rpath app_extension_api_only git_build target_qt c++11 strict_c++ c++14 c99 c11 hide_symbols separate_debug_info need_fwd_pri qt_install_module create_cmake sliced_bundle compiler_supports_fpmath create_pc have_target dll debug_info objective_c any_bundle thread uic opengl moc resources +QMAKE_PRL_VERSION = 5.14.2 +QMAKE_PRL_LIBS = -F$$[QT_INSTALL_LIBS] -framework QtWidgets -framework QtGui -framework AppKit -framework Metal -framework QtCore -framework DiskArbitration -framework IOKit +QMAKE_PRL_LIBS_FOR_CMAKE = -F$$[QT_INSTALL_LIBS];-framework;QtWidgets;-framework;QtGui;-framework;AppKit;-framework;Metal;-framework;QtCore;-framework;DiskArbitration;-framework;IOKit; diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtVirtualKeyboard.framework/QtVirtualKeyboard b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtVirtualKeyboard.framework/QtVirtualKeyboard new file mode 100755 index 0000000..96580db Binary files /dev/null and b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtVirtualKeyboard.framework/QtVirtualKeyboard differ diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtVirtualKeyboard.framework/Resources/Info.plist b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtVirtualKeyboard.framework/Resources/Info.plist new file mode 100644 index 0000000..5b1c15b --- /dev/null +++ b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtVirtualKeyboard.framework/Resources/Info.plist @@ -0,0 +1,20 @@ + + + + + CFBundleExecutable + QtVirtualKeyboard + CFBundleIdentifier + org.qt-project.QtVirtualKeyboard + CFBundlePackageType + FMWK + CFBundleShortVersionString + 5.14 + CFBundleSignature + ???? + CFBundleVersion + 5.14.2 + NOTE + Please, do NOT change this file -- It was generated by Qt/QMake. + + diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtVirtualKeyboard.framework/Resources/QtVirtualKeyboard.prl b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtVirtualKeyboard.framework/Resources/QtVirtualKeyboard.prl new file mode 100644 index 0000000..62c91b5 --- /dev/null +++ b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtVirtualKeyboard.framework/Resources/QtVirtualKeyboard.prl @@ -0,0 +1,7 @@ + +QMAKE_PRO_INPUT = virtualkeyboard.pro +QMAKE_PRL_TARGET = QtVirtualKeyboard +QMAKE_PRL_CONFIG = lex yacc depend_includepath testcase_targets import_plugins import_qpa_plugin asset_catalogs rez prepare_docs qt_docs_targets qt_build_extra file_copies qmake_use qt warn_on release link_prl app_bundle incremental global_init_link_order lib_version_first sdk clang_pch_style shared shared qt_framework release macos osx macx mac darwin unix posix gcc clang llvm sse2 aesni sse3 ssse3 sse4_1 sse4_2 avx avx2 avx512f avx512bw avx512cd avx512dq avx512er avx512ifma avx512pf avx512vbmi avx512vl compile_examples f16c force_debug_info largefile precompile_header rdrnd shani x86SimdAlways prefix_build force_independent no_warn_empty_obj_files utf8_source create_prl link_prl no_private_qt_headers_warning QTDIR_build qt_example_installs exceptions_off testcase_exceptions explicitlib testcase_no_bundle qtquickcompiler link_pkgconfig disable-hunspell lang-all lang-ar_AR lang-bg_BG lang-cs_CZ lang-da_DK lang-de_DE lang-el_GR lang-en_GB lang-en_US lang-es_ES lang-es_MX lang-et_EE lang-fa_FA lang-fi_FI lang-fr_CA lang-fr_FR lang-he_IL lang-hi_IN lang-hr_HR lang-hu_HU lang-id_ID lang-it_IT lang-ja_JP lang-ko_KR lang-ms_MY lang-nb_NO lang-nl_NL lang-pl_PL lang-pt_BR lang-pt_PT lang-ro_RO lang-ru_RU lang-sk_SK lang-sl_SI lang-sq_AL lang-sr_SP lang-sv_SE lang-th_TH lang-tr_TR lang-uk_UA lang-vi_VN lang-zh_CN lang-zh_TW openwnn hangul pinyin tcime thai cangjie zhuyin module_frameworks lib_bundle relative_qt_rpath app_extension_api_only git_build target_qt c++11 strict_c++ c++14 c99 c11 hide_symbols separate_debug_info need_fwd_pri qt_install_module create_cmake sliced_bundle compiler_supports_fpmath create_pc have_target dll debug_info objective_c any_bundle thread opengl moc resources +QMAKE_PRL_VERSION = 5.14.2 +QMAKE_PRL_LIBS = -F$$[QT_INSTALL_LIBS] -framework QtQuick -framework QtGui -framework AppKit -framework Metal -framework QtQmlModels -framework QtQml -framework QtNetwork -framework QtCore -framework DiskArbitration -framework IOKit +QMAKE_PRL_LIBS_FOR_CMAKE = -F$$[QT_INSTALL_LIBS];-framework;QtQuick;-framework;QtGui;-framework;AppKit;-framework;Metal;-framework;QtQmlModels;-framework;QtQml;-framework;QtNetwork;-framework;QtCore;-framework;DiskArbitration;-framework;IOKit; diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtVirtualKeyboard.framework/Versions/5/QtVirtualKeyboard b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtVirtualKeyboard.framework/Versions/5/QtVirtualKeyboard new file mode 100755 index 0000000..96580db Binary files /dev/null and b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtVirtualKeyboard.framework/Versions/5/QtVirtualKeyboard differ diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtVirtualKeyboard.framework/Versions/5/Resources/Info.plist b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtVirtualKeyboard.framework/Versions/5/Resources/Info.plist new file mode 100644 index 0000000..5b1c15b --- /dev/null +++ b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtVirtualKeyboard.framework/Versions/5/Resources/Info.plist @@ -0,0 +1,20 @@ + + + + + CFBundleExecutable + QtVirtualKeyboard + CFBundleIdentifier + org.qt-project.QtVirtualKeyboard + CFBundlePackageType + FMWK + CFBundleShortVersionString + 5.14 + CFBundleSignature + ???? + CFBundleVersion + 5.14.2 + NOTE + Please, do NOT change this file -- It was generated by Qt/QMake. + + diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtVirtualKeyboard.framework/Versions/5/Resources/QtVirtualKeyboard.prl b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtVirtualKeyboard.framework/Versions/5/Resources/QtVirtualKeyboard.prl new file mode 100644 index 0000000..62c91b5 --- /dev/null +++ b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtVirtualKeyboard.framework/Versions/5/Resources/QtVirtualKeyboard.prl @@ -0,0 +1,7 @@ + +QMAKE_PRO_INPUT = virtualkeyboard.pro +QMAKE_PRL_TARGET = QtVirtualKeyboard +QMAKE_PRL_CONFIG = lex yacc depend_includepath testcase_targets import_plugins import_qpa_plugin asset_catalogs rez prepare_docs qt_docs_targets qt_build_extra file_copies qmake_use qt warn_on release link_prl app_bundle incremental global_init_link_order lib_version_first sdk clang_pch_style shared shared qt_framework release macos osx macx mac darwin unix posix gcc clang llvm sse2 aesni sse3 ssse3 sse4_1 sse4_2 avx avx2 avx512f avx512bw avx512cd avx512dq avx512er avx512ifma avx512pf avx512vbmi avx512vl compile_examples f16c force_debug_info largefile precompile_header rdrnd shani x86SimdAlways prefix_build force_independent no_warn_empty_obj_files utf8_source create_prl link_prl no_private_qt_headers_warning QTDIR_build qt_example_installs exceptions_off testcase_exceptions explicitlib testcase_no_bundle qtquickcompiler link_pkgconfig disable-hunspell lang-all lang-ar_AR lang-bg_BG lang-cs_CZ lang-da_DK lang-de_DE lang-el_GR lang-en_GB lang-en_US lang-es_ES lang-es_MX lang-et_EE lang-fa_FA lang-fi_FI lang-fr_CA lang-fr_FR lang-he_IL lang-hi_IN lang-hr_HR lang-hu_HU lang-id_ID lang-it_IT lang-ja_JP lang-ko_KR lang-ms_MY lang-nb_NO lang-nl_NL lang-pl_PL lang-pt_BR lang-pt_PT lang-ro_RO lang-ru_RU lang-sk_SK lang-sl_SI lang-sq_AL lang-sr_SP lang-sv_SE lang-th_TH lang-tr_TR lang-uk_UA lang-vi_VN lang-zh_CN lang-zh_TW openwnn hangul pinyin tcime thai cangjie zhuyin module_frameworks lib_bundle relative_qt_rpath app_extension_api_only git_build target_qt c++11 strict_c++ c++14 c99 c11 hide_symbols separate_debug_info need_fwd_pri qt_install_module create_cmake sliced_bundle compiler_supports_fpmath create_pc have_target dll debug_info objective_c any_bundle thread opengl moc resources +QMAKE_PRL_VERSION = 5.14.2 +QMAKE_PRL_LIBS = -F$$[QT_INSTALL_LIBS] -framework QtQuick -framework QtGui -framework AppKit -framework Metal -framework QtQmlModels -framework QtQml -framework QtNetwork -framework QtCore -framework DiskArbitration -framework IOKit +QMAKE_PRL_LIBS_FOR_CMAKE = -F$$[QT_INSTALL_LIBS];-framework;QtQuick;-framework;QtGui;-framework;AppKit;-framework;Metal;-framework;QtQmlModels;-framework;QtQml;-framework;QtNetwork;-framework;QtCore;-framework;DiskArbitration;-framework;IOKit; diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtVirtualKeyboard.framework/Versions/Current/QtVirtualKeyboard b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtVirtualKeyboard.framework/Versions/Current/QtVirtualKeyboard new file mode 100755 index 0000000..96580db Binary files /dev/null and b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtVirtualKeyboard.framework/Versions/Current/QtVirtualKeyboard differ diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtVirtualKeyboard.framework/Versions/Current/Resources/Info.plist b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtVirtualKeyboard.framework/Versions/Current/Resources/Info.plist new file mode 100644 index 0000000..5b1c15b --- /dev/null +++ b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtVirtualKeyboard.framework/Versions/Current/Resources/Info.plist @@ -0,0 +1,20 @@ + + + + + CFBundleExecutable + QtVirtualKeyboard + CFBundleIdentifier + org.qt-project.QtVirtualKeyboard + CFBundlePackageType + FMWK + CFBundleShortVersionString + 5.14 + CFBundleSignature + ???? + CFBundleVersion + 5.14.2 + NOTE + Please, do NOT change this file -- It was generated by Qt/QMake. + + diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtVirtualKeyboard.framework/Versions/Current/Resources/QtVirtualKeyboard.prl b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtVirtualKeyboard.framework/Versions/Current/Resources/QtVirtualKeyboard.prl new file mode 100644 index 0000000..62c91b5 --- /dev/null +++ b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtVirtualKeyboard.framework/Versions/Current/Resources/QtVirtualKeyboard.prl @@ -0,0 +1,7 @@ + +QMAKE_PRO_INPUT = virtualkeyboard.pro +QMAKE_PRL_TARGET = QtVirtualKeyboard +QMAKE_PRL_CONFIG = lex yacc depend_includepath testcase_targets import_plugins import_qpa_plugin asset_catalogs rez prepare_docs qt_docs_targets qt_build_extra file_copies qmake_use qt warn_on release link_prl app_bundle incremental global_init_link_order lib_version_first sdk clang_pch_style shared shared qt_framework release macos osx macx mac darwin unix posix gcc clang llvm sse2 aesni sse3 ssse3 sse4_1 sse4_2 avx avx2 avx512f avx512bw avx512cd avx512dq avx512er avx512ifma avx512pf avx512vbmi avx512vl compile_examples f16c force_debug_info largefile precompile_header rdrnd shani x86SimdAlways prefix_build force_independent no_warn_empty_obj_files utf8_source create_prl link_prl no_private_qt_headers_warning QTDIR_build qt_example_installs exceptions_off testcase_exceptions explicitlib testcase_no_bundle qtquickcompiler link_pkgconfig disable-hunspell lang-all lang-ar_AR lang-bg_BG lang-cs_CZ lang-da_DK lang-de_DE lang-el_GR lang-en_GB lang-en_US lang-es_ES lang-es_MX lang-et_EE lang-fa_FA lang-fi_FI lang-fr_CA lang-fr_FR lang-he_IL lang-hi_IN lang-hr_HR lang-hu_HU lang-id_ID lang-it_IT lang-ja_JP lang-ko_KR lang-ms_MY lang-nb_NO lang-nl_NL lang-pl_PL lang-pt_BR lang-pt_PT lang-ro_RO lang-ru_RU lang-sk_SK lang-sl_SI lang-sq_AL lang-sr_SP lang-sv_SE lang-th_TH lang-tr_TR lang-uk_UA lang-vi_VN lang-zh_CN lang-zh_TW openwnn hangul pinyin tcime thai cangjie zhuyin module_frameworks lib_bundle relative_qt_rpath app_extension_api_only git_build target_qt c++11 strict_c++ c++14 c99 c11 hide_symbols separate_debug_info need_fwd_pri qt_install_module create_cmake sliced_bundle compiler_supports_fpmath create_pc have_target dll debug_info objective_c any_bundle thread opengl moc resources +QMAKE_PRL_VERSION = 5.14.2 +QMAKE_PRL_LIBS = -F$$[QT_INSTALL_LIBS] -framework QtQuick -framework QtGui -framework AppKit -framework Metal -framework QtQmlModels -framework QtQml -framework QtNetwork -framework QtCore -framework DiskArbitration -framework IOKit +QMAKE_PRL_LIBS_FOR_CMAKE = -F$$[QT_INSTALL_LIBS];-framework;QtQuick;-framework;QtGui;-framework;AppKit;-framework;Metal;-framework;QtQmlModels;-framework;QtQml;-framework;QtNetwork;-framework;QtCore;-framework;DiskArbitration;-framework;IOKit; diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtWidgets.framework/QtWidgets b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtWidgets.framework/QtWidgets new file mode 100755 index 0000000..e1c2a6c Binary files /dev/null and b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtWidgets.framework/QtWidgets differ diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtWidgets.framework/Resources/Info.plist b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtWidgets.framework/Resources/Info.plist new file mode 100644 index 0000000..e5e3d3f --- /dev/null +++ b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtWidgets.framework/Resources/Info.plist @@ -0,0 +1,20 @@ + + + + + CFBundleExecutable + QtWidgets + CFBundleIdentifier + org.qt-project.QtWidgets + CFBundlePackageType + FMWK + CFBundleShortVersionString + 5.14 + CFBundleSignature + ???? + CFBundleVersion + 5.14.2 + NOTE + Please, do NOT change this file -- It was generated by Qt/QMake. + + diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtWidgets.framework/Resources/QtWidgets.prl b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtWidgets.framework/Resources/QtWidgets.prl new file mode 100644 index 0000000..52cf579 --- /dev/null +++ b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtWidgets.framework/Resources/QtWidgets.prl @@ -0,0 +1,7 @@ + +QMAKE_PRO_INPUT = widgets.pro +QMAKE_PRL_TARGET = QtWidgets +QMAKE_PRL_CONFIG = lex yacc depend_includepath testcase_targets import_plugins import_qpa_plugin asset_catalogs rez prepare_docs qt_docs_targets qt_build_extra file_copies qmake_use qt warn_on release link_prl app_bundle incremental global_init_link_order lib_version_first sdk clang_pch_style shared shared qt_framework release macos osx macx mac darwin unix posix gcc clang llvm sse2 aesni sse3 ssse3 sse4_1 sse4_2 avx avx2 avx512f avx512bw avx512cd avx512dq avx512er avx512ifma avx512pf avx512vbmi avx512vl compile_examples f16c force_debug_info largefile precompile_header rdrnd shani x86SimdAlways prefix_build force_independent no_warn_empty_obj_files utf8_source create_prl link_prl no_private_qt_headers_warning QTDIR_build qt_example_installs exceptions_off testcase_exceptions explicitlib testcase_no_bundle warning_clean uic qt_tracepoints generated_privates module_frameworks lib_bundle relative_qt_rpath app_extension_api_only git_build target_qt c++11 strict_c++ c++14 c99 c11 hide_symbols separate_debug_info need_fwd_pri qt_install_module create_cmake sliced_bundle compiler_supports_fpmath create_pc have_target dll debug_info objective_c any_bundle thread opengl moc resources +QMAKE_PRL_VERSION = 5.14.2 +QMAKE_PRL_LIBS = -F$$[QT_INSTALL_LIBS] -framework QtGui -framework AppKit -framework Metal -framework QtCore -framework DiskArbitration -framework IOKit +QMAKE_PRL_LIBS_FOR_CMAKE = -F$$[QT_INSTALL_LIBS];-framework;QtGui;-framework;AppKit;-framework;Metal;-framework;QtCore;-framework;DiskArbitration;-framework;IOKit; diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtWidgets.framework/Versions/5/QtWidgets b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtWidgets.framework/Versions/5/QtWidgets new file mode 100755 index 0000000..e1c2a6c Binary files /dev/null and b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtWidgets.framework/Versions/5/QtWidgets differ diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtWidgets.framework/Versions/5/Resources/Info.plist b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtWidgets.framework/Versions/5/Resources/Info.plist new file mode 100644 index 0000000..e5e3d3f --- /dev/null +++ b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtWidgets.framework/Versions/5/Resources/Info.plist @@ -0,0 +1,20 @@ + + + + + CFBundleExecutable + QtWidgets + CFBundleIdentifier + org.qt-project.QtWidgets + CFBundlePackageType + FMWK + CFBundleShortVersionString + 5.14 + CFBundleSignature + ???? + CFBundleVersion + 5.14.2 + NOTE + Please, do NOT change this file -- It was generated by Qt/QMake. + + diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtWidgets.framework/Versions/5/Resources/QtWidgets.prl b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtWidgets.framework/Versions/5/Resources/QtWidgets.prl new file mode 100644 index 0000000..52cf579 --- /dev/null +++ b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtWidgets.framework/Versions/5/Resources/QtWidgets.prl @@ -0,0 +1,7 @@ + +QMAKE_PRO_INPUT = widgets.pro +QMAKE_PRL_TARGET = QtWidgets +QMAKE_PRL_CONFIG = lex yacc depend_includepath testcase_targets import_plugins import_qpa_plugin asset_catalogs rez prepare_docs qt_docs_targets qt_build_extra file_copies qmake_use qt warn_on release link_prl app_bundle incremental global_init_link_order lib_version_first sdk clang_pch_style shared shared qt_framework release macos osx macx mac darwin unix posix gcc clang llvm sse2 aesni sse3 ssse3 sse4_1 sse4_2 avx avx2 avx512f avx512bw avx512cd avx512dq avx512er avx512ifma avx512pf avx512vbmi avx512vl compile_examples f16c force_debug_info largefile precompile_header rdrnd shani x86SimdAlways prefix_build force_independent no_warn_empty_obj_files utf8_source create_prl link_prl no_private_qt_headers_warning QTDIR_build qt_example_installs exceptions_off testcase_exceptions explicitlib testcase_no_bundle warning_clean uic qt_tracepoints generated_privates module_frameworks lib_bundle relative_qt_rpath app_extension_api_only git_build target_qt c++11 strict_c++ c++14 c99 c11 hide_symbols separate_debug_info need_fwd_pri qt_install_module create_cmake sliced_bundle compiler_supports_fpmath create_pc have_target dll debug_info objective_c any_bundle thread opengl moc resources +QMAKE_PRL_VERSION = 5.14.2 +QMAKE_PRL_LIBS = -F$$[QT_INSTALL_LIBS] -framework QtGui -framework AppKit -framework Metal -framework QtCore -framework DiskArbitration -framework IOKit +QMAKE_PRL_LIBS_FOR_CMAKE = -F$$[QT_INSTALL_LIBS];-framework;QtGui;-framework;AppKit;-framework;Metal;-framework;QtCore;-framework;DiskArbitration;-framework;IOKit; diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtWidgets.framework/Versions/Current/QtWidgets b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtWidgets.framework/Versions/Current/QtWidgets new file mode 100755 index 0000000..e1c2a6c Binary files /dev/null and b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtWidgets.framework/Versions/Current/QtWidgets differ diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtWidgets.framework/Versions/Current/Resources/Info.plist b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtWidgets.framework/Versions/Current/Resources/Info.plist new file mode 100644 index 0000000..e5e3d3f --- /dev/null +++ b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtWidgets.framework/Versions/Current/Resources/Info.plist @@ -0,0 +1,20 @@ + + + + + CFBundleExecutable + QtWidgets + CFBundleIdentifier + org.qt-project.QtWidgets + CFBundlePackageType + FMWK + CFBundleShortVersionString + 5.14 + CFBundleSignature + ???? + CFBundleVersion + 5.14.2 + NOTE + Please, do NOT change this file -- It was generated by Qt/QMake. + + diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtWidgets.framework/Versions/Current/Resources/QtWidgets.prl b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtWidgets.framework/Versions/Current/Resources/QtWidgets.prl new file mode 100644 index 0000000..52cf579 --- /dev/null +++ b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/QtWidgets.framework/Versions/Current/Resources/QtWidgets.prl @@ -0,0 +1,7 @@ + +QMAKE_PRO_INPUT = widgets.pro +QMAKE_PRL_TARGET = QtWidgets +QMAKE_PRL_CONFIG = lex yacc depend_includepath testcase_targets import_plugins import_qpa_plugin asset_catalogs rez prepare_docs qt_docs_targets qt_build_extra file_copies qmake_use qt warn_on release link_prl app_bundle incremental global_init_link_order lib_version_first sdk clang_pch_style shared shared qt_framework release macos osx macx mac darwin unix posix gcc clang llvm sse2 aesni sse3 ssse3 sse4_1 sse4_2 avx avx2 avx512f avx512bw avx512cd avx512dq avx512er avx512ifma avx512pf avx512vbmi avx512vl compile_examples f16c force_debug_info largefile precompile_header rdrnd shani x86SimdAlways prefix_build force_independent no_warn_empty_obj_files utf8_source create_prl link_prl no_private_qt_headers_warning QTDIR_build qt_example_installs exceptions_off testcase_exceptions explicitlib testcase_no_bundle warning_clean uic qt_tracepoints generated_privates module_frameworks lib_bundle relative_qt_rpath app_extension_api_only git_build target_qt c++11 strict_c++ c++14 c99 c11 hide_symbols separate_debug_info need_fwd_pri qt_install_module create_cmake sliced_bundle compiler_supports_fpmath create_pc have_target dll debug_info objective_c any_bundle thread opengl moc resources +QMAKE_PRL_VERSION = 5.14.2 +QMAKE_PRL_LIBS = -F$$[QT_INSTALL_LIBS] -framework QtGui -framework AppKit -framework Metal -framework QtCore -framework DiskArbitration -framework IOKit +QMAKE_PRL_LIBS_FOR_CMAKE = -F$$[QT_INSTALL_LIBS];-framework;QtGui;-framework;AppKit;-framework;Metal;-framework;QtCore;-framework;DiskArbitration;-framework;IOKit; diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/libcrypto.1.0.0.dylib b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/libcrypto.1.0.0.dylib new file mode 100755 index 0000000..4c033b3 Binary files /dev/null and b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/libcrypto.1.0.0.dylib differ diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/libiodbc.2.dylib b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/libiodbc.2.dylib new file mode 100644 index 0000000..3782a49 Binary files /dev/null and b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/libiodbc.2.dylib differ diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/libpq.5.dylib b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/libpq.5.dylib new file mode 100755 index 0000000..cb585a4 Binary files /dev/null and b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/libpq.5.dylib differ diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/libssl.1.0.0.dylib b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/libssl.1.0.0.dylib new file mode 100755 index 0000000..adf3c87 Binary files /dev/null and b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Frameworks/libssl.1.0.0.dylib differ diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Info.plist b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Info.plist new file mode 100644 index 0000000..c0f8490 --- /dev/null +++ b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleExecutable + lv_host + CFBundleIconFile + + CFBundleIdentifier + com.yourcompany.lv-host + CFBundlePackageType + APPL + CFBundleSignature + ???? + LSMinimumSystemVersion + 10.15 + NOTE + This file was generated by Qt/QMake. + NSPrincipalClass + NSApplication + NSSupportsAutomaticGraphicsSwitching + + + diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/MacOS/lv_host b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/MacOS/lv_host new file mode 100755 index 0000000..2dac662 Binary files /dev/null and b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/MacOS/lv_host differ diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/MacOS/my1.db b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/MacOS/my1.db new file mode 100644 index 0000000..a605c23 Binary files /dev/null and b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/MacOS/my1.db differ diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/MacOS/my2.db b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/MacOS/my2.db new file mode 100644 index 0000000..7e5ddc5 Binary files /dev/null and b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/MacOS/my2.db differ diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/PkgInfo b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/PkgInfo new file mode 100644 index 0000000..6f749b0 --- /dev/null +++ b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/PkgInfo @@ -0,0 +1 @@ +APPL???? diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/PlugIns/iconengines/libqsvgicon.dylib b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/PlugIns/iconengines/libqsvgicon.dylib new file mode 100755 index 0000000..9f333a5 Binary files /dev/null and b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/PlugIns/iconengines/libqsvgicon.dylib differ diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/PlugIns/imageformats/libqgif.dylib b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/PlugIns/imageformats/libqgif.dylib new file mode 100755 index 0000000..6d189f1 Binary files /dev/null and b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/PlugIns/imageformats/libqgif.dylib differ diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/PlugIns/imageformats/libqicns.dylib b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/PlugIns/imageformats/libqicns.dylib new file mode 100755 index 0000000..29a1a18 Binary files /dev/null and b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/PlugIns/imageformats/libqicns.dylib differ diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/PlugIns/imageformats/libqico.dylib b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/PlugIns/imageformats/libqico.dylib new file mode 100755 index 0000000..459f13f Binary files /dev/null and b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/PlugIns/imageformats/libqico.dylib differ diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/PlugIns/imageformats/libqjpeg.dylib b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/PlugIns/imageformats/libqjpeg.dylib new file mode 100755 index 0000000..0cc7908 Binary files /dev/null and b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/PlugIns/imageformats/libqjpeg.dylib differ diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/PlugIns/imageformats/libqmacheif.dylib b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/PlugIns/imageformats/libqmacheif.dylib new file mode 100755 index 0000000..c28ae3d Binary files /dev/null and b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/PlugIns/imageformats/libqmacheif.dylib differ diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/PlugIns/imageformats/libqmacjp2.dylib b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/PlugIns/imageformats/libqmacjp2.dylib new file mode 100755 index 0000000..4b7af85 Binary files /dev/null and b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/PlugIns/imageformats/libqmacjp2.dylib differ diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/PlugIns/imageformats/libqtga.dylib b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/PlugIns/imageformats/libqtga.dylib new file mode 100755 index 0000000..de4da7d Binary files /dev/null and b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/PlugIns/imageformats/libqtga.dylib differ diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/PlugIns/imageformats/libqtiff.dylib b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/PlugIns/imageformats/libqtiff.dylib new file mode 100755 index 0000000..fd451c9 Binary files /dev/null and b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/PlugIns/imageformats/libqtiff.dylib differ diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/PlugIns/imageformats/libqwbmp.dylib b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/PlugIns/imageformats/libqwbmp.dylib new file mode 100755 index 0000000..c929c3c Binary files /dev/null and b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/PlugIns/imageformats/libqwbmp.dylib differ diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/PlugIns/imageformats/libqwebp.dylib b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/PlugIns/imageformats/libqwebp.dylib new file mode 100755 index 0000000..3a6074f Binary files /dev/null and b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/PlugIns/imageformats/libqwebp.dylib differ diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/PlugIns/platforminputcontexts/libqtvirtualkeyboardplugin.dylib b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/PlugIns/platforminputcontexts/libqtvirtualkeyboardplugin.dylib new file mode 100755 index 0000000..000b085 Binary files /dev/null and b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/PlugIns/platforminputcontexts/libqtvirtualkeyboardplugin.dylib differ diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/PlugIns/platforms/libqcocoa.dylib b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/PlugIns/platforms/libqcocoa.dylib new file mode 100755 index 0000000..c1b3cd4 Binary files /dev/null and b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/PlugIns/platforms/libqcocoa.dylib differ diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/PlugIns/printsupport/libcocoaprintersupport.dylib b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/PlugIns/printsupport/libcocoaprintersupport.dylib new file mode 100755 index 0000000..578f0ad Binary files /dev/null and b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/PlugIns/printsupport/libcocoaprintersupport.dylib differ diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/PlugIns/sqldrivers/libqsqlite.dylib b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/PlugIns/sqldrivers/libqsqlite.dylib new file mode 100755 index 0000000..ba3e7c3 Binary files /dev/null and b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/PlugIns/sqldrivers/libqsqlite.dylib differ diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/PlugIns/sqldrivers/libqsqlmysql.dylib b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/PlugIns/sqldrivers/libqsqlmysql.dylib new file mode 100755 index 0000000..8c5cd91 Binary files /dev/null and b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/PlugIns/sqldrivers/libqsqlmysql.dylib differ diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/PlugIns/sqldrivers/libqsqlodbc.dylib b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/PlugIns/sqldrivers/libqsqlodbc.dylib new file mode 100755 index 0000000..8e09bbd Binary files /dev/null and b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/PlugIns/sqldrivers/libqsqlodbc.dylib differ diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/PlugIns/sqldrivers/libqsqlpsql.dylib b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/PlugIns/sqldrivers/libqsqlpsql.dylib new file mode 100755 index 0000000..8e13bb1 Binary files /dev/null and b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/PlugIns/sqldrivers/libqsqlpsql.dylib differ diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/PlugIns/styles/libqmacstyle.dylib b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/PlugIns/styles/libqmacstyle.dylib new file mode 100755 index 0000000..7737159 Binary files /dev/null and b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/PlugIns/styles/libqmacstyle.dylib differ diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/PlugIns/virtualkeyboard/libqtvirtualkeyboard_hangul.dylib b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/PlugIns/virtualkeyboard/libqtvirtualkeyboard_hangul.dylib new file mode 100755 index 0000000..2c4bcf5 Binary files /dev/null and b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/PlugIns/virtualkeyboard/libqtvirtualkeyboard_hangul.dylib differ diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/PlugIns/virtualkeyboard/libqtvirtualkeyboard_openwnn.dylib b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/PlugIns/virtualkeyboard/libqtvirtualkeyboard_openwnn.dylib new file mode 100755 index 0000000..4274a27 Binary files /dev/null and b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/PlugIns/virtualkeyboard/libqtvirtualkeyboard_openwnn.dylib differ diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/PlugIns/virtualkeyboard/libqtvirtualkeyboard_pinyin.dylib b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/PlugIns/virtualkeyboard/libqtvirtualkeyboard_pinyin.dylib new file mode 100755 index 0000000..7edd2e6 Binary files /dev/null and b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/PlugIns/virtualkeyboard/libqtvirtualkeyboard_pinyin.dylib differ diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/PlugIns/virtualkeyboard/libqtvirtualkeyboard_tcime.dylib b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/PlugIns/virtualkeyboard/libqtvirtualkeyboard_tcime.dylib new file mode 100755 index 0000000..6fbecc0 Binary files /dev/null and b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/PlugIns/virtualkeyboard/libqtvirtualkeyboard_tcime.dylib differ diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/PlugIns/virtualkeyboard/libqtvirtualkeyboard_thai.dylib b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/PlugIns/virtualkeyboard/libqtvirtualkeyboard_thai.dylib new file mode 100755 index 0000000..555d775 Binary files /dev/null and b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/PlugIns/virtualkeyboard/libqtvirtualkeyboard_thai.dylib differ diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Resources/empty.lproj b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Resources/empty.lproj new file mode 100644 index 0000000..e69de29 diff --git a/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Resources/qt.conf b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Resources/qt.conf new file mode 100644 index 0000000..02408fe --- /dev/null +++ b/eg4battery/tmp/bms-tool-ref/lv_host.app/Contents/Resources/qt.conf @@ -0,0 +1,4 @@ +[Paths] +Plugins = PlugIns +Imports = Resources/qml +Qml2Imports = Resources/qml diff --git a/eg4battery/tmp/eg4-snapshot b/eg4battery/tmp/eg4-snapshot new file mode 100755 index 0000000..95815f1 --- /dev/null +++ b/eg4battery/tmp/eg4-snapshot @@ -0,0 +1,126 @@ +#!/home/noise/.local/share/uv/tools/powermon/bin/python +"""eg4-snapshot — single-shot register dump for correlation against the +EG4 BMS Tool UI. Prints a 47-row table with: + + reg | raw u16 | hex | our decode | BMS Tool UI field + ----+---------+-----+------------------------------+------------------- + +Run while the BMS Tool is open on the same pack; fill in the BMS Tool +column by reading the matching values off the UI. Send the result back +and we refine `decode_eg4_modbus_regs` in `bin/eg4-battery`. + +Usage: + eg4-snapshot # auto-detect FTDI, query 0x40 + eg4-snapshot [addr] # explicit port / address +""" +import glob, sys, time, serial + +PORT = sys.argv[1] if len(sys.argv) > 1 else None +ADDR = int(sys.argv[2], 0) if len(sys.argv) > 2 else 0x40 + +if PORT is None: + hits = sorted(glob.glob("/dev/serial/by-id/usb-FTDI_*")) + if not hits: + sys.exit("no FTDI adapter visible") + PORT = hits[0] + + +def crc16(data): + c = 0xFFFF + for b in data: + c ^= b + for _ in range(8): + c = (c >> 1) ^ 0xA001 if c & 1 else c >> 1 + return c + + +def modbus_read_47(addr): + body = bytes([addr, 0x03, 0, 0, 0, 47]) + cr = crc16(body) + return body + bytes([cr & 0xFF, cr >> 8]) + + +def _s16(v): + return v - 0x10000 if v & 0x8000 else v + + +# What we THINK each register means right now. Keep in sync with +# bin/eg4-battery::decode_eg4_modbus_regs. +MAP = [ + # (name, decode fn) + ("Total_Voltage", lambda v, regs: f"{v/100:.2f} V"), + ("Current_I (signed)", lambda v, regs: f"{_s16(v)/100:+.2f} A"), + ("Vol_Cell01 (mV)", lambda v, regs: f"{v/1000:.3f} V"), + ("Vol_Cell02 (mV)", lambda v, regs: f"{v/1000:.3f} V"), + ("Vol_Cell03 (mV)", lambda v, regs: f"{v/1000:.3f} V"), + ("Vol_Cell04 (mV)", lambda v, regs: f"{v/1000:.3f} V"), + ("Vol_Cell05 (mV)", lambda v, regs: f"{v/1000:.3f} V"), + ("Vol_Cell06 (mV)", lambda v, regs: f"{v/1000:.3f} V"), + ("Vol_Cell07 (mV)", lambda v, regs: f"{v/1000:.3f} V"), + ("Vol_Cell08 (mV)", lambda v, regs: f"{v/1000:.3f} V"), + ("Vol_Cell09 (mV)", lambda v, regs: f"{v/1000:.3f} V"), + ("Vol_Cell10 (mV)", lambda v, regs: f"{v/1000:.3f} V"), + ("Vol_Cell11 (mV)", lambda v, regs: f"{v/1000:.3f} V"), + ("Vol_Cell12 (mV)", lambda v, regs: f"{v/1000:.3f} V"), + ("Vol_Cell13 (mV)", lambda v, regs: f"{v/1000:.3f} V"), + ("Vol_Cell14 (mV)", lambda v, regs: f"{v/1000:.3f} V"), + ("Vol_Cell15 (mV)", lambda v, regs: f"{v/1000:.3f} V"), + ("Vol_Cell16 (mV)", lambda v, regs: f"{v/1000:.3f} V"), + ("Temp_01", lambda v, regs: f"{v} °C"), + ("Temp_02", lambda v, regs: f"{v} °C"), + ("Temp_03", lambda v, regs: f"{v} °C"), + ("Temp_04 (MOS?)", lambda v, regs: f"{v} °C"), + ("SOC", lambda v, regs: f"{v} %"), + ("SOH", lambda v, regs: f"{v} %"), + ("Temp_PCB", lambda v, regs: f"{v} °C"), + ("? reserved-25", lambda v, regs: f"{v}"), + ("? reserved-26", lambda v, regs: f"{v}"), + ("? reserved-27", lambda v, regs: f"{v}"), + ("? reserved-28", lambda v, regs: f"{v}"), + ("? reserved-29", lambda v, regs: f"{v}"), + ("Heater/Status", lambda v, regs: f"{v}"), + ("MAX_Curren", lambda v, regs: f"{v/100:.2f} A"), + ("? reg-32", lambda v, regs: f"{v} (0x{v:04x})"), + ("Warning bits", lambda v, regs: f"0x{v:04x} ({bin(v)[2:].zfill(16)})"), + ("Protection bits", lambda v, regs: f"0x{v:04x} ({bin(v)[2:].zfill(16)})"), + ("Error_Code", lambda v, regs: f"{v}"), + ("Cell_Num", lambda v, regs: f"{v}"), + ("Capacity", lambda v, regs: f"{v/10:.1f} Ah"), + ("Remaining", lambda v, regs: f"{v/100:.2f} Ah"), + ("CycleNum", lambda v, regs: f"{v}"), + ("Battery_Mode", lambda v, regs: f"{v}"), + ("BMS_Version (hi)", lambda v, regs: f"0x{v:04x}"), + ("BMS_Version (lo)", lambda v, regs: f"0x{v:04x}"), + ("? reg-43", lambda v, regs: f"{v}"), + ("? reg-44", lambda v, regs: f"{v}"), + ("? reg-45", lambda v, regs: f"{v}"), + ("uptime counter", lambda v, regs: f"{v}"), +] + +assert len(MAP) == 47, f"MAP has {len(MAP)} entries, expected 47" + + +with serial.Serial(PORT, 9600, bytesize=8, parity="N", stopbits=1, timeout=0.3) as p: + p.reset_input_buffer() + p.write(modbus_read_47(ADDR)) + time.sleep(0.5) + buf = p.read(512) + +if len(buf) < 5 + 94 or buf[0] != ADDR or buf[1] != 0x03: + sys.exit(f"bad response ({len(buf)} B): {buf.hex(' ')[:80]}") + +bc = buf[2] +data = buf[3:3 + bc] +regs = [(data[i] << 8) | data[i + 1] for i in range(0, bc, 2)] + +print(f"# eg4-snapshot port={PORT} addr=0x{ADDR:02x} time={time.strftime('%Y-%m-%d %H:%M:%S')}") +print() +print(f"{'reg':>4} {'raw':>6} {'hex':>7} {'our decode':<34} BMS Tool UI value (fill me in)") +print(f"{'---':>4} {'-----':>6} {'-----':>7} {'-'*34} {'-'*40}") +for i, (label, fn) in enumerate(MAP): + raw = regs[i] + try: + dec = fn(raw, regs) + except Exception as e: + dec = f"(decode error: {e})" + print(f"{i:>4} {raw:>6} 0x{raw:04x} {label:<34} {dec}") diff --git a/eg4battery/tmp/port-probe b/eg4battery/tmp/port-probe new file mode 100755 index 0000000..fbd3070 --- /dev/null +++ b/eg4battery/tmp/port-probe @@ -0,0 +1,64 @@ +#!/home/noise/.local/share/uv/tools/powermon/bin/python +"""One-shot diagnostic: autodetect the currently-plugged FTDI, then +run 3 tests and print results. + + 1. Passive listen 10 s @ 9600 baud. + 2. Passive listen 10 s @ 19200 baud. + 3. Active probe: send canonical 7E 01 01 00 FE 0D at 9600, wait 2 s. + +Usage: port-probe