diff --git a/custom_components/somneo/pysomneo/.gitignore b/custom_components/somneo/pysomneo/.gitignore deleted file mode 100644 index b6e4761..0000000 --- a/custom_components/somneo/pysomneo/.gitignore +++ /dev/null @@ -1,129 +0,0 @@ -# Byte-compiled / optimized / DLL files -__pycache__/ -*.py[cod] -*$py.class - -# C extensions -*.so - -# Distribution / packaging -.Python -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -wheels/ -pip-wheel-metadata/ -share/python-wheels/ -*.egg-info/ -.installed.cfg -*.egg -MANIFEST - -# PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. -*.manifest -*.spec - -# Installer logs -pip-log.txt -pip-delete-this-directory.txt - -# Unit test / coverage reports -htmlcov/ -.tox/ -.nox/ -.coverage -.coverage.* -.cache -nosetests.xml -coverage.xml -*.cover -*.py,cover -.hypothesis/ -.pytest_cache/ - -# Translations -*.mo -*.pot - -# Django stuff: -*.log -local_settings.py -db.sqlite3 -db.sqlite3-journal - -# Flask stuff: -instance/ -.webassets-cache - -# Scrapy stuff: -.scrapy - -# Sphinx documentation -docs/_build/ - -# PyBuilder -target/ - -# Jupyter Notebook -.ipynb_checkpoints - -# IPython -profile_default/ -ipython_config.py - -# pyenv -.python-version - -# pipenv -# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. -# However, in case of collaboration, if having platform-specific dependencies or dependencies -# having no cross-platform support, pipenv may install dependencies that don't work, or not -# install all needed dependencies. -#Pipfile.lock - -# PEP 582; used by e.g. github.com/David-OConnor/pyflow -__pypackages__/ - -# Celery stuff -celerybeat-schedule -celerybeat.pid - -# SageMath parsed files -*.sage.py - -# Environments -.env -.venv -env/ -venv/ -ENV/ -env.bak/ -venv.bak/ - -# Spyder project settings -.spyderproject -.spyproject - -# Rope project settings -.ropeproject - -# mkdocs documentation -/site - -# mypy -.mypy_cache/ -.dmypy.json -dmypy.json - -# Pyre type checker -.pyre/ diff --git a/custom_components/somneo/pysomneo/README.md b/custom_components/somneo/pysomneo/README.md deleted file mode 100644 index ca43bbf..0000000 --- a/custom_components/somneo/pysomneo/README.md +++ /dev/null @@ -1 +0,0 @@ -# pysomneo \ No newline at end of file diff --git a/custom_components/somneo/pysomneo/pysomneo/__init__.py b/custom_components/somneo/pysomneo/pysomneo/__init__.py deleted file mode 100644 index 29b3e00..0000000 --- a/custom_components/somneo/pysomneo/pysomneo/__init__.py +++ /dev/null @@ -1,303 +0,0 @@ -import requests -import urllib3 -import json -import xml.etree.ElementTree as ET -import logging -import datetime -import asyncio - -_LOGGER = logging.getLogger('pysomneo') - -WORKDAYS_BINARY_MASK = 62 -WEEKEND_BINARY_MASK = 192 - -class Somneo(object): - """ - Class represents the Somneo wake-up light. - """ - - def __init__(self, host=None): - """Initialize.""" - urllib3.disable_warnings() - self.host = host - self._base_url = 'https://' + host + '/di/v1/products/1/' - self._session = requests.Session() - - self.light_data = None - self.sensor_data = None - self.alarm_data = dict() - self.alarm_toggle_data = dict() - self.alarm_time_data = dict() - self.set_time_task = None - - def get_device_info(self): - """ Get Device information """ - try: - response = self._session.request('GET', 'https://' + self.host + '/upnp/description.xml', verify=False, - timeout=20) - except requests.Timeout: - _LOGGER.error('Connection to Somneo timed out.') - raise - except requests.RequestException: - _LOGGER.error('Error connecting to Somneo.') - raise - - root = ET.fromstring(response.content) - - device_info = dict() - device_info['manufacturer'] = root[1][2].text - device_info['model'] = root[1][3].text - device_info['modelnumber'] = root[1][4].text - device_info['serial'] = root[1][6].text - - return device_info - - def _internal_call(self, method, url, headers, payload): - """Call to the API.""" - args = dict() - url = self._base_url + url - - if payload: - args['data'] = json.dumps(payload) - - if headers: - args['headers'] = headers - - while True: - try: - r = self._session.request(method, url, verify=False, timeout=20, **args) - except requests.Timeout: - _LOGGER.error('Connection to Somneo timed out.') - raise - except requests.ConnectionError: - continue - except requests.RequestException: - _LOGGER.error('Error connecting to Somneo.') - raise - else: - if r.status_code == 422: - _LOGGER.error('Invalid URL.') - raise Exception("Invalid URL.") - break - - if method == 'GET': - return r.json() - else: - return - - def _get(self, url, args=None, payload=None): - return self._internal_call('GET', url, None, payload) - - def _put(self, url, args=None, payload=None): - return self._internal_call('PUT', url, {"Content-Type": "application/json"}, payload) - - def toggle_light(self, state, brightness=None): - """ Toggle the light on or off """ - payload = self.light_data - payload['onoff'] = state - payload['ngtlt'] = False - if brightness: - payload['ltlvl'] = int(brightness / 255 * 25) - self._put('wulgt', payload=payload) - - def set_alarm(self, hour, minute, days, alarm): - _LOGGER.debug("set_time_alarm position " + str(self.alarm_data[alarm]['position']) - + str(hour) + ":" + str(minute) + " days " + str(days)) - self.alarm_data[alarm]['time'] = datetime.time(int(hour), int(minute)) - self.alarm_data[alarm]['days'] = days - payload = dict() - payload['prfnr'] = self.alarm_data[alarm]['position'] # Alarm position - payload['prfen'] = self.alarm_data[alarm]['enabled'] # Alarm enabled / disabled - payload['prfvs'] = True # Add/Remove alarm from alarm list - payload['almhr'] = int(hour) # Alarm hour - payload['almmn'] = int(minute) # Alarm Min - payload['pwrsz'] = 0 # TODO enable / disable PowerWake - payload['pszhr'] = 0 # TODO set power wake (hour) - payload['pszmn'] = 0 # TODO set power wake (min) - payload['ctype'] = 0 # TODO set the default sunrise ("Sunny day" if curve > 0 or "No light" if curve == 0) - payload['curve'] = 20 # TODO set light level - payload['durat'] = 30 # TODO set sunrise duration - payload['daynm'] = days # set days to repeat the alarm - # payload['snddv'] = "wus" # TODO set the wake_up sound - # payload['snztm'] = 0 # TODO set snooze time - self._put('wualm/prfwu', payload=payload) - - def set_days_alarm(self, days, alarm): - self.set_alarm(int(self.alarm_data[alarm]['time'].hour), - int(self.alarm_data[alarm]['time'].minute), days, alarm) - - def set_workdays_alarm(self, is_on, alarm): - days = (self.alarm_data[alarm]['days'] & WEEKEND_BINARY_MASK) - if is_on: - days = days + WORKDAYS_BINARY_MASK - _LOGGER.debug("Workday " + str(is_on) + " days =" + str(days)) - self.set_days_alarm(days, alarm) - - def set_weekend_alarm(self, is_on, alarm): - days = (self.alarm_data[alarm]['days'] & WORKDAYS_BINARY_MASK) - if is_on: - days = days + WEEKEND_BINARY_MASK - _LOGGER.debug("Weekend " + str(is_on) + " days =" + str(days)) - self.set_days_alarm(days, alarm) - - def set_time_alarm(self, hour, minute, alarm): - self.set_alarm(hour, minute, self.alarm_data[alarm]['days'], alarm) - - def toggle_alarm(self, status, alarm): - """ Toggle the light on or off """ - self.alarm_data[alarm]['enabled'] = status - payload = self.alarm_toggle_data - payload['prfnr'] = self.alarm_data[alarm]['position'] - payload['prfvs'] = True - payload['prfen'] = status - self._put('wualm/prfwu', payload=payload) - - def toggle_night_light(self, state): - """ Toggle the light on or off """ - payload = self.light_data - payload['onoff'] = False - payload['ngtlt'] = state - self._put('wulgt', payload=payload) - - def update(self): - """Get the latest update from Somneo.""" - - # Get light information - self.light_data = self._get('wulgt') - - # Get sensor data - self.sensor_data = self._get('wusrd') - - # Get alarm data - enabled_alarms = self._get('wualm/aenvs') - time_alarms = self._get('wualm/aalms') - - for alarm, enabled in enumerate(enabled_alarms['prfen']): - alarm_name = 'alarm' + str(alarm) - self.alarm_data[alarm_name] = dict() - self.alarm_data[alarm_name]['position'] = alarm + 1 - self.alarm_data[alarm_name]['enabled'] = bool(enabled) - self.alarm_data[alarm_name]['time'] = datetime.time(int(time_alarms['almhr'][alarm]), - int(time_alarms['almmn'][alarm])) - self.alarm_data[alarm_name]['days'] = int(time_alarms['daynm'][alarm]) - - def light_status(self): - """Return the status of the light.""" - return self.light_data['onoff'], int(int(self.light_data['ltlvl']) / 25 * 255) - - def night_light_status(self): - """Return the status of the night light.""" - return self.light_data['ngtlt'] - - def alarms(self): - """Return the list of alarms.""" - alarms = dict() - for alarm in list(self.alarm_data): - alarms[alarm] = self.alarm_data[alarm]['enabled'] - - return alarms - - def day_int(self, mon, tue, wed, thu, fri, sat, sun): - return mon * 2 + tue * 4 + wed * 8 + thu * 16 + fri * 32 + sat * 64 + sun * 128 - - def is_workday(self, alarm): - days_int = self.alarm_data[alarm]['days'] - return (days_int & 62) == 62 - - def is_weekend(self, alarm): - days_int = self.alarm_data[alarm]['days'] - return (days_int & 192) == 192 - - def alarm_settings(self, alarm): - """Return the time and days alarm is set.""" - alarm_time = self.alarm_data[alarm]['time'].isoformat() - - alarm_days = [] - days_int = self.alarm_data[alarm]['days'] - if days_int & 2: - alarm_days.append('mon') - if days_int & 4: - alarm_days.append('tue') - if days_int & 8: - alarm_days.append('wed') - if days_int & 16: - alarm_days.append('thu') - if days_int & 32: - alarm_days.append('fri') - if days_int & 64: - alarm_days.append('sat') - if days_int & 128: - alarm_days.append('sun') - - return alarm_time, alarm_days - - def next_alarm(self): - """Get the next alarm that is set.""" - next_alarm = None - for alarm in list(self.alarm_data): - if self.alarm_data[alarm]['enabled'] == True: - nu_tijd = datetime.datetime.now() - nu_dag = datetime.date.today() - alarm_time = self.alarm_data[alarm]['time'] - alarm_days_int = self.alarm_data[alarm]['days'] - alarm_days = [] - if alarm_days_int & 2: - alarm_days.append(1) - if alarm_days_int & 4: - alarm_days.append(2) - if alarm_days_int & 8: - alarm_days.append(3) - if alarm_days_int & 16: - alarm_days.append(4) - if alarm_days_int & 32: - alarm_days.append(5) - if alarm_days_int & 64: - alarm_days.append(6) - if alarm_days_int & 128: - alarm_days.append(7) - - day_today = nu_tijd.isoweekday() - - if not alarm_days: - alarm_time_full = datetime.datetime.combine(nu_dag, alarm_time) - if alarm_time_full > nu_tijd: - new_next_alarm = alarm_time_full - elif alarm_time_full + datetime.timedelta(days=1) > nu_tijd: - new_next_alarm = alarm_time_full - else: - for d in range(0, 7): - test_day = day_today + d - if test_day > 7: - test_day -= 7 - if test_day in alarm_days: - alarm_time_full = datetime.datetime.combine(nu_dag, alarm_time) + datetime.timedelta(days=d) - if alarm_time_full > nu_tijd: - new_next_alarm = alarm_time_full - break - - if next_alarm: - if new_next_alarm < next_alarm: - next_alarm = new_next_alarm - else: - next_alarm = new_next_alarm - - if next_alarm: - return next_alarm.isoformat() - else: - return None - - def temperature(self): - """Return the temperature.""" - return self.sensor_data['mstmp'] - - def humidity(self): - """Return the temperature.""" - return self.sensor_data['msrhu'] - - def luminance(self): - """Return the temperature.""" - return self.sensor_data['mslux'] - - def noise(self): - """Return the temperature.""" - return self.sensor_data['mssnd'] diff --git a/custom_components/somneo/pysomneo/setup.py b/custom_components/somneo/pysomneo/setup.py deleted file mode 100644 index a0e598f..0000000 --- a/custom_components/somneo/pysomneo/setup.py +++ /dev/null @@ -1,28 +0,0 @@ -from setuptools import setup - -setup( - name = 'pysomneo', - packages = ['pysomneo'], - install_requires=['requests>=2.24.0','urllib3==1.26.5'], - version = '0.1.14', - description = 'A library to communicate with the API of Philips Somneo.', - author='Ruud van der Horst', - author_email='ik@ruudvdhorst.nl', - classifiers=[ - 'Development Status :: 3 - Alpha', - 'Environment :: Other Environment', - 'Intended Audience :: Developers', - 'License :: OSI Approved :: ' + - 'GNU Lesser General Public License v3 or later (LGPLv3+)', - 'Operating System :: OS Independent', - 'Programming Language :: Python', - 'Programming Language :: Python :: 2.6', - 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.1', - 'Programming Language :: Python :: 3.2', - 'Programming Language :: Python :: 3.3', - 'Topic :: Home Automation', - 'Topic :: Software Development :: Libraries :: Python Modules' - ] -) diff --git a/hacs.json b/hacs.json new file mode 100644 index 0000000..0424269 --- /dev/null +++ b/hacs.json @@ -0,0 +1,7 @@ +{ + "name": "Philips Somneo", + "render_readme": true, + "domains": ["sensor","light","switch","select","number"], + "iot_class": "Local Polling", + "homeassistant": "2021.7" + } \ No newline at end of file diff --git a/images/icon.png b/images/icon.png new file mode 100644 index 0000000..ad08a27 Binary files /dev/null and b/images/icon.png differ diff --git a/images/icon@2x.png b/images/icon@2x.png new file mode 100644 index 0000000..52bbc30 Binary files /dev/null and b/images/icon@2x.png differ