-
-
Notifications
You must be signed in to change notification settings - Fork 38.2k
Added support for TekSavvy bandwidth sensor #11186
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 4 commits
71aacf3
d39afa9
5b7a653
5c4a242
7e89944
036f846
f96250a
3d7f247
3341005
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,168 @@ | ||
| """ | ||
| Support for TekSavvy. | ||
|
|
||
| Get bandwidth comsumption data using Teksavvy API | ||
| You can get your API key here: | ||
| https://myaccount.teksavvy.com/ApiKey/ApiKeyManagement | ||
|
|
||
| TekSavvy only counts download only as part of the bandwidth | ||
|
|
||
| For more details about this platform, please refer to the documentation at | ||
| https://home-assistant.io/components/sensor.teksavvy/ | ||
| """ | ||
| import logging | ||
| from datetime import timedelta | ||
| import http.client | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 'http.client' imported but unused |
||
| import json | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 'json' imported but unused |
||
| import asyncio | ||
| import async_timeout | ||
| from homeassistant.helpers.aiohttp_client import async_get_clientsession | ||
|
|
||
| import voluptuous as vol | ||
| from homeassistant.helpers.entity import Entity | ||
| from homeassistant.components.sensor import PLATFORM_SCHEMA | ||
| import homeassistant.helpers.config_validation as cv | ||
| from homeassistant.const import ( | ||
| CONF_API_KEY, CONF_NAME, CONF_MONITORED_VARIABLES) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please keep the import groups and sorted. Use
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. Thanks for the isort tip. |
||
|
|
||
| from homeassistant.util import Throttle | ||
|
|
||
| _LOGGER = logging.getLogger(__name__) | ||
|
|
||
| DEFAULT_NAME = 'TekSavvy' | ||
| CONF_TOTAL_BANDWIDTH = 'total_bandwidth' | ||
|
|
||
| GIGABYTES = 'GB' # type: str | ||
| PERCENT = '%' # type: str | ||
|
|
||
| MIN_TIME_BETWEEN_UPDATES = timedelta(hours=1) | ||
| REQUEST_TIMEOUT = 5 # seconds | ||
|
|
||
| SENSOR_TYPES = { | ||
| 'usage': ['Usage', PERCENT, 'mdi:percent'], | ||
| 'usage_gb': ['Usage', GIGABYTES, 'mdi:download'], | ||
| 'limit': ['Data limit', GIGABYTES, 'mdi:download'], | ||
| 'onpeak_download': ['On Peak Download', GIGABYTES, 'mdi:download'], | ||
| 'onpeak_upload': ['On Peak Upload ', GIGABYTES, 'mdi:upload'], | ||
| 'onpeak_total': ['On Peak Total', GIGABYTES, 'mdi:download'], | ||
| 'offpeak_download': ['Off Peak download', GIGABYTES, 'mdi:download'], | ||
| 'offpeak_upload': ['Off Peak Upload', GIGABYTES, 'mdi:upload'], | ||
| 'offpeak_total': ['Off Peak Total', GIGABYTES, 'mdi:download'], | ||
| 'onpeak_remaining': ['Remaining', GIGABYTES, 'mdi:download'] | ||
| } | ||
|
|
||
| API_HA_MAP = ( | ||
| ('OnPeakDownload', 'onpeak_download'), | ||
| ('OnPeakUpload', 'onpeak_upload'), | ||
| ('OffPeakDownload', 'offpeak_download'), | ||
| ('OffPeakUpload', 'offpeak_upload')) | ||
|
|
||
| PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ | ||
| vol.Required(CONF_MONITORED_VARIABLES): | ||
| vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]), | ||
| vol.Required(CONF_API_KEY): cv.string, | ||
| vol.Required(CONF_TOTAL_BANDWIDTH): cv.string, | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Only allow integers here. This way you don't need the casting on line 73.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. Allows only positive integer now. |
||
| vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, | ||
| }) | ||
|
|
||
| @asyncio.coroutine | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. expected 2 blank lines, found 1 |
||
| def async_setup_platform(hass, config, async_add_devices, discovery_info=None): | ||
| """Setup the sensor platform.""" | ||
| websession = async_get_clientsession(hass) | ||
| apikey = config.get(CONF_API_KEY) | ||
| bandwidthcapstr = config.get(CONF_TOTAL_BANDWIDTH) | ||
| try: | ||
| bandwidthcap = int(bandwidthcapstr) | ||
| ts_data = TekSavvyData(hass.loop, websession, apikey, bandwidthcap) | ||
| ret = yield from ts_data.async_update() | ||
| except ValueError as error: | ||
| _LOGGER.error("Failed conversion %s %s", CONF_TOTAL_BANDWIDTH, error) | ||
| if ret == False: | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. comparison to False should be 'if cond is False:' or 'if not cond:' |
||
| return | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You should log an invalid API key. I guess that for the users it's not very helpful that a conversation failed.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Removed the conversion since we no longer have the cast. Also, added an error message if the user has an invalid API key. |
||
|
|
||
| name = config.get(CONF_NAME) | ||
| sensors = [] | ||
| for variable in config[CONF_MONITORED_VARIABLES]: | ||
| sensors.append(TekSavvySensor(ts_data, variable, name)) | ||
| async_add_devices(sensors, True) | ||
|
|
||
| class TekSavvySensor(Entity): | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. expected 2 blank lines, found 1 |
||
| """TekSavvy Bandwidth sensor.""" | ||
|
|
||
| def __init__(self, teksavvydata, sensor_type, name): | ||
| """Initialize the sensor.""" | ||
| self.client_name = name | ||
| self.type = sensor_type | ||
| self._name = SENSOR_TYPES[sensor_type][0] | ||
| self._unit_of_measurement = SENSOR_TYPES[sensor_type][1] | ||
| self._icon = SENSOR_TYPES[sensor_type][2] | ||
| self.teksavvydata = teksavvydata | ||
| self._state = None | ||
|
|
||
| @property | ||
| def name(self): | ||
| """Return the name of the sensor.""" | ||
| return '{} {}'.format(self.client_name, self._name) | ||
|
|
||
| @property | ||
| def state(self): | ||
| """Return the state of the sensor.""" | ||
| return self._state | ||
|
|
||
| @property | ||
| def unit_of_measurement(self): | ||
| """Return the unit of measurement of this entity, if any.""" | ||
| return self._unit_of_measurement | ||
|
|
||
| @property | ||
| def icon(self): | ||
| """Icon to use in the frontend, if any.""" | ||
| return self._icon | ||
|
|
||
| @asyncio.coroutine | ||
| def async_update(self): | ||
| """Get the latest data from TekSavvy and update the state.""" | ||
| yield from self.teksavvydata.async_update() | ||
| if self.type in self.teksavvydata.data: | ||
| self._state = round(self.teksavvydata.data[self.type], 2) | ||
|
|
||
| class TekSavvyData(object): | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. expected 2 blank lines, found 1 |
||
| """Get data from TekSavvy API.""" | ||
|
|
||
| def __init__(self, loop, websession, api_key, bandwidth_cap): | ||
| """Initialize the data object.""" | ||
| self.loop = loop | ||
| self.websession = websession | ||
| self.api_key = api_key | ||
| self.bandwidth_cap = bandwidth_cap | ||
| self.data = dict() | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can use
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
| self.data["limit"] = self.bandwidth_cap | ||
|
|
||
| @asyncio.coroutine | ||
| @Throttle(MIN_TIME_BETWEEN_UPDATES) | ||
| def async_update(self): | ||
| """Get the TekSavvy bandwidth data from the web service.""" | ||
| headers = {"TekSavvy-APIKey": self.api_key} | ||
| _LOGGER.info("Updaing TekSavvy data") | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Make this
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
| url = "https://api.teksavvy.com/"\ | ||
| "web/Usage/UsageSummaryRecords?$filter=IsCurrent%20eq%20true" | ||
| with async_timeout.timeout(REQUEST_TIMEOUT, loop=self.loop): | ||
| req = yield from self.websession.get(url, headers = headers) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. unexpected spaces around keyword / parameter equals |
||
| if req.status != 200: | ||
| _LOGGER.error("Request failed with status: %u", req.status) | ||
| return False | ||
| else: | ||
| data = yield from req.json() | ||
| for (api, ha_name) in API_HA_MAP: | ||
| self.data[ha_name] = float(data["value"][0][api]) | ||
| on_peak_download = self.data["onpeak_download"] | ||
| on_peak_upload = self.data["onpeak_upload"] | ||
| off_peak_download = self.data["offpeak_download"] | ||
| off_peak_upload = self.data["offpeak_upload"] | ||
| limit = self.data["limit"] | ||
| self.data["usage"] = 100*on_peak_download/self.bandwidth_cap | ||
| self.data["usage_gb"] = on_peak_download | ||
| self.data["onpeak_total"] = on_peak_download + on_peak_upload | ||
| self.data["offpeak_total"] = off_peak_download + off_peak_upload | ||
| self.data["onpeak_remaining"] = limit - on_peak_download | ||
| return True | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This belongs to the documentation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed.