diff --git a/custom_components/niu/__init__.py b/custom_components/niu/__init__.py index ba0033e..4a647cb 100644 --- a/custom_components/niu/__init__.py +++ b/custom_components/niu/__init__.py @@ -1,11 +1,12 @@ """niu component.""" from __future__ import annotations + import logging from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant -from .const import CONF_SENSORS, DOMAIN, CONF_AUTH +from .const import CONF_AUTH, CONF_SENSORS, DOMAIN _LOGGER = logging.getLogger(__name__) @@ -16,17 +17,17 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up Niu Smart Plug from a config entry.""" - + niu_auth = entry.data.get(CONF_AUTH, None) if niu_auth == None: return False - - sensors_selected = niu_auth[CONF_SENSORS] + + sensors_selected = niu_auth[CONF_SENSORS] if len(sensors_selected) < 1: _LOGGER.error("You did NOT selected any sensor... cant setup the integration..") return False - if 'LastTrackThumb' in sensors_selected: + if "LastTrackThumb" in sensors_selected: PLATFORMS.append("camera") await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) diff --git a/custom_components/niu/api.py b/custom_components/niu/api.py index 7b3d660..b41cffc 100644 --- a/custom_components/niu/api.py +++ b/custom_components/niu/api.py @@ -1,11 +1,15 @@ -from .const import * +from datetime import datetime, timedelta import hashlib -import requests import json -from datetime import datetime, timedelta + # from homeassistant.util import Throttle from time import gmtime, strftime +import requests + +from .const import * + + class NiuApi: def __init__(self, username, password, scooter_id) -> None: self.username = username @@ -20,14 +24,17 @@ def __init__(self, username, password, scooter_id) -> None: def initApi(self): self.token = self.get_token() api_uri = MOTOINFO_LIST_API_URI - self.sn = self.get_vehicles_info(api_uri)["data"]["items"][self.scooter_id]["sn_id"] - self.sensor_prefix = self.get_vehicles_info(api_uri)["data"]["items"][self.scooter_id]["scooter_name"] + self.sn = self.get_vehicles_info(api_uri)["data"]["items"][self.scooter_id][ + "sn_id" + ] + self.sensor_prefix = self.get_vehicles_info(api_uri)["data"]["items"][ + self.scooter_id + ]["scooter_name"] self.updateBat() self.updateMoto() self.updateMotoInfo() self.updateTrackInfo() - def get_token(self): username = self.username password = self.password @@ -49,9 +56,7 @@ def get_token(self): data = json.loads(r.content.decode()) return data["data"]["token"]["access_token"] - def get_vehicles_info(self, path): - token = self.token url = API_BASE_URL + path @@ -65,8 +70,10 @@ def get_vehicles_info(self, path): data = json.loads(r.content.decode()) return data - - def get_info(self,path, ): + def get_info( + self, + path, + ): sn = self.sn token = self.token url = API_BASE_URL + path @@ -77,7 +84,6 @@ def get_info(self,path, ): "user-agent": "manager/4.10.4 (android; IN2020 11);lang=zh-CN;clientIdentifier=Domestic;timezone=Asia/Shanghai;model=IN2020;deviceName=IN2020;ostype=android", } try: - r = requests.get(url, headers=headers, params=params) except ConnectionError: @@ -89,8 +95,10 @@ def get_info(self,path, ): return False return data - - def post_info(self, path,): + def post_info( + self, + path, + ): sn, token = self.sn, self.token url = API_BASE_URL + path params = {} @@ -106,7 +114,6 @@ def post_info(self, path,): return False return data - def post_info_track(self, path): sn, token = self.sn, self.token url = API_BASE_URL + path @@ -132,7 +139,6 @@ def post_info_track(self, path): return False return data - def getDataBat(self, id_field): return self.dataBat["data"]["batteries"]["compartmentA"][id_field] @@ -154,9 +160,7 @@ def getDataTrack(self, id_field): (self.dataTrackInfo["data"][0][id_field]) / 1000 ).strftime("%Y-%m-%d %H:%M:%S") if id_field == "ridingtime": - return strftime( - "%H:%M:%S", gmtime(self.dataTrackInfo["data"][0][id_field]) - ) + return strftime("%H:%M:%S", gmtime(self.dataTrackInfo["data"][0][id_field])) if id_field == "track_thumb": thumburl = self.dataTrackInfo["data"][0][id_field].replace( "app-api.niucache.com", "app-api-fk.niu.com" @@ -176,7 +180,8 @@ def updateMotoInfo(self): def updateTrackInfo(self): self.dataTrackInfo = self.post_info_track(TRACK_LIST_API_URI) -'''class NiuDataBridge(object): + +"""class NiuDataBridge(object): async def __init__(self, api): # hass, username, password, country, scooter_id): @@ -246,4 +251,4 @@ def updateMotoInfo(self): @Throttle(timedelta(seconds=1)) def updateTrackInfo(self): - self._dataTrackInfo = self.api.post_info_track(TRACK_LIST_API_URI)''' \ No newline at end of file + self._dataTrackInfo = self.api.post_info_track(TRACK_LIST_API_URI)""" diff --git a/custom_components/niu/camera.py b/custom_components/niu/camera.py index 592bac1..fb8c0ae 100644 --- a/custom_components/niu/camera.py +++ b/custom_components/niu/camera.py @@ -1,17 +1,18 @@ """Last Track for Niu Integration integration. Author: Giovanni P. (@pikka97) """ -from .api import NiuApi -from .const import * - -import httpx import logging from typing import final -from homeassistant.components.generic.camera import GenericCamera +import httpx + from homeassistant.components.camera import STATE_IDLE +from homeassistant.components.generic.camera import GenericCamera from homeassistant.helpers.httpx_client import get_async_client +from .api import NiuApi +from .const import * + _LOGGER = logging.getLogger(__name__) GET_IMAGE_TIMEOUT = 10 @@ -19,19 +20,32 @@ async def async_setup_entry(hass, entry, async_add_entities) -> None: niu_auth = entry.data.get(CONF_AUTH, None) if niu_auth == None: - _LOGGER.error("The authenticator of your Niu integration is None.. can not setup the integration...") + _LOGGER.error( + "The authenticator of your Niu integration is None.. can not setup the integration..." + ) return False - + username = niu_auth[CONF_USERNAME] password = niu_auth[CONF_PASSWORD] scooter_id = niu_auth[CONF_SCOOTER_ID] - api = NiuApi(username, password, scooter_id) + api = NiuApi(username, password, scooter_id) await hass.async_add_executor_job(api.initApi) - camera_name = api.sensor_prefix + ' Last Track Camera' - - entry = {'name': camera_name, 'still_image_url': "", 'stream_source': None, 'authentication': 'basic', 'username': None, 'password': None, 'limit_refetch_to_url_change': False, 'content_type': 'image/jpeg', 'framerate': 2, 'verify_ssl': True} + camera_name = api.sensor_prefix + " Last Track Camera" + + entry = { + "name": camera_name, + "still_image_url": "", + "stream_source": None, + "authentication": "basic", + "username": None, + "password": None, + "limit_refetch_to_url_change": False, + "content_type": "image/jpeg", + "framerate": 2, + "verify_ssl": True, + } async_add_entities([LastTrackCamera(hass, api, entry, camera_name, camera_name)]) @@ -40,42 +54,38 @@ def __init__(self, hass, api, device_info, identifier: str, title: str) -> None: self._api = api super().__init__(hass, device_info, identifier, title) - - @property @final def state(self) -> str: """Return the camera state.""" return STATE_IDLE - @property def is_on(self) -> bool: """Return true if on.""" - return self._last_image != b'' - + return self._last_image != b"" @property def device_info(self): - device_name = 'Niu E-scooter' + device_name = "Niu E-scooter" dev = { - "identifiers": {('niu', device_name)}, + "identifiers": {("niu", device_name)}, "name": device_name, "manufacturer": "Niu", "model": 1.0, - } + } return dev async def async_camera_image( self, width: int | None = None, height: int | None = None ) -> bytes | None: - get_last_track = lambda : self._api.getDataTrack("track_thumb") + get_last_track = lambda: self._api.getDataTrack("track_thumb") last_track_url = await self.hass.async_add_executor_job(get_last_track) - if last_track_url == self._last_url and self._previous_image != b'': - #The path image is the same as before so the image is the same: + if last_track_url == self._last_url and self._previous_image != b"": + # The path image is the same as before so the image is the same: return self._previous_image - + try: async_client = get_async_client(self.hass, verify_ssl=self.verify_ssl) response = await async_client.get( diff --git a/custom_components/niu/config_flow.py b/custom_components/niu/config_flow.py index 4630b27..67812a9 100644 --- a/custom_components/niu/config_flow.py +++ b/custom_components/niu/config_flow.py @@ -3,10 +3,6 @@ """ from __future__ import annotations -from .const import * -from .api import NiuApi - - import logging from typing import Any @@ -15,33 +11,36 @@ from homeassistant import config_entries from homeassistant.data_entry_flow import FlowResult from homeassistant.exceptions import HomeAssistantError - from homeassistant.helpers import selector +from .api import NiuApi +from .const import * + _LOGGER = logging.getLogger(__name__) STEP_USER_DATA_SCHEMA = vol.Schema( { - vol.Required(CONF_USERNAME): str, - vol.Required(CONF_PASSWORD): str, - vol.Required(CONF_SCOOTER_ID, default=DEFAULT_SCOOTER_ID): int, + vol.Required(CONF_USERNAME): str, + vol.Required(CONF_PASSWORD): str, + vol.Required(CONF_SCOOTER_ID, default=DEFAULT_SCOOTER_ID): int, vol.Required(CONF_SENSORS, default=AVAILABLE_SENSORS): selector.SelectSelector( - selector.SelectSelectorConfig(options=AVAILABLE_SENSORS, - multiple=True, - mode=selector.SelectSelectorMode.LIST), - ) + selector.SelectSelectorConfig( + options=AVAILABLE_SENSORS, + multiple=True, + mode=selector.SelectSelectorMode.LIST, + ), + ), } ) - class NiuAuthenticator: def __init__(self, username, password, scooter_id, sensors_selected) -> None: self.username = username self.password = password self.scooter_id = scooter_id self.sensors_selected = sensors_selected - + async def authenticate(self, hass): api = NiuApi(self.username, self.password, self.scooter_id) try: @@ -49,7 +48,7 @@ async def authenticate(self, hass): if isinstance(token, bool): return token else: - return token != '' + return token != "" except: return False @@ -59,24 +58,31 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): VERSION = 1 - async def async_step_user(self, user_input: dict[str, Any] | None = None) -> FlowResult: + async def async_step_user( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: """Invoked when a user clicks the add button""" - - - integration_title = 'Niu EScooter Integration' + + integration_title = "Niu EScooter Integration" errors = {} - + if user_input != None: username = user_input[CONF_USERNAME] password = user_input[CONF_PASSWORD] scooter_id = user_input[CONF_SCOOTER_ID] sensors_selected = user_input[CONF_SENSORS] - niu_auth = NiuAuthenticator(username, password, scooter_id, sensors_selected) + niu_auth = NiuAuthenticator( + username, password, scooter_id, sensors_selected + ) auth_result = await niu_auth.authenticate(self.hass) if auth_result: - return self.async_create_entry(title=integration_title, data={CONF_AUTH:niu_auth.__dict__}) + return self.async_create_entry( + title=integration_title, data={CONF_AUTH: niu_auth.__dict__} + ) else: # The user used wrong credentials... - errors['base']= "invalid_auth" + errors["base"] = "invalid_auth" - return self.async_show_form(step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors) \ No newline at end of file + return self.async_show_form( + step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors + ) diff --git a/custom_components/niu/const.py b/custom_components/niu/const.py index 42b58af..12d2a0a 100644 --- a/custom_components/niu/const.py +++ b/custom_components/niu/const.py @@ -12,7 +12,7 @@ CONF_USERNAME = "username" CONF_PASSWORD = "password" CONF_SCOOTER_ID = "scooter_id" -CONF_AUTH = 'conf_auth' +CONF_AUTH = "conf_auth" CONF_SENSORS = "sensors_selected" DEFAULT_SCOOTER_ID = 0 @@ -51,15 +51,17 @@ "LastTrackDistance", "LastTrackAverageSpeed", "LastTrackRidingtime", - "LastTrackThumb" + "LastTrackThumb", ] +import voluptuous as vol + +from homeassistant.components.sensor import PLATFORM_SCHEMA + # Sensors schemas from homeassistant.const import CONF_MONITORED_VARIABLES import homeassistant.helpers.config_validation as cv -from homeassistant.components.sensor import PLATFORM_SCHEMA -import voluptuous as vol PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( { @@ -285,5 +287,5 @@ SENSOR_TYPE_TRACK, "none", "mdi:map", - ] -} \ No newline at end of file + ], +} diff --git a/custom_components/niu/sensor.py b/custom_components/niu/sensor.py index 785fe9e..8d4685f 100644 --- a/custom_components/niu/sensor.py +++ b/custom_components/niu/sensor.py @@ -2,25 +2,24 @@ Support for Niu Scooters by Marcel Westra. Asynchronous version implementation by Giovanni P. (@pikka97) """ -from .api import NiuApi -from .const import * - from datetime import timedelta import logging from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle +from .api import NiuApi +from .const import * _LOGGER = logging.getLogger(__name__) - async def async_setup_entry(hass, entry, async_add_entities) -> None: - niu_auth = entry.data.get(CONF_AUTH, None) if niu_auth == None: - _LOGGER.error("The authenticator of your Niu integration is None.. can not setup the integration...") + _LOGGER.error( + "The authenticator of your Niu integration is None.. can not setup the integration..." + ) return False username = niu_auth[CONF_USERNAME] @@ -34,7 +33,7 @@ async def async_setup_entry(hass, entry, async_add_entities) -> None: # add sensors devices = [] for sensor in sensors_selected: - if sensor != 'LastTrackThumb': + if sensor != "LastTrackThumb": sensor_config = SENSOR_TYPES[sensor] devices.append( NiuSensor( @@ -49,21 +48,21 @@ async def async_setup_entry(hass, entry, async_add_entities) -> None: sensor_config[4], api.sn, sensor_config[5], - )) + ) + ) else: - #Last Track Thumb sensor will be used as camera... now just skip it + # Last Track Thumb sensor will be used as camera... now just skip it pass - + async_add_entities(devices) return True - class NiuSensor(Entity): def __init__( self, hass, - api : NiuApi, + api: NiuApi, name, sensor_id, uom, @@ -87,8 +86,6 @@ def __init__( self._icon = icon self._state = None - - @property def unique_id(self): return self._unique_id @@ -115,13 +112,13 @@ def device_class(self): @property def device_info(self): - device_name = 'Niu E-scooter' + device_name = "Niu E-scooter" return { - "identifiers": {('niu', device_name)}, + "identifiers": {("niu", device_name)}, "name": device_name, "manufacturer": "Niu", "model": 1.0, - } + } @property def extra_state_attributes(self): @@ -148,19 +145,19 @@ async def async_update(self): elif self._sensor_grp == SENSOR_TYPE_MOTO: await self._hass.async_add_executor_job(self._api.updateMoto) self._state = self._api.getDataMoto(self._id_name) - + elif self._sensor_grp == SENSOR_TYPE_POS: await self._hass.async_add_executor_job(self._api.updateMoto) self._state = self._api.getDataPos(self._id_name) - + elif self._sensor_grp == SENSOR_TYPE_DIST: await self._hass.async_add_executor_job(self._api.updateBat) self._state = self._api.getDataDist(self._id_name) - + elif self._sensor_grp == SENSOR_TYPE_OVERALL: await self._hass.async_add_executor_job(self._api.updateMotoInfo) self._state = self._api.getDataOverall(self._id_name) - + elif self._sensor_grp == SENSOR_TYPE_TRACK: await self._hass.async_add_executor_job(self._api.updateTrackInfo) self._state = self._api.getDataTrack(self._id_name) diff --git a/tox.ini b/tox.ini index 973cb7b..9fdf2ec 100644 --- a/tox.ini +++ b/tox.ini @@ -25,8 +25,6 @@ force_grid_wrap=0 use_parentheses=True line_length=88 indent = " " -# by default isort don't check module indexes -not_skip = __init__.py # will group `import x` and `from x import` of the same module. force_sort_within_sections = true sections = FUTURE,STDLIB,INBETWEENS,THIRDPARTY,FIRSTPARTY,LOCALFOLDER