-
Notifications
You must be signed in to change notification settings - Fork 43
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Initial implementation of bhyve integration
- Loading branch information
0 parents
commit 49a6a49
Showing
11 changed files
with
538 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
__pycache__ | ||
*.pyc | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
# bhyve-home-assistant | ||
|
||
BHyve component for [Home Assistant](https://www.home-assistant.io/). | ||
|
||
## Supported Features | ||
* Battery sensor for `sprinkler_timer` devices | ||
|
||
## Installation | ||
|
||
Recommended installation is via HACS | ||
[![hacs_badge](https://img.shields.io/badge/HACS-Default-orange.svg?style=for-the-badge)](https://github.com/custom-components/hacs) | ||
|
||
### Sample Configuration | ||
|
||
```yaml | ||
bhyve: | ||
username: !secret bhyve_username | ||
password: !secret bhyve_password | ||
|
||
sensor: | ||
- platform: bhyve | ||
``` | ||
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
import logging | ||
from datetime import timedelta | ||
|
||
import voluptuous as vol | ||
from requests.exceptions import HTTPError, ConnectTimeout | ||
|
||
from homeassistant.const import ( | ||
CONF_USERNAME, CONF_PASSWORD, CONF_SCAN_INTERVAL, CONF_HOST) | ||
from homeassistant.helpers import config_validation as cv | ||
|
||
__version__ = '0.0.1' | ||
|
||
_LOGGER = logging.getLogger(__name__) | ||
|
||
CONF_ATTRIBUTION = "Data provided by api.orbitbhyve.com" | ||
DATA_BHYVE = 'data_bhyve' | ||
DEFAULT_BRAND = 'Orbit BHyve' | ||
DOMAIN = 'bhyve' | ||
|
||
NOTIFICATION_ID = 'bhyve_notification' | ||
NOTIFICATION_TITLE = 'Orbit BHyve Component Setup' | ||
|
||
DEFAULT_HOST = 'https://api.orbitbhyve.com' | ||
|
||
CONFIG_SCHEMA = vol.Schema({ | ||
DOMAIN: vol.Schema({ | ||
vol.Required(CONF_USERNAME): cv.string, | ||
vol.Required(CONF_PASSWORD): cv.string, | ||
vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.url | ||
}), | ||
}, extra=vol.ALLOW_EXTRA) | ||
|
||
|
||
def setup(hass, config): | ||
"""Set up the BHyve component.""" | ||
|
||
_LOGGER.info('bhyve loading') | ||
|
||
# Read config | ||
conf = config[DOMAIN] | ||
username = conf.get(CONF_USERNAME) | ||
password = conf.get(CONF_PASSWORD) | ||
host = conf.get(CONF_HOST) | ||
|
||
_LOGGER.info(host) | ||
|
||
try: | ||
from .pybhyve import PyBHyve | ||
|
||
bhyve = PyBHyve(username=username, password=password) | ||
if not bhyve.is_connected: | ||
return False | ||
|
||
hass.data[DATA_BHYVE] = bhyve | ||
|
||
except (ConnectTimeout, HTTPError) as ex: | ||
_LOGGER.error("Unable to connect to Orbit BHyve: %s", str(ex)) | ||
hass.components.persistent_notification.create( | ||
'Error: {}<br />You will need to restart hass after fixing.'.format(ex), | ||
title=NOTIFICATION_TITLE, | ||
notification_id=NOTIFICATION_ID) | ||
return False | ||
|
||
return True |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
{ | ||
"domain": "bhyve", | ||
"name": "Orbit BHyve Integration", | ||
"documentation": "https://github.com/sebr/bhyve-home-assistant/blob/master/README.md", | ||
"dependencies": [], | ||
"codeowners": ["@sebr"], | ||
"requirements": [] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
import base64 | ||
import datetime | ||
import logging | ||
import os | ||
import threading | ||
import time | ||
|
||
from .backend import BHyveBackEnd | ||
from .config import BHyveConfig | ||
from .constant import (DEVICES_PATH, API_POLL_PERIOD) | ||
from .device import BHyveDevice | ||
|
||
logging.basicConfig(level=logging.DEBUG) | ||
_LOGGER = logging.getLogger('pybhyve') | ||
|
||
__version__ = '0.0.1' | ||
|
||
|
||
class PyBHyve(object): | ||
|
||
def __init__(self, **kwargs): | ||
|
||
self.info('pybhyve loading') | ||
# Set up the config first. | ||
self._cfg = BHyveConfig(self, **kwargs) | ||
|
||
self._be = BHyveBackEnd(self) | ||
|
||
self._devices = [] | ||
|
||
self.info('pybhyve starting') | ||
self._last_poll = 0 | ||
self._refresh_devices() | ||
|
||
def __repr__(self): | ||
# Representation string of object. | ||
return "<{0}: {1}>".format(self.__class__.__name__, self._cfg.name) | ||
|
||
def _refresh_devices(self): | ||
now = time.time() | ||
if (now - self._last_poll < API_POLL_PERIOD): | ||
self.debug('Skipping refresh, not enough time has passed') | ||
return | ||
|
||
self._devices = [] | ||
devices = self._be.get(DEVICES_PATH + "?t={}".format(time.time())) | ||
|
||
for device in devices: | ||
deviceName = device.get('name') | ||
deviceType = device.get('type') | ||
self.info('Created device: {} [{}]'.format(deviceType, deviceName)) | ||
self._devices.append(BHyveDevice(deviceName, self, device)) | ||
|
||
self._last_poll = now | ||
|
||
@property | ||
def cfg(self): | ||
return self._cfg | ||
|
||
@property | ||
def devices(self): | ||
return self._devices | ||
|
||
@property | ||
def is_connected(self): | ||
return self._be.is_connected() | ||
|
||
def get_device(self, device_id): | ||
self._refresh_devices() | ||
for device in self._devices: | ||
if device.device_id == device_id: | ||
return device | ||
return None | ||
|
||
def update(self): | ||
pass | ||
|
||
def error(self, msg): | ||
_LOGGER.error(msg) | ||
|
||
def warning(self, msg): | ||
_LOGGER.warning(msg) | ||
|
||
def info(self, msg): | ||
_LOGGER.info(msg) | ||
|
||
def debug(self, msg): | ||
_LOGGER.debug(msg) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
import json | ||
import pprint | ||
import re | ||
import threading | ||
import time | ||
import uuid | ||
|
||
import requests | ||
import requests.adapters | ||
|
||
from .constant import (LOGIN_PATH, DEVICES_PATH) | ||
|
||
# include token and session details | ||
class BHyveBackEnd(object): | ||
|
||
def __init__(self, bhyve): | ||
|
||
self._bhyve = bhyve | ||
self._lock = threading.Condition() | ||
self._req_lock = threading.Lock() | ||
|
||
self._requests = {} | ||
self._callbacks = {} | ||
|
||
self._token = None | ||
|
||
# login | ||
self._session = None | ||
self._logged_in = self._login() | ||
if not self._logged_in: | ||
self._bhyve.warning('failed to log in') | ||
return | ||
|
||
|
||
def _request(self, path, method='GET', params=None, headers=None, stream=False, timeout=None): | ||
if params is None: | ||
params = {} | ||
if headers is None: | ||
headers = {} | ||
if timeout is None: | ||
timeout = self._bhyve.cfg.request_timeout | ||
try: | ||
with self._req_lock: | ||
url = self._bhyve.cfg.host + path | ||
self._bhyve.debug('starting request=' + str(url)) | ||
# self._bhyve.debug('starting request=' + str(params)) | ||
# self._bhyve.debug('starting request=' + str(headers)) | ||
if method == 'GET': | ||
r = self._session.get(url, params=params, headers=headers, stream=stream, timeout=timeout) | ||
if stream is True: | ||
return r | ||
elif method == 'PUT': | ||
r = self._session.put(url, json=params, headers=headers, timeout=timeout) | ||
elif method == 'POST': | ||
r = self._session.post(url, json=params, headers=headers, timeout=timeout) | ||
except Exception as e: | ||
self._bhyve.warning('request-error={}'.format(type(e).__name__)) | ||
return None | ||
|
||
self._bhyve.debug('finish request=' + str(r.status_code)) | ||
if r.status_code != 200: | ||
return None | ||
|
||
body = r.json() | ||
# self._bhyve.debug(pprint.pformat(body, indent=2)) | ||
|
||
return body | ||
|
||
# login and set up session | ||
def _login(self): | ||
|
||
# attempt login | ||
self._session = requests.Session() | ||
body = self.post(LOGIN_PATH, { 'session': {'email': self._bhyve.cfg.username, 'password': self._bhyve.cfg.password} }) | ||
if body is None: | ||
self._bhyve.debug('login failed') | ||
return False | ||
|
||
# save new login information | ||
self._token = body['orbit_session_token'] | ||
self._user_id = body['user_id'] | ||
|
||
headers = { | ||
'Accept': 'application/json, text/plain, */*', | ||
'Host': re.sub('https?://', '', self._bhyve.cfg.host), | ||
'Content-Type': 'application/json; charset=utf-8;', | ||
'Referer': self._bhyve.cfg.host, | ||
'Orbit-Session-Token': self._token | ||
} | ||
headers['User-Agent'] = ('Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) ' | ||
'Chrome/72.0.3626.81 Safari/537.36') | ||
|
||
self._session.headers.update(headers) | ||
return True | ||
|
||
def is_connected(self): | ||
return self._logged_in | ||
|
||
def get(self, path, params=None, headers=None, stream=False, timeout=None): | ||
return self._request(path, 'GET', params, headers, stream, timeout) | ||
|
||
def put(self, path, params=None, headers=None, timeout=None): | ||
return self._request(path, 'PUT', params, headers, False, timeout) | ||
|
||
def post(self, path, params=None, headers=None, timeout=None): | ||
return self._request(path, 'POST', params, headers, False, timeout) | ||
|
||
@property | ||
def session(self): | ||
return self._session | ||
|
||
def devices(self): | ||
return self.get(DEVICES_PATH + "?t={}".format(time.time())) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
from .constant import DEFAULT_HOST | ||
|
||
class BHyveConfig(object): | ||
def __init__(self, bhyve, **kwargs): | ||
""" The constructor. | ||
Args: | ||
kwargs (kwargs): Configuration options. | ||
""" | ||
self._bhyve = bhyve | ||
self._kw = kwargs | ||
|
||
@property | ||
def name(self, default='bhyve'): | ||
return self._kw.get('name', default) | ||
|
||
@property | ||
def username(self, default='unknown'): | ||
return self._kw.get('username', default) | ||
|
||
@property | ||
def password(self, default='unknown'): | ||
return self._kw.get('password', default) | ||
|
||
@property | ||
def host(self, default=DEFAULT_HOST): | ||
return self._kw.get('host', default) | ||
|
||
@property | ||
def request_timeout(self, default=60): | ||
return self._kw.get('request_timeout', default) | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
DEFAULT_HOST = 'https://api.orbitbhyve.com' | ||
|
||
LOGIN_PATH = '/v1/session' | ||
DEVICES_PATH = '/v1/devices' | ||
|
||
API_POLL_PERIOD = 300 |
Oops, something went wrong.