Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
b49ae90
First go at integrating new way of creating a token
little-quokka Jun 19, 2024
9da12ac
Call /userinfo in login
little-quokka Jun 19, 2024
ea89dd5
Fix login call ... special thanks to @heldchen (a true hero ... no pu…
little-quokka Jun 19, 2024
89429ad
updating from quokkas branch
danielcherubini Jun 19, 2024
eaff330
Refactored with auth.py
danielcherubini Jun 19, 2024
61301ca
Fixes refresh token issue
danielcherubini Jun 19, 2024
6223314
Cleaning up
danielcherubini Jun 19, 2024
de2869e
Add PanasonicSession with improved token handling and error checking.
little-quokka Jun 19, 2024
9e8f524
better login flow
danielcherubini Jun 19, 2024
03cf97c
More cleanup
danielcherubini Jun 19, 2024
fdca27f
Working
danielcherubini Jun 19, 2024
dd07276
Formatting fixes
danielcherubini Jun 19, 2024
3f5ec2b
Rewrite of auth.py
danielcherubini Jun 19, 2024
9c632ed
Ensure that with each API call the token is tested and potentially up…
little-quokka Jun 20, 2024
4d06824
Improved error handling; Crack open token to check expiry; Move excep…
little-quokka Jun 20, 2024
caebec3
Update default token file name to allow this to be run on windows as …
little-quokka Jun 20, 2024
5e3f9d1
Fix low hanging fruit of issues reported by pylint (moved from score …
little-quokka Jun 20, 2024
c3f39d1
Make contents of pcomfortcloud/ a library and use pcomfortcloud.py as…
little-quokka Jun 20, 2024
f9c9555
Merge branch 'master' of https://github.com/little-quokka/python-pana…
danielcherubini Jun 20, 2024
a47d074
Revert "Merge branch 'master' of https://github.com/little-quokka/pyt…
danielcherubini Jun 20, 2024
fa02514
Revert "Integrate recent changes from little-quokka/python-panasonic-…
danielcherubini Jun 20, 2024
1a4baa7
Merge pull request #2 from danielcherubini/revert-1-master
danielcherubini Jun 20, 2024
0fdd24a
adding from little-quokka's branch
danielcherubini Jun 20, 2024
3919864
linting
danielcherubini Jun 20, 2024
87afbd2
Merge pull request #3 from danielcherubini/little-quokka
danielcherubini Jun 20, 2024
3cd4989
Uses automatic app version
danielcherubini Jun 20, 2024
7ed3c5e
cleaning out app version code from apiclient
danielcherubini Jun 20, 2024
329f112
Commenting out the auto app verison update call, and adding comments why
danielcherubini Jun 20, 2024
50d5030
Moving Appversion to gist
danielcherubini Jun 20, 2024
91409a9
Updating app version scraper
danielcherubini Jun 20, 2024
965b1ec
Adding @heldchen's fixes for the 404s
danielcherubini Jun 21, 2024
81eae60
fixing issue with method
danielcherubini Jun 21, 2024
e1024ad
Renaming panasonicsession.py back to session.py
danielcherubini Jun 22, 2024
93c0b44
Small cleanups
danielcherubini Jun 22, 2024
4eb30d1
removing aiohttp from requirements.txt
danielcherubini Jun 22, 2024
977f943
Fixing bad import
danielcherubini Jun 24, 2024
cf3bebc
fixes issue with app version
danielcherubini Jun 24, 2024
ddcd6fc
adding more controlflow around appversion
danielcherubini Jun 24, 2024
fea9970
Fixes string issue thanks @heldchen
danielcherubini Jun 24, 2024
f2cb4bc
Updating README.md
danielcherubini Jun 24, 2024
572f7f0
Making Token optional
danielcherubini Jun 24, 2024
0e2c839
Removing Optional Token from README.md
danielcherubini Jun 24, 2024
3c9d178
Lowercasing the headers
danielcherubini Jun 26, 2024
a333c5f
updating the fallback
danielcherubini Jun 26, 2024
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
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -103,4 +103,7 @@ venv.bak/
# mypy
.mypy_cache/

.vscode
.vscode
.idea

token.json
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# python-panasonic-comfort-cloud

A python module for reading and changing status of panasonic climate devices through Panasonic Comfort Cloud app api

## Command line usage
Expand All @@ -17,7 +18,7 @@ positional arguments:
set Set status of a device
dump Dump raw data of a device
history Dump history of a device

optional arguments:
-h, --help show this help message and exit
-t TOKEN, --token TOKEN
Expand Down Expand Up @@ -95,12 +96,11 @@ optional arguments:

## Module usage


```python
import pcomfortcloud

session = pcomfortcloud.Session('user@example.com', 'mypassword')
session.login()
session = pcomfortcloud.ApiClient('user@example.com', 'mypassword')
session.start_session()

devices = session.get_devices()

Expand All @@ -114,8 +114,10 @@ session.set_device(devices[0]['id'],
```

## PyPi package

can be found at https://pypi.org/project/pcomfortcloud/

### How to publish package;

- `python .\setup.py sdist bdist_wheel`
- `python -m twine upload dist/*`
302 changes: 299 additions & 3 deletions pcomfortcloud.py
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Why is main.py deleted and moved into this file? Is there any specific reason for that?

Original file line number Diff line number Diff line change
@@ -1,5 +1,301 @@
#!/usr/bin/env python3

""" Command line interface for Panasonic Comfort Cloud """
from pcomfortcloud import __main__
__main__.main()
import argparse
import os
import json
from enum import Enum

import pcomfortcloud


def print_result(obj, indent=0):
for key in obj:
value = obj[key]

if isinstance(value, dict):
print(" "*indent + key)
print_result(value, indent + 4)
elif isinstance(value, Enum):
print(
" "*indent + "{0: <{width}}: {1}".format(key, value.name, width=25-indent))
elif isinstance(value, list):
print(" "*indent + "{0: <{width}}:".format(key, width=25-indent))
for elt in value:
print_result(elt, indent + 4)
print("")
else:
print(" "*indent +
"{0: <{width}}: {1}".format(key, value, width=25-indent))


def str2bool(boolean_string_value):
if boolean_string_value.lower() in ('yes', 'true', 't', 'y', '1'):
return True
if boolean_string_value.lower() in ('no', 'false', 'f', 'n', '0'):
return False
raise argparse.ArgumentTypeError('Boolean value expected.')


def main():
""" Start pcomfortcloud Comfort Cloud command line """

parser = argparse.ArgumentParser(
description='Read or change status of pcomfortcloud Climate devices')

parser.add_argument(
'username',
help='Username for pcomfortcloud Comfort Cloud')

parser.add_argument(
'password',
help='Password for pcomfortcloud Comfort Cloud')

parser.add_argument(
'-t', '--token',
help='File to store token in',
default='token.json')

parser.add_argument(
'-r', '--raw',
help='Raw dump of response',
type=str2bool, nargs='?', const=True,
default=False)

commandparser = parser.add_subparsers(
help='commands',
dest='command')

commandparser.add_parser(
'list',
help="Get a list of all devices")

get_parser = commandparser.add_parser(
'get',
help="Get status of a device")

get_parser.add_argument(
dest='device',
type=int,
help='Device number #')

set_parser = commandparser.add_parser(
'set',
help="Set status of a device")

set_parser.add_argument(
dest='device',
type=int,
help='Device number #'
)

set_parser.add_argument(
'-p', '--power',
choices=[
pcomfortcloud.constants.Power.On.name,
pcomfortcloud.constants.Power.Off.name],
help='Power mode')

set_parser.add_argument(
'-t', '--temperature',
type=float,
help="Temperature")

set_parser.add_argument(
'-f', '--fanSpeed',
choices=[
pcomfortcloud.constants.FanSpeed.Auto.name,
pcomfortcloud.constants.FanSpeed.Low.name,
pcomfortcloud.constants.FanSpeed.LowMid.name,
pcomfortcloud.constants.FanSpeed.Mid.name,
pcomfortcloud.constants.FanSpeed.HighMid.name,
pcomfortcloud.constants.FanSpeed.High.name],
help='Fan speed')

set_parser.add_argument(
'-m', '--mode',
choices=[
pcomfortcloud.constants.OperationMode.Auto.name,
pcomfortcloud.constants.OperationMode.Cool.name,
pcomfortcloud.constants.OperationMode.Dry.name,
pcomfortcloud.constants.OperationMode.Heat.name,
pcomfortcloud.constants.OperationMode.Fan.name],
help='Operation mode')

set_parser.add_argument(
'-e', '--eco',
choices=[
pcomfortcloud.constants.EcoMode.Auto.name,
pcomfortcloud.constants.EcoMode.Quiet.name,
pcomfortcloud.constants.EcoMode.Powerful.name],
help='Eco mode')

set_parser.add_argument(
'-n', '--nanoe',
choices=[
pcomfortcloud.constants.NanoeMode.On.name,
pcomfortcloud.constants.NanoeMode.Off.name,
pcomfortcloud.constants.NanoeMode.ModeG.name,
pcomfortcloud.constants.NanoeMode.All.name],
help='Nanoe mode')

# set_parser.add_argument(
# '--airswingauto',
# choices=[
# pcomfortcloud.constants.AirSwingAutoMode.Disabled.name,
# pcomfortcloud.constants.AirSwingAutoMode.AirSwingLR.name,
# pcomfortcloud.constants.AirSwingAutoMode.AirSwingUD.name,
# pcomfortcloud.constants.AirSwingAutoMode.Both.name],
# help='Automation of air swing')

set_parser.add_argument(
'-y', '--airSwingVertical',
choices=[
pcomfortcloud.constants.AirSwingUD.Auto.name,
pcomfortcloud.constants.AirSwingUD.Down.name,
pcomfortcloud.constants.AirSwingUD.DownMid.name,
pcomfortcloud.constants.AirSwingUD.Mid.name,
pcomfortcloud.constants.AirSwingUD.UpMid.name,
pcomfortcloud.constants.AirSwingUD.Up.name],
help='Vertical position of the air swing')

set_parser.add_argument(
'-x', '--airSwingHorizontal',
choices=[
pcomfortcloud.constants.AirSwingLR.Auto.name,
pcomfortcloud.constants.AirSwingLR.Left.name,
pcomfortcloud.constants.AirSwingLR.LeftMid.name,
pcomfortcloud.constants.AirSwingLR.Mid.name,
pcomfortcloud.constants.AirSwingLR.RightMid.name,
pcomfortcloud.constants.AirSwingLR.Right.name],
help='Horizontal position of the air swing')

dump_parser = commandparser.add_parser(
'dump',
help="Dump data of a device")

dump_parser.add_argument(
dest='device',
type=int,
help='Device number 1-x')

history_parser = commandparser.add_parser(
'history',
help="Dump history of a device")

history_parser.add_argument(
dest='device',
type=int,
help='Device number 1-x')

history_parser.add_argument(
dest='mode',
type=str,
help='mode (Day, Week, Month, Year)')

history_parser.add_argument(
dest='date',
type=str,
help='date of day like 20190807')

args = parser.parse_args()

if os.path.isfile(args.token):
with open(args.token, "r") as token_file:
json_token = json.load(token_file)
else:
json_token = None

session = pcomfortcloud.ApiClient(
args.username,
args.password,
json_token,
args.raw)

session.start_session()
json_token = session.get_token()

with open(args.token, "w") as token_file:
json.dump(json_token, token_file, indent=4)

try:
if args.command == 'list':
print("list of devices and its device id (1-x)")
for idx, device in enumerate(session.get_devices()):
if idx > 0:
print('')

print("device #{}".format(idx + 1))
print_result(device, 4)

if args.command == 'get':
if int(args.device) <= 0 or int(args.device) > len(session.get_devices()):
raise Exception("device not found, acceptable device id is from {} to {}".format(
1, len(session.get_devices())))

device = session.get_devices()[int(args.device) - 1]
print("reading from device '{}' ({})".format(
device['name'], device['id']))

print_result(session.get_device(device['id']))

if args.command == 'set':
if int(args.device) <= 0 or int(args.device) > len(session.get_devices()):
raise Exception("device not found, acceptable device id is from {} to {}".format(
1, len(session.get_devices())))

device = session.get_devices()[int(args.device) - 1]
print("writing to device '{}' ({})".format(
device['name'], device['id']))

kwargs = {}

if args.power is not None:
kwargs['power'] = pcomfortcloud.constants.Power[args.power]

if args.temperature is not None:
kwargs['temperature'] = args.temperature

if args.fanSpeed is not None:
kwargs['fanSpeed'] = pcomfortcloud.constants.FanSpeed[args.fanSpeed]

if args.mode is not None:
kwargs['mode'] = pcomfortcloud.constants.OperationMode[args.mode]

if args.eco is not None:
kwargs['eco'] = pcomfortcloud.constants.EcoMode[args.eco]

if args.nanoe is not None:
kwargs['nanoe'] = pcomfortcloud.constants.NanoeMode[args.nanoe]

if args.airSwingHorizontal is not None:
kwargs['airSwingHorizontal'] = pcomfortcloud.constants.AirSwingLR[args.airSwingHorizontal]

if args.airSwingVertical is not None:
kwargs['airSwingVertical'] = pcomfortcloud.constants.AirSwingUD[args.airSwingVertical]

session.set_device(device['id'], **kwargs)

if args.command == 'dump':
if int(args.device) <= 0 or int(args.device) > len(session.get_devices()):
raise Exception("device not found, acceptable device id is from {} to {}".format(
1, len(session.get_devices())))

device = session.get_devices()[int(args.device) - 1]

print_result(session.dump(device['id']))

if args.command == 'history':
if int(args.device) <= 0 or int(args.device) > len(session.get_devices()):
raise Exception("device not found, acceptable device id is from {} to {}".format(
1, len(session.get_devices())))

device = session.get_devices()[int(args.device) - 1]

print_result(session.history(device['id'], args.mode, args.date))

except pcomfortcloud.ResponseError as ex:
print(ex)


if __name__ == "__main__":
main()
17 changes: 11 additions & 6 deletions pcomfortcloud/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,22 @@
"""

__all__ = [
'ApiClient',
'Error',
'LoginError',
'ResponseError',
'Session'
'RequestError',
'ResponseError'
]

from .session import (
from .apiclient import (
ApiClient
)

from .exceptions import (
Error,
LoginError,
ResponseError,
Session
RequestError,
ResponseError
)

from . import constants
from . import constants
Loading