Metadata-Version: 2.4
Name: franklinwh-cloud
Version: 0.3.0
Summary: Unofficial Python client library and CLI for FranklinWH energy storage systems via the Cloud API
Author: david2069
License: MIT License
        
        Copyright (c) 2026 david2069
        
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.
        
        ---
        
        ADDITIONAL TERMS
        
        This software interacts with FranklinWH's undocumented cloud API. By using
        this software, you acknowledge and agree that:
        
        1. This is UNOFFICIAL software, NOT affiliated with, endorsed by, or
           supported by FranklinWH Inc. or any of its subsidiaries.
        
        2. The FranklinWH cloud API is undocumented and may change, break, or become
           unavailable without notice. The authors accept no responsibility for
           service interruptions, API changes, or authentication modifications by
           FranklinWH.
        
        3. You assume ALL RISK associated with using this software, including but
           not limited to: equipment damage, data loss, service disruption, warranty
           implications, and any financial consequences.
        
        4. You are solely responsible for compliance with FranklinWH's terms of
           service and any applicable laws or regulations in your jurisdiction.
        
        5. The authors provide no guarantee of continued maintenance, updates, or
           compatibility with future FranklinWH API versions.
        
        6. This software controls energy storage equipment. Improper use may affect
           electrical systems, battery health, grid interconnection, or building
           safety. You should understand what each API call does before using it.
        
Project-URL: Homepage, https://david2069.github.io/franklinwh-cloud/
Project-URL: Documentation, https://david2069.github.io/franklinwh-cloud/
Project-URL: Source, https://github.com/david2069/franklinwh-cloud
Project-URL: Bug Tracker, https://github.com/david2069/franklinwh-cloud/issues
Project-URL: Changelog, https://github.com/david2069/franklinwh-cloud/blob/main/CHANGELOG.md
Keywords: franklinwh,battery,solar,energy-storage,home-energy-management,agate,apower,tou
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: End Users/Desktop
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Home Automation
Classifier: Topic :: Scientific/Engineering :: Information Analysis
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: httpx[http2]
Requires-Dist: jsonschema
Provides-Extra: test
Requires-Dist: pytest>=8.0; extra == "test"
Requires-Dist: pytest-asyncio>=0.23; extra == "test"
Requires-Dist: pytest-cov>=5.0; extra == "test"
Requires-Dist: respx>=0.21; extra == "test"
Dynamic: license-file

# FranklinWH Cloud Client

