aio-panasonic-comfort-cloud
aio-panasonic-comfort-cloud: Asynchronous Python library for Panasonic Comfort Cloud API
This library provides asynchronous access to the Panasonic Comfort Cloud API, enabling developers to interact with Panasonic air conditioning units.
Installation
pip install aio-panasonic-comfort-cloud
Quick Start
Basic Usage
import asyncio
import aiohttp
from aio_panasonic_comfort_cloud import ApiClient
async def main():
async with aiohttp.ClientSession() as session:
client = ApiClient("your_email@example.com", "your_password", session)
# Start the session (authenticate and fetch devices)
await client.start_session()
# Get list of devices
devices = client.get_devices()
for device_info in devices:
print(f"Device: {device_info.name}")
# Get full device status
device = await client.get_device(device_info)
params = device.parameters
print(f" Power: {params.power.name}")
print(f" Mode: {params.mode.name}")
print(f" Fan Speed: {params.fan_speed.name}")
print(f" Target Temp: {params.target_temperature}°C")
print(f" Inside Temp: {params.inside_temperature}°C")
# Clean up the session
await client.stop_session()
asyncio.run(main())
Controlling a Device
Use ChangeRequestBuilder for a fluent API to build and apply changes:
from aio_panasonic_comfort_cloud import ApiClient, ChangeRequestBuilder, constants
# ... (start session as above)
device = await client.get_device(devices[0])
builder = ChangeRequestBuilder(device)
builder.set_power_mode(constants.Power.On)
builder.set_hvac_mode(constants.OperationMode.Cool)
builder.set_target_temperature(24)
builder.set_fan_speed(constants.FanSpeed.Auto)
if builder.has_changes:
await client.set_device_raw(device, builder.build())
Avoiding Unwanted Power-On
Some changes (swing direction, hvac mode, target temperature, eco mode) require the
device to be on to take effect, so by default the library silently powers the device
on for you if it's currently off. If you'd rather not have those changes wake up an
AC that was deliberately left off, construct the client with auto_power_on=False.
While the device is off, matching changes are buffered instead of sent; they're
applied together the next time the device is explicitly powered on.
client = ApiClient("your_email@example.com", "your_password", session, auto_power_on=False)
# ... device is off ...
builder = client.new_change_request(device) # wires auto_power_on from the client
builder.set_horizontal_swing(constants.AirSwingLR.Left)
await client.set_device_raw(device, builder.build()) # buffered, nothing sent yet
# later, when the device is turned on the buffered swing change is merged in and sent together
builder = client.new_change_request(device)
builder.set_power_mode(constants.Power.On)
await client.set_device_raw(device, builder.build())
Use client.has_pending_changes(device) to check whether a device has buffered
changes waiting to be applied.
Available Enums
| Category | Values |
|---|---|
| Power | Off, On |
| OperationMode | Auto, Dry, Cool, Heat, Fan |
| FanSpeed | Auto, Low, LowMid, Mid, HighMid, High |
| EcoMode | Auto, Powerful, Quiet |
| AirSwingUD | Auto, Up, UpMid, Mid, DownMid, Down, Swing |
| AirSwingLR | Auto, Left, LeftMid, Mid, RightMid, Right, Unavailable |
| NanoeMode | Unavailable, Off, On, ModeG, All |
ChangeRequestBuilder Methods
set_power_mode(value)— Set power on/offset_hvac_mode(value)— Set operation mode (cool, heat, etc.)set_target_temperature(value)— Set target temperature in °Cset_fan_speed(value)— Set fan speedset_eco_mode(value)— Set eco modeset_horizontal_swing(value)— Set horizontal air swingset_vertical_swing(value)— Set vertical air swingset_nanoe_mode(value)— Set Nanoe modeset_eco_navi_mode(value)— Set EcoNavi modeset_eco_function_mode(value)— Set EcoFunction mode
Getting Energy History
from datetime import date
from aio_panasonic_comfort_cloud import constants
today = date.today().strftime("%Y%m%d")
history = await client.history(device_info.id, constants.DataMode.Day, today)
Weekly Timer (Weekly Schedule)
An air conditioner's weekly schedule holds seven days (Monday first) with up to six
patterns per day. Each pattern has a start time (HH:MM) and the settings the unit
switches to at that time. The whole schedule is written at once: read it, edit it,
and send it back.
from aio_panasonic_comfort_cloud import WeeklyTimerPattern, constants
timer = await client.get_weekly_timer(device_info)
for day in timer.days:
for pattern in day.patterns:
print(day.weekday.name, pattern.start_time, pattern.power, pattern.mode, pattern.temperature)
monday = timer.day(constants.WeeklyTimerWeekday.Monday)
monday.add_pattern(WeeklyTimerPattern(
start_time="06:30",
power=constants.Power.On,
mode=constants.OperationMode.Heat,
temperature=21.5,
fan_speed=constants.FanSpeed.Auto,
))
monday.remove_pattern("22:00")
timer.enabled = True # the app always enables the schedule when saving edits
await client.set_weekly_timer(timer)
# Turn the schedule on/off without touching its patterns
await client.set_weekly_timer_enabled(device_info, False)
Pattern settings left as None aren't sent. Values the library doesn't model (for
example the raw mode values 5 and 6 some units use) are kept and sent back unchanged;
see pattern.mode_raw and pattern.raw.
Aquarea (Air to Water heat pump) Support
Aquarea units show up in the same account/group listing as air conditioners,
but expose a different status shape (hot water tank + heating/cooling zones
instead of a single parameters object), so they're kept separate from
get_devices():
devices = client.get_devices() # air conditioners
aquarea_devices = client.aquarea_devices # Aquarea heat pumps
for device_info in aquarea_devices:
device = await client.get_aquarea_device(device_info)
params = device.parameters
print(f"{device_info.name}: {params.operation_status.name} / {params.operation_mode.name}")
if params.has_tank:
print(f" Tank: {params.tank.temperature}°C -> {params.tank.heat_set}°C")
for zone in params.zones:
print(f" Zone {zone.id} ({zone.name}): {zone.temperature}°C -> {zone.heat_set}°C")
# Refresh status in place
await client.try_update_aquarea_device(device)
Controlling a unit:
from aio_panasonic_comfort_cloud import constants
await client.set_aquarea_operation_status(device_info, constants.AquareaOperationStatus.On)
await client.set_aquarea_operation_mode(device_info, constants.AquareaUpdateOperationMode.Heat)
await client.set_aquarea_tank_temperature(device_info, 55)
await client.set_aquarea_tank_operation_status(device_info, constants.AquareaOperationStatus.On)
await client.set_aquarea_zone_temperature(device_info, zone_id=1, temperature=22, mode="heat")
await client.set_aquarea_quiet_mode(device_info, constants.AquareaQuietMode.Level1)
await client.set_aquarea_force_dhw(device_info, constants.AquareaForceDHW.On)
Eco/Comfort "special status" applies a per-zone temperature offset on top of
the current setpoint rather than being a simple flag, so setting it needs
the full AquareaDevice (not just its PanasonicDeviceInfo) to know the
current setpoints/status to offset from:
device = await client.get_aquarea_device(device_info)
await client.set_aquarea_special_status(device, constants.AquareaSpecialStatus.Eco)
# ... or constants.AquareaSpecialStatus.Comfort, or None to turn it off
Energy consumption/cost history (heat/cool/tank breakdown) uses a separate
endpoint from air conditioners, with its own AquareaDataMode (Day/Month/Year
— no "Week", and different values from the AC-only DataMode):
from datetime import date
today = date.today().strftime("%Y%m%d")
consumption = await client.async_get_aquarea_consumption(
device_info, constants.AquareaDataMode.Day, today
)
for entry in consumption:
print(f"{entry.data_time}: heat={entry.heat_consumption}kWh cool={entry.cool_consumption}kWh "
f"tank={entry.tank_consumption}kWh total={entry.total_consumption}kWh")
set_aquarea_special_status is unverified/best-effort like the HWS control
methods above — see its docstring for why.
HWS (Standalone Heat Pump Hot Water Tank) Support
Some accounts have a standalone hot water heat pump (e.g. an HE-UM40CR),
distinct from an Aquarea combi unit — it has no heating/cooling zones, just
a tank. These report deviceType: "11" and, confusingly, do have a
parameters object like an air conditioner, but with tank-specific fields
(tankTemperature, hpuOperationStatus, operationMode, boostMode)
instead — and the usual deviceStatus/deviceHistoryData calls reject them
with a 403. They're kept separate from get_devices()/aquarea_devices:
hws_devices = client.hws_devices
for device_info in hws_devices:
device = client.get_hws_device(device_info) # no network call — built
# from the group listing
params = device.parameters
print(f"{device_info.name}: {params.tank_temperature}°C, boost={params.boost_mode.name}")
# Refresh (re-fetches /device/group, there's no per-device status call)
await client.try_update_hws_device(device)
Reading status this way is confirmed working against a real device. Control
(set_hws_tank_temperature, set_hws_boost_mode, set_hws_operation_status,
set_hws_operation_mode) goes through the same /deviceStatus/control
endpoint air conditioners use, with the HWS's tank-specific fields
(tankTemperature, hpuOperationStatus, operationMode, boostMode)
wrapped in parameters — matching the shape /device/group already reports
for these devices. An earlier version of this targeted a separate
/device/a2wInfoUpdate endpoint reported from an app capture, but that
endpoint doesn't actually exist (confirmed 403 "Missing Authentication
Token" — API Gateway's error for an unregistered route — against a live
account); the /deviceStatus/control approach is still unverified against
a live HWS device, please open an issue if it doesn't work as-is.
Terms / Privacy Policy Agreements
Panasonic occasionally updates its Terms of Use, Privacy Policy or Cookie
Policy; when that happens, API calls start failing with error code 4103
until the account re-accepts them. You can fetch and handle this yourself:
# Fetch the current documents (set include_content=True to get the full text)
documents = await client.get_agreement_documents(include_content=True)
for doc in documents:
print(doc["type"], doc["version"], doc.get("content", "")[:80])
# See what's already been accepted on this account
accepted = await client.get_agreement_status()
# Just the documents still awaiting acceptance (e.g. to show the user first)
pending = await client.get_pending_agreements(include_content=True)
await client.accept_agreements([{"type": d["type"], "version": d["version"]} for d in pending])
# Auto-accept anything outdated/missing (Terms, Privacy, Cookie Policy —
# the Turkey-only Service Agreement is intentionally excluded, matching
# the official app's behavior of only surfacing it to a subset of accounts)
await client.ensure_all_agreements_accepted()
This isn't called automatically on login — auto-accepting legal agreements
is a decision your application should make deliberately, not something the
library does silently. A typical pattern is to catch
AgreementNotAcceptedError from start_session()/_get_groups() and call
ensure_all_agreements_accepted() (or show the fetched document text to the
user first) in response.
2FA / MFA Support
If your account has two-factor authentication enabled, start_session()
raises MFARequiredError instead of logging in. Catch it, prompt the user
for the OTP code from their authenticator app, and retry with it:
from aio_panasonic_comfort_cloud.exceptions import MFARequiredError
try:
await client.start_session()
except MFARequiredError:
otp_code = input("Enter the 2FA code: ")
await client.start_session(otp_code=otp_code)
Alternative: Browser-Based Authentication
start_session() drives Panasonic's login page itself — it POSTs your
credentials and scrapes the resulting HTML/redirects, which has to correctly
handle whatever Auth0 renders for every connection type (password, MFA,
social login, ...). As an alternative that sidesteps all of that, you can let
a real browser (a WebView, the system browser, etc.) handle the login instead
and just hand the result back to the library:
# 1. Build the URL and open it in any browser
auth_url, code_verifier = client.get_browser_authorization_url()
print(f"Open this URL and log in: {auth_url}")
# 2. After login, the browser is redirected to a URL starting with
# "panasonic-iot-cfc://...callback?code=...". Capture that redirect
# (however your application observes it — a WebView navigation listener,
# a custom URI scheme handler, pasting it in, etc.) and finish the login:
redirect_url = input("Paste the redirect URL here: ")
await client.complete_browser_authentication(redirect_url, code_verifier)
# From here on, the client behaves exactly as if start_session() had been
# called — get_devices(), get_device(), etc. all work normally.
devices = client.get_devices()
This is entirely separate from start_session()/authenticate() — it
doesn't change how the default username/password flow behaves, it's just
another way to obtain the same tokens. Because Auth0's own hosted page
handles the actual login, this path naturally supports MFA, social login,
etc. without any special-casing in the library.
Full Example
example.py is an interactive test tool. It logs in, lists every device
on the account (air conditioners, Aquarea and HWS units) and opens a menu per device to
read its status and try the library's calls, including the weekly timer editor.
python example.py -u you@example.com -p secret # or set PANASONIC_USERNAME / PANASONIC_PASSWORD
python example.py --oauth # log in through a browser instead
python example.py --agreements-only # only check/accept terms, then exit
python example.py --debug # log every request and response
python example.py --no-auto-power-on # buffer changes while the unit is off
License
MIT
Metadata
Release files for aio-panasonic-comfort-cloud 2026.10.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| aio_panasonic_comfort_cloud-2026.10.0.tar.gz | 71.9 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| aio_panasonic_comfort_cloud-2026.10.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 140.2 kB
Release files / aio_panasonic_comfort_cloud-2026.10.0.tar.gz
| Download URL | aio_panasonic_comfort_cloud-2026.10.0.tar.gz |
|---|---|
| Size | 71.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
656d388807d3d5a6e60d9fa9c6ae72cf5bc2fd8e53e64305023bdb64cfa73192
|
|
BLAKE2b-256 checksum How to use checksums |
afefbdcb046e04a9a6ba44d6195e803df3efd23fd088aec441db847d9fcab509
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 13, 2026.
Transparency logRelease files / aio_panasonic_comfort_cloud-2026.10.0-py3-none-any.whl
| Download URL | aio_panasonic_comfort_cloud-2026.10.0-py3-none-any.whl |
|---|---|
| Size | 68.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
fd7dfa27cb5f27a540d1ee87d5a34ca7de0d93ae6df87f1a977ba6846730da53
|
|
BLAKE2b-256 checksum How to use checksums |
2c9ddbeaf5adc7a30b5584c0e9e2ea094e57b5ca70994eac5a69f1dd6758ddf8
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 13, 2026.
Transparency logSecurity for aio-panasonic-comfort-cloud 2026.10.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for aio_panasonic_comfort_cloud-2026.10.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 13, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.