From ae40fe3feb2cd82fdbb9ebace2b73f11cf45dc76 Mon Sep 17 00:00:00 2001 From: dscao Date: Mon, 16 Jan 2023 04:27:07 +0800 Subject: [PATCH] add switch arp_filter add switch with const.py --- custom_components/ikuai/__init__.py | 1 + custom_components/ikuai/const.py | 14 ++ custom_components/ikuai/data_fetcher.py | 48 +++++- custom_components/ikuai/device_tracker.py | 13 -- custom_components/ikuai/switch.py | 185 ++++++++++++++++++++-- 5 files changed, 234 insertions(+), 27 deletions(-) diff --git a/custom_components/ikuai/__init__.py b/custom_components/ikuai/__init__.py index 47d758b..5c1a6b3 100644 --- a/custom_components/ikuai/__init__.py +++ b/custom_components/ikuai/__init__.py @@ -114,6 +114,7 @@ async def get_access_token(self): self._token_expire_time = time.time() + 60*60*2 return self._token else: + _LOGGER.info("The password has been incorrect for many times, please reconfigure the ikuai integration.") return async def _async_update_data(self): diff --git a/custom_components/ikuai/const.py b/custom_components/ikuai/const.py index 6454228..5fb91a5 100644 --- a/custom_components/ikuai/const.py +++ b/custom_components/ikuai/const.py @@ -117,6 +117,20 @@ }, } + +SWITCH_TYPES = { + "ikuai_arp_filter": { + "icon": "mdi:account-lock", + "label": "iKuai非绑定MAC不允许上网", + "name": "Arp_filter", + "turn_on_body": {"func_name":"arp","action":"seting","param":{"arp_filter":1}}, + "turn_off_body": {"func_name":"arp","action":"seting","param":{"arp_filter":0}}, + "show_body": {"func_name":"arp","action":"show","param":{"TYPE":"options"}}, + "show_on": {'arp_filter': 1}, + "show_off": {'arp_filter': 0}, + }, +} + DEVICE_TRACKERS = { "myiphone": { "label": "我的手机", diff --git a/custom_components/ikuai/data_fetcher.py b/custom_components/ikuai/data_fetcher.py index 4eb87bc..d3b2868 100644 --- a/custom_components/ikuai/data_fetcher.py +++ b/custom_components/ikuai/data_fetcher.py @@ -17,7 +17,8 @@ from .const import ( LOGIN_URL, ACTION_URL, - DEVICE_TRACKERS, + DEVICE_TRACKERS, + SWITCH_TYPES, ) _LOGGER = logging.getLogger(__name__) @@ -397,12 +398,47 @@ async def _get_ikuai_device_tracker(self, sess_key, macaddress): self._datarefreshtimes[macaddress] = 0 _LOGGER.debug("%s refreshtimes: %s", macaddress, self._datarefreshtimes[macaddress]) elif self._datarefreshtimes.get(macaddress): - if self._datarefreshtimes[macaddress] < 2 : + if self._datarefreshtimes[macaddress] < 5 : self._data["tracker"].append(self._datatracker[macaddress]) self._datarefreshtimes[macaddress] = self._datarefreshtimes[macaddress] + 1 return + + async def _get_ikuai_switch(self, sess_key, name, show_body, show_on, show_off): + header = { + 'Cookie': 'Cookie: username=admin; login=1; sess_key='+sess_key, + 'Accept': 'application/json, text/plain, */*', + 'Accept-Encoding': 'gzip, deflate', + 'Accept-Language': 'zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7', + 'Content-Type': 'application/json;charset=UTF-8', + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.40 Safari/537.36', + } + + json_body = show_body + + url = self._host + ACTION_URL + _LOGGER.debug("Requests remaining: %s: %s", url, json_body) + try: + async with timeout(10): + resdata = await self._hass.async_add_executor_job(self.requestpost_json, url, header, json_body) + except ( + ClientConnectorError + ) as error: + raise UpdateFailed(error) + _LOGGER.debug(resdata) + if resdata == 401: + self._data = 401 + return + if resdata["Result"] == 10014: + self._data = 401 + return + if resdata.get("Data") == show_on: + self._data["switch"].append({"name":name,"onoff":"on"}) + elif resdata.get("Data") == show_off: + self._data["switch"].append({"name":name,"onoff":"off"}) + return + async def get_data(self, sess_key): threads = [ @@ -423,6 +459,14 @@ async def get_data(self, sess_key): ] await asyncio.wait(threads) + self._data["switch"] = [] + threads = [] + for switch in SWITCH_TYPES: + threads = [ + self._get_ikuai_switch(sess_key, SWITCH_TYPES[switch]['name'], SWITCH_TYPES[switch]['show_body'], SWITCH_TYPES[switch]['show_on'], SWITCH_TYPES[switch]['show_off']) + ] + await asyncio.wait(threads) + self._data["tracker"] = [] threads = [] for device_tracker in DEVICE_TRACKERS: diff --git a/custom_components/ikuai/device_tracker.py b/custom_components/ikuai/device_tracker.py index abef233..0c3da79 100644 --- a/custom_components/ikuai/device_tracker.py +++ b/custom_components/ikuai/device_tracker.py @@ -60,19 +60,6 @@ def __init__(self, hass, kind, coordinator): self._attrs = {} self._querytime = "" - - async def get_access_token(self): - if time.time() < self._token_expire_time: - return self._token - else: - if self._allow_login == True: - self._token = await self._fetcher._login_ikuai() - if self._token == 10001: - self._allow_login = False - self._token_expire_time = time.time() + 60*60*2 - return self._token - else: - return @property def name(self): diff --git a/custom_components/ikuai/switch.py b/custom_components/ikuai/switch.py index c962652..ed98131 100644 --- a/custom_components/ikuai/switch.py +++ b/custom_components/ikuai/switch.py @@ -19,6 +19,7 @@ CONF_PASS, CONF_HOST, ACTION_URL, + SWITCH_TYPES, ) @@ -34,17 +35,183 @@ async def async_setup_entry(hass, config_entry, async_add_entities): pas = config_entry.data[CONF_PASS] switchs = [] - #_LOGGER.debug(coordinator.data.get("mac_control")) + switchsmac = [] + + if SWITCH_TYPES: + _LOGGER.debug("setup switchs") + for switch in SWITCH_TYPES: + switchs.append(IKUAISwitch(hass, switch, coordinator, host, username, passwd, pas)) + _LOGGER.debug(SWITCH_TYPES[switch]["name"]) + async_add_entities(switchs, False) + if coordinator.data.get("mac_control"): listmacdata = coordinator.data.get("mac_control") if isinstance(listmacdata, list): _LOGGER.debug(listmacdata) for mac in listmacdata: - #_LOGGER.debug(mac) - switchs.append(PVESwitch(hass, coordinator, host, username, passwd, pas, mac["id"])) - async_add_entities(switchs, False) + _LOGGER.debug(mac) + switchsmac.append(IKUAISwitchmac(hass, coordinator, host, username, passwd, pas, mac["id"])) + async_add_entities(switchsmac, False) + + + +class IKUAISwitch(SwitchEntity): + _attr_has_entity_name = True + def __init__(self, hass, kind, coordinator, host, username, passwd, pas): + """Initialize.""" + super().__init__() + self.kind = kind + self.coordinator = coordinator + self._state = None + self._attr_device_info = { + "identifiers": {(DOMAIN, self.coordinator.host)}, + "name": self.coordinator.data["device_name"], + "manufacturer": "iKuai", + "model": "iKuai Router", + "sw_version": self.coordinator.data["sw_version"], + } + self._attr_icon = SWITCH_TYPES[self.kind]['icon'] + self._attr_device_class = "switch" + self._attr_entity_registry_enabled_default = True + self._hass = hass + self._token = "" + self._token_expire_time = 0 + self._allow_login = True + self._fetcher = DataFetcher(hass, host, username, passwd, pas) + self._host = host + self._name = SWITCH_TYPES[self.kind]['name'] + self._turn_on_body = SWITCH_TYPES[self.kind]['turn_on_body'] + self._turn_off_body = SWITCH_TYPES[self.kind]['turn_off_body'] + self._change = True + self._switchonoff = None + + listswitch = self.coordinator.data.get("switch") + + for switchdata in listswitch: + if switchdata["name"] == self._name: + self._switchonoff = switchdata["onoff"] + + self._is_on = self._switchonoff == "on" + self._state = "on" if self._is_on == True else "off" + + + async def get_access_token(self): + if time.time() < self._token_expire_time: + return self._token + else: + if self._allow_login == True: + self._token = await self._fetcher._login_ikuai() + if self._token == 10001: + self._allow_login = False + self._token_expire_time = time.time() + 60*60*2 + return self._token + else: + return + + @property + def name(self): + """Return the name.""" + return f"{self._name}" + + @property + def unique_id(self): + return f"{DOMAIN}_switch_{self.coordinator.host}_{self._name}" + + + @property + def should_poll(self): + """Return the polling requirement of the entity.""" + return False + + @property + def is_on(self): + """Check if switch is on.""" + return self._is_on + + async def async_turn_on(self, **kwargs): + """Turn switch on.""" + self._is_on = True + self._change = False + json_body = self._turn_on_body + await self._switch(json_body) + self._switchonoff = "on" -class PVESwitch(SwitchEntity): + + async def async_turn_off(self, **kwargs): + """Turn switch off.""" + self._is_on = False + self._change = False + json_body = self._turn_off_body + await self._switch(json_body) + self._switchonoff = "off" + + async def async_added_to_hass(self): + """Connect to dispatcher listening for entity data notifications.""" + self.async_on_remove( + self.coordinator.async_add_listener(self.async_write_ha_state) + ) + + async def async_update(self): + """Update entity.""" + await self.coordinator.async_request_refresh() + + listswitch = self.coordinator.data.get("switch") + + for switchdata in listswitch: + if switchdata["name"] == self._name: + self._switchonoff = switchdata["onoff"] + + self._is_on = self._switchonoff == "on" + self._state = "on" if self._is_on == True else "off" + self._change = True + + + def requestpost_json(self, url, headerstr, json_body): + responsedata = requests.post(url, headers=headerstr, json = json_body, verify=False) + if responsedata.status_code != 200: + return responsedata.status_code + json_text = responsedata.content.decode('utf-8') + resdata = json.loads(json_text) + return resdata + + async def _switch(self, action_body): + if self._allow_login == True: + sess_key = await self.get_access_token() + header = { + 'Cookie': 'Cookie: username=admin; login=1; sess_key='+sess_key, + 'Accept': 'application/json, text/plain, */*', + 'Accept-Encoding': 'gzip, deflate', + 'Accept-Language': 'zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7', + 'Content-Type': 'application/json;charset=UTF-8', + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.40 Safari/537.36', + } + + json_body = action_body + + url = self._host + ACTION_URL + + try: + async with timeout(10): + resdata = await self._hass.async_add_executor_job(self.requestpost_json, url, header, json_body) + except ( + ClientConnectorError + ) as error: + raise UpdateFailed(error) + _LOGGER.debug("Requests remaining: %s", url) + _LOGGER.debug(resdata) + if resdata == 401: + self._data = 401 + return + if resdata["Result"] == 10014: + self._data = 401 + return + + _LOGGER.info("操作ikuai: %s ", json_body) + return "OK" + + + +class IKUAISwitchmac(SwitchEntity): _attr_has_entity_name = True def __init__(self, hass, coordinator, host, username, passwd, pas, mac): """Initialize.""" @@ -113,11 +280,6 @@ def should_poll(self): """Return the polling requirement of the entity.""" return True - # @property - # def state(self): - # """Return the state.""" - # return self._state - @property def extra_state_attributes(self): """Return device state attributes.""" @@ -211,5 +373,4 @@ async def _mac_control_switch(self, action_body): return _LOGGER.info("操作ikuai: %s ", json_body) - return "OK" - + return "OK" \ No newline at end of file