Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
155 changes: 155 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
# Changelog
All notable changes to this project will be documented in this file.

The format is based on https://keepachangelog.com/en/1.1.0/,
and this project adheres to Semantic Versioning.

---

## [1.1.2] – 2026-03-xx
### Added
- Persistent HVAC mode support, the ability to toggle Smart Schedule for heating/cooling (this will trigger an API call). (#43)
- Full Smart AC Control support.
- New `/zones/{id}/set` parameter `heating_mode` (0 = off, 1 = HEAT, 2 = COOL).
- Ability to switch an entire zone on or off with a single API call via `/zones/set/` (as either an overlay or a persistent change).
- Automatic database history purge via CLI and manual purge via API (`/purgehistory/now/`).
- This changelog. (#44)

### Changed
- Improved synchronization logic between HomeKit state and cloud‑derived zone data (HomeKit device types now take precedence and names are stored correctly).
- `heating_enabled=true` now restores the last known active heating or cooling mode.
- Updated documentation to reflect persistent HVAC behavior and Smart AC Control support.
- Updated `index.html` to support Smart AC Control (COOL modes/colors) and persistent Smart Schedule switching.
- Updated unit tests to cover new and modified features.

### Fixed
- Corrected case‑sensitivity issues in UUID comparisons.
- Home Assistant add‑on now only moves the database when the public database does not already exist.
- Removed large top‑page spacing in `index.html`.

---

## [1.1.1] – 2026-03-05
### Added
- Home Assistant add‑on upgrade now supports standalone accessories.
- Containerization support including Dockerfile, entrypoint script, and installation documentation.
- Additional tests for multi‑pairing support and temperature sync.

### Changed
- Updated version to 1.1.1.
- Improved cloud sync behavior and humidity/temperature handling.
- Polling is now always enabled for Smart AC Control devices.
- General cleanup of duplicated imports and inline code.

### Fixed
- Fixed accessory ID collision in HomeKit change handler.
- Corrected multiple standalone accessory issues including window detection, NoneType sync, and crash conditions.

---

## [1.1.0] – 2026-02-26
### Added
- Standalone HomeKit accessory support (e.g., Smart AC Control V3+).
- Open‑window detection logic and corresponding unit tests.
- Dark theme for the UI.
- Additional open‑window detection when AC is active.

### Changed
- Version updated to 1.1.0.
- UI updates including index.html improvements.
- README and installation documentation updates.

### Fixed
- Resolved Windows/Linux line‑ending issues in tests.
- Fixed missing zone information (issue #19).
- Improved humidity sync using cloud API.

---

## [1.0.3] – 2026-02-07
### Added
- Home Assistant Add‑on.
- Additional tests and fixes for project scripts.
- Added pytest‑httpx to development requirements.

### Changed
- Cleanup of development dependencies and initial packaging support.
- Improved test coverage and ruff cleanup.

### Fixed
- Fixed broken unit tests.
- Resolved zeroconf test issues.
- Corrected bridge IP display regression.

---

## [1.0.2] – 2025-11-25
### Fixed
- Corrected zone leader update during synchronization.

---

## [1.0.1] – 2025-11-17 to 2025-11-24
### Added
- Improved default icons for heating devices.

### Changed
- Documentation and README updates.

### Fixed
- Removed invalid argument in code.
- Fixed command processing for non‑thermostat devices.
- Corrected fetching of zones and thermostats by ID.

---

## [1.0.0] – 2025-11-03 to 2025-11-21
### Added
- Bearer token support.
- User‑Agent header for outgoing requests.
- Domoticz plugin improvements and voice tag enhancements.
- SSE refresh improvements.
- Auto‑setup support for dzga/dzga‑flask.
- Optimistic update handling for integrations.
- Historic data exposure in the UI.
- Minimal web UI for diagnostics and setup.
- REST API consistency improvements.

### Changed
- Major logging cleanup and improvements.
- Improved shutdown sequence.
- Enhanced thermostat history visualization.
- Updated initial heartbeat and event logging.

### Fixed
- Multiple Domoticz plugin fixes.
- Device creation fixes.
- Resolved Python version confusion.

---

## [0.9.0] – 2025-10-30 to 2025-11-02
### Added
- Initial proxy code.
- Polling/eventing system groundwork.
- Zones, storage, and event system.
- Cloud data feeding for improved reporting.
- Ability to set heating per zone via REST.
- Preliminary project roadmap.

### Changed
- Major README enhancements.
- API cleanup and consistency improvements.
- Improved state reporting.

### Fixed
- Fixes for local Domoticz integration issues.

---

## [0.1.0] – 2025-10-30
### Added
- Initial working prototype.
- Basic REST API.
- Early event system.
- Initial project structure.
9 changes: 6 additions & 3 deletions home-assistant/config.yaml
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
name: "TadoLocal Server"
description: "TadoLocal Server, based on AmpScm/TadoLocal"
version: "1.1.1"
version: "1.1.2"
slug: "tado-local-server"
url: "https://github.com/AmpScm/TadoLocal"
init: false
webui: "http://[HOST]:[PORT:4407]"
arch:
- aarch64
- amd64
- armhf
- armv7
Expand All @@ -19,13 +20,15 @@ options:
bridge_ip: "1.2.3.4"
bridge_pin: "123-45-678"
keep_db_private: true
log_level: info
purge_history: 0
accessories: []
log_level: info
schema:
bridge_ip: str
bridge_pin: str
keep_db_private: bool
log_level: list(debug|info)
purge_history: int
accessories:
- ip: str
pin: str
log_level: list(debug|info)
29 changes: 20 additions & 9 deletions home-assistant/run.sh
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
#!/usr/bin/with-contenv bashio
echo "Tado-local server starting.."
echo "Tado-local server starting..."
CONFIG_PSTH=/data/options.json
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Path?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, leftover from debugging :-) wanted to be sure the new script was loading in the HA docker container.

ARGS=""
PRIVATE_DB_PATH=/data/tado-local.db
PUBLIC_DB_PATH=/homeassistant_config/.storage/tado-local.db

# Get the variables from HA
BRIDGE_IP="$(bashio::config 'bridge_ip')"
Expand All @@ -21,19 +23,28 @@ fi
# Determine where to store the database based on the keep_db_private option
LOCAL_DB="$(bashio::config 'keep_db_private')"
if [ "$LOCAL_DB" = true ]; then
echo "INFO: keep_db_private is true, using /data/tado-local.db (not accessible outside the container)"
ARGS="${ARGS} --state /data/tado-local.db"
echo "INFO: keep_db_private is true, using ${PRIVATE_DB_PATH} (not accessible outside the container)"
ARGS="${ARGS} --state ${PRIVATE_DB_PATH}"
else
echo "INFO: keep_db_private is false, using /homeassistant_config/.storage/tado-local.db (accessible outside the container)"
ARGS="${ARGS} --state /homeassistant_config/.storage/tado-local.db"
if [ -f "/data/tado-local.db" ]; then
echo "INFO: keep_db_private is false, using ${PUBLIC_DB_PATH} (accessible outside the container)"
ARGS="${ARGS} --state ${PUBLIC_DB_PATH}"
if [ -f "${PRIVATE_DB_PATH}" ] && [ ! -f "${PUBLIC_DB_PATH}" ]; then
# Forward compatibility: if the old database location exists, move it to the new location and use it.
echo "*** WARNING: DB found at /data/tado-local.db. move to new location and use it. ***"
echo "--- mv /data/tado-local.db /homeassistant_config/.storage/tado-local.db"
mv /data/tado-local.db /homeassistant_config/.storage/tado-local.db
echo "*** WARNING: DB found at ${PRIVATE_DB_PATH}. move to new location and use it. ***"
echo "--- mv ${PRIVATE_DB_PATH} ${PUBLIC_DB_PATH}"
mv $PRIVATE_DB_PATH $PUBLIC_DB_PATH
fi
fi

# Check if purge_history is set and add it to the arguments
PURGE_DAYS="$(bashio::config 'purge_history')"
if [ -n "$PURGE_DAYS" ] && [[ "$PURGE_DAYS" =~ ^[0-9]+$ ]] && [ "$PURGE_DAYS" -ne 0 ]; then
echo "INFO: purge history after ${PURGE_DAYS} days"
ARGS="${ARGS} --purgehistory ${PURGE_DAYS}"
elif [ -n "$PURGE_DAYS" ] && ! [[ "$PURGE_DAYS" =~ ^[0-9]+$ ]]; then
echo "WARNING: Invalid purge_history value '${PURGE_DAYS}'. It must be a non-negative integer. Ignoring purge_history setting."
fi

# Get the number of accessories configured in HA
ACCESSORY_COUNT="$(bashio::config 'accessories|length' || echo 0)"
echo "Accessories found: ${ACCESSORY_COUNT}"
Expand Down
4 changes: 4 additions & 0 deletions home-assistant/translations/en.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ configuration:
description: "Store the TadoLocal database in the add-on data folder (/data) instead of placing it in the Home Assistant configuration folder (/config/.storage).
By placing it in /config/.storage, it becomes accessible or editable via Home Assistant."

purge_history:
name: "Purge history after (days)"
description: "Automatically purge device state history records older than the specified number of days. Set to 0 to disable automatic purging."

accessories:
name: "Accessories"
description: "List of Tado accessories to pair. (Like Smart AC Control)"
Expand Down
4 changes: 4 additions & 0 deletions home-assistant/translations/nl.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ configuration:
description: "Bewaar de TadoLocal database in de datafolder (/data) van de add-on in plaats van deze in de Home Assistant-configuratiemap (/config/.storage) te plaatsen.
Door de /config/.storage te plaatsen is deze te benaderen of bewerken via HA."

purge_history:
name: "Geschiedenis automatisch opschonen na (dagen)"
description: "Automatisch geschiedenisrecords van apparaatstatussen ouder dan het opgegeven aantal dagen opschonen. Stel in op 0 om automatisch opschonen uit te schakelen."

accessories:
name: "Accessoires"
description: "Lijst met Tado-accessoires die gepaired moeten worden. (Zoals Smart AC Control)"
Expand Down
15 changes: 14 additions & 1 deletion tado_local/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ def handle_signal(signum, frame):
tado_api = TadoLocalAPI(str(db_path))

# Initialize Tado Cloud API (always enabled)
cloud_api = TadoCloudAPI(str(db_path), tado_api=tado_api)
cloud_api = TadoCloudAPI(str(db_path), tado_api=tado_api, purge_history_days=args.purgehistory)

# Check if already authenticated
if not cloud_api.is_authenticated():
Expand Down Expand Up @@ -404,6 +404,9 @@ def main():
# Reconnect to a previously paired standalone accessory (no PIN needed)
tado-local --bridge-ip 192.168.1.100 --accessory-ip 192.168.1.101

# Keep database clean by clearing device history after 30 days
tado-local --bridge-ip 192.168.1.100 --accessory-ip 192.168.1.101 --purgehistory 30

API Endpoints:
GET / - API information
GET /status - System status
Expand Down Expand Up @@ -464,6 +467,10 @@ def main():
"--accessory-pin", action="append", default=[],
help="HomeKit PIN for a standalone accessory (repeatable, order must match --accessory-ip)"
)
parser.add_argument(
"--purgehistory", type=int,
help="Automatically delete device history records older than specified number of days (e.g., 30)"
)
# Parse CLI arguments
args = parser.parse_args()

Expand Down Expand Up @@ -540,6 +547,12 @@ def main():
logger.error(f"Failed to write PID file: {e}")
exit(1)

# Check that --purgehistory value is valid if provided
if args.purgehistory is not None:
if args.purgehistory < 7:
logger.error("Invalid value for --purgehistory: must be a non-negative integer and at least 7 days")
exit(1)

# Run with proper error handling
try:
asyncio.run(run_server(args))
Expand Down
8 changes: 4 additions & 4 deletions tado_local/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,10 +196,10 @@ def _process_raw_accessories(self, raw_accessories):

for service in a.get('services', []):
# AccessoryInformation service UUID
if service.get('type') == '0000003E-0000-1000-8000-0026BB765291':
if service.get('type').upper() == '0000003E-0000-1000-8000-0026BB765291':
for char in service.get('characteristics', []):
# SerialNumber characteristic UUID
if char.get('type') == '00000030-0000-1000-8000-0026BB765291':
if char.get('type').upper() == '00000030-0000-1000-8000-0026BB765291':
serial_number = char.get('value')
break
if serial_number:
Expand Down Expand Up @@ -295,7 +295,7 @@ async def initialize_device_states(self):
for pairing_id, items in by_pairing.items():
pairing = self.aid_to_pairing.get(items[0][0], self.pairing)
for i in range(0, len(items), batch_size):
batch = items[i : i + batch_size]
batch = items[i: i + batch_size]
char_keys = [(aid, iid) for aid, iid, _, _ in batch]

try:
Expand Down Expand Up @@ -931,7 +931,7 @@ async def _poll_characteristics(self, char_list, source="POLLING"):
for pairing_id, items in by_pairing.items():
pairing = self.aid_to_pairing.get(items[0][0], self.pairing)
for i in range(0, len(items), batch_size):
batch = items[i : i + batch_size]
batch = items[i: i + batch_size]

try:
results = await pairing.get_characteristics(batch)
Expand Down
Loading
Loading