![Tests](https://github.com/david2069/franklinwh-cloud/actions/workflows/test.yml/badge.svg)

A Python client library for interacting with FranklinWH energy storage systems via the cloud API.

> 📦 **Package**: `franklinwh-cloud-client` (install from source or GitHub — not yet on PyPI) | **import**: `from franklinwh_cloud import Client`

> 📊 **Fork of [richo/franklinwh-python](https://github.com/richo/franklinwh-python)** — see [FORK_ANALYSIS.md](FORK_ANALYSIS.md) for a detailed comparison of additions (60+ API methods, 45+ sensor fields, TOU scheduling, power control, and more).

> 🔒 **API Citizenship**: See [API_CLIENT_GUIDE.md](API_CLIENT_GUIDE.md) for rate limiting strategies, client identity headers, and how to prepare for authentication changes.

> ⚠️ **Disclaimer**: This library is **unofficial** and not endorsed by FranklinWH. It interfaces with a proprietary Cloud API designed exclusively for the official FranklinWH mobile app and authorised installer portal — not for third-party use. It is provided **"AS IS"** for educational and informational purposes only, with **no warranty of fitness for any purpose**. Use at your own risk. See the [full disclaimer](https://david2069.github.io/franklinwh-cloud/#about-this-library) for details.

## ✨ Features

- **Authentication**: Automatic token management and refresh, homeowner + installer account types
- **Real-time Data**: Battery status, solar production, grid usage, home loads
- **Mode Control**: Switch between operating modes (Time-of-Use, Self-Consumption, Emergency Backup)
- **TOU Schedules**: Manage Time-of-Use scheduling with multi-season, weekday/weekend support
- **Device Info**: Gateway details, network status, device inventory, BMS cell-level data
- **Performance Monitoring**: API call metrics, response timing (min/avg/max), error rates, and CloudFront edge tracking
- **CloudFront Edge Tracking**: Automatic PoP location monitoring, failover detection, cache hit rates, and distribution ID tracking
- **Client Identity**: Honest identification headers for responsible API citizenship
- **Rate Limiting**: Opt-in client-side throttling with per-minute/hour/daily budgets
- **Stale Data Cache**: Per-endpoint TTL caching for graceful degradation when the cloud is slow or unavailable
- **Modular Architecture**: Domain-specific mixins (stats, modes, TOU, storm, power, devices, account)
- **CLI Tool**: Subcommand-based CLI with `fetch` for arbitrary endpoint access, debug tracing, JSON output

## 🔧 Technical Highlights

| Capability | Implementation |
|------------|----------------|
| **HTTP/2** | All API calls use `httpx` with `http2=True` — multiplexed requests, header compression, persistent connections |
| **CloudFront Monitoring** | Tracks PoP edge location, cache hit rate, distribution IDs, and failover transitions in real-time |
| **Configurable Base URL** | `Client(fetcher, gw, url_base=...)` — ready for FranklinWH DNS migration (`energy.` → `api.`) |
| **Dual Account Types** | `LOGIN_TYPE_USER` (0) and `LOGIN_TYPE_INSTALLER` (1) constants for homeowner and installer login |
| **Stale-While-Revalidate** | Per-endpoint TTL cache returns last-known-good data during API outages |
| **Modbus Mode Constants** | Bidirectional `CLOUD_TO_MODBUS_MODE` / `MODBUS_TO_CLOUD_MODE` mapping for Modbus TCP integration |
| **70+ API Endpoints** | Comprehensive coverage across TOU scheduling, power control, BMS, storm, smart circuits, compliance |

## 📦 Installation

### From wheel (recommended for downstream projects like FEM)

```bash
pip install dist/franklinwh_cloud_client-0.3.0-py3-none-any.whl
```

### From source (editable, for development)

```bash
git clone https://github.com/david2069/franklinwh-cloud.git
cd franklinwh-cloud
python3 -m venv venv
source venv/bin/activate
pip install -e .
```

### With test dependencies

```bash
pip install -e ".[test]"
```

### From GitHub (direct)

```bash
# Latest (tracks main branch)
pip install git+https://github.com/david2069/franklinwh-cloud.git@main

# Pinned release (recommended for production / Docker)
pip install git+https://github.com/david2069/franklinwh-cloud.git@v0.3.0
```

## ⚙️ Configuration

Create `franklinwh.ini` with your credentials:

```ini
[energy.franklinwh.com]
email = your.email@example.com
password = your_password

[gateways.enabled]
serialno = YOUR_GATEWAY_SERIAL
```

**Security Note**: The `.ini` file is in `.gitignore` to protect your credentials.

Alternatively, set environment variables:
```bash
export FRANKLIN_USERNAME="your.email@example.com"
export FRANKLIN_PASSWORD="your_password"
export FRANKLIN_GATEWAY="YOUR_GATEWAY_SERIAL"
```

## 🚀 Quick Start

### Python API

```python
import asyncio
from franklinwh import Client, TokenFetcher

async def main():
    fetcher = TokenFetcher("email@example.com", "password")
    client = Client(fetcher, "YOUR_GATEWAY_SERIAL")

    # Get real-time stats
    stats = await client.get_stats()
    print(f"Battery: {stats.current.battery_soc}%")
    print(f"Solar: {stats.current.solar_production} kW")
    print(f"Mode: {stats.current.work_mode_desc}")

    # API metrics (automatic)
    metrics = client.get_metrics()
    print(f"API calls: {metrics['total_api_calls']}, avg {metrics['avg_response_time_s']:.3f}s")

asyncio.run(main())
```

### CLI Tool

```bash
# System overview — power, SOC, batteries, weather, grid, metrics
franklinwh-cli status

# Device discovery — 3 verbosity tiers
franklinwh-cli discover          # High-level: site, aGate, flags, state
franklinwh-cli discover -v       # Verbose: + firmware, warranty, relays, accessories
franklinwh-cli discover -vv      # Pedantic: + full firmware, NEM, PTO date
franklinwh-cli discover --json   # Full JSON export

# Operating mode
franklinwh-cli mode
franklinwh-cli mode --set tou --soc 20

# TOU schedule inspection
franklinwh-cli tou --dispatch

# Direct API passthrough (33 methods available)
franklinwh-cli raw help
franklinwh-cli raw get_power_info
franklinwh-cli raw get_bms_info AP_SERIAL_NUMBER

# API metrics
franklinwh-cli metrics

# Real-time battery monitor (auto-refresh dashboard)
franklinwh-cli monitor              # full dashboard, 30s refresh, Ctrl+C to exit
franklinwh-cli monitor -i 10        # refresh every 10 seconds
franklinwh-cli monitor -d 5         # run for 5 minutes then stop
franklinwh-cli monitor --compact    # single-line mode (no screen clearing)
franklinwh-cli monitor --json       # JSON stream per interval
```

**Output modes:**
```bash
franklinwh-cli status --json    # JSON output
franklinwh-cli status --no-color  # disable ANSI colours
```

**Debug & tracing:**
```bash
franklinwh-cli status -v          # API call summaries
franklinwh-cli status -vv         # + request/response headers
franklinwh-cli status -vvv        # + full raw JSON payloads
franklinwh-cli tou --trace tou    # only TOU mixin debug (46 log points!)
franklinwh-cli status --trace all # everything
franklinwh-cli status --api-trace # per-call timing
franklinwh-cli status -vv --log-file debug.log
```

## 🏗️ Architecture

```mermaid
graph TB
    CLI["franklinwh-cli"] --> Client
    FEM["FEM / HA Addon"] --> Client
    Scripts["Python Scripts"] --> Client

    subgraph "franklinwh_cloud"
        Client["Client<br/>(composes all mixins)"]
        Client --> API["api.py<br/>HTTP transport, auth"]
        API --> RL["RateLimiter"]
        API --> ET["EdgeTracker<br/>CloudFront PoP"]
        API --> SC["StaleDataCache"]
        
        Client --> Discover["mixins/discover.py"]
        Client --> Stats["mixins/stats.py"]
        Client --> Modes["mixins/modes.py"]
        Client --> TOU["mixins/tou.py"]
        Client --> Power["mixins/power.py"]
        Client --> Devices["mixins/devices.py"]
        Client --> Storm["mixins/storm.py"]
        Client --> Account["mixins/account.py"]
    end

    API --> CF["CloudFront CDN"]
    CF --> FW["FranklinWH Cloud"]
    FW -->|"FranklinWH Official Client<br/>(sendMqtt format)"| aGate[aGate]

    MB["franklinwh_modbus"] -->|"Modbus TCP<br/>(SunSpec/Raw)"| Net["Network<br/>(LAN / WiFi / Remote)"]
    Net -->|port 502| aGate

    aGate --> aPower[aPower Batteries]
    aGate --> PV[Solar PV]
    aGate --> SmC[Smart Circuits]

    style CLI fill:#22c55e,color:#fff
    style Client fill:#3b82f6,color:#fff
    style ET fill:#eab308,color:#000
    style FW fill:#7c3aed,color:#fff
    style MB fill:#d97706,color:#fff
    style Net fill:#6b7280,color:#fff
    style aGate fill:#059669,color:#fff
```

```
franklinwh_cloud/
├── client.py            # Client class (inherits all mixins)
├── models.py            # Stats, Current, Totals, GridStatus dataclasses
├── api.py               # HTTP transport, auth, session management
├── exceptions.py        # Custom exception hierarchy
├── metrics.py           # ClientMetrics — API call instrumentation
├── discovery.py         # DeviceSnapshot dataclass
├── const/               # Operating modes, TOU, device constants
│   ├── modes.py, tou.py, devices.py, device_catalog.json
├── mixins/              # Domain-specific API method groups (8 modules)
│   ├── discover.py      # client.discover() — 3-tier device survey
│   ├── stats.py         # get_stats, get_runtime_data, get_power_by_day
│   ├── modes.py         # get_mode, set_mode, get_mode_info
│   ├── tou.py           # TOU schedule CRUD + dispatch details
│   ├── storm.py         # weather, storm hedge settings
│   ├── power.py         # grid status, PCS settings, power control
│   ├── devices.py       # device info, BMS, composite info
│   └── account.py       # site info, notifications, alarms, warranty
├── cli.py               # CLI entry point
├── cli_output.py        # Terminal rendering + colour utilities
└── cli_commands/        # CLI subcommand modules
    ├── status.py        # Power flow, SOC, mode, weather, grid
    ├── discover.py      # Device discovery — 3 tiers, system readiness
    ├── monitor.py       # Real-time dashboard (full/compact/JSON)
    ├── metrics.py       # API probe + CloudFront edge data
    ├── bms.py           # Battery Management System inspection
    ├── diag.py          # System diagnostics report
    ├── tou.py           # TOU schedule with dispatch details
    ├── mode.py          # Operating mode get/set
    └── raw.py           # Direct API method calls
```

## 📖 Documentation

| Document | Description |
|----------|-------------|
| [API_CLIENT_GUIDE.md](API_CLIENT_GUIDE.md) | Rate limiting, CloudFront edge tracking, metrics, monitor usage |
| [FORK_ANALYSIS.md](FORK_ANALYSIS.md) | Detailed comparison with upstream `richo/franklinwh-python` |
| [HISTORY.md](HISTORY.md) | Project timeline from fork to independence |
| [UPSTREAM_STRATEGY.md](UPSTREAM_STRATEGY.md) | Contributing back to upstream — the trust ladder |
| [CHANGELOG.md](CHANGELOG.md) | Version history and release notes |
| [CONTRIBUTING.md](CONTRIBUTING.md) | Development setup, code standards, PR process |
| [ISSUES.md](ISSUES.md) | How to report bugs and request features |

## 🧪 Testing

```bash
# Unit tests only (no API credentials needed)
pytest -m "not live" -q

# Live API tests (requires franklinwh.ini or env vars)
pytest -m live -v

# All tests
pytest -v

# Record results for traceability (AP-11)
./tests/run_and_record.sh CLI-refactor
cat tests/results/test_history.log
```

**Current coverage**: 286 tests across all 8 domains

## 📚 API Reference

### Client Methods

| Domain | Key Methods |
|--------|-------------|
| **Discovery** | `discover(tier=1)` → `DeviceSnapshot` — site, aGate, batteries, flags, accessories, warranty |
| **Stats** | `get_stats()`, `get_runtime_data()`, `get_power_by_day(date)`, `get_power_details(type, date)` |
| **Modes** | `get_mode()`, `set_mode(mode, soc)`, `get_mode_info()` |
| **TOU** | `get_tou_info(type)`, `set_tou(schedule)`, `get_gateway_tou_list()`, `get_tou_dispatch_detail()` |
| **Tariff** | `get_utility_companies()`, `get_tariff_list()`, `get_tariff_detail()`, `apply_tariff_template()` |
| **Storm** | `get_weather()`, `get_storm_settings()`, `get_storm_list()` |
| **Power** | `get_grid_status()`, `get_power_control_settings()`, `get_power_info()` |
| **Devices** | `get_device_info()`, `get_bms_info(serial)`, `get_device_composite_info()` |
| **Account** | `siteinfo()`, `get_warranty_info()`, `get_alarm_codes_list()`, `get_notification_settings()` |
| **Metrics** | `get_metrics()` → `{total_api_calls, avg_response_time_s, calls_by_method, errors, ...}` |

### Data Structures

```python
stats.current.battery_soc          # Battery State of Charge (%)
stats.current.solar_production     # Solar production (kW)
stats.current.grid_use             # Grid usage (kW, negative = export)
stats.current.home_load            # Home consumption (kW)
stats.current.work_mode_desc       # Operating mode name
stats.current.grid_status          # GridStatus enum (NORMAL/DOWN/OFF)
stats.totals.solar                 # Daily solar production (kWh)
stats.totals.grid_import           # Daily grid import (kWh)
stats.totals.home_use              # Daily home consumption (kWh)
```

## 🗺️ Roadmap

### Installer Account Support (CLI-only, read-only)

The FranklinWH Cloud API supports **installer accounts** — these are privileged accounts used by solar installers to manage fleets of customer aGate gateways. The login endpoint (`appUserOrInstallerLogin`) supports both account types via a `type` parameter: `0` = app user, `1` = installer.

**Already implemented:**
- `LOGIN_TYPE_USER = 0` and `LOGIN_TYPE_INSTALLER = 1` constants
- `login_type` parameter on `TokenFetcher`, `login()`, and `_login()`
- Configurable `url_base` on `Client` for future DNS changes

**Planned scope:**
- CLI `--installer` flag to authenticate as an installer
- `discover` command to list all customer gateways in the installer's fleet
- `status` command with `--gateway SN` to view any customer's system
- **Read-only only** — no write operations (mode changes, TOU updates) via installer CLI
- Per-gateway selection required (no fleet-wide operations)

**Usage:**
```python
from franklinwh_cloud.client import TokenFetcher, LOGIN_TYPE_INSTALLER

# Installer login
fetcher = TokenFetcher("installer@company.com", "password", login_type=LOGIN_TYPE_INSTALLER)
client = Client(fetcher, "CUSTOMER_GATEWAY_SN")
info = await client.siteinfo()  # returns installerId, userTypes, roles, etc.
```

> ⚠️ Installer accounts can access and modify multiple customer sites. This library intentionally **limits installer support to read-only CLI operations** as a matter of responsible API citizenship. Developers who fork this library assume their own responsibility for write operations.

## 🤝 Contributing

See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup, code standards,
API citizenship requirements, and pull request process.

See [ISSUES.md](ISSUES.md) for how to report bugs and request features.

## 📝 License

MIT License with Additional Terms — see [LICENSE](LICENSE) for details.

The Additional Terms address the specific risks of interacting with
undocumented energy equipment APIs. **You must read and understand the
LICENSE before using this software.**

## 🙏 Acknowledgments

- **[FranklinWH](https://www.franklinwh.com)** - For innovative energy storage systems
- **[richo](https://github.com/richo/franklinwh-python)** - Original library foundation
- This project was developed with AI assistance (Claude, Gemini)

## ⚖️ Disclaimer

> **UNOFFICIAL SOFTWARE — NOT AFFILIATED WITH FRANKLINWH**
>
> By using this software, you confirm that you have read and understood the
> [LICENSE](LICENSE) and its Additional Terms.
>
> This software is provided **AS-IS**, without warranty of any kind, express or implied,
> including but not limited to the warranties of merchantability, fitness for a particular
> purpose, and non-infringement. Use entirely at your own risk.
>
> This library interacts with FranklinWH's undocumented cloud API, which may change,
> break, or become unavailable without notice. The authors accept no liability for
> service interruptions, data loss, equipment damage, or any other consequences
> arising from the use of this software.
>
> **MIT License** — see [LICENSE](LICENSE) for details.

This disclaimer is also logged once at startup by the library for audit trail purposes.
