Skip to main content

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

Device Capabilities

Not every unit accepts every value. PanasonicDevice exposes what a unit supports, following the same rules as the Comfort Cloud app:

  • supported_fan_speeds — the accepted FanSpeed values. Room units get all five steps; ducted/commercial ("CAC") units may be limited to Low/Mid/High or have a fixed fan speed (empty list). Includes Auto only when supports_fan_auto.
  • supports_fan_auto — False for CAC units with a fixed fan speed or in Fan mode.
  • supports_quiet_mode / supports_powerful_mode — whether EcoMode.Quiet / EcoMode.Powerful are available.
  • supported_eco_modes — Auto plus whichever of Powerful/Quiet the unit has.
  • info.is_cac — True for the CAC device types (constants.CAC_DEVICE_TYPES).

ChangeRequestBuilder Methods

  • set_power_mode(value) — Set power on/off
  • set_hvac_mode(value) — Set operation mode (cool, heat, etc.)
  • set_target_temperature(value) — Set target temperature in °C
  • set_fan_speed(value) — Set fan speed
  • set_eco_mode(value) — Set eco mode
  • set_horizontal_swing(value) — Set horizontal air swing
  • set_vertical_swing(value) — Set vertical air swing
  • set_nanoe_mode(value) — Set Nanoe mode
  • set_eco_navi_mode(value) — Set EcoNavi mode
  • set_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.

Aquarea Weekly Timer

Aquarea has its own weekly schedule, separate from the air conditioners' /weeklyTimer. It has the same layout: seven days (Monday first), each with up to six patterns. An Aquarea pattern has a start time, one operation mode for the unit, and the on/off state and setpoint of zone 1, zone 2 and the tank. Leave the settings of a zone or tank the unit doesn't have as None. The response also holds the allowed setpoint ranges.

from aio_panasonic_comfort_cloud import AquareaWeeklyTimerPattern, constants

timer = await client.get_aquarea_weekly_timer(device_info)
print(timer.enabled, timer.zone_limits(1), timer.tank_status)

pattern = AquareaWeeklyTimerPattern(start_time="06:00", mode=constants.AquareaUpdateOperationMode.Heat)
pattern.set_zone(1, constants.AquareaOperationStatus.On, heat=22)
pattern.tank_on_off = constants.AquareaOperationStatus.On
pattern.tank_temperature = 50

for day in timer.days[:5]:  # Monday-Friday
    day.add_pattern(AquareaWeeklyTimerPattern(pattern.to_dict()))

timer.enabled = True  # the app always enables the schedule when saving edits
await client.set_aquarea_weekly_timer(timer)

await client.set_aquarea_weekly_timer_enabled(device_info, False)

This follows the official app's requests but hasn't been tested against a real unit yet.

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.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for aio-panasonic-comfort-cloud 2026.10.1
File Size Uploaded Provenance
aio_panasonic_comfort_cloud-2026.10.1.tar.gz 78.6 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for aio-panasonic-comfort-cloud 2026.10.1
File Interpreter ABI Platform
aio_panasonic_comfort_cloud-2026.10.1-py3-none-any.whl Python 3 none any Details

Total release size: 152.3 kB

Release files / aio_panasonic_comfort_cloud-2026.10.1.tar.gz

Download URL aio_panasonic_comfort_cloud-2026.10.1.tar.gz
Size 78.6 kB
Tags Source
SHA-256 checksum
How to use checksums
114bd1373e75d46477aadd26bfd962c5459e23e63038b4672ece1061c178f21c
BLAKE2b-256 checksum
How to use checksums
27c8d4d6441c09c1fa293077718066fde03e9815f76cbb348d2f83ee28342883
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 14, 2026.

Transparency log

Release files / aio_panasonic_comfort_cloud-2026.10.1-py3-none-any.whl

Download URL aio_panasonic_comfort_cloud-2026.10.1-py3-none-any.whl
Size 73.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
0db5739dee7635e8532c2f8edba3ab0a991b0dc3e69918b7cc27bd2b0c8b3d87
BLAKE2b-256 checksum
How to use checksums
c79ad9f3ce320c4f77bc0619dd256c7e5d626069c82a92b586766b02abb5128d
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 14, 2026.

Transparency log

Security for aio-panasonic-comfort-cloud 2026.10.1

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.

Select from 2 files

Attestation for aio_panasonic_comfort_cloud-2026.10.1.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 14, 2026.

Transparency log

A 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.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page