404
+Page not found
+diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 00000000..247fb444 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,45 @@ +name: Documentation + +on: + push: + branches: + - main + pull_request: + paths: + - mkdocs.yml + - docs/** + - requirements-dev.txt + - Taskfile.yaml + workflow_dispatch: + +permissions: + contents: write + +jobs: + docs: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.13' + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements-dev.txt + + - name: Build documentation + run: mkdocs build --strict + + - name: Deploy to GitHub Pages + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + mkdocs gh-deploy --strict --force diff --git a/README.md b/README.md index dfe890a4..5986348a 100644 --- a/README.md +++ b/README.md @@ -83,6 +83,10 @@ This integration follows best practices for code quality and maintainability: - **Type Hints**: Full type annotations throughout the codebase for improved IDE support and type checking. - **Test Coverage**: Dedicated tests for each vacuum model with full coverage. +### Documentation + +For more detailed information, please visit our [MkDocs documentation site](https://damacus.github.io/robovac/). + ### Running Tests ```bash diff --git a/Taskfile.yaml b/Taskfile.yaml index fd4c0281..ca771665 100644 --- a/Taskfile.yaml +++ b/Taskfile.yaml @@ -48,6 +48,26 @@ tasks: cmds: - markdownlint-cli2 "**/*.md" "!vendor" "!.venv" --fix + docs-build: + desc: Build MkDocs documentation + cmds: + - uv run mkdocs build --strict + + docs-serve: + desc: Serve MkDocs documentation locally + cmds: + - uv run mkdocs serve --strict --dev-addr=127.0.0.1:8000 + + docs-docker-build: + desc: Build MkDocs documentation in Docker + cmds: + - docker run --rm -v "$PWD":/docs -w /docs python:3.11-slim /bin/sh -c "pip install mkdocs && mkdocs build --strict" + + docs-docker-serve: + desc: Serve MkDocs documentation in Docker + cmds: + - docker run --rm -v "$PWD":/docs -w /docs -p 8000:8000 python:3.11-slim /bin/sh -c "pip install mkdocs && mkdocs serve --dev-addr=0.0.0.0:8000" + ha-start: desc: Start Home assistant, run from dev container cmds: diff --git a/custom_components/robovac/robovac.py b/custom_components/robovac/robovac.py index 5ac8ee6c..4bb041c1 100644 --- a/custom_components/robovac/robovac.py +++ b/custom_components/robovac/robovac.py @@ -5,7 +5,7 @@ from .case_insensitive_lookup import case_insensitive_lookup from .tuyalocalapi import TuyaDevice from .vacuums import ROBOVAC_MODELS -from .vacuums.base import RobovacCommand, RobovacModelDetails +from .vacuums.base import RobovacModelDetails, RobovacCommand import logging @@ -47,6 +47,36 @@ def __init__(self, model_code: str, *args: Any, **kwargs: Any): ) current_model_details = ROBOVAC_MODELS[model_code] + # Determine protocol version: prefer model-defined, else default to (3, 3) + def _coerce_version(v: Any) -> tuple[int, int]: + try: + # Already a tuple[int,int] + if isinstance(v, tuple) and len(v) == 2: + major, minor = v + return (int(major), int(minor)) + # Float like 3.4 or 3.5 + if isinstance(v, float) or isinstance(v, int): + major = int(v) + minor = int(round((float(v) - major) * 10)) + return (major, minor) + # String like "3.5" + if isinstance(v, str): + parts = v.split(".") + if len(parts) >= 2: + return (int(parts[0]), int(parts[1])) + return (int(parts[0]), 0) + except Exception: + pass + # Fallback default + return (3, 3) + + # Only honor protocol_version if explicitly set on the model class. + # Using __dict__ avoids inheriting the Protocol's typed default (3.1). + model_version: Any = current_model_details.__dict__.get("protocol_version", None) + coerced_version = _coerce_version(model_version) if model_version is not None else (3, 3) + if "version" not in kwargs: + kwargs["version"] = coerced_version + super().__init__(current_model_details, *args, **kwargs) self.model_code = model_code diff --git a/custom_components/robovac/vacuums/T2194.py b/custom_components/robovac/vacuums/T2194.py index d62bac8b..c466aa9c 100644 --- a/custom_components/robovac/vacuums/T2194.py +++ b/custom_components/robovac/vacuums/T2194.py @@ -5,8 +5,7 @@ class T2194(RobovacModelDetails): homeassistant_features = ( - VacuumEntityFeature.BATTERY - | VacuumEntityFeature.CLEAN_SPOT + VacuumEntityFeature.CLEAN_SPOT | VacuumEntityFeature.FAN_SPEED | VacuumEntityFeature.LOCATE | VacuumEntityFeature.PAUSE diff --git a/docs/PROTOCOL_35_SUPPORT.md b/docs/PROTOCOL_35_SUPPORT.md new file mode 100644 index 00000000..1bfffba1 --- /dev/null +++ b/docs/PROTOCOL_35_SUPPORT.md @@ -0,0 +1,157 @@ +# Protocol 3.5 Support + +## Overview + +The RoboVac integration now supports Tuya protocol versions 3.4 and 3.5, which use HMAC-SHA256 for message authentication instead of CRC32. This is required for newer devices like the X8 Pro SES (T2276). + +## Protocol Versions + +| Version | Authentication | Encoding | Notes | +|---------|--------------------------|----------|--------------------------| +| 3.1 | CRC32 (payload only) | Base64 | Legacy protocol | +| 3.2 | CRC32 (payload only) | Base64 | Same as 3.1 with type_0d | +| 3.3 | CRC32 (header + payload) | Raw | Most common | +| 3.4 | HMAC-SHA256 | Raw | Newer devices | +| 3.5 | HMAC-SHA256 | Raw | Latest protocol | + +## Implementation Details + +### HMAC-SHA256 (Protocol 3.4+) + +For protocol versions 3.4 and above, messages use HMAC-SHA256 for authentication: + +1. **Message Structure:** + + ```text + [Header: 16 bytes] + [Payload: variable] + [HMAC: 32 bytes] + [Suffix: 4 bytes] + ``` + +2. **HMAC Calculation:** + - Key: Device local key (16 characters) + - Data: Header + Payload + - Algorithm: HMAC-SHA256 + - Output: 32 bytes + +3. **Verification:** + - Received messages are validated by computing HMAC and comparing + - Invalid HMAC results in message rejection + +### CRC32 (Protocol < 3.4) + +Earlier protocols use CRC32 for checksums: + +1. **Protocol 3.3:** + - CRC32 calculated on: Header + Payload + - 4-byte checksum + +2. **Protocol < 3.3:** + - CRC32 calculated on: Payload only + - 4-byte checksum + - Payload is base64 encoded + +## Using Protocol 3.5 + +### In Code + +The `TuyaDevice` class accepts a `version` parameter: + +```python +from custom_components.robovac.tuyalocalapi import TuyaDevice + +# Protocol 3.5 +device = TuyaDevice( + model_details=model, + device_id="your_device_id", + host="192.168.1.100", + local_key="your_local_key", + timeout=10.0, + ping_interval=30.0, + update_entity_state=callback, + version=(3, 5) # Specify protocol version +) +``` + +### For Model Definitions + +Models can specify their required protocol version in the model file: + +```python +# In custom_components/robovac/vacuums/T2276.py +class T2276(RobovacModelDetails): + # Model uses protocol 3.5 + protocol_version = (3, 5) +``` + +Then in `RoboVac.__init__()`: + +```python +# Get protocol version from model details +protocol_version = getattr( + self.model_details, + 'protocol_version', + (3, 3) # Default to 3.3 +) + +# Pass to TuyaDevice +TuyaDevice.__init__( + self, + model_details=self.model_details, + device_id=device_id, + host=host, + local_key=local_key, + timeout=timeout, + ping_interval=ping_interval, + update_entity_state=update_entity_state, + version=protocol_version, + port=port, +) +``` + +## Troubleshooting + +### "HMAC verification failed" + +This error indicates: + +- Wrong local key +- Protocol version mismatch +- Corrupted message + +**Solution:** Verify the local key and protocol version for your device. + +### "Incomplete read" errors + +For devices that previously had incomplete read errors (like T2276), upgrading to protocol 3.5 should resolve the issue. + +**Steps:** + +1. Add `protocol_version = (3, 5)` to the model file +2. Update `RoboVac.__init__()` to use the protocol version +3. Test with your device + +### Determining Protocol Version + +To find the correct protocol version for your device: + +1. **Check device documentation** - Newer devices typically use 3.4+ +2. **Try incrementally:** + - Start with 3.3 (most common) + - If incomplete reads occur, try 3.4 + - If still issues, try 3.5 +3. **Check logs** - Look for authentication or checksum errors + +## Testing + +All protocol versions are tested with the existing test suite: + +```bash +task test +``` + +The implementation maintains backward compatibility, so existing devices using protocols 3.1-3.3 continue to work without changes. + +## References + +- [TinyTuya Protocol Documentation](https://github.com/jasonacox/tinytuya) +- [Tuya IoT Protocol](https://developer.tuya.com/) +- Issue #42: X8 Pro SES incomplete read errors diff --git a/docs/archive/README.md b/docs/archive/README.md index 9f602ea9..31ee6db3 100644 --- a/docs/archive/README.md +++ b/docs/archive/README.md @@ -9,4 +9,4 @@ This directory contains historical documentation from completed improvement init ## Reference -These documents are kept for historical reference and to understand the evolution of the codebase. For current development guidance, see the main [DEVELOPMENT.md](../DEVELOPMENT.md) file in the project root. +These documents are kept for historical reference and to understand the evolution of the codebase. For current development guidance, see the main [Development Workflow](../development/index.md) section. diff --git a/docs/development.md b/docs/development.md new file mode 100644 index 00000000..5d860459 --- /dev/null +++ b/docs/development.md @@ -0,0 +1,321 @@ +# Development Guide + +This document provides guidance for developers working on the RoboVac integration. + +## Project Structure + +```text +custom_components/robovac/ +├── __init__.py # Integration initialization +├── config_flow.py # Configuration flow for setup +├── robovac.py # Core RoboVac class with command logic +├── vacuum.py # Home Assistant vacuum entity +├── sensor.py # Sensor entities +├── tuyalocalapi.py # Tuya device communication +├── tuyalocaldiscovery.py # Device discovery +├── vacuums/ +│ ├── base.py # Base classes and enums +│ ├── T2250.py through T2262.py # Model-specific command mappings +│ └── ... (other models) +└── strings.json # Localization strings + +tests/ +├── test_vacuum/ +│ ├── test_t2251_command_mappings.py # Model-specific tests +│ ├── test_get_robovac_human_readable_value.py +│ ├── test_get_robovac_command_value.py +│ └── ... (other tests) +└── conftest.py # Pytest configuration and fixtures +``` + +## Key Concepts + +### Command Mappings + +Each vacuum model defines command mappings in `vacuums/T*.py` files. These mappings translate between: + +- **Device codes**: Numeric DPS codes used by the Tuya protocol +- **Command names**: Enum values like `RobovacCommand.MODE`, `RobovacCommand.FAN_SPEED` +- **Command values**: User-friendly strings like `"auto"`, `"turbo"`, `"Standard"` + +Example structure: + +```python +RobovacCommand.MODE: { + "code": 5, # DPS code for this command + "values": { + "auto": "Auto", # Key: input value, Value: output value + "small_room": "SmallRoom", + "spot": "Spot", + }, +}, +``` + +### Naming Conventions + +- **Keys** (input values): Lowercase snake_case (e.g., `"auto"`, `"small_room"`) +- **Values** (output values): PascalCase (e.g., `"Auto"`, `"SmallRoom"`) +- **Case-insensitive matching**: Device responses are matched case-insensitively + +### Case-Insensitive Matching + +Device responses are matched case-insensitively, so `"AUTO"`, `"auto"`, and `"Auto"` all map to the same value. This eliminates the need for duplicate bidirectional mappings and simplifies the command mapping definitions. + +## Development Workflow + +### Setting Up Development Environment + +1. Clone the repository +2. Install dependencies: `pip install -r requirements.txt` +3. Install development dependencies: `pip install -r requirements-dev.txt` +4. Run tests to verify setup: `task test` + +### Running Tests + +```bash +# Run all tests +task test + +# Run specific test file +pytest tests/test_vacuum/test_t2251_command_mappings.py -v + +# Run with coverage +task test # Already includes coverage report + +# Run tests matching a pattern +pytest tests/test_vacuum/ -k "mode" -v +``` + +### Code Quality Checks + +```bash +# Check code style and formatting +task lint + +# Verify type hints +task type-check + +# Both lint and type-check +task lint && task type-check +``` + +### Markdown Documentation + +```bash +# Check markdown formatting +task markdownlint + +# Fix markdown issues +task markdownlint --fix +``` + +## Adding a New Vacuum Model + +### 1. Create Model File + +Create `custom_components/robovac/vacuums/TXXX.py`: + +```python +"""Model name (TXXX)""" +from homeassistant.components.vacuum import VacuumEntityFeature +from .base import RoboVacEntityFeature, RobovacCommand, RobovacModelDetails + + +class TXXX(RobovacModelDetails): + homeassistant_features = ( + VacuumEntityFeature.BATTERY + | VacuumEntityFeature.START + | VacuumEntityFeature.STOP + | VacuumEntityFeature.PAUSE + | VacuumEntityFeature.RETURN_HOME + | VacuumEntityFeature.FAN_SPEED + | VacuumEntityFeature.LOCATE + ) + robovac_features = ( + RoboVacEntityFeature.CLEANING_TIME + | RoboVacEntityFeature.CLEANING_AREA + ) + commands = { + RobovacCommand.START_PAUSE: { + "code": 2, + }, + RobovacCommand.MODE: { + "code": 5, + "values": { + "auto": "Auto", + "small_room": "SmallRoom", + }, + }, + # ... other commands + } +``` + +### 2. Register Model + +Add to `custom_components/robovac/vacuums/__init__.py`: + +```python +from .TXXX import TXXX + +ROBOVAC_MODELS = { + # ... existing models + "TXXX": TXXX, +} +``` + +### 3. Create Tests + +Create `tests/test_vacuum/test_txxx_command_mappings.py`: + +```python +"""Tests for TXXX command mappings.""" + +import pytest +from unittest.mock import patch + +from custom_components.robovac.robovac import RoboVac +from custom_components.robovac.vacuums.base import RobovacCommand + + +@pytest.fixture +def mock_txxx_robovac(): + """Create a mock TXXX RoboVac instance for testing.""" + with patch("custom_components.robovac.robovac.TuyaDevice.__init__", return_value=None): + robovac = RoboVac( + model_code="TXXX", + device_id="test_id", + host="192.168.1.100", + local_key="test_key", + ) + return robovac + + +def test_txxx_model_has_required_commands(mock_txxx_robovac): + """Test that TXXX model has required commands defined.""" + commands = mock_txxx_robovac.model_details.commands + + assert RobovacCommand.MODE in commands + assert RobovacCommand.FAN_SPEED in commands + # ... other assertions +``` + +## Logging Strategy + +### Log Levels + +- **DEBUG**: Diagnostic information for troubleshooting (e.g., value lookups, state changes) +- **INFO**: General informational messages (e.g., "Data points now available") +- **WARNING**: Actual problems that need attention (e.g., initialization failures, update failures) +- **ERROR**: Serious errors (e.g., vacuum not initialized for critical operations) + +### Examples + +```python +# DEBUG: Diagnostic information +_LOGGER.debug("Successfully updated vacuum %s", self._attr_name) + +# WARNING: Actual problem +_LOGGER.warning("Cannot update vacuum %s: IP address not set", self._attr_name) + +# ERROR: Serious issue +_LOGGER.error("Cannot locate vacuum: vacuum not initialized") +``` + +## Testing Best Practices + +### Test Structure + +Follow the pattern from `test_t2251_command_mappings.py`: + +1. Create a fixture for the mock RoboVac instance +2. Test command value mappings +3. Test case-insensitive matching +4. Test model structure (commands present) + +### Test Coverage + +- Aim for 100% coverage of modified code +- Test both success and failure paths +- Use mocking to isolate units under test +- Follow TDD: write tests before implementation +- Run `task test` to verify coverage + +### Running Tests with Coverage + +```bash +# View coverage report +task test + +# Generate HTML coverage report +pytest --cov=custom_components.robovac --cov-report=html +``` + +## Commit Guidelines + +Follow [Conventional Commits](https://www.conventionalcommits.org/): + +- `feat:` New feature +- `fix:` Bug fix +- `refactor:` Code refactoring without behavior change +- `test:` Adding or updating tests +- `docs:` Documentation updates +- `chore:` Maintenance tasks + +Examples: + +```text +feat: add T2250 command mappings +fix: handle "No error" status correctly +test: add T2251 command mapping tests +refactor: improve value lookup logic +docs: update development guide +``` + +## Common Tasks + +### Debugging a Failing Test + +```bash +# Run with verbose output +pytest tests/test_vacuum/test_t2251_command_mappings.py -v -s + +# Run with pdb debugger +pytest tests/test_vacuum/test_t2251_command_mappings.py --pdb + +# Run specific test +pytest tests/test_vacuum/test_t2251_command_mappings.py::test_t2251_mode_has_values -v +``` + +### Checking Type Hints + +```bash +# Run type checker +task type-check + +# Check specific file +mypy custom_components/robovac/robovac.py +``` + +### Formatting Code + +```bash +# Run linter +task lint + +# Auto-fix formatting issues +black custom_components/robovac/ +``` + +## Resources + +- [Home Assistant Developer Documentation](https://developers.home-assistant.io/) +- [Tuya Local API Documentation](https://github.com/jasonacox/tinytuya) +- [Pytest Documentation](https://docs.pytest.org/) +- [Python Type Hints](https://docs.python.org/3/library/typing.html) + +## Getting Help + +- Check existing issues and PRs +- Review the troubleshooting guide +- Enable debug logging to diagnose issues +- Create detailed issue reports with logs diff --git a/docs/development/index.md b/docs/development/index.md new file mode 100644 index 00000000..c8dec0cf --- /dev/null +++ b/docs/development/index.md @@ -0,0 +1,3 @@ +# Development Workflow + +This section will describe how to contribute to the RoboVac integration. Content migration from existing documents is in progress. diff --git a/docs/dps_codes.md b/docs/dps_codes.md new file mode 100644 index 00000000..14611508 --- /dev/null +++ b/docs/dps_codes.md @@ -0,0 +1,104 @@ +# Understanding and Working with DPS Codes + +## What is a DPS Code? + +DPS (Data Point Specification) codes are numeric identifiers used in Tuya-based devices like Eufy RoboVacs to control various functions and retrieve device states. Each DPS code maps to a specific function or feature of your vacuum, such as: + +- Battery level +- Cleaning mode +- Fan speed +- Error status +- Cleaning area +- Cleaning time + +In the RoboVac integration, these codes are used to communicate with your vacuum via the Tuya local API. Think of them as the language that allows Home Assistant to talk to your vacuum. + +## How to Find DPS Codes for Your Device + +Finding the correct DPS codes for your vacuum model is essential for adding support for new devices. Here are methods to discover these codes: + +### Method 1: Use the Model DPS Analysis Tool + +The repository includes a test that analyzes DPS codes for all supported models: + +1. Navigate to the `tests/test_vacuum/test_model_dps_analysis.py` file (or run `analyze_model_dps.py` in the root directory). +2. Run this script to generate a report showing: + - Which models use default codes + - Which models use custom codes + - What specific custom codes are used by each model + +This can help you understand patterns in how different models use DPS codes. + +### Method 2: Enable Debug Logging + +1. Enable debug logging for the RoboVac component in your Home Assistant configuration: + + ```yaml + logger: + default: info + logs: + custom_components.robovac: debug + ``` + +2. Restart Home Assistant and monitor the logs while operating your vacuum. +3. Look for log entries showing `"Updating entity values from data points: ..."` which will display all the DPS codes and values being reported by your device. + +### Method 3: Analyze Network Traffic (Advanced) + +For more advanced users: + +1. Use a tool like Wireshark to capture traffic between your Home Assistant instance and your vacuum. +2. Filter for traffic to/from your vacuum's IP address. +3. Look for Tuya protocol messages, which contain DPS code information in their payload. +4. Decode these messages to identify which DPS codes your vacuum is using and their functions. + +### Method 4: External Tools + +If you're comfortable with Python, you can try these tools to capture raw device data: + +- [eufy-device-id-python](https://github.com/markbajaj/eufy-device-id-python) - Gets device info from Eufy servers +- `tinytuya scan` - Scans local network for Tuya devices (after you have credentials) + +## Adding Support for a New Device + +To add support for a new device, you'll need to: + +1. **Determine your model code**: This is typically the first 5 characters of the full model number. +2. **Identify required DPS codes**: Using the methods above, determine which DPS codes your vacuum uses. +3. **Create a model-specific class**: If your vacuum uses non-default DPS codes, you'll need to create a model-specific class in the `custom_components/robovac/vacuums/` directory. +4. **Map commands to DPS codes**: In your model class, create mappings between RoboVac commands and the DPS codes your vacuum uses. + +### Example Implementation + +Here's a simplified example of how a model-specific class looks: + +```python +from custom_components.robovac.vacuums.base import RobovacModelDetails, RoboVacEntityFeature, RobovacCommand + +class YourModel(RobovacModelDetails): + """Implementation for Your Specific Model.""" + + # Define which Home Assistant features this vacuum supports + homeassistant_features = ( + # Add relevant features here + ) + + # Define which RoboVac features this vacuum supports + robovac_features = ( + # Add relevant features here + ) + + # Map commands to DPS codes - override any that differ from defaults + dps_codes = { + "BATTERY_LEVEL": "104", # Example - use actual codes from your device + "STATUS": "15", + # Add other codes as needed + } + + # Define available commands + commands = { + RobovacCommand.START_PAUSE: True, + RobovacCommand.BATTERY: True, + # Add other commands as appropriate + } +``` diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 00000000..bca60576 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,19 @@ +# RoboVac Documentation + +Welcome to the unified documentation hub for the RoboVac integration with Home Assistant. + +## Getting Started + +- **[DPS Codes Guide](dps_codes.md)**: Learn how to find and use Data Point Specification codes for your device. +- **[Troubleshooting](troubleshooting.md)**: Solutions for common issues and connection problems. +- **[Development](development.md)**: Guide for contributors and developers. + +## Reference + +- **[Protocol 3.5 Support](PROTOCOL_35_SUPPORT.md)**: Details about Tuya protocol 3.5 support. + +## Contributing + +1. Set up the development environment with `task install-dev`. +2. Run the test suite using `task test`. +3. Ensure all linting and type checks pass before submitting changes. diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md new file mode 100644 index 00000000..186a584b --- /dev/null +++ b/docs/troubleshooting.md @@ -0,0 +1,31 @@ +# RoboVac Troubleshooting Guide + +This document provides solutions to common issues with the RoboVac integration. + +## Table of Contents + +- [Understanding and Working with DPS Codes](#understanding-and-working-with-dps-codes) +- [Common Connection Issues](#common-connection-issues) +- [Feature Support Issues](#feature-support-issues) + +## Understanding and Working with DPS Codes + +Please refer to the dedicated [DPS Codes Guide](dps_codes.md) for detailed information on what DPS codes are, how to find them, and how to use them to add support for new devices. + +## Common Connection Issues + +### Coming Soon + +This section will be expanded in the future. + +## Feature Support Issues + +### Coming Soon + +This section will be expanded in the future. + +### Common Troubleshooting Tips + +- **Device not responding**: Verify your IP address and access token are correct. +- **Command not working**: Check if the DPS code mapping is correct for that specific command. +- **Values not updating**: Ensure the DPS code for status information is correctly mapped. diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 00000000..fd84818a --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,28 @@ +site_name: RoboVac Documentation +site_description: Unified documentation for the RoboVac Home Assistant integration +site_author: RoboVac Contributors +repo_url: https://github.com/damacus/robovac +site_url: https://damacus.github.io/robovac/ + +nav: + - Home: index.md + - Guides: + - Getting DPS Codes: dps_codes.md + - Troubleshooting: troubleshooting.md + - Development Workflow: development.md + - Reference: + - Protocol 3.5 Support: PROTOCOL_35_SUPPORT.md + +theme: + name: mkdocs + +plugins: + - search + +markdown_extensions: + - attr_list + - admonition + - codehilite + - footnotes + - toc: + permalink: true diff --git a/requirements-dev.txt b/requirements-dev.txt index 997633fa..e9d82162 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -12,3 +12,6 @@ lefthook>=1.6.0 # Git hooks manager # Type checking types-requests>=2.31.0 + +# Documentation +mkdocs>=1.6.0 diff --git a/requirements.txt b/requirements.txt index dea734fe..d74fdfe3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,10 @@ cryptography==46.0.3 +<<<<<<< HEAD homeassistant==2025.12.0 +======= +homeassistant==2025.10.3 +tinytuya==1.17.4 +>>>>>>> 6ec8de5 (Save) requests==2.32.5 setuptools==80.9.0 voluptuous==0.15.2 diff --git a/site/404.html b/site/404.html new file mode 100644 index 00000000..9428c5b8 --- /dev/null +++ b/site/404.html @@ -0,0 +1,170 @@ + + +
+ + + + + + + +Page not found
+T2276 (X8 Pro SES) vacuum is experiencing connection issues with the following symptoms:
+{}) returned from vacuumModel Support: Partial +Tests: ✅ Passing +Protocol: ⚠️ Issues
+From user logs:
+2025-10-22 13:10:04.065 DEBUG [custom_components.robovac.tuyalocalapi] Incomplete read
+2025-10-22 13:10:22.225 DEBUG [custom_components.robovac.tuyalocalapi] Disconnected
+2025-10-22 13:10:22.241 DEBUG [custom_components.robovac.tuyalocalapi] Connecting
+
+
+The vacuum connects but doesn't respond to status requests, resulting in:
+2025-05-09 08:23:04.568 DEBUG [custom_components.robovac.vacuum] Updating entity values from data points: {}
+
+
+T2276 is currently configured with the following DPS codes:
+These codes are identical to T2275 and T2278 models.
+Good news: You don't need to manually extract credentials! This integration already does that automatically when you enter your Eufy app email/password in the Home Assistant config flow.
+If you have a T2276 vacuum, please help us debug the communication issue:
+configuration.yaml:yaml
+ logger:
+ default: info
+ logs:
+ custom_components.robovac: debug
+ custom_components.robovac.tuyalocalapi: debug
Restart Home Assistant, then let it try to connect to your T2276 for a few minutes.
+Share the relevant sections showing:
+Device Information:
+Does the vacuum work in the Eufy app?
+Test Alternative Integration:
+If it works, note any differences in behavior
+Optional - Advanced Users: + If you're comfortable with Python, you can try these tools to capture raw device data:
+tinytuya scan - Scans local network for Tuya devices (after you have credentials)But this is NOT required - debug logs are more helpful!
+Identify protocol or configuration differences
+Protocol Analysis:
+Capture raw Tuya messages for analysis
+Test Different Configurations:
+The RoboVac integration now supports Tuya protocol versions 3.4 and 3.5, which use HMAC-SHA256 for message authentication instead of CRC32. This is required for newer devices like the X8 Pro SES (T2276).
+| Version | +Authentication | +Encoding | +Notes | +
|---|---|---|---|
| 3.1 | +CRC32 (payload only) | +Base64 | +Legacy protocol | +
| 3.2 | +CRC32 (payload only) | +Base64 | +Same as 3.1 with type_0d | +
| 3.3 | +CRC32 (header + payload) | +Raw | +Most common | +
| 3.4 | +HMAC-SHA256 | +Raw | +Newer devices | +
| 3.5 | +HMAC-SHA256 | +Raw | +Latest protocol | +
For protocol versions 3.4 and above, messages use HMAC-SHA256 for authentication:
+text
+ [Header: 16 bytes] + [Payload: variable] + [HMAC: 32 bytes] + [Suffix: 4 bytes]
Output: 32 bytes
+Verification:
+Earlier protocols use CRC32 for checksums:
+4-byte checksum
+Protocol < 3.3:
+The TuyaDevice class accepts a version parameter:
from custom_components.robovac.tuyalocalapi import TuyaDevice
+
+# Protocol 3.5
+device = TuyaDevice(
+ model_details=model,
+ device_id="your_device_id",
+ host="192.168.1.100",
+ local_key="your_local_key",
+ timeout=10.0,
+ ping_interval=30.0,
+ update_entity_state=callback,
+ version=(3, 5) # Specify protocol version
+)
+
+
+Models can specify their required protocol version in the model file:
+# In custom_components/robovac/vacuums/T2276.py
+class T2276(RobovacModelDetails):
+ # Model uses protocol 3.5
+ protocol_version = (3, 5)
+
+
+Then in RoboVac.__init__():
# Get protocol version from model details
+protocol_version = getattr(
+ self.model_details,
+ 'protocol_version',
+ (3, 3) # Default to 3.3
+)
+
+# Pass to TuyaDevice
+TuyaDevice.__init__(
+ self,
+ model_details=self.model_details,
+ device_id=device_id,
+ host=host,
+ local_key=local_key,
+ timeout=timeout,
+ ping_interval=ping_interval,
+ update_entity_state=update_entity_state,
+ version=protocol_version,
+ port=port,
+)
+
+
+This error indicates:
+Solution: Verify the local key and protocol version for your device.
+For devices that previously had incomplete read errors (like T2276), upgrading to protocol 3.5 should resolve the issue.
+Steps:
+protocol_version = (3, 5) to the model fileRoboVac.__init__() to use the protocol versionTo find the correct protocol version for your device:
+All protocol versions are tested with the existing test suite:
+task test
+
+
+The implementation maintains backward compatibility, so existing devices using protocols 3.1-3.3 continue to work without changes.
+Replace custom tuyalocalapi.py implementation with LocalTuya's battle-tested pytuya module to support Tuya protocol versions 3.1-3.5.
LocalTuya PyTuya: https://github.com/rospogrigio/localtuya/tree/master/custom_components/localtuya/pytuya
+pytuya/__init__.py from LocalTuyacustom_components/robovac/pytuya/Maintain as separate module initially
+Update dependencies
+cryptography - no new dependencies neededVerify version compatibility
+Create wrapper/adapter layer
+robovac.pyTuyaDevice imports with PyTuyaSet protocol version per model
+Update model configurations
+protocol_version attribute to modelsSet 3.5 for T2276
+Update vacuum.py
Test all commands (start, stop, return home, etc.)
+Regression test existing models
+Ensure no breaking changes
+Run full test suite
+tuyalocalapi.py if fully replacedClean up unused code
+Update documentation
+Document protocol version configuration
+Final verification
+device = TuyaDevice(dev_id, address, local_key, version=3.1)
+await device.status()
+await device.set_dp(value, dp_index)
+
+
+device = TuyaInterface(dev_id, address, local_key)
+device.set_version(3.5) # New capability!
+status = await device.status()
+await device.set_dp(value, dp_index)
+
+
+New: Can be set with set_version()
Error handling:
+Need to map to our error handling
+Async handling:
+If migration fails:
+Total: 8-12 hours of work
+| Risk | +Probability | +Impact | +Mitigation | +
|---|---|---|---|
| Breaking existing models | +Medium | +High | +Thorough testing, gradual rollout | +
| API incompatibility | +Low | +Medium | +Adapter layer, careful mapping | +
| Performance issues | +Low | +Low | +PyTuya is proven in production | +
| New bugs | +Medium | +Medium | +Comprehensive test coverage | +
This document outlines improvements needed for the more-mappings-work branch changes, focusing on code quality, test coverage, maintainability, and consistency.
Changes Made:
+robovac.py getRoboVacHumanReadableValue()vacuum.py error checking to include "No error" stringProblem: +Current approach duplicates dictionary entries to handle both directions:
+"values": {
+ "auto": "Auto", # Human-to-device
+ "Auto": "Auto", # Bidirectional (redundant with case-insensitive)
+ "small_room": "SmallRoom",
+ "SmallRoom": "SmallRoom",
+}
+
+
+Impact:
+Solution:
+Implementation Steps:
+Files Affected:
+vacuums/T2250.py, T2252.py, T2253.py, T2254.py, T2255.py, T2259.py, T2262.pyProblem: +Different models use inconsistent capitalization for the same values:
+"auto": "Auto", "standard": "Standard""Auto": "Auto", "Standard": "Standard"Impact:
+Solution:
+Implementation Steps:
+Problem: +Modified models (T2250-T2262 series) lack specific tests:
+Impact:
+Solution: +Following TDD principles, create comprehensive tests:
+Test Files Needed:
+test_t2250_command_mappings.pytest_t2251_command_mappings.pytest_t2252_command_mappings.pytest_t2253_command_mappings.pytest_t2254_command_mappings.pytest_t2255_command_mappings.pytest_t2259_command_mappings.pytest_t2262_command_mappings.pyEach Test Should Cover:
+Template Structure (based on existing test_t2277_command_mappings.py):
"""Tests for T2XXX vacuum model commands."""
+import pytest
+from custom_components.robovac.robovac import RoboVac
+from custom_components.robovac.vacuums.base import RobovacCommand
+
+@pytest.fixture
+def mock_t2xxx_robovac():
+ """Create a T2XXX RoboVac instance for testing."""
+ # Use existing fixture pattern
+
+def test_error_code_mapping(mock_t2xxx_robovac):
+ """Test error code 0 maps to 'No error'."""
+
+def test_mode_mappings(mock_t2xxx_robovac):
+ """Test MODE command accepts all expected values."""
+
+def test_fan_speed_mappings(mock_t2xxx_robovac):
+ """Test FAN_SPEED command mappings."""
+
+def test_case_insensitive_mode(mock_t2xxx_robovac):
+ """Test MODE accepts case-insensitive values."""
+
+
+Problem: +All modified models only define error code 0:
+RobovacCommand.ERROR: {
+ "code": 106,
+ "values": {
+ "0": "No error",
+ },
+},
+
+
+Impact:
+Solution:
+Example Target:
+RobovacCommand.ERROR: {
+ "code": 106,
+ "values": {
+ "0": "No error",
+ "1": "Wheel stuck",
+ "2": "Side brush stuck",
+ "3": "Suction motor stuck",
+ # TODO: Add more error codes as discovered
+ },
+},
+
+
+Problem: +Current warning logs extensive debug info for every unmapped value:
+_LOGGER.warning(
+ "Command %s with value %r (type: %s, str=%r) not found for model %s. "
+ "Available keys: %r (first key repr: %r). "
+ "If you know the status...",
+ # 7 parameters
+)
+
+
+Impact:
+Solution:
+DEBUG for detailed type/key infoWARNING for actual issuesProposed:
+_LOGGER.debug(
+ "Unmapped value for %s: %r (type: %s). Available: %s",
+ command_name, value, type(value).__name__, list(values.keys())[:5]
+)
+_LOGGER.warning(
+ "Command %s value %s not found for model %s. "
+ "Please report this to maintainers with the status shown in Eufy app.",
+ command_name, value, self.model_code
+)
+
+
+Problem: +Some functions lack complete type hints, especially around command value mappings.
+Solution:
+TypedDict for command structuresFiles:
+robovac.pyvacuum.pyvacuums/base.pyProblem: +Case-insensitive lookup logic is inline and not reusable.
+Solution: +Extract to utility function:
+def case_insensitive_dict_lookup(
+ d: dict[str, str],
+ key: str
+) -> str | None:
+ """Case-insensitive dictionary lookup.
+
+ Args:
+ d: Dictionary to search
+ key: Key to find (case-insensitive)
+
+ Returns:
+ Value if found, None otherwise
+ """
+ # Try exact match first (O(1))
+ if key in d:
+ return d[key]
+
+ # Try case-insensitive (O(n))
+ key_lower = key.lower()
+ for k, v in d.items():
+ if k.lower() == key_lower:
+ return v
+
+ return None
+
+
+Benefits:
+Problem: +No tests verify the full flow from device value → human-readable → UI display.
+Solution: +Add integration tests that:
+getRoboVacHumanReadableValue() called correctlyProblem:
+vacuum.py checks multiple error conditions:
elif (
+ self.error_code is not None
+ and self.error_code not in [0, "no_error", "No error"]
+):
+
+
+Impact:
+Solution:
+def is_error_state(error_code: Any) -> bool:
+ """Check if error code indicates an error state.
+
+ Args:
+ error_code: Error code from device
+
+ Returns:
+ True if error state, False otherwise
+ """
+ if error_code is None or error_code == 0:
+ return False
+
+ # Handle string "no error" case-insensitively
+ if isinstance(error_code, str) and error_code.lower() == "no error":
+ return False
+
+ return True
+
+
+Needs:
+Potential Improvements:
+Use task test (per memory) to run full test suite.
test_t2277_command_mappings.pyconftest.pyWarnings now reserved for actual problems (initialization, update failures)
+Extract case-insensitive lookup utility
+Updated tests to verify debug logging behavior
+Add comprehensive type hints
+All markdown linting passes
+✅ Code cleanup
+No debug code remains
+✅ Final verification
+task test (143 passed)task type-check passingrobovac.py - Core RoboVac class with command mapping logicvacuum.py - Home Assistant vacuum entityvacuums/base.py - Base classes and enumsvacuums/T2*.py - Model-specific command mappingstests/test_vacuum/test_get_robovac_human_readable_value.py - Test patterns# Run all tests
+task test
+
+# Run specific test file
+pytest tests/test_vacuum/test_t2250_command_mappings.py -v
+
+# Run with coverage
+task test # Already includes coverage
+
+
+task lint
+task type-check
+
+
+task test - ensure all tests passtask lint - fix any style issuestask type-check - verify type hintsfeat:, fix:, refactor:, test:getRoboVacHumanReadableValue()Document Version: 1.0 +Created: 2025-10-21 +Branch: more-mappings-work +Author: Code Review Bot (Cascade)
The branch needs test coverage for modified models, removal of duplicate mappings, and consistent conventions.
+Why: Case-insensitive matching makes them redundant +Files: T2250.py, T2252.py, T2253.py, T2254.py, T2255.py, T2259.py, T2262.py
+# Remove this:
+"Auto": "Auto", # Bidirectional duplicate
+"SmallRoom": "SmallRoom",
+
+# Keep this:
+"auto": "Auto",
+"small_room": "SmallRoom",
+
+
+Critical Gap: No tests for 8 modified models
+Create these files following test_t2277_command_mappings.py pattern:
test_t2250_command_mappings.pytest_t2251_command_mappings.pytest_t2252_command_mappings.pytest_t2253_command_mappings.pytest_t2254_command_mappings.pytest_t2255_command_mappings.pytest_t2259_command_mappings.pytest_t2262_command_mappings.pyEach test must cover:
+Problem: T2250 uses "auto", T2252 uses "Auto"
Solution: All keys lowercase snake_case, all values PascalCase
+task test # Run all tests
+task lint # Check code style
+task type-check # Verify type hints
+
+
+| File | +Change | +Tests Needed | +
|---|---|---|
robovac.py |
+Case-insensitive matching | +✅ Done | +
vacuum.py |
+Error check includes "No error" | +⚠️ Integration test needed | +
T2250.py - T2262.py (8 files) |
+Error mappings + bidirectional | +❌ No tests | +
task test passesIMPROVEMENT_PLAN.md - Complete analysis and strategytests/test_vacuum/test_t2277_command_mappings.py - Test templateEstimated Time: 6 hours total +Risk: Low (backward compatible changes) +Blocks: None
This directory contains historical documentation from completed improvement initiatives.
+These documents are kept for historical reference and to understand the evolution of the codebase. For current development guidance, see the main Development Workflow section.
Is the error still present? ⚠️ YES
+The underlying problem still exists, but the symptom has changed:
+Users with T2262 vacuum models report these status values cannot be translated:
+Current T2262 Definition:
+RobovacCommand.STATUS: {
+ "code": 15,
+},
+
+
+Missing the values dictionary that maps device responses to human-readable strings.
What Happens:
+getRoboVacHumanReadableValue() can't find mappingCRITICAL BLOCKER: We need actual device data.
+Example from T2277 (code 153):
+"Charging": "BBADGgA=", # Base64-encoded
+"Sleeping": "AhAB",
+
+
+T2262's code 15 likely uses completely different values.
+Contact users via issue #186 and request:
+# Enable debug logging in Home Assistant
+logger:
+ default: info
+ logs:
+ custom_components.robovac: debug
+
+
+Need them to capture:
+These models also have STATUS code 15 with no values:
+ISSUE_186_FIX_PLAN.md¶Comprehensive implementation plan including:
+This plan is ready to hand off to another LLM or developer.
+| Phase | +Status | +Blocker | +
|---|---|---|
| Data Collection | +⏸️ Waiting | +Need user input | +
| Implementation | +⏸️ Blocked | +Waiting on data | +
| Testing | +⏸️ Blocked | +Waiting on implementation | +
| User Validation | +⏸️ Blocked | +Waiting on implementation | +
Hi everyone! To fix this issue properly, I need actual device data from T2262 owners.
+
+Could you please:
+
+1. Enable debug logging in Home Assistant:
+
+ ```yaml
+ logger:
+ default: info
+ logs:
+ custom_components.robovac: debug
+ ```
+
+1. Watch your logs and note when you see messages like:
+ "Command status with value X not found for model T2262"
+
+1. For each message, tell me:
+ - The raw value (X in the message)
+ - What the Eufy app showed at that exact time
+
+Example:
+
+- Raw value: "charging"
+- Eufy app showed: "Charging"
+
+Especially interested in these states:
+
+- Charging
+- Sleeping
+- Completed/Finished
+- Standby/Idle
+- Paused
+- Cleaning/Auto
+- Returning home
+
+Thank you! 🙏
+
+
+Wait for user responses with data
+Hand off to LLM with collected data + ISSUE_186_FIX_PLAN.md
ISSUE_186_FIX_PLAN.md thoroughlytask testISSUE_186_FIX_PLAN.md - Detailed implementation planISSUE_186_ANALYSIS.md - This summarytests/test_vacuum/test_t2262_command_mappings.pycustom_components/robovac/vacuums/T2262.pycustom_components/robovac/vacuums/T2277.pyAnalysis Date: 2025-10-22
+Status: Error confirmed, plan created, awaiting user data
Title: T2262: Missing command mapping for status value "Charging"
+Problem: T2262 vacuum model has STATUS command defined (code 15) but lacks value mappings. When the device sends status updates like "Charging", "Sleeping", or "completed", they cannot be translated to human-readable values, resulting in debug logs and potentially confusing status displays.
+Affected Users: Multiple users (SGXander, JBenson74, dxmnkd316) reported this issue.
+Related Models: T2253 also has the same issue (STATUS code 15, no values defined).
+custom_components/robovac/vacuums/T2262.py):python
+ RobovacCommand.STATUS: {
+ "code": 15,
+ },
No values dict defined
Missing Status Values (reported by users):
+Error value "0" (already handled in ERROR command)
+Similar Working Model (T2277) - Uses different code (153):
+python
+ RobovacCommand.STATUS: {
+ "code": 153,
+ "values": {
+ "Charging": "BBADGgA=",
+ "Sleeping": "AhAB",
+ "completed": "BhADGgIIAQ==",
+ "Standby": "AA==",
+ "Paused": "CAoAEAUyAggB",
+ # ... bidirectional mappings
+ },
+ }
getRoboVacHumanReadableValue() returns the original valueCRITICAL: We lack actual T2262 device data showing:
+Why This Matters: STATUS code 15 (T2262) likely uses different encoding than code 153 (T2277). Cannot safely copy mappings between models.
+Objective: Gather actual T2262 device data from users.
+Action Items:
+yaml
+ logger:
+ default: info
+ logs:
+ custom_components.robovac: debug
Example statuses: Charging, Sleeping, Completed, Standby, Paused, Cleaning, etc.
+Document findings in issue comments before proceeding to implementation.
+Prerequisites:
+File: tests/test_vacuum/test_t2262_command_mappings.py
Add comprehensive STATUS value tests:
+def test_t2262_status_human_readable_values(mock_t2262_robovac) -> None:
+ """Test T2262 STATUS command translates device values to human-readable strings."""
+ # TODO: Replace with ACTUAL device values from Phase 1
+ # Example structure (DO NOT USE without real data):
+ # assert mock_t2262_robovac.getRoboVacHumanReadableValue(
+ # RobovacCommand.STATUS, "DEVICE_VALUE_HERE"
+ # ) == "Charging"
+
+ # Add tests for ALL collected status values:
+ # - Charging
+ # - Sleeping
+ # - Completed
+ # - Standby
+ # - Paused
+ # - Cleaning/Auto
+ # - Returning home
+ # - etc.
+ pass # Remove after adding real tests
+
+
+def test_t2262_status_case_insensitive(mock_t2262_robovac) -> None:
+ """Test T2262 STATUS values work with case-insensitive lookup."""
+ # Test that case variations of device values still match
+ pass # Implement with real data
+
+
+def test_t2262_status_bidirectional_mapping(mock_t2262_robovac) -> None:
+ """Test T2262 STATUS supports bidirectional lookups if needed."""
+ # Only if device uses bidirectional mapping (check T2277 pattern)
+ pass # Implement if needed
+
+
+Run tests to verify they fail:
+task test
+
+
+Expected: New tests fail because T2262.py lacks STATUS values.
+File: custom_components/robovac/vacuums/T2262.py
Update STATUS command with collected data:
+RobovacCommand.STATUS: {
+ "code": 15,
+ "values": {
+ # TODO: Add mappings from Phase 1 data collection
+ # Follow this pattern based on actual device data:
+ # "device_value": "HumanReadable",
+ # "HumanReadable": "device_value", # If bidirectional needed
+
+ # EXAMPLE ONLY - Replace with real data:
+ # "charging": "Charging",
+ # "Charging": "charging",
+ # "sleeping": "Sleeping",
+ # "Sleeping": "sleeping",
+ # "completed": "Completed",
+ # etc.
+ },
+},
+
+
+Important Notes:
+task test
+
+
+Expected: All new STATUS tests pass.
+task test
+
+
+Expected: All existing tests still pass (no regressions).
+task lint
+task type-check
+
+
+Expected: No linting or type errors.
+python
+ RobovacCommand.STATUS: {
+ "code": 15,
+ # Mappings confirmed by users: @SGXander, @dxmnkd316
+ # Firmware version: [if known]
+ # Date verified: 2025-10-22
+ "values": {
+ # ...
+ },
+ },
Update DEVELOPMENT.md if new patterns introduced
+Add to CODE_COVERAGE_TODO.md if other models need similar fixes
+Check if other models need similar fixes:
+Models with STATUS code 15 and no values:
+Decision: Fix T2262 first, then evaluate if pattern applies to others.
+All tests must pass before merging:
+Before marking issue as resolved:
+STOP if:
+ASK for:
+ALWAYS:
+NEVER:
+Follow existing patterns:
+Conventional Commits:
+# After Phase 1
+git checkout -b fix/issue-186-t2262-status-mappings
+
+# After implementation
+git add tests/test_vacuum/test_t2262_command_mappings.py
+git commit -m "test: add T2262 STATUS value mapping tests"
+
+git add custom_components/robovac/vacuums/T2262.py
+git commit -m "fix(T2262): add STATUS command value mappings
+
+- Add Charging, Sleeping, completed status values
+- Fixes #186
+- Confirmed with users @SGXander @dxmnkd316"
+
+# After validation
+git push origin fix/issue-186-t2262-status-mappings
+
+
+PR Description Template:
+## Fixes #186
+
+### Changes
+- Added STATUS value mappings for T2262 model
+- Added comprehensive test coverage for STATUS values
+
+### Testing
+- ✅ All tests pass
+- ✅ Lint/type checks pass
+- ✅ User validation: @username confirmed working
+
+### Data Source
+Status mappings provided by users:
+- @SGXander
+- @dxmnkd316
+
+### Notes
+[Any important context about the mappings]
+
+
+Issue can be closed when:
+If you need clarification:
+Total: ~3-8 days (mostly waiting for user feedback)
+tests/test_vacuum/test_t2278_command_mappings.pyLast Updated: 2025-10-22 +Status: Ready for data collection +Assigned To: [LLM or developer name]
VacuumEntityFeature.BATTERY from 35+ vacuum models_attr_battery_level from vacuum.py entityMaintain existing battery sensor functionality (no breaking changes)
+Error Handling Improvements
+getErrorMessageWithContext() function with actionable troubleshootingEnhanced error messages with user guidance
+Model Validation Tools
+model_validator.py: Library module with series detection (C, G, L, X series)model_validator_cli.py: Standalone CLI tool for usersStyle Issues:
+Conciseness:
+Readability:
+Maintainability:
+Task: Create test file for battery feature validation
+File: tests/test_vacuum/test_battery_feature_removal.py
Test Cases:
+# Test 1: Verify VacuumEntityFeature.BATTERY is NOT in homeassistant_features for all models
+# Test 2: Verify battery command mappings still exist (backward compatibility)
+# Test 3: Verify battery level property is removed from vacuum entity
+# Test 4: Verify battery sensor still works independently
+
+
+Implementation Details:
+custom_components/robovac/vacuumsVacuumEntityFeature.BATTERY not in homeassistant_featuresRobovacCommand.BATTERY still exists in commands_attr_battery_level attributeRun tests (should fail):
+task test tests/test_vacuum/test_battery_feature_removal.py
+
+
+Task: Remove VacuumEntityFeature.BATTERY from all 35 vacuum model files
Files to modify: All vacuum models in custom_components/robovac/vacuums/T2*.py
Pattern to apply:
+# BEFORE:
+homeassistant_features = (
+ VacuumEntityFeature.BATTERY
+ | VacuumEntityFeature.FAN_SPEED
+ | ...
+)
+
+# AFTER:
+homeassistant_features = (
+ VacuumEntityFeature.FAN_SPEED
+ | ...
+)
+
+
+Models to update: (35 files)
+Implementation:
+Run tests (should pass):
+task test tests/test_vacuum/test_battery_feature_removal.py
+
+
+Task: Remove deprecated battery level attribute from vacuum.py
+File: custom_components/robovac/vacuum.py
Search for:
+_attr_battery_level declarationsbattery_level propertyModification:
+Validation:
+Run tests:
+task test
+
+
+Commit message:
+fix: remove deprecated battery feature from vacuum models
+
+Remove VacuumEntityFeature.BATTERY from all 35 vacuum models
+to prepare for Home Assistant 2026.8 compatibility.
+
+- Remove battery feature flag from homeassistant_features
+- Maintain battery command mappings for backward compatibility
+- Battery reporting continues via dedicated battery sensor
+- No breaking changes for existing users
+
+Refs: #171
+
+
+Task: Create test file for error message enhancements
+File: tests/test_errors.py
Test Cases:
+# Test 1: Verify typo fix - "Laser sensor stuck" not "Laser sesor stuck"
+# Test 2: Test getErrorMessage() returns correct messages
+# Test 3: Test getErrorMessageWithContext() returns message + context
+# Test 4: Test context includes troubleshooting steps
+# Test 5: Test context includes series-specific guidance
+# Test 6: Test unknown error codes return gracefully
+
+
+Implementation Details:
+Run tests (should fail):
+task test tests/test_errors.py
+
+
+Task: Fix "sesor" → "sensor" typo
+File: custom_components/robovac/errors.py
Change:
+# Line 20
+# BEFORE:
+19: "Laser sesor stuck",
+
+# AFTER:
+19: "Laser sensor stuck",
+
+
+Run tests (should pass typo test):
+task test tests/test_errors.py::test_laser_sensor_typo_fixed
+
+
+Task: Add enhanced error messaging function
+File: custom_components/robovac/errors.py
Function signature:
+def getErrorMessageWithContext(
+ code: str | int,
+ model_code: str | None = None
+) -> dict[str, str | list[str]]:
+ """Get error message with troubleshooting context.
+
+ Args:
+ code: The error code to look up
+ model_code: Optional model code for model-specific guidance
+
+ Returns:
+ Dictionary containing:
+ - message: The error message
+ - troubleshooting: List of troubleshooting steps
+ - common_causes: List of common causes
+ - series_info: Model series-specific information (if applicable)
+ """
+
+
+Implementation Details:
+Error context examples:
+TROUBLESHOOTING_CONTEXT = {
+ 1: { # Front bumper stuck
+ "troubleshooting": [
+ "Check front bumper for obstructions",
+ "Clean bumper sensors",
+ "Ensure bumper moves freely"
+ ],
+ "common_causes": [
+ "Hair or debris blocking bumper",
+ "Damaged bumper spring",
+ "Sensor misalignment"
+ ]
+ },
+ 19: { # Laser sensor stuck
+ "troubleshooting": [
+ "Remove any stickers or tape from laser sensor",
+ "Clean laser sensor cover",
+ "Check for physical damage to sensor",
+ "Restart vacuum"
+ ],
+ "common_causes": [
+ "Protective film not removed",
+ "Dust or debris on sensor",
+ "Physical damage to sensor cover"
+ ]
+ },
+ # ... add for other common errors
+}
+
+
+Run tests (should pass):
+task test tests/test_errors.py
+
+
+Task: Ensure all error functions have proper typing
+Files:
+custom_components/robovac/errors.pyRequirements:
+Run type check:
+task type-check
+
+
+Commit message:
+feat: enhance error messages with troubleshooting context
+
+Add getErrorMessageWithContext() function to provide users with
+actionable troubleshooting steps and common causes for error codes.
+
+- Fix typo: "Laser sesor stuck" → "Laser sensor stuck"
+- Add TROUBLESHOOTING_CONTEXT with detailed guidance
+- Support model series-specific troubleshooting
+- Maintain backward compatibility with getErrorMessage()
+
+Refs: #171
+
+
+Task: Create comprehensive test file for model validator
+File: tests/test_model_validator.py
Test Cases:
+# Series Detection Tests
+# Test 1: Detect C series (RoboVac C models)
+# Test 2: Detect G series (RoboVac G series)
+# Test 3: Detect L series (Clean L series)
+# Test 4: Detect X series (RoboVac X series)
+# Test 5: Handle unknown series
+# Test 6: Handle None/invalid model codes
+
+# Model Validation Tests
+# Test 7: Validate supported models return True
+# Test 8: Validate unsupported models return False
+# Test 9: Get model list
+# Test 10: Get supported model for device code (T2278 → T2278)
+
+# Suggestion Tests
+# Test 11: Suggest similar models for unsupported model
+# Test 12: Suggest series alternatives
+# Test 13: Handle edge cases (empty suggestions)
+
+# Troubleshooting Tests
+# Test 14: Get series-specific troubleshooting guide
+# Test 15: Get general troubleshooting guide
+# Test 16: Validate troubleshooting content is helpful
+
+
+Implementation Details:
+Run tests (should fail):
+task test tests/test_model_validator.py
+
+
+Task: Implement model validator library
+File: custom_components/robovac/model_validator.py
Module structure:
+"""Model validation and series detection for RoboVac devices.
+
+This module provides utilities for validating RoboVac model codes,
+detecting model series (C, G, L, X), and providing troubleshooting
+guidance for unsupported models.
+"""
+
+from typing import Dict, List, Optional, Tuple
+import re
+
+# Series detection patterns
+SERIES_PATTERNS = {
+ 'C': r'^T2(103|123)$', # RoboVac C series
+ 'G': r'^T2(150|261)$', # RoboVac G series
+ 'L': r'^T2(2[67][0-9]|278|320)$', # Clean L series
+ 'X': r'^T2(080|117|118|119|120|128|130|132|181|190|192|193|194|25[0-9]|262)$', # RoboVac X series
+}
+
+def detect_series(model_code: str) -> Optional[str]:
+ """Detect the series of a RoboVac model."""
+
+def is_supported_model(model_code: str) -> bool:
+ """Check if a model code is supported."""
+
+def get_supported_models() -> List[str]:
+ """Get list of all supported model codes."""
+
+def suggest_similar_models(model_code: str, max_suggestions: int = 5) -> List[Tuple[str, str]]:
+ """Suggest similar supported models for an unsupported model."""
+
+def get_troubleshooting_guide(model_code: str) -> Dict[str, str | List[str]]:
+ """Get series-specific troubleshooting guide."""
+
+
+Implementation Requirements:
+Run tests (should pass):
+task test tests/test_model_validator.py
+
+
+Task: Ensure code quality standards
+Files:
+custom_components/robovac/model_validator.pyRequirements:
+Run quality checks:
+task lint
+task type-check
+
+
+Commit message:
+feat: add model validator library with series detection
+
+Add model_validator.py module to provide:
+- Automatic model series identification (C, G, L, X)
+- Model validation against supported models
+- Smart suggestions for unsupported models
+- Series-specific troubleshooting guides
+
+Refs: #171
+
+
+Task: Create test file for CLI functionality
+File: tests/test_model_validator_cli.py
Test Cases:
+# Argument Parsing Tests
+# Test 1: Parse model code argument
+# Test 2: Parse --list flag
+# Test 3: Parse --help flag
+# Test 4: Handle missing arguments
+
+# Output Tests
+# Test 5: Supported model shows success message
+# Test 6: Unsupported model shows suggestions
+# Test 7: List mode shows all models
+# Test 8: Output format is user-friendly
+
+# Integration Tests
+# Test 9: Standalone mode works without imports
+# Test 10: Integrated mode uses actual ROBOVAC_MODELS
+
+
+Implementation Details:
+Run tests (should fail):
+task test tests/test_model_validator_cli.py
+
+
+Task: Implement standalone CLI tool
+File: custom_components/robovac/model_validator_cli.py
Module structure:
+#!/usr/bin/env python3
+"""Standalone CLI tool for RoboVac model validation.
+
+This tool can be used independently to validate RoboVac model compatibility
+and get suggestions for unsupported models.
+
+Usage:
+ python model_validator_cli.py T2278
+ python model_validator_cli.py --list
+"""
+
+import argparse
+import sys
+from typing import Optional
+
+try:
+ from .model_validator import (
+ is_supported_model,
+ get_supported_models,
+ suggest_similar_models,
+ get_troubleshooting_guide,
+ detect_series
+ )
+ STANDALONE = False
+except ImportError:
+ # Fallback for standalone usage
+ STANDALONE = True
+ # Minimal implementation with hardcoded model list
+
+def main():
+ """Main CLI entry point."""
+ parser = argparse.ArgumentParser(
+ description="Validate RoboVac model compatibility"
+ )
+ parser.add_argument(
+ "model",
+ nargs="?",
+ help="Model code to validate (e.g., T2278)"
+ )
+ parser.add_argument(
+ "--list",
+ action="store_true",
+ help="List all supported models"
+ )
+ args = parser.parse_args()
+
+ # Implementation
+
+if __name__ == "__main__":
+ sys.exit(main())
+
+
+Implementation Requirements:
+Run tests (should pass):
+task test tests/test_model_validator_cli.py
+
+
+Task: Finalize CLI tool
+Files:
+custom_components/robovac/model_validator_cli.pyREADME.md (add usage documentation)Requirements:
+python custom_components/robovac/model_validator_cli.py T2278Run lint:
+task lint
+
+
+Commit message:
+feat: add standalone CLI tool for model validation
+
+Add model_validator_cli.py tool for users to validate
+RoboVac model compatibility before setting up integration.
+
+- Supports standalone usage without integration installed
+- Shows model validation status
+- Suggests similar models for unsupported devices
+- Lists all supported models with --list flag
+- Provides troubleshooting guidance
+
+Usage: python model_validator_cli.py T2278
+
+Refs: #171
+
+
+Task: Create end-to-end integration tests
+File: tests/test_integration_pr171.py
Test Cases:
+# Test 1: Vacuum entity initializes without battery feature
+# Test 2: Battery sensor still reports battery level
+# Test 3: Error messages include context when displayed
+# Test 4: Model validator works with actual vacuum models
+# Test 5: CLI tool works with integration
+
+
+Run tests:
+task test tests/test_integration_pr171.py
+
+
+Task: Update project documentation
+Files to update:
+README.md: Add model validator CLI usageDEVELOPMENT.md: Document error context systemCHANGELOG.md: Add entry for this feature setREADME.md additions:
+## Model Validation
+
+Before setting up the integration, you can validate if your RoboVac model is supported:
+
+```bash
+python custom_components/robovac/model_validator_cli.py T2278
+
+
+To see all supported models:
+python custom_components/robovac/model_validator_cli.py --list
+
+
+Run markdown lint:
+npx markdownlint-cli2 "**/*.md"
+
+
+Task: Run complete test suite
+Commands:
+task lint
+task type-check
+task test
+
+
+Validation:
+Commit message:
+docs: update documentation for PR #171 features
+
+Add documentation for:
+- Model validator CLI tool usage
+- Error message improvements
+- Battery deprecation changes
+- Integration guide updates
+
+Refs: #171
+
+
+Task: Create summary of changes for review
+File: IMPLEMENTATION_SUMMARY_PR171.md
Contents:
+Task: Run full validation suite
+# Run all tests
+task test
+
+# Check code quality
+task lint
+task type-check
+
+# Verify documentation
+npx markdownlint-cli2 "**/*.md"
+
+# Manual testing
+python custom_components/robovac/model_validator_cli.py T2278
+python custom_components/robovac/model_validator_cli.py T9999
+python custom_components/robovac/model_validator_cli.py --list
+
+
+task lint before committingtask type-check before committingtask test after each phase# Run specific test file
+task test tests/test_model_validator.py
+
+# Run specific test function
+task test tests/test_model_validator.py::test_detect_c_series
+
+# Run all tests
+task test
+
+# Run with coverage
+```bash
+task test --cov
+
+
+<type>: <description>
+
+<body>
+
+Refs: #171
+
+
+Types: feat, fix, refactor, test, docs, chore
Total: 12-18 hours of focused development
+.windsurf/rules/tests/test_vacuum/test_t2278_command_mappings.pyPR Link: https://github.com/damacus/robovac/pull/171
+PR Title: Enhance user experience: battery deprecation fixes and model validation tools
+Status: Open, awaiting merge
+Reviewed PR #171 against the project's code style guidelines:
+VacuumEntityFeature.BATTERY from 35+ vacuum models_attr_battery_level from vacuum entitygetErrorMessageWithContext() function with troubleshooting guidanceA comprehensive, multi-phased implementation plan has been created:
+File: IMPLEMENTATION_PLAN_PR171.md
_attr_battery_level from vacuum entitygetErrorMessageWithContext() functionmodel_validator.py modulemodel_validator_cli.py toolAll phases follow strict Test-Driven Development:
+task linttask type-checktask testgetErrorMessageWithContext() implemented and testedmodel_validator.py implemented and testedmodel_validator_cli.py implemented and testedTotal: 12-18 hours of focused development
+IMPLEMENTATION_PLAN_PR171.mdtests/test_vacuum/test_battery_feature_removal.pytests/test_errors.pytests/test_model_validator.pytests/test_model_validator_cli.pytests/test_integration_pr171.pycustom_components/robovac/model_validator.pycustom_components/robovac/model_validator_cli.pyIMPLEMENTATION_SUMMARY_PR171.md (after completion)custom_components/robovac/vacuums/T2*.pycustom_components/robovac/vacuum.pycustom_components/robovac/errors.pyREADME.mdDEVELOPMENT.mdCHANGELOG.mdmodel_validator.py be exposed as a public API?PR #171 adds valuable features with good code quality. The implementation plan provides a comprehensive, test-first approach to replicate this functionality on the current branch while adhering to project coding standards.
+Recommendation: Follow the detailed implementation plan in IMPLEMENTATION_PLAN_PR171.md to implement these features with full test coverage and quality assurance.
This guide provides a quick reference for implementing the features from PR #171 on your current branch.
+Full details: See IMPLEMENTATION_PLAN_PR171.md
# Review summary
+cat PR171_REVIEW_SUMMARY.md
+
+# Read full implementation plan
+cat IMPLEMENTATION_PLAN_PR171.md
+
+
+# Create test file
+touch tests/test_vacuum/test_battery_feature_removal.py
+
+# Write failing tests first (see plan for test cases)
+# Then implement changes
+
+
+For each phase:
+After each phase:
+task lint
+task type-check
+task test
+
+
+| Phase | +Task | +Time | +Status | +
|---|---|---|---|
| 1 | +Battery Deprecation | +2-3h | +⬜ Not Started | +
| 2 | +Error Messages | +2-3h | +⬜ Not Started | +
| 3 | +Model Validator | +3-4h | +⬜ Not Started | +
| 4 | +CLI Tool | +2-3h | +⬜ Not Started | +
| 5 | +Integration & Docs | +2-3h | +⬜ Not Started | +
| 6 | +Review & Cleanup | +1-2h | +⬜ Not Started | +
Total: 12-18 hours
+# Run specific test file
+task test tests/test_errors.py
+
+# Run specific test
+task test tests/test_errors.py::test_laser_sensor_typo_fixed
+
+# Run all tests
+task test
+
+# Run with coverage
+task test --cov
+
+
+<type>: <short description>
+
+<detailed description>
+
+- Bullet point 1
+- Bullet point 2
+
+Refs: #171
+
+
+Types: feat, fix, refactor, test, docs, chore
custom_components/robovac/model_validator.pycustom_components/robovac/model_validator_cli.pytests/test_battery_feature_removal.pytests/test_errors.pytests/test_model_validator.pytests/test_model_validator_cli.pytests/test_integration_pr171.pycustom_components/robovac/vacuums/T2*.py)custom_components/robovac/vacuum.pycustom_components/robovac/errors.pyREADME.mdDEVELOPMENT.mdCHANGELOG.mdtask test not pytestMark each step as complete:
+IMPLEMENTATION_PLAN_PR171.mdPR171_REVIEW_SUMMARY.md.windsurf/rules/tests/test_vacuum/test_t2278_command_mappings.pyBefore committing any phase:
+# Check linting
+task lint
+
+# Check types
+task type-check
+
+# Run tests
+task test
+
+# Check markdown
+npx markdownlint-cli2 "**/*.md"
+
+
+All should pass! ✅
+At the end, verify:
+# Ensure you're on the right branch
+git status
+
+# Start Phase 1, Step 1.1
+# Open: tests/test_vacuum/test_battery_feature_removal.py
+
+
+Good luck! Follow the detailed plan and you'll have all features implemented with full test coverage. 🚀
This directory contains working documents from PR #171 - Type Annotations Implementation.
+PR #171 successfully added comprehensive type annotations to the entire test suite:
+October 22, 2025
+This section will describe how to contribute to the RoboVac integration. Content migration from existing documents is in progress.
DPS (Data Point Specification) codes are numeric identifiers used in Tuya-based devices like Eufy RoboVacs to control various functions and retrieve device states. Each DPS code maps to a specific function or feature of your vacuum, such as:
+In the RoboVac integration, these codes are used to communicate with your vacuum via the Tuya local API. Think of them as the language that allows Home Assistant to talk to your vacuum.
+Finding the correct DPS codes for your vacuum model is essential for adding support for new devices. Here are methods to discover these codes:
+The repository includes a test that analyzes DPS codes for all supported models:
+tests/test_vacuum/test_model_dps_analysis.py file (or run analyze_model_dps.py in the root directory).This can help you understand patterns in how different models use DPS codes.
+yaml
+ logger:
+ default: info
+ logs:
+ custom_components.robovac: debug
"Updating entity values from data points: ..." which will display all the DPS codes and values being reported by your device.For more advanced users:
+If you're comfortable with Python, you can try these tools to capture raw device data:
+tinytuya scan - Scans local network for Tuya devices (after you have credentials)To add support for a new device, you'll need to:
+custom_components/robovac/vacuums/ directory.Here's a simplified example of how a model-specific class looks:
+from custom_components.robovac.vacuums.base import RobovacModelDetails, RoboVacEntityFeature, RobovacCommand
+
+class YourModel(RobovacModelDetails):
+ """Implementation for Your Specific Model."""
+
+ # Define which Home Assistant features this vacuum supports
+ homeassistant_features = (
+ # Add relevant features here
+ )
+
+ # Define which RoboVac features this vacuum supports
+ robovac_features = (
+ # Add relevant features here
+ )
+
+ # Map commands to DPS codes - override any that differ from defaults
+ dps_codes = {
+ "BATTERY_LEVEL": "104", # Example - use actual codes from your device
+ "STATUS": "15",
+ # Add other codes as needed
+ }
+
+ # Define available commands
+ commands = {
+ RobovacCommand.START_PAUSE: True,
+ RobovacCommand.BATTERY: True,
+ # Add other commands as appropriate
+ }
+Welcome to the unified documentation hub for the RoboVac integration with Home Assistant.
+task install-dev.task test.