deploy
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
name: Release
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
release-zip:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: ZIP Component Dir
|
||||
run: |
|
||||
cd ${{ github.workspace }}/custom_components/state_grid
|
||||
zip -r state_grid.zip ./
|
||||
|
||||
- name: Upload zip to release
|
||||
uses: svenstaro/upload-release-action@v2
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
file: ${{ github.workspace }}/custom_components/state_grid/state_grid.zip
|
||||
asset_name: state_grid.zip
|
||||
tag: ${{ github.ref }}
|
||||
overwrite: true
|
||||
|
||||
- name: Upload zip to OSS
|
||||
uses: tvrcgo/upload-to-oss@master
|
||||
with:
|
||||
key-id: ${{ secrets.ALIYUN_OSS_ACCESS_KEY_ID }}
|
||||
key-secret: ${{ secrets.ALIYUN_OSS_ACCESS_KEY_SECRET }}
|
||||
region: ${{ secrets.ALIYUN_OSS_REGION }}
|
||||
bucket: ${{ secrets.ALIYUN_OSS_BUCKET }}
|
||||
assets: |
|
||||
${{ github.workspace }}/custom_components/state_grid/state_grid.zip:/state_grid.zip
|
||||
${{ github.workspace }}/custom_components/state_grid/state_grid.zip:/integration/hass-box/state_grid/${{ github.ref_name }}/state_grid.zip
|
||||
@@ -0,0 +1,15 @@
|
||||
name: Validate
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
validate-hassfest:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: 📥 Checkout the repository
|
||||
uses: actions/checkout@v3.5.3
|
||||
|
||||
- name: 🏃 Hassfest validation
|
||||
uses: home-assistant/actions/hassfest@master
|
||||
@@ -0,0 +1,7 @@
|
||||
# State Grid
|
||||
|
||||
问题反馈可至 HassBox 小程序联系客服
|
||||
|
||||
或 入群反馈(本群仅用于国家电网集成问题反馈,严禁其他话题,违者❎)
|
||||
|
||||

|
||||
@@ -0,0 +1,15 @@
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.const import Platform
|
||||
|
||||
from .const import DOMAIN
|
||||
from .utils.store import async_load_from_store
|
||||
from .data_client import StateGridDataClient
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
|
||||
"""Set up this integration using UI."""
|
||||
config = await async_load_from_store(hass, "state_grid.config") or None
|
||||
hass.data[DOMAIN] = StateGridDataClient(hass = hass, config = config)
|
||||
await hass.config_entries.async_forward_entry_setups(config_entry, [Platform.SENSOR])
|
||||
return True
|
||||
@@ -0,0 +1,272 @@
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.selector import selector
|
||||
|
||||
|
||||
from .utils.logger import LOGGER
|
||||
from .const import DOMAIN
|
||||
|
||||
from .data_client import StateGridDataClient
|
||||
|
||||
class StateGridConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
|
||||
VERSION = 1
|
||||
|
||||
async def async_step_user(self, user_input=None):
|
||||
if self._async_current_entries():
|
||||
return self.async_abort(reason="single_instance_allowed")
|
||||
if self.hass.data.get(DOMAIN):
|
||||
return self.async_abort(reason="single_instance_allowed")
|
||||
|
||||
self.data_client = StateGridDataClient(hass=self.hass)
|
||||
|
||||
options = {
|
||||
"scan_login": "网上国网 App 扫码登录",
|
||||
"code_login": "手机短信验证码登录",
|
||||
}
|
||||
|
||||
return self.async_show_menu(
|
||||
step_id="user",
|
||||
menu_options=options
|
||||
)
|
||||
|
||||
async def async_step_scan_login(self, user_input=None):
|
||||
result = await self.data_client.get_qr_code()
|
||||
if result['errcode'] != 0:
|
||||
return self.async_abort(
|
||||
reason="get_qr_code_error",
|
||||
description_placeholders={'errmsg': result["errmsg"]}
|
||||
)
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="check_qr_code",
|
||||
description_placeholders={
|
||||
"qr_image": '<img style="width: 200px;" src="data:image/png;base64,' + result["data"] + '"/>'
|
||||
}
|
||||
)
|
||||
|
||||
async def async_step_check_qr_code(self, user_input=None):
|
||||
result = await self.data_client.check_qr_code()
|
||||
|
||||
if result['errcode'] != 0:
|
||||
return self.async_abort(
|
||||
reason="check_qr_code_error",
|
||||
description_placeholders={'errmsg': result["errmsg"]}
|
||||
)
|
||||
|
||||
return self.async_create_entry(
|
||||
title="国家电网",
|
||||
data={}
|
||||
)
|
||||
|
||||
async def async_step_code_login(self, user_input=None):
|
||||
errors = {}
|
||||
if user_input is None:
|
||||
user_input = {}
|
||||
else:
|
||||
phone = user_input['phone']
|
||||
result = await self.data_client.send_phone_code(phone)
|
||||
if result['errcode'] == 0:
|
||||
return await self.async_step_verfiy_code()
|
||||
else:
|
||||
errors["phone"] = result["errmsg"]
|
||||
|
||||
data_schema = {
|
||||
vol.Required("phone") : selector({
|
||||
"text": {
|
||||
"type": "number"
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="code_login",
|
||||
data_schema=vol.Schema(data_schema),
|
||||
errors=errors
|
||||
)
|
||||
|
||||
async def async_step_verfiy_code(self, user_input=None):
|
||||
errors = {}
|
||||
if user_input is None:
|
||||
user_input = {}
|
||||
else:
|
||||
code = user_input['code']
|
||||
result = await self.data_client.verfiy_phone_code(code)
|
||||
if result['errcode'] == 0:
|
||||
return self.async_create_entry(
|
||||
title="国家电网",
|
||||
data={}
|
||||
)
|
||||
|
||||
errors["code"] = result["errmsg"]
|
||||
|
||||
data_schema = {
|
||||
vol.Required("code") : selector({
|
||||
"text": {
|
||||
"type": "number"
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="verfiy_code",
|
||||
data_schema=vol.Schema(data_schema),
|
||||
errors=errors
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
@callback
|
||||
def async_get_options_flow(entry: config_entries.ConfigEntry):
|
||||
return OptionsFlowHandler(entry)
|
||||
|
||||
class OptionsFlowHandler(config_entries.OptionsFlow):
|
||||
|
||||
def __init__(self, config_entry: config_entries.ConfigEntry):
|
||||
self.config_entry = config_entry
|
||||
|
||||
async def async_step_init(self, user_input=None):
|
||||
self.data_client: StateGridDataClient = self.hass.data[DOMAIN]
|
||||
if self.data_client.need_login is True:
|
||||
return await self.async_step_user()
|
||||
else:
|
||||
return await self.async_step_debug()
|
||||
|
||||
async def async_step_debug(self, user_input=None):
|
||||
if user_input is None:
|
||||
user_input = {}
|
||||
else:
|
||||
self.data_client.refresh_interval = int(user_input['refresh_interval'])
|
||||
self.data_client.is_debug = user_input['is_debug']
|
||||
await self.data_client.save_data()
|
||||
return self.async_create_entry(
|
||||
title="国家电网",
|
||||
data={}
|
||||
)
|
||||
|
||||
data_schema = {
|
||||
vol.Required("refresh_interval", default=str(self.data_client.refresh_interval)) : selector({
|
||||
"select": {
|
||||
"options": [
|
||||
{"label":"每1小时", "value": "1"},
|
||||
{"label":"每2小时", "value": "2"},
|
||||
{"label":"每3小时", "value": "3"},
|
||||
{"label":"每4小时", "value": "4"},
|
||||
{"label":"每5小时", "value": "5"},
|
||||
{"label":"每6小时", "value": "6"},
|
||||
{"label":"每7小时", "value": "7"},
|
||||
{"label":"每8小时", "value": "8"},
|
||||
{"label":"每9小时", "value": "9"},
|
||||
{"label":"每10小时", "value": "10"},
|
||||
{"label":"每11小时", "value": "11"},
|
||||
{"label":"每12小时", "value": "12"}
|
||||
]
|
||||
}
|
||||
}),
|
||||
vol.Required("is_debug", default=self.data_client.is_debug): selector({
|
||||
"boolean": {}
|
||||
})
|
||||
}
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="debug",
|
||||
data_schema=vol.Schema(data_schema)
|
||||
)
|
||||
|
||||
async def async_step_user(self, user_input=None):
|
||||
options = {
|
||||
"scan_login": "网上国网 App 扫码登录",
|
||||
"code_login": "手机短信验证码登录",
|
||||
}
|
||||
|
||||
return self.async_show_menu(
|
||||
step_id="user",
|
||||
menu_options=options
|
||||
)
|
||||
|
||||
async def async_step_scan_login(self, user_input=None):
|
||||
result = await self.data_client.get_qr_code()
|
||||
if result['errcode'] != 0:
|
||||
return self.async_abort(
|
||||
reason="get_qr_code_error",
|
||||
description_placeholders={'errmsg': result["errmsg"]}
|
||||
)
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="check_qr_code",
|
||||
description_placeholders={
|
||||
"qr_image": '<img style="width: 200px;" src="data:image/png;base64,' + result["data"] + '"/>'
|
||||
}
|
||||
)
|
||||
|
||||
async def async_step_check_qr_code(self, user_input=None):
|
||||
result = await self.data_client.check_qr_code()
|
||||
if result['errcode'] != 0:
|
||||
return self.async_abort(
|
||||
reason="check_qr_code_error",
|
||||
description_placeholders={'errmsg': result["errmsg"]}
|
||||
)
|
||||
await self.data_client.refresh_data(force_refresh=True)
|
||||
await self.data_client.coordinator.async_request_refresh()
|
||||
return self.async_create_entry(
|
||||
title="国家电网",
|
||||
data={}
|
||||
)
|
||||
|
||||
async def async_step_code_login(self, user_input=None):
|
||||
errors = {}
|
||||
if user_input is None:
|
||||
user_input = {}
|
||||
else:
|
||||
phone = user_input['phone']
|
||||
result = await self.data_client.send_phone_code(phone)
|
||||
if result['errcode'] == 0:
|
||||
return await self.async_step_verfiy_code()
|
||||
else:
|
||||
errors["phone"] = result["errmsg"]
|
||||
|
||||
data_schema = {
|
||||
vol.Required("phone") : selector({
|
||||
"text": {
|
||||
"type": "number"
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="code_login",
|
||||
data_schema=vol.Schema(data_schema),
|
||||
errors=errors
|
||||
)
|
||||
|
||||
async def async_step_verfiy_code(self, user_input=None):
|
||||
errors = {}
|
||||
if user_input is None:
|
||||
user_input = {}
|
||||
else:
|
||||
code = user_input['code']
|
||||
result = await self.data_client.verfiy_phone_code(code)
|
||||
if result['errcode'] == 0:
|
||||
await self.data_client.refresh_data(force_refresh=True)
|
||||
await self.data_client.coordinator.async_request_refresh()
|
||||
return self.async_create_entry(
|
||||
title="国家电网",
|
||||
data={}
|
||||
)
|
||||
|
||||
errors["code"] = result["errmsg"]
|
||||
|
||||
data_schema = {
|
||||
vol.Required("code") : selector({
|
||||
"text": {
|
||||
"type": "number"
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="verfiy_code",
|
||||
data_schema=vol.Schema(data_schema),
|
||||
errors=errors
|
||||
)
|
||||
@@ -0,0 +1,4 @@
|
||||
DOMAIN = "state_grid"
|
||||
PACKAGE_NAME = "custom_components.state_grid"
|
||||
VERSION = "0.1.9"
|
||||
VERSION_STORAGE = 1
|
||||
@@ -0,0 +1,28 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import timedelta
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
||||
|
||||
from .data_client import StateGridDataClient
|
||||
|
||||
from .const import DOMAIN
|
||||
from .utils.logger import LOGGER
|
||||
|
||||
class StateGridCoordinator(DataUpdateCoordinator):
|
||||
|
||||
def __init__(self, hass: HomeAssistant) -> None:
|
||||
super().__init__(
|
||||
hass,
|
||||
LOGGER,
|
||||
name=DOMAIN,
|
||||
update_interval=timedelta(seconds=300)
|
||||
)
|
||||
self.first_setup = True
|
||||
self.data_client: StateGridDataClient = hass.data[DOMAIN]
|
||||
|
||||
async def _async_update_data(self):
|
||||
await self.data_client.refresh_data(setup=self.first_setup)
|
||||
self.first_setup = False
|
||||
return self.data_client.get_door_account()
|
||||
@@ -0,0 +1,396 @@
|
||||
_Aw='get_door_ladder_api'
|
||||
_Av='queryDate'
|
||||
_Au='queryYear'
|
||||
_At='access_token'
|
||||
_As='businessType'
|
||||
_Ar='qrCodeSerial'
|
||||
_Aq='redirect_url'
|
||||
_Ap='_access_token'
|
||||
_Ao='dataVersion'
|
||||
_An='refresh_interval'
|
||||
_Am='doorAccountDict'
|
||||
_Al='refreshToken'
|
||||
_Ak='accessToken'
|
||||
_Aj='/osg-web0004/member/c24/f01'
|
||||
_Ai='BCP_00026'
|
||||
_Ah='serviceCode_smt'
|
||||
_Ag='WEBA10070900'
|
||||
_Af='serviceType'
|
||||
_Ae='jM_custType'
|
||||
_Ad='jM_busiTypeCode'
|
||||
_Ac='doorNumberManeger'
|
||||
_Ab='month_t_ele_num'
|
||||
_Aa='month_n_ele_num'
|
||||
_AZ='month_v_ele_num'
|
||||
_AY='month_p_ele_num'
|
||||
_AX='month_ele_num'
|
||||
_AW='constType'
|
||||
_AV='provinceCode'
|
||||
_AU='elecTypeCode'
|
||||
_AT='powerUserList'
|
||||
_AS='publicKey'
|
||||
_AR='WEBA10070800'
|
||||
_AQ='timeDay'
|
||||
_AP='WEBA10070700'
|
||||
_AO='channelNo'
|
||||
_AN='month'
|
||||
_AM='yearTotalCost'
|
||||
_AL='consType'
|
||||
_AK='provinceId'
|
||||
_AJ='proCode'
|
||||
_AI='userAccountId'
|
||||
_AH='0000'
|
||||
_AG='refresh_token'
|
||||
_AF='skey'
|
||||
_AE='userInfo'
|
||||
_AD='0101046'
|
||||
_AC='billYear'
|
||||
_AB='loginAccount'
|
||||
_AA='keyCode'
|
||||
_A9='querytypeCode'
|
||||
_A8='01010049'
|
||||
_A7='account'
|
||||
_A6='daily_bill_list'
|
||||
_A5='userName'
|
||||
_A4='acctId'
|
||||
_A3='resultCode'
|
||||
_A2='quInfo'
|
||||
_A1='BCP_000026'
|
||||
_A0='app'
|
||||
_z='WEBALIPAY_01'
|
||||
_y='order'
|
||||
_x='state_grid'
|
||||
_w='latestBillMonth'
|
||||
_v='account_balance'
|
||||
_u='list'
|
||||
_t='authFlag'
|
||||
_s='09'
|
||||
_r='0101183'
|
||||
_q='consNo'
|
||||
_p='token'
|
||||
_o=True
|
||||
_n='0101154'
|
||||
_m='getday'
|
||||
_l='monthBillList'
|
||||
_k='bizrt'
|
||||
_j='timestamp'
|
||||
_i=False
|
||||
_h='month_ele'
|
||||
_g='consNo_dst'
|
||||
_f='errmsg'
|
||||
_e='clearCache'
|
||||
_d='promotCode'
|
||||
_c='01'
|
||||
_b='SGAPP'
|
||||
_a='devciceId'
|
||||
_Z='devciceIp'
|
||||
_Y='orgNo'
|
||||
_X='tenant'
|
||||
_W='member'
|
||||
_V='stepelect'
|
||||
_U='proNo'
|
||||
_T='promotType'
|
||||
_S='target'
|
||||
_R='userId'
|
||||
_Q='srvrt'
|
||||
_P='subBusiTypeCode'
|
||||
_O='serCat'
|
||||
_N='serialNo'
|
||||
_M='0902'
|
||||
_L='srvCode'
|
||||
_K='uscInfo'
|
||||
_J='busiTypeCode'
|
||||
_I='channelCode'
|
||||
_H='code'
|
||||
_G='1'
|
||||
_F=None
|
||||
_E='source'
|
||||
_D='funcCode'
|
||||
_C='serviceCode'
|
||||
_B='errcode'
|
||||
_A='data'
|
||||
import json,time,aiohttp,urllib.parse,datetime
|
||||
from.const import VERSION
|
||||
from.utils.logger import LOGGER
|
||||
from.utils.store import async_save_to_store
|
||||
from.utils.crypt import a,b,c,d,e
|
||||
configuration={_K:{_W:_M,_Z:'',_a:'',_X:_x},_E:_b,_S:'32101',_I:_M,_AO:_M,'toPublish':_c,'siteId':'2012000000033700',_L:'',_N:'',_D:'',_C:{_y:_n,'uploadPic':'0101296','pauseSCode':'0101250','pauseTCode':'0101251','listconsumers':'0101093','messageList':'0101343','submit':'0101003','sbcMsg':'0101210','powercut':'0104514','BkAuth01':'f15','BkAuth02':'f18','BkAuth03':'f02','BkAuth04':'f17','BkAuth05':'f05','BkAuth06':'f16','BkAuth07':'f01','BkAuth08':'f03'},'electricityArchives':{'servicecode':'0104505',_E:_M},'subscriptionList':{_L:'APP_SGPMS_05_030',_N:'22',_I:_M,_D:'22',_S:'-1'},'userInformation':{_C:'01008183',_E:_b},'userInform':{_C:_r,_E:_b},'elesum':{_I:_M,_D:_z,_d:_G,_T:_G,_C:'0101143',_E:_A0},_A7:{_I:_M,_D:'WEBA1007200'},_Ac:{_E:_M,_S:'-1',_I:_s,_AO:_s,_C:_A8,_D:'WEBA40050000',_K:{_W:_M,_Z:'',_a:'',_X:_x}},'doorAuth':{_E:_b,_C:'f04'},'xinZ':{_O:'101',_Ad:'101','fJ_busiTypeCode':'102',_Ae:'03','fJ_custType':'02',_Af:_c,_P:'',_D:_AP,_y:_n,_E:_b,_A9:_G},'onedo':{_C:_AD,_E:_b,_D:_AP,'queryType':'03'},'xinHuTongDian':{_O:'110',_J:'211',_P:'21102',_D:'WEBA10071200',_I:_M,_E:_s,_C:_r},'company':{_O:'104',_D:_AP,_Af:'02',_A9:_G,_t:_G,_E:_b,_y:_n},'charge':{_I:_s,_D:'WEBA10071300',_AO:'0901',_O:'102',_Ae:_c,_Ad:'102'},'other':{_I:_s,_D:'WEBA10079700',_O:'129',_J:'999',_P:'21501',_C:_A1,_L:'',_N:''},'vatchange':{'submit':'0101003',_J:'320',_P:'',_O:'115',_D:'WEBA10074000',_t:_G},'bill':{_e:_G,_D:_z,_T:_G,_C:_A1},_V:{_I:_M,_D:_z,_T:_G,_e:_s,_C:_A1,_E:_A0},_m:{_I:_M,_e:'11',_D:_z,_d:_G,_T:_G,_C:_A1,_E:_A0},'mouthOut':{_I:_M,_e:'11',_D:_z,_d:_G,_T:_G,_C:_A1,_E:_A0},'meter':{_O:'114',_J:'304',_D:'WEBA10071000',_P:'',_C:_AD,_N:''},'complaint':{_J:'005','srvMode':_M,'anonymousFlag':'0','replyMode':_c,'retvisitFlag':_c},'report':{_J:'006'},'tradewinds':{_J:'019'},'somesay':{_J:'091'},'faultrepair':{_D:_Ag,_C:_r,_O:'111',_J:'001',_P:'21505'},'electronicInvoice':{_O:'105',_J:'0'},'rename':{_C:_AD,_D:'WEBA10076100',_J:'210',_O:'109',_t:_G,'gh_busiTypeCode':'211','gh_subusi':'21101',_N:'',_L:''},'pause':{_P:'',_C:_A8,_D:'WEBA10073600',_O:'107',_J:'203','jr_busi':'201',_N:'',_L:''},'capacityRecovery':{_C:_A8,_E:_b,_L:'',_N:'',_D:'WEBA10073700','busiTypeCode_stop':'204','busiTypeCode_less':'202',_J:'202',_P:'',_O:'108',_AQ:'5',_t:_G},'electricityPriceChange':{_C:_r,_J:'215',_P:'21502',_O:'113',_t:_G,_AQ:'15',_D:'WEBA10073900WEB',_L:'',_N:''},'electricityPriceStrategyChange':{_C:'01008183',_J:'215',_P:'21506',_O:'160',_D:'WEBV00000517WEB',_L:'',_N:''},'eemandValueAdjustment':{_C:_r,_L:'',_N:'',_O:'112',_D:'WEBA10073800',_J:'215',_P:'21504',_t:_G,_AQ:'5','getMonthServiceCode':_AD},'businessProgress':{_C:_r,_L:_c,_D:'WEB01'},'increase':{_E:_b,_N:'',_L:'',_Ah:_A8,_C:_n,_y:_n,_D:_AR,_A9:_G,_O:'106',_J:'111',_P:''},'fjincrea':{_O:'105',_J:'110',_P:'',_E:_b,_D:_AR,_N:'',_L:'',_Ah:_A8,_C:_n,_y:_n,_A9:_G},'persIncrea':{_O:'105',_J:'109',_y:_n,_P:'',_E:_b,_D:_AR,_A9:_G},'fgdChange':{_C:_r,_L:_c,_I:_s,_D:_Ag,_J:'215',_P:'21505',_O:'111',_t:_G},'createOrder':{_I:_M,_D:_z,_L:'BCP_000001','chargeMode':'02','conType':_c,'bizTypeId':'BT_ELEC'},'largePopulation':{_J:'383',_D:'WEBA10076800',_P:'',_L:'',_T:'',_d:'',_I:'0901',_O:'383',_C:'',_N:''},'biaoJiCode':{_C:'0104507',_E:'1704',_I:'1704'},'twoGuar':{_J:'402',_P:'40201',_D:'web_twoGuar'},'electTrend':{_C:_Ai,_I:_M},'emergency':{_C:_Ai,_D:'A10000000',_I:_M},'infoPublic':{_C:'2545454',_E:_A0}}
|
||||
appKey='3def6c365d284881bf1a9b2b502ee68c'
|
||||
appSecret='ab7357dae64944a197ace37398897f64'
|
||||
baseApi='https://www.95598.cn/api'
|
||||
get_request_key_api='/oauth2/outer/c02/f02'
|
||||
get_qr_code_api='/osg-open-uc0001/member/c8/f24'
|
||||
get_qr_code_status_api='/osg-web0004/open/c50/f02'
|
||||
get_qr_code_token_api='/osg-uc0013/member/c4/f04'
|
||||
send_code_api='/osg-open-uc0001/member/c8/f04'
|
||||
code_login_api='/osg-uc0013/member/c4/f02'
|
||||
getCertificationApi='/osg-open-uc0001/member/c8/f11'
|
||||
get_request_authorize_api='/oauth2/oauth/authorize'
|
||||
get_web_token_api='/oauth2/outer/getWebToken'
|
||||
refresh_web_token_api='/oauth2/outer/refresh_web_token'
|
||||
get_door_number_api='/osg-open-uc0001/member/c9/f02'
|
||||
get_door_balance_api='/osg-open-bc0001/member/c05/f01'
|
||||
get_door_bill_api='/osg-open-bc0001/member/c01/f02'
|
||||
get_door_ladder_api='/osg-open-bc0001/member/c04/f03'
|
||||
getJiaoFeiRecordApi=_Aj
|
||||
get_door_daily_bill_api=_Aj
|
||||
sessionIdControlApiList=[get_qr_code_api,get_qr_code_status_api,get_qr_code_token_api,send_code_api,code_login_api]
|
||||
keyCodeControlApiList=[get_qr_code_status_api,get_qr_code_token_api,send_code_api,code_login_api,getCertificationApi,get_request_authorize_api,get_web_token_api,refresh_web_token_api,get_door_number_api,get_door_balance_api,get_door_bill_api,get_door_ladder_api,getJiaoFeiRecordApi,get_door_daily_bill_api]
|
||||
authControlApiList=[get_door_number_api,get_door_balance_api,get_door_bill_api,get_door_ladder_api,getJiaoFeiRecordApi,get_door_daily_bill_api]
|
||||
tControlApiList=[getCertificationApi,get_door_balance_api,get_door_bill_api,get_door_ladder_api,getJiaoFeiRecordApi,get_door_daily_bill_api]
|
||||
def json_dumps(data):return json.dumps(data,separators=(',',':'),ensure_ascii=_i)
|
||||
def normal_round(num,ndigits=0):
|
||||
A=ndigits
|
||||
if A==0:return int(num+.5)
|
||||
else:B=10**A;return int(num*B+.5)/B
|
||||
def catchFloat(data,key):
|
||||
if key in data:
|
||||
try:return normal_round(float(data[key]),2)
|
||||
except:return 0
|
||||
else:return 0
|
||||
class StateGridDataClient:
|
||||
hass=_F;coordinator=_F;session=_F;dataVersion=_F;keyCode=_F;publicKey=_F;need_login=_i;retry_times=0;phone=_F;codeKey=_F;serialNo=_F;qrCodeSerial=_F;userInfo=_F;accountInfo=_F;powerUserList=_F;doorAccountDict={};cookie=[];timestamp=int(time.time()*1000);accessToken=_F;refreshToken=_F;token=_F;expirationDate=_F;refresh_interval=6;is_debug=_i
|
||||
def __init__(A,hass,config=_F):
|
||||
B=config;A.hass=hass;C=aiohttp.TCPConnector(ssl=_i);D=aiohttp.CookieJar(quote_cookie=_o);A.session=aiohttp.ClientSession(cookie_jar=D,connector=C)
|
||||
if B is not _F:
|
||||
try:A.keyCode=B[_AA];A.publicKey=B[_AS];A.accessToken=B[_Ak];A.refreshToken=B[_Al];A.token=B[_p];A.userInfo=B[_AE];A.powerUserList=B[_AT];A.doorAccountDict=B[_Am];A.refresh_interval=B[_An];A.is_debug=B['is_debug'];A.dataVersion=B[_Ao]
|
||||
except Exception as E:LOGGER.error(E)
|
||||
async def save_data(B):A={};A[_AA]=B.keyCode;A[_AS]=B.publicKey;A[_Ak]=B.accessToken;A[_Al]=B.refreshToken;A[_p]=B.token;A[_AE]=B.userInfo;A[_AT]=B.powerUserList;A[_Am]=B.doorAccountDict;A[_An]=B.refresh_interval;A['is_debug']=B.is_debug;A[_Ao]=VERSION;await async_save_to_store(B.hass,'state_grid.config',A)
|
||||
def encrypt_post_data(A,data):B={_Ap:A.accessToken[len(A.accessToken)//2:]if A.accessToken else'','_t':A.token[len(A.token)//2:]if A.token else'','_data':data,_j:A.timestamp};return A.encrypt_wapper_data(B)
|
||||
def encrypt_wapper_data(A,data):B=a(json_dumps(data),A.keyCode);return{_A:B+c(B+str(A.timestamp)),_AF:d(A.keyCode,A.publicKey),_j:str(A.timestamp)}
|
||||
def handle_request_result_message(E,api,result):
|
||||
D='message';C='resultMessage';A=result;LOGGER.error(api+'-'+json_dumps(A));B=_F
|
||||
if _A in A and _Q in A[_A]and C in A[_A][_Q]:B=A[_A][_Q][C]
|
||||
elif _Q in A and C in A[_Q]:B=A[_Q][C]
|
||||
elif D in A:B=A[D]
|
||||
else:B=json_dumps(A)
|
||||
return B
|
||||
async def __fetch_safe(C,api,data):
|
||||
B=await C.__fetch(api,data)
|
||||
if _H not in B:return B
|
||||
A=B[_H]
|
||||
if 10015==A or 10108==A or 10009==A or 10207==A or 10005==A or 10010==A or 30010==A or 10002==A:
|
||||
await C.__refresh_token()
|
||||
if C.need_login is _o:return B
|
||||
else:return await C.__fetch(api,data)
|
||||
else:return B
|
||||
async def __fetch(B,api,data,header=_F):
|
||||
T='encryptData';S='464606a4-184c-4beb-b442-2ab7761d0796';R='key_code';Q='state';P='sign';O='grant_type';N='application/json;charset=UTF-8';M='Content-Type';L=header;K='client_secret';I='client_id';E=api;B.timestamp=int(time.time()*1000);D=B.timestamp
|
||||
if B.keyCode is _F:B.keyCode=e(32,16,2)
|
||||
F=B.keyCode;G={'User-Agent':'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Mobile Safari/537.36','Accept':N,M:N,'version':'1.0',_E:'0901',_j:str(D),'wsgwType':'web','appKey':appKey};A=data
|
||||
if E==get_request_key_api:A={I:appKey,K:appSecret};H=a(json_dumps(A),F);A={_A:H+c(H+str(D)),_AF:d(F,'042BC7AD510BF9793B7744C8854C56A8C95DD1027EE619247A332EC6ED5B279F435A23D62441FE861F4B0C963347ECD5792F380B64CA084BE8BE41151F8B8D19C8'),I:appKey,_j:str(D)}
|
||||
elif E==get_qr_code_api:A={_Ap:'','_t':'','_data':A,_j:D}
|
||||
elif E==get_request_authorize_api:
|
||||
A={I:appKey,'response_type':_H,_Aq:'/test',_j:D,'rsi':B.token};A=urllib.parse.urlencode(A);G[M]='application/x-www-form-urlencoded; charset=UTF-8';G[_AA]=F
|
||||
async with B.session.post(baseApi+E,data=A,headers=G)as J:B.session.cookie_jar.update_cookies(J.cookies);C=await J.json();C=b(C[_A],B.token);C=json.loads(C);return C
|
||||
elif E==get_web_token_api:A={O:'authorization_code',P:c(appKey+str(D)),K:appSecret,Q:S,R:F,I:appKey,_j:D,_H:A[_H]};H=a(json_dumps(A),F);A={_A:H+c(H+str(D)),_AF:d(F,B.publicKey),_j:str(D)}
|
||||
elif E==refresh_web_token_api:A={O:_AG,P:c(appKey+str(D)),K:appSecret,Q:S,R:F,I:appKey,_j:D,_AG:B.refreshToken};H=a(json_dumps(A),F);A={_A:H+c(H+str(D)),_AF:d(F,B.publicKey),_j:str(D)};E=get_web_token_api
|
||||
else:A=B.encrypt_post_data(A)
|
||||
if L is not _F:G.update(L)
|
||||
if E in sessionIdControlApiList:G['sessionId']='web'+str(D)
|
||||
if E in keyCodeControlApiList:G[_AA]=F
|
||||
if E in authControlApiList:G['Authorization']='Bearer '+B.accessToken[:len(B.accessToken)//2]
|
||||
if E in tControlApiList:G['t']=B.token[:len(B.token)//2]
|
||||
async with B.session.post(baseApi+E,json=A,headers=G)as J:
|
||||
C=await J.text()
|
||||
if C.startswith('{'):
|
||||
C=json.loads(C)
|
||||
if T in C:C=b(C[T],F);C=json.loads(C)
|
||||
return C
|
||||
async def __get_request_key(A):
|
||||
A.keyCode=_F;B=await A.__fetch(get_request_key_api,{});C=A.handle_request_result_message('get_request_key_api',B)
|
||||
if B[_H]==_G:A.keyCode=B[_A][_AA];A.publicKey=B[_A][_AS];return{_B:0}
|
||||
return{_B:1,_f:C}
|
||||
async def __get_qr_code(B):
|
||||
C={_K:{_Z:'',_X:_x,_W:_M,_a:''},_A2:{'optType':_c,_N:e(28,10,1)}};A=await B.__fetch(get_qr_code_api,C);D=B.handle_request_result_message('get_qr_code_api',A)
|
||||
if A[_H]==1:
|
||||
if A[_A]and A[_A][_Q]and A[_A][_Q][_A3]==_AH:B.qrCodeSerial=A[_A][_k][_Ar];E=A[_A][_k]['qrCode'];return{_B:0,_A:E}
|
||||
return{_B:1,_f:D}
|
||||
async def __get_qr_code_status(B):
|
||||
C={_k:{_Ar:B.qrCodeSerial}};D={_p:'98'+e(10,10,1)};A=await B.__fetch(get_qr_code_status_api,C,D);E=B.handle_request_result_message('get_qr_code_status_api',A)
|
||||
if _H in A and A[_H]==1:
|
||||
if _A in A and A[_A]!='null':B.token=A[_A];return{_B:0}
|
||||
else:return{_B:1,_f:'未使用网上国网 App 扫码或确认登录'}
|
||||
return{_B:1,_f:E}
|
||||
async def __get_qr_code_token(B):
|
||||
C={_K:{_X:_x,_W:_M,'isEncrypt':_o},_p:B.token};A=await B.__fetch(get_qr_code_token_api,C);D=B.handle_request_result_message('get_qr_code_token_api',A)
|
||||
if _Q in A and _A3 in A[_Q]and A[_Q][_A3]==_AH:B.userInfo=A[_k][_AE];return{_B:0}
|
||||
return{_B:1,_f:D}
|
||||
async def __send_code(B,phone):
|
||||
C=phone;B.phone=C;D={_K:{_Z:'',_X:_x,_W:_M,_a:''},_A2:{'sendType':'0',_A7:C,_As:'login','accountType':''},'Channels':'web'};A=await B.__fetch(send_code_api,D);E=B.handle_request_result_message('send_code_api',A)
|
||||
if A[_H]==1:
|
||||
if A[_A]and A[_A][_Q]and A[_A][_Q][_A3]==_AH:B.codeKey=A[_A][_k]['codeKey'];return{_B:0}
|
||||
return{_B:1,_f:E}
|
||||
async def __verfiy_code(A,code):
|
||||
C={_K:{_Z:'',_X:_x,_W:_M,_a:''},_A2:{_A7:A.phone,_As:'login',_H:code,'optSys':'ios','pushId':'00000','codeKey':A.codeKey},'Channels':'web'};B=await A.__fetch(code_login_api,C);D=A.handle_request_result_message('code_login_api',B)
|
||||
if _Q in B and _A3 in B[_Q]and B[_Q][_A3]==_AH:A.token=B[_k][_p];A.userInfo=B[_k][_AE][0];return{_B:0}
|
||||
return{_B:1,_f:D}
|
||||
async def __get_request_authorize(B):
|
||||
A=await B.__fetch(get_request_authorize_api,{});E=B.handle_request_result_message('get_request_authorize_api',A)
|
||||
if _H in A and A[_H]==_G:C=A[_A][_Aq];D=C.rfind('code=');B.authorizeCode=C[D+5:D+5+32];return{_B:0}
|
||||
return{_B:1,_f:E}
|
||||
async def __get_web_token(A):
|
||||
C={_H:A.authorizeCode};B=await A.__fetch(get_web_token_api,C);D=A.handle_request_result_message('get_web_token_api',B)
|
||||
if _H in B and B[_H]==_G:A.accessToken=B[_A][_At];A.refreshToken=B[_A][_AG];return{_B:0}
|
||||
return{_B:1,_f:D}
|
||||
async def __refresh_web_token(B):
|
||||
A=await B.__fetch(refresh_web_token_api,{});C=B.handle_request_result_message('refresh_web_token_api',A)
|
||||
if _H in A and A[_H]==_G:B.accessToken=A[_A][_At];B.refreshToken=A[_A][_AG];return{_B:0}
|
||||
return{_B:1,_f:C}
|
||||
async def __get_door_number(A):
|
||||
B=configuration[_Ac];G={_C:B[_C],_E:B[_E],_S:B[_S],_K:{_W:B[_K][_W],_Z:B[_K][_Z],_a:B[_K][_a],_X:B[_K][_X]},_A2:{_R:A.userInfo[_R]},_p:A.token};C=await A.__fetch_safe(get_door_number_api,G);H=A.handle_request_result_message('get_door_number_api',C)
|
||||
if _H in C and C[_H]==1 and _A in C and _k in C[_A]:
|
||||
E={}
|
||||
if A.powerUserList is not _F:E={A[_g]:A for A in A.powerUserList}
|
||||
F=[]
|
||||
for D in C[_A][_k][_AT]:
|
||||
if D[_g]in E:F.append(E[D[_g]])
|
||||
elif _AU in D and D[_AU]!='05':F.append(D)
|
||||
A.powerUserList=F;return{_B:0}
|
||||
return{_B:1,_f:H}
|
||||
async def __get_door_balance(B,door_account):
|
||||
A=door_account;E={_A:{_L:'',_N:'',_I:configuration[_A7][_I],_D:configuration[_A7][_D],_A4:B.userInfo[_R],_A5:B.userInfo.get(_AB,B.userInfo.get('nickname',_F)),_T:_G,_d:_G,_AI:B.userInfo[_R],_u:[{'consNoSrc':A[_g],_AJ:A.get(_U,A.get(_AK,_F)),'sceneType':A.get('consSortCode',A.get(_AU,_F)),_q:A[_q],_Y:A[_Y]}]},_C:'0101143',_E:configuration[_E],_S:A.get(_U,A.get(_AK,_F))};C=await B.__fetch_safe(get_door_balance_api,E);B.handle_request_result_message('get_door_balance_api',C)
|
||||
if _H in C and C[_H]==1 and _A in C and _u in C[_A]:
|
||||
D=C[_A][_u]
|
||||
if len(D)!=0:A[_v]=D[0]
|
||||
async def __get_door_bill(C,door_account,monthDate):
|
||||
I='dataInfo';G='mothEleList';E=monthDate;A=door_account;J={_A:{_A4:C.userInfo[_R],_I:configuration[_I],_e:'11',_AL:A[_AW],_D:'ALIPAY_01',_Y:A[_Y],_AJ:A[_U],_d:_G,_T:_G,_N:'',_L:'',_A5:'',_AV:A[_U],_AI:C.userInfo[_R],_q:A[_q],_Au:E.year},_C:_A1,_E:_A0,_S:A[_U]};B=await C.__fetch_safe(get_door_bill_api,J);C.handle_request_result_message('get_door_bill_api',B)
|
||||
if _H in B and B[_H]==1 and _A in B:
|
||||
if I in B[_A]:A[_AM]=B[_A][I]
|
||||
if G in B[_A]:
|
||||
if _AC not in A or A[_AC]!=E.year:A[_l]=B[_A][G];A[_AC]=E.year
|
||||
else:
|
||||
F={A.month:A for A in A[_l]};H=B[_A][G]
|
||||
for D in H:
|
||||
if D.month in F and _h in F[D.month]:D[_h]=F[D.month][_h]
|
||||
A[_l]=H
|
||||
if len(A[_l])>0:A[_w]=A[_l][-1]
|
||||
async def __get_door_ladder(C,door_account,monthBill):
|
||||
E=monthBill;A=door_account;I=A[_g];F=datetime.datetime.strptime(E[_AN],'%Y%m');G=f"{F.year}-{F.month:02d}";H={_A:{_I:configuration[_V][_I],_D:configuration[_V][_D],_T:configuration[_V][_T],_e:configuration[_V][_e],_q:A[_g],_d:A[_U],_Y:A[_Y],_Av:G,_AV:A[_U],_AL:A[_AW],_AI:C.userInfo[_R],_N:'',_L:'',_A5:C.userInfo[_AB],_A4:C.userInfo[_R]},_C:configuration[_V][_C],_E:configuration[_V][_E],_S:A[_U]};B=await C.__fetch(get_door_ladder_api,H);J=C.handle_request_result_message(_Aw,B)
|
||||
if _H in B and B[_H]==1 and _A in B and _u in B[_A]:
|
||||
D=B[_A][_u]
|
||||
if len(D)!=0:D=D[0];E['ladder']=D
|
||||
async def __get_door_mouth_bill(I,door_account,monthBill):
|
||||
S=monthBill;O='quantity';N='priceName';K='amtGroupList';H='readList';E=door_account;C='amtList';B='pointList';T=datetime.datetime.strptime(S[_AN],'%Y%m');V=f"{T.year}-{T.month:02d}";W={_A:{_I:configuration[_V][_I],_D:configuration[_V][_D],_T:configuration[_V][_T],_e:configuration[_V][_e],_q:E[_g],_d:E[_U],_Y:E[_Y],_Av:V,_AV:E[_U],_AL:E[_AW],_AI:I.userInfo[_R],_N:'',_L:'',_A5:I.userInfo[_AB],_A4:I.userInfo[_R]},_C:configuration[_V][_C],_E:configuration[_V][_E],_S:E[_U]};F=await I.__fetch(get_door_ladder_api,W);X=I.handle_request_result_message(_Aw,F)
|
||||
if _H in F and F[_H]==1 and _A in F and _u in F[_A]:
|
||||
A=F[_A][_u][0];U=0;P=0;Q=0;L=0;R=0;M=[]
|
||||
if H in A and len(A[H])>0:M=A[H]
|
||||
elif B in A and len(A[B])>0 and H in A[B][0]and len(A[B][0][H])>0:M=A[B][0][H]
|
||||
if len(M)>0:U=catchFloat(M[0],'activeCount')
|
||||
J=[]
|
||||
if C in A and len(A[C])>0:J=A[C]
|
||||
elif B in A and len(A[B])>0 and C in A[B][0]and len(A[B][0][C])>0:J=A[B][0][C]
|
||||
elif B in A and len(A[B])>0 and K in A[B][0]and len(A[B][0][K])>0 and C in A[B][0][K][0]and len(A[B][0][K][0][C])>0:J=A[B][0][K][0][C]
|
||||
if len(J)>0:
|
||||
for D in J:
|
||||
if D[N]=='峰':P=catchFloat(D,O)
|
||||
elif D[N]=='谷':Q=catchFloat(D,O)
|
||||
elif D[N]=='平':L=catchFloat(D,O)
|
||||
elif D[N]=='尖':R=catchFloat(D,O)
|
||||
if L==0:L=P+Q+R
|
||||
G={};G[_AX]=normal_round(U,2);G[_AY]=normal_round(P,2);G[_AZ]=normal_round(Q,2);G[_Aa]=normal_round(L,2);G[_Ab]=normal_round(R,2);S[_h]=G
|
||||
async def __get_door_daily_bill(B,door_account,year,start_date,end_date):
|
||||
D='sevenEleList';A=door_account;E={'params1':{_C:configuration[_C],_E:configuration[_E],_S:configuration[_S],_K:{_W:configuration[_K][_W],_Z:configuration[_K][_Z],_a:configuration[_K][_a],_X:configuration[_K][_X]},_A2:{_R:B.userInfo[_R]},_p:B.token},'params3':{_A:{_A4:B.userInfo[_R],_q:A[_g],_AL:_c,'endTime':end_date,_Y:A[_Y],_Au:year,_AJ:A.get(_U,A.get(_AK,_F)),_N:'',_L:'','startTime':start_date,_A5:B.userInfo[_AB],_D:configuration[_m][_D],_I:configuration[_m][_I],_e:configuration[_m][_e],_d:configuration[_m][_d],_T:configuration[_m][_T]},_C:configuration[_m][_C],_E:configuration[_m][_E],_S:A.get(_U,A.get(_AK,_F))},'params4':'010103'};C=await B.__fetch_safe(get_door_daily_bill_api,E);F=B.handle_request_result_message('get_door_daily_bill_api',C)
|
||||
if _H in C and C[_H]==1 and _A in C and D in C[_A]:A[_A6]=C[_A][D]
|
||||
async def __get_door_pay_record(A,door_account):B=door_account;D=B[_g];C={'params1':{_C:configuration[_C],_E:configuration[_E],_S:configuration[_S],_K:{_W:configuration[_K][_W],_Z:configuration[_K][_Z],_a:configuration[_K][_a],_X:configuration[_K][_X]},_A2:{_R:A.userInfo[_R]},_p:A.token},'params3':{_A:{_A4:A.userInfo[_R],'bgnPayDate':'2023-04-24',_I:configuration[_I],_q:B[_g],'endPayDate':'2024-04-24',_D:'webALIPAY_01','number':100,_Y:B[_Y],'page':_G,_AJ:B[_U],_d:_G,_T:_G,_N:'',_L:'',_A5:A.userInfo[_AB]},_C:'0101051',_E:_c,_S:B[_U]},'params4':'010104'};E=await A.__fetch(getJiaoFeiRecordApi,C)
|
||||
async def get_qr_code(B):
|
||||
A=await B.__get_request_key()
|
||||
if _B in A and A[_B]!=0:return A
|
||||
return await B.__get_qr_code()
|
||||
async def check_qr_code(B):
|
||||
A=await B.__get_qr_code_status()
|
||||
if _B in A and A[_B]!=0:return A
|
||||
A=await B.__get_qr_code_token()
|
||||
if _B in A and A[_B]!=0:return A
|
||||
return await B.__get_token()
|
||||
async def send_phone_code(B,phone):
|
||||
A=await B.__get_request_key()
|
||||
if _B in A and A[_B]!=0:return A
|
||||
return await B.__send_code(phone)
|
||||
async def verfiy_phone_code(B,code):
|
||||
A=await B.__verfiy_code(code)
|
||||
if _B in A and A[_B]!=0:return A
|
||||
return await B.__get_token()
|
||||
async def __get_token(B):
|
||||
A=await B.__get_request_key()
|
||||
if _B in A and A[_B]!=0:return A
|
||||
A=await B.__get_request_authorize()
|
||||
if _B in A and A[_B]!=0:return A
|
||||
A=await B.__get_web_token()
|
||||
if _B in A and A[_B]!=0:return A
|
||||
A=await B.__get_door_number()
|
||||
if _B in A and A[_B]!=0:return A
|
||||
B.need_login=_i;await B.save_data();return{_B:0,_A:B.powerUserList}
|
||||
async def __refresh_token(A):
|
||||
B=await A.__get_request_key()
|
||||
if _B in B and B[_B]!=0:return
|
||||
B=await A.__refresh_web_token()
|
||||
if _B in B and B[_B]==0:A.need_login=_i;A.retry_times=0;await A.save_data()
|
||||
elif A.retry_times>=3:A.need_login=_o
|
||||
else:A.retry_times=A.retry_times+1
|
||||
async def refresh_data(B,setup=_i,force_refresh=_i):
|
||||
t='last_month_ele_cost';s='year_ele_cost';r='thisTPq';q='thisNPq';p='thisVPq';o='thisPPq';n='%Y%m%d';m='day';l='refresh_time';f=setup;e='last_month_ele_num';d='dayElePq';U='year_ele_num';G='balance'
|
||||
if f is _o:
|
||||
if B.dataVersion!=VERSION:B.powerUserList=_F
|
||||
g=await B.__get_door_number()
|
||||
if _B in g and g[_B]!=0:B.need_login=_o
|
||||
if B.need_login is _o:
|
||||
if B.powerUserList is not _F:
|
||||
for A in B.get_door_account_list():A[l]='Token刷新失败,请重新登录'
|
||||
LOGGER.error('国家电网 - Token刷新失败,请重新登录!');return
|
||||
u=f or force_refresh or int(time.time()*1000)-B.timestamp>B.refresh_interval*3600*1000
|
||||
if u is _i:return
|
||||
V=datetime.datetime.now();H=V-datetime.timedelta(days=1);v=f"{H.year}-{H.month:02d}-{H.day:02d}";W=H-datetime.timedelta(days=40);w=f"{W.year}-{W.month:02d}-{W.day:02d}"
|
||||
for A in B.powerUserList:
|
||||
x=A[_g];B.doorAccountDict[x]=A;await B.__get_door_balance(A)
|
||||
if B.retry_times!=0:return
|
||||
if _v in A:
|
||||
y=catchFloat(A[_v],'accountBalance');z=catchFloat(A[_v],'estiAmt');X=catchFloat(A[_v],'prepayBal');Y=catchFloat(A[_v],'sumMoney')
|
||||
if y!=0:A[G]=Y
|
||||
elif X==0 or X==Y:A[G]=Y
|
||||
else:A[G]=X-z
|
||||
h=catchFloat(A[_v],'historyOwe')
|
||||
if h>0:A[G]=-h
|
||||
else:LOGGER.error('国家电网账户余额获取失败!')
|
||||
if G not in A:A[G]=0
|
||||
await B.__get_door_daily_bill(A,V.year,w,v)
|
||||
if _A6 not in A:LOGGER.error('国家电网无法获取日用电数据!');continue
|
||||
Z=0;L=_i
|
||||
for i in range(10):
|
||||
D=A[_A6][i]
|
||||
try:float(D[d]);L=_o;break
|
||||
except:Z=Z+1
|
||||
j=0;a=0;b=0;M=0;c=0
|
||||
if L:
|
||||
for i in range(Z):A[_A6].pop(0)
|
||||
D=A[_A6][0];E=datetime.datetime.strptime(D[m],n);A['daily_lasted_date']=f"{E.year}-{E.month:02d}-{E.day:02d}";j=catchFloat(D,d);a=catchFloat(D,o);b=catchFloat(D,p);M=catchFloat(D,q);c=catchFloat(D,r)
|
||||
if M==0:M=a+b+c
|
||||
A['daily_ele_num']=normal_round(j,2);A['daily_p_ele_num']=normal_round(a,2);A['daily_v_ele_num']=normal_round(b,2);A['daily_n_ele_num']=normal_round(M,2);A['daily_t_ele_num']=normal_round(c,2);N=0;I=0;J=0;F=0;K=0
|
||||
if L:
|
||||
for C in A[_A6]:
|
||||
A0=datetime.datetime.strptime(C[m],n)
|
||||
if A0.month!=E.month:break
|
||||
N+=catchFloat(C,d);I+=catchFloat(C,o);J+=catchFloat(C,p);F+=catchFloat(C,q);K+=catchFloat(C,r)
|
||||
if F==0:F=I+J+K
|
||||
A[_AX]=normal_round(N,2);A[_AY]=normal_round(I,2);A[_AZ]=normal_round(J,2);A[_Aa]=normal_round(F,2);A[_Ab]=normal_round(K,2)
|
||||
if L:
|
||||
O=E-datetime.timedelta(days=E.day);A1=f"{O.year}{O.month:02d}"
|
||||
if _AC not in A or A[_AC]!=O.year or _w not in A or A[_w][_AN]!=A1:await B.__get_door_bill(A,O)
|
||||
if _l in A:
|
||||
for C in A[_l]:
|
||||
if _h not in C:await B.__get_door_mouth_bill(A,C)
|
||||
if _AM in A:A[U]=catchFloat(A[_AM],'totalEleNum');A[s]=catchFloat(A[_AM],'totalEleCost')
|
||||
if U not in A:A[U]=0;A[s]=0
|
||||
if _w in A:A[e]=catchFloat(A[_w],'monthEleNum');A[t]=catchFloat(A[_w],'monthEleCost');k=datetime.datetime.strptime(A[_w][_AN],'%Y%m')
|
||||
if e not in A:A[e]=0;A[t]=0;k=H
|
||||
P=0;Q=0;R=0;S=0;T=0
|
||||
if k.month==12:P=N;Q=I;R=J;S=F;T=K
|
||||
else:
|
||||
if _l in A:
|
||||
for C in A[_l]:
|
||||
if _h in C:P+=C[_h][_AX];Q+=C[_h][_AY];R+=C[_h][_AZ];S+=C[_h][_Aa];T+=C[_h][_Ab]
|
||||
P+=N;Q+=I;R+=J;S+=F;T+=K
|
||||
A[U]=normal_round(P,2);A['year_p_ele_num']=normal_round(Q,2);A['year_v_ele_num']=normal_round(R,2);A['year_n_ele_num']=normal_round(S,2);A['year_t_ele_num']=normal_round(T,2);A[l]=datetime.datetime.strftime(V,'%Y-%m-%d %H:%M:%S')
|
||||
await B.save_data()
|
||||
def get_door_account_list(A):return list(A.doorAccountDict.values())
|
||||
def get_door_account(A):return A.doorAccountDict
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"domain": "state_grid",
|
||||
"name": "国家电网",
|
||||
"codeowners": ["@hassbox"],
|
||||
"config_flow": true,
|
||||
"documentation": "https://hassbox.cn/",
|
||||
"iot_class": "cloud_polling",
|
||||
"version": "0.1.9"
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
from homeassistant.components.sensor import (
|
||||
DOMAIN as SENSOR_DOMAIN,
|
||||
SensorDeviceClass,
|
||||
SensorEntity,
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import UnitOfEnergy
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
import datetime
|
||||
|
||||
from .const import DOMAIN, VERSION
|
||||
from .data_client import StateGridDataClient
|
||||
from .coordinator import StateGridCoordinator
|
||||
|
||||
UNIT_YUAN = "元"
|
||||
|
||||
ENTITY_ID_SENSOR_FORMAT = SENSOR_DOMAIN + ".state_grid_"
|
||||
|
||||
SENSOR_TYPES = [
|
||||
{
|
||||
"key":"balance",
|
||||
"name": "账户余额",
|
||||
"native_unit_of_measurement": UNIT_YUAN,
|
||||
"device_class": SensorDeviceClass.MONETARY,
|
||||
"state_class": SensorStateClass.TOTAL
|
||||
},
|
||||
{
|
||||
"key": "year_ele_num",
|
||||
"name": "年度累计用电",
|
||||
"native_unit_of_measurement": UnitOfEnergy.KILO_WATT_HOUR,
|
||||
"device_class": SensorDeviceClass.ENERGY,
|
||||
"state_class": SensorStateClass.TOTAL
|
||||
},
|
||||
{
|
||||
"key": "year_p_ele_num",
|
||||
"name": "年度累计峰用电",
|
||||
"native_unit_of_measurement": UnitOfEnergy.KILO_WATT_HOUR,
|
||||
"device_class": SensorDeviceClass.ENERGY,
|
||||
"state_class": SensorStateClass.TOTAL
|
||||
},
|
||||
{
|
||||
"key": "year_v_ele_num",
|
||||
"name": "年度累计谷用电",
|
||||
"native_unit_of_measurement": UnitOfEnergy.KILO_WATT_HOUR,
|
||||
"device_class": SensorDeviceClass.ENERGY,
|
||||
"state_class": SensorStateClass.TOTAL
|
||||
},
|
||||
{
|
||||
"key": "year_n_ele_num",
|
||||
"name": "年度累计平用电",
|
||||
"native_unit_of_measurement": UnitOfEnergy.KILO_WATT_HOUR,
|
||||
"device_class": SensorDeviceClass.ENERGY,
|
||||
"state_class": SensorStateClass.TOTAL
|
||||
},
|
||||
{
|
||||
"key": "year_t_ele_num",
|
||||
"name": "年度累计尖用电",
|
||||
"native_unit_of_measurement": UnitOfEnergy.KILO_WATT_HOUR,
|
||||
"device_class": SensorDeviceClass.ENERGY,
|
||||
"state_class": SensorStateClass.TOTAL
|
||||
},
|
||||
{
|
||||
"key": "year_ele_cost",
|
||||
"name": "年度累计电费",
|
||||
"native_unit_of_measurement": UNIT_YUAN,
|
||||
"device_class": SensorDeviceClass.MONETARY,
|
||||
"state_class": SensorStateClass.TOTAL
|
||||
},
|
||||
{
|
||||
"key": "last_month_ele_num",
|
||||
"name": "上个月用电",
|
||||
"native_unit_of_measurement": UnitOfEnergy.KILO_WATT_HOUR,
|
||||
"device_class": SensorDeviceClass.ENERGY,
|
||||
"state_class": SensorStateClass.TOTAL
|
||||
},
|
||||
{
|
||||
"key": "last_month_ele_cost",
|
||||
"name": "上个月电费",
|
||||
"native_unit_of_measurement": UNIT_YUAN,
|
||||
"device_class": SensorDeviceClass.MONETARY,
|
||||
"state_class": SensorStateClass.TOTAL
|
||||
},
|
||||
{
|
||||
"key": "month_ele_num",
|
||||
"name": "当月累计用电",
|
||||
"native_unit_of_measurement": UnitOfEnergy.KILO_WATT_HOUR,
|
||||
"device_class": SensorDeviceClass.ENERGY,
|
||||
"state_class": SensorStateClass.TOTAL
|
||||
},
|
||||
{
|
||||
"key": "month_p_ele_num",
|
||||
"name": "当月累计峰用电",
|
||||
"native_unit_of_measurement": UnitOfEnergy.KILO_WATT_HOUR,
|
||||
"device_class": SensorDeviceClass.ENERGY,
|
||||
"state_class": SensorStateClass.TOTAL
|
||||
},
|
||||
{
|
||||
"key": "month_v_ele_num",
|
||||
"name": "当月累计谷用电",
|
||||
"native_unit_of_measurement": UnitOfEnergy.KILO_WATT_HOUR,
|
||||
"device_class": SensorDeviceClass.ENERGY,
|
||||
"state_class": SensorStateClass.TOTAL
|
||||
},
|
||||
{
|
||||
"key": "month_n_ele_num",
|
||||
"name": "当月累计平用电",
|
||||
"native_unit_of_measurement": UnitOfEnergy.KILO_WATT_HOUR,
|
||||
"device_class": SensorDeviceClass.ENERGY,
|
||||
"state_class": SensorStateClass.TOTAL
|
||||
},
|
||||
{
|
||||
"key": "month_t_ele_num",
|
||||
"name": "当月累计尖用电",
|
||||
"native_unit_of_measurement": UnitOfEnergy.KILO_WATT_HOUR,
|
||||
"device_class": SensorDeviceClass.ENERGY,
|
||||
"state_class": SensorStateClass.TOTAL
|
||||
},
|
||||
{
|
||||
"key": "daily_ele_num",
|
||||
"name": "日总用电",
|
||||
"native_unit_of_measurement": UnitOfEnergy.KILO_WATT_HOUR,
|
||||
"device_class": SensorDeviceClass.ENERGY,
|
||||
"state_class": SensorStateClass.TOTAL
|
||||
},
|
||||
{
|
||||
"key": "daily_p_ele_num",
|
||||
"name": "日峰用电",
|
||||
"native_unit_of_measurement": UnitOfEnergy.KILO_WATT_HOUR,
|
||||
"device_class": SensorDeviceClass.ENERGY,
|
||||
"state_class": SensorStateClass.TOTAL
|
||||
},
|
||||
{
|
||||
"key": "daily_v_ele_num",
|
||||
"name": "日谷用电",
|
||||
"native_unit_of_measurement": UnitOfEnergy.KILO_WATT_HOUR,
|
||||
"device_class": SensorDeviceClass.ENERGY,
|
||||
"state_class": SensorStateClass.TOTAL
|
||||
},
|
||||
{
|
||||
"key": "daily_n_ele_num",
|
||||
"name": "日平用电",
|
||||
"native_unit_of_measurement": UnitOfEnergy.KILO_WATT_HOUR,
|
||||
"device_class": SensorDeviceClass.ENERGY,
|
||||
"state_class": SensorStateClass.TOTAL
|
||||
},
|
||||
{
|
||||
"key": "daily_t_ele_num",
|
||||
"name": "日尖用电",
|
||||
"native_unit_of_measurement": UnitOfEnergy.KILO_WATT_HOUR,
|
||||
"device_class": SensorDeviceClass.ENERGY,
|
||||
"state_class": SensorStateClass.TOTAL
|
||||
},
|
||||
{
|
||||
"key": "daily_lasted_date",
|
||||
"name": "最新日用电日期"
|
||||
},
|
||||
{
|
||||
"key": "refresh_time",
|
||||
"name": "最近刷新时间"
|
||||
}
|
||||
]
|
||||
|
||||
SENSOR_TYPES_FOR_LADDER = [
|
||||
{
|
||||
"key": "ladder_level",
|
||||
"name": "当前阶梯"
|
||||
},
|
||||
]
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
||||
) -> None:
|
||||
data_client: StateGridDataClient = hass.data[DOMAIN]
|
||||
coordinator = StateGridCoordinator(hass)
|
||||
data_client.coordinator = coordinator
|
||||
await coordinator.async_config_entry_first_refresh()
|
||||
door_account_list = data_client.get_door_account_list()
|
||||
for door_account in door_account_list:
|
||||
async_add_entities(
|
||||
[StateGridSensor(door_account, sensor_type, entry.entry_id, coordinator) for sensor_type in SENSOR_TYPES]
|
||||
)
|
||||
# if door_account["ladder_flag"] == 1:
|
||||
# async_add_entities(
|
||||
# StateGridSensor(door_account, sensor_type, entry.entry_id)
|
||||
# for sensor_type in SENSOR_TYPES_FOR_LADDER
|
||||
# )
|
||||
|
||||
class StateGridSensor(CoordinatorEntity[StateGridCoordinator], SensorEntity):
|
||||
|
||||
_attr_has_entity_name = True
|
||||
|
||||
def __init__(
|
||||
self, door_account, sensor_type, entry_id: str, coordinator: StateGridCoordinator,
|
||||
) -> None:
|
||||
super().__init__(coordinator)
|
||||
self.door_account = door_account
|
||||
self.sensor_type = sensor_type
|
||||
self.entity_id = SENSOR_DOMAIN + ".state_grid" + "_" + door_account["consNo_dst"] + "_" + sensor_type["key"]
|
||||
self._attr_name = sensor_type["name"]
|
||||
self._attr_unique_id = entry_id + "-" + door_account["consNo_dst"] + "-" + sensor_type["key"]
|
||||
|
||||
|
||||
if "device_class" in sensor_type:
|
||||
self._attr_device_class = sensor_type["device_class"]
|
||||
|
||||
if "state_class" in sensor_type:
|
||||
self._attr_state_class = sensor_type["state_class"]
|
||||
|
||||
if "native_unit_of_measurement" in sensor_type:
|
||||
self._attr_native_unit_of_measurement = sensor_type["native_unit_of_measurement"]
|
||||
|
||||
self._attr_device_info = {
|
||||
"name": door_account["elecAddr_dst"],
|
||||
"identifiers": {(DOMAIN, door_account["consNo_dst"])},
|
||||
"sw_version": VERSION,
|
||||
"manufacturer": "HassBox",
|
||||
"model": "户号:" + door_account["consName_dst"] + " - " + door_account["consNo_dst"]
|
||||
}
|
||||
|
||||
@property
|
||||
def native_value(self):
|
||||
data = self.coordinator.data[self.door_account["consNo_dst"]]
|
||||
return data[self.sensor_type["key"]]
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
{
|
||||
"config": {
|
||||
"flow_title": "国家电网",
|
||||
"step": {
|
||||
"user": {
|
||||
"title": "国家电网",
|
||||
"description": "集成国家电网可以选择网上国网 App 扫码登录或短信验证码登录。如登录异常,可至 **HassBox全屋智能** 微信公众号联系客服。"
|
||||
},
|
||||
"code_login": {
|
||||
"title": "短信验证码登录",
|
||||
"description": "手机号需先在网上电网 App 注册并绑定户号",
|
||||
"data": {
|
||||
"phone": "请输入手机号"
|
||||
}
|
||||
},
|
||||
"verfiy_code": {
|
||||
"title": "短信验证码登录",
|
||||
"data": {
|
||||
"code": "请输入验证码"
|
||||
}
|
||||
},
|
||||
"check_qr_code": {
|
||||
"title": "网上国网 App 扫码登录",
|
||||
"description": "\n {qr_image} \n\n\n 请使用 网上国网 App 扫码登录,确认登录后再点击提交。"
|
||||
}
|
||||
},
|
||||
"abort": {
|
||||
"single_instance_allowed": "该集成已经配置过了,且只能配置一次。若要重新配置,请先删除旧集成。",
|
||||
"get_qr_code_error": "{errmsg}",
|
||||
"check_qr_code_error": "{errmsg}"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"step": {
|
||||
"debug": {
|
||||
"title": "国家电网",
|
||||
"description": "国家电网当日用电等数据一般隔天才会刷新,且各地区时间不一,可根据情况适当调整接口轮询间隔。",
|
||||
"data": {
|
||||
"refresh_interval": "轮询间隔",
|
||||
"is_debug": "调试模式"
|
||||
},
|
||||
"data_description": {
|
||||
"is_debug": "打开调试开关后,将会打印所有请求日志。可在[配置]-[系统]-[日志]中查看。\n如需反馈异常,可至 **HassBox全屋智能** 微信公众号联系客服。"
|
||||
}
|
||||
},
|
||||
"user": {
|
||||
"title": "重新登录国家电网",
|
||||
"description": "刷新 Token 异常,需重新登录。\n如需反馈异常,可至 **HassBox全屋智能** 微信公众号联系客服。"
|
||||
},
|
||||
"code_login": {
|
||||
"title": "短信验证码登录",
|
||||
"description": "手机号需先在网上电网 App 注册并绑定户号",
|
||||
"data": {
|
||||
"phone": "请输入手机号"
|
||||
}
|
||||
},
|
||||
"verfiy_code": {
|
||||
"title": "短信验证码登录",
|
||||
"data": {
|
||||
"code": "请输入验证码"
|
||||
}
|
||||
},
|
||||
"check_qr_code": {
|
||||
"title": "网上国网 App 扫码登录",
|
||||
"description": "\n {qr_image} \n\n\n 请使用 网上国网 App 扫码登录,确认登录后再点击提交。"
|
||||
}
|
||||
},
|
||||
"abort": {
|
||||
"single_instance_allowed": "该集成已经配置过了,且只能配置一次。若要重新配置,请先删除旧集成。",
|
||||
"get_qr_code_error": "{errmsg}",
|
||||
"check_qr_code_error": "{errmsg}"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
{
|
||||
"config": {
|
||||
"flow_title": "国家电网",
|
||||
"step": {
|
||||
"user": {
|
||||
"title": "国家电网",
|
||||
"description": "集成国家电网可以选择网上国网 App 扫码登录或短信验证码登录。如登录异常,可至 **HassBox全屋智能** 微信公众号联系客服。"
|
||||
},
|
||||
"code_login": {
|
||||
"title": "短信验证码登录",
|
||||
"description": "手机号需先在网上电网 App 注册并绑定户号",
|
||||
"data": {
|
||||
"phone": "请输入手机号"
|
||||
}
|
||||
},
|
||||
"verfiy_code": {
|
||||
"title": "短信验证码登录",
|
||||
"data": {
|
||||
"code": "请输入验证码"
|
||||
}
|
||||
},
|
||||
"check_qr_code": {
|
||||
"title": "网上国网 App 扫码登录",
|
||||
"description": "\n {qr_image} \n\n\n 请使用 网上国网 App 扫码登录,确认登录后再点击提交。"
|
||||
}
|
||||
},
|
||||
"abort": {
|
||||
"single_instance_allowed": "该集成已经配置过了,且只能配置一次。若要重新配置,请先删除旧集成。",
|
||||
"get_qr_code_error": "{errmsg}",
|
||||
"check_qr_code_error": "{errmsg}"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"step": {
|
||||
"debug": {
|
||||
"title": "国家电网",
|
||||
"description": "国家电网当日用电等数据一般隔天才会刷新,且各地区时间不一,可根据情况适当调整接口轮询间隔。",
|
||||
"data": {
|
||||
"refresh_interval": "轮询间隔",
|
||||
"is_debug": "调试模式"
|
||||
},
|
||||
"data_description": {
|
||||
"is_debug": "打开调试开关后,将会打印所有请求日志。可在[配置]-[系统]-[日志]中查看。\n如需反馈异常,可至 **HassBox全屋智能** 微信公众号联系客服。"
|
||||
}
|
||||
},
|
||||
"user": {
|
||||
"title": "重新登录国家电网",
|
||||
"description": "刷新 Token 异常,需重新登录。\n如需反馈异常,可至 **HassBox全屋智能** 微信公众号联系客服。"
|
||||
},
|
||||
"code_login": {
|
||||
"title": "短信验证码登录",
|
||||
"description": "手机号需先在网上电网 App 注册并绑定户号",
|
||||
"data": {
|
||||
"phone": "请输入手机号"
|
||||
}
|
||||
},
|
||||
"verfiy_code": {
|
||||
"title": "短信验证码登录",
|
||||
"data": {
|
||||
"code": "请输入验证码"
|
||||
}
|
||||
},
|
||||
"check_qr_code": {
|
||||
"title": "网上国网 App 扫码登录",
|
||||
"description": "\n {qr_image} \n\n\n 请使用 网上国网 App 扫码登录,确认登录后再点击提交。"
|
||||
}
|
||||
},
|
||||
"abort": {
|
||||
"single_instance_allowed": "该集成已经配置过了,且只能配置一次。若要重新配置,请先删除旧集成。",
|
||||
"get_qr_code_error": "{errmsg}",
|
||||
"check_qr_code_error": "{errmsg}"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
{
|
||||
"config": {
|
||||
"flow_title": "国家电网",
|
||||
"step": {
|
||||
"user": {
|
||||
"title": "国家电网",
|
||||
"description": "集成国家电网可以选择网上国网 App 扫码登录或短信验证码登录。如登录异常,可至 **HassBox全屋智能** 微信公众号联系客服。"
|
||||
},
|
||||
"code_login": {
|
||||
"title": "短信验证码登录",
|
||||
"description": "手机号需先在网上电网 App 注册并绑定户号",
|
||||
"data": {
|
||||
"phone": "请输入手机号"
|
||||
}
|
||||
},
|
||||
"verfiy_code": {
|
||||
"title": "短信验证码登录",
|
||||
"data": {
|
||||
"code": "请输入验证码"
|
||||
}
|
||||
},
|
||||
"check_qr_code": {
|
||||
"title": "网上国网 App 扫码登录",
|
||||
"description": "\n {qr_image} \n\n\n 请使用 网上国网 App 扫码登录,确认登录后再点击提交。"
|
||||
}
|
||||
},
|
||||
"abort": {
|
||||
"single_instance_allowed": "该集成已经配置过了,且只能配置一次。若要重新配置,请先删除旧集成。",
|
||||
"get_qr_code_error": "{errmsg}",
|
||||
"check_qr_code_error": "{errmsg}"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"step": {
|
||||
"debug": {
|
||||
"title": "国家电网",
|
||||
"description": "国家电网当日用电等数据一般隔天才会刷新,且各地区时间不一,可根据情况适当调整接口轮询间隔。",
|
||||
"data": {
|
||||
"refresh_interval": "轮询间隔",
|
||||
"is_debug": "调试模式"
|
||||
},
|
||||
"data_description": {
|
||||
"is_debug": "打开调试开关后,将会打印所有请求日志。可在[配置]-[系统]-[日志]中查看。\n如需反馈异常,可至 **HassBox全屋智能** 微信公众号联系客服。"
|
||||
}
|
||||
},
|
||||
"user": {
|
||||
"title": "重新登录国家电网",
|
||||
"description": "刷新 Token 异常,需重新登录。\n如需反馈异常,可至 **HassBox全屋智能** 微信公众号联系客服。"
|
||||
},
|
||||
"code_login": {
|
||||
"title": "短信验证码登录",
|
||||
"description": "手机号需先在网上电网 App 注册并绑定户号",
|
||||
"data": {
|
||||
"phone": "请输入手机号"
|
||||
}
|
||||
},
|
||||
"verfiy_code": {
|
||||
"title": "短信验证码登录",
|
||||
"data": {
|
||||
"code": "请输入验证码"
|
||||
}
|
||||
},
|
||||
"check_qr_code": {
|
||||
"title": "网上国网 App 扫码登录",
|
||||
"description": "\n {qr_image} \n\n\n 请使用 网上国网 App 扫码登录,确认登录后再点击提交。"
|
||||
}
|
||||
},
|
||||
"abort": {
|
||||
"single_instance_allowed": "该集成已经配置过了,且只能配置一次。若要重新配置,请先删除旧集成。",
|
||||
"get_qr_code_error": "{errmsg}",
|
||||
"check_qr_code_error": "{errmsg}"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,232 @@
|
||||
_G='Data length error!'
|
||||
_F='%08x'
|
||||
_E='utf8'
|
||||
_D='%%0%dx'
|
||||
_C=None
|
||||
_B='utf-8'
|
||||
_A='p'
|
||||
import random,urllib.parse,base64,binascii
|
||||
from math import ceil
|
||||
import copy
|
||||
def str_to_bytes(input_str):
|
||||
A=[]
|
||||
for B in input_str:
|
||||
C=urllib.parse.quote(B)
|
||||
if len(C)==1:A.append(ord(B))
|
||||
else:
|
||||
for D in C.split('%')[1:]:A.append(int('0x'+D,16))
|
||||
return A
|
||||
def bytes_to_hex(bytes_input):
|
||||
B=''
|
||||
for C in bytes_input:
|
||||
A=hex(C)[2:]
|
||||
if len(A)==1:A='0'+A
|
||||
B+=A
|
||||
return B
|
||||
def string_to_hex(s):return''if s==''else bytes_to_hex(str_to_bytes(s))
|
||||
def d(data,publicKey):A=data;A=string_to_hex(A).encode();B=BB(public_key=publicKey,mode=1);C=B.encrypt(A);return'04'+C.hex()
|
||||
def e(e=_C,c=_C,n=_C):
|
||||
D='0123456789';A=list(D)
|
||||
if n==1:A=list(D)
|
||||
else:A=list('0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ')
|
||||
B=[]
|
||||
if c is _C:c=len(A)
|
||||
if e:
|
||||
for E in range(e):B.append(A[int(random.random()*c)])
|
||||
else:
|
||||
for C in range(36):
|
||||
if C in[8,13,18,23]:B.append('-')
|
||||
elif C==14:B.append('4')
|
||||
else:
|
||||
e=int(random.random()*16)
|
||||
if C==19:B.append(A[3&e|8])
|
||||
else:B.append(A[e])
|
||||
return''.join(B)
|
||||
def a(data,key):B=data;A=key;B=B.encode(_B);A=A.encode(_B);C=AA(padding_mode=3);C.set_key(A,SM4_ENCRYPT);D=C.crypt_cbc(A[0:8]+A[-8:],B);E=base64.b64encode(D);return E.decode()
|
||||
def b(data,key):B=data;A=key;B=B.encode(_B);A=A.encode(_B);D=base64.b64decode(B);C=AA(padding_mode=3);C.set_key(A,SM4_DECRYPT);E=C.crypt_cbc(A[0:8]+A[-8:],D);return E.decode()
|
||||
def c(data):A=data;A=A.encode(_B);A=bytes_to_list(A);return m_hash(A)
|
||||
xor=lambda a,b:list(map(lambda x,y:x^y,a,b))
|
||||
rotl=lambda x,n:x<<n&4294967295|x>>32-n&4294967295
|
||||
get_uint32_be=lambda key_data:key_data[0]<<24|key_data[1]<<16|key_data[2]<<8|key_data[3]
|
||||
put_uint32_be=lambda n:[n>>24&255,n>>16&255,n>>8&255,n&255]
|
||||
pkcs7_padding=lambda data,block=16:data+[16-len(data)%block for A in range(16-len(data)%block)]
|
||||
zero_padding=lambda data,block=16:data+[0 for A in range(16-len(data)%block)]
|
||||
pkcs7_unpadding=lambda data:data[:-data[-1]]
|
||||
zero_unpadding=lambda data,i=1:data[:-i]if data[-i]==0 else i+1
|
||||
list_to_bytes=lambda data:b''.join([bytes((A,))for A in data])
|
||||
bytes_to_list=lambda data:[A for A in data]
|
||||
random_hex=lambda x:''.join([random.choice('0123456789abcdef')for A in range(x)])
|
||||
def pboc_padding(data,block=16):
|
||||
B=block;A=data;A=A.hex().upper();B=B*2
|
||||
if len(A)%B!=0:A=A+'80'
|
||||
while len(A)%B!=0:A=A+'00'
|
||||
return bytes_to_list(bytes.fromhex(A))
|
||||
def iso9797m2_padding(data,block=16):
|
||||
B=block;A=data;A=A.hex().upper();B=B*2;A=A+'80'
|
||||
while len(A)%B!=0:A=A+'00'
|
||||
return bytes_to_list(bytes.fromhex(A))
|
||||
def pboc_unpadding(data):
|
||||
A=data
|
||||
if len(A)<16:raise Exception(_G)
|
||||
if len(A)==16:0
|
||||
else:
|
||||
while A[-1:]!=[128]:A.pop()
|
||||
A.pop()
|
||||
return A
|
||||
def iso9797m2_unpadding(data):
|
||||
A=data
|
||||
if len(A)<=16:raise Exception(_G)
|
||||
while A[-1:]!=[128]:A.pop()
|
||||
A.pop();return A
|
||||
default_ecc_table={'n':'FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123',_A:'FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF','g':'32c4ae2c1f1981195f9904466a39c9948fe30bbff2660be1715a4589334c74c7bc3736a2f4f6779c59bdcee36b692153d0a9877cc62a474002df32e52139f0a0','a':'FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC','b':'28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93'}
|
||||
class BB:
|
||||
def __init__(A,public_key,ecc_table=default_ecc_table,mode=1,asn1=False):'\n mode: 0-C1C2C3, 1-C1C3C2 (default is 1)\n ';C=ecc_table;B=public_key;A.public_key=B[2:]if B.startswith('04')and len(B)==130 else B;A.para_len=len(C['n']);A.ecc_a3=(int(C['a'],base=16)+3)%int(C[_A],base=16);A.ecc_table=C;assert mode in(0,1),'mode must be one of (0, 1)';A.mode=mode;A.asn1=asn1
|
||||
def _kg(B,k,Point):
|
||||
C=Point;C='%s%s'%(C,'1');E='8'
|
||||
for G in range(B.para_len-1):E+='0'
|
||||
F=int(E,16);A=C;D=False
|
||||
for H in range(B.para_len*4):
|
||||
if D:A=B._double_point(A)
|
||||
if k&F!=0:
|
||||
if D:A=B._add_point(A,C)
|
||||
else:D=True;A=C
|
||||
k=k<<1
|
||||
return B._convert_jacb_to_nor(A)
|
||||
def _double_point(A,Point):
|
||||
G=Point;N=len(G);J=2*A.para_len
|
||||
if N<A.para_len*2:return
|
||||
else:
|
||||
K=int(G[0:A.para_len],16);L=int(G[A.para_len:J],16)
|
||||
if N==J:H=1
|
||||
else:H=int(G[J:],16)
|
||||
C=H*H%int(A.ecc_table[_A],base=16);I=L*L%int(A.ecc_table[_A],base=16);D=(K+C)%int(A.ecc_table[_A],base=16);E=(K-C)%int(A.ecc_table[_A],base=16);B=D*E%int(A.ecc_table[_A],base=16);D=L*H%int(A.ecc_table[_A],base=16);E=I*8%int(A.ecc_table[_A],base=16);F=K*E%int(A.ecc_table[_A],base=16);B=B*3%int(A.ecc_table[_A],base=16);C=C*C%int(A.ecc_table[_A],base=16);C=A.ecc_a3*C%int(A.ecc_table[_A],base=16);B=(B+C)%int(A.ecc_table[_A],base=16);O=(D+D)%int(A.ecc_table[_A],base=16);D=B*B%int(A.ecc_table[_A],base=16);I=I*E%int(A.ecc_table[_A],base=16);P=(D-F)%int(A.ecc_table[_A],base=16)
|
||||
if F%2==1:E=(F+(F+int(A.ecc_table[_A],base=16)>>1)-D)%int(A.ecc_table[_A],base=16)
|
||||
else:E=(F+(F>>1)-D)%int(A.ecc_table[_A],base=16)
|
||||
B=B*E%int(A.ecc_table[_A],base=16);Q=(B-I)%int(A.ecc_table[_A],base=16);M=_D%A.para_len;M=M*3;return M%(P,Q,O)
|
||||
def _add_point(A,P1,P2):
|
||||
E=2*A.para_len;J=len(P1);M=len(P2)
|
||||
if J<E or M<E:return
|
||||
else:
|
||||
H=int(P1[0:A.para_len],16);K=int(P1[A.para_len:E],16)
|
||||
if J==E:F=1
|
||||
else:F=int(P1[E:],16)
|
||||
N=int(P2[0:A.para_len],16);O=int(P2[A.para_len:E],16);B=F*F%int(A.ecc_table[_A],base=16);C=O*F%int(A.ecc_table[_A],base=16);D=N*B%int(A.ecc_table[_A],base=16);B=B*C%int(A.ecc_table[_A],base=16);C=(D-H)%int(A.ecc_table[_A],base=16);D=(D+H)%int(A.ecc_table[_A],base=16);G=C*C%int(A.ecc_table[_A],base=16);B=(B-K)%int(A.ecc_table[_A],base=16);P=F*C%int(A.ecc_table[_A],base=16);C=C*G%int(A.ecc_table[_A],base=16);D=D*G%int(A.ecc_table[_A],base=16);Q=B*B%int(A.ecc_table[_A],base=16);G=H*G%int(A.ecc_table[_A],base=16);L=(Q-D)%int(A.ecc_table[_A],base=16);C=K*C%int(A.ecc_table[_A],base=16);D=(G-L)%int(A.ecc_table[_A],base=16);B=B*D%int(A.ecc_table[_A],base=16);R=(B-C)%int(A.ecc_table[_A],base=16);I=_D%A.para_len;I=I*3;return I%(L,R,P)
|
||||
def _convert_jacb_to_nor(A,Point):
|
||||
C=Point;E=2*A.para_len;H=int(C[0:A.para_len],16);I=int(C[A.para_len:E],16);F=int(C[E:],16);B=pow(F,int(A.ecc_table[_A],base=16)-2,int(A.ecc_table[_A],base=16));G=B*B%int(A.ecc_table[_A],base=16);J=G*B%int(A.ecc_table[_A],base=16);K=H*G%int(A.ecc_table[_A],base=16);L=I*J%int(A.ecc_table[_A],base=16);M=F*B%int(A.ecc_table[_A],base=16)
|
||||
if M==1:D=_D%A.para_len;D=D*2;return D%(K,L)
|
||||
else:return
|
||||
def encrypt(A,data):
|
||||
D='%s%s%s';B=data.hex();E=random_hex(A.para_len);F=A._kg(int(E,16),A.ecc_table['g']);C=A._kg(int(E,16),A.public_key);K=C[0:A.para_len];L=C[A.para_len:2*A.para_len];G=len(B);H=m_kdf(C.encode(_E),G/2)
|
||||
if int(H,16)==0:return
|
||||
else:
|
||||
M=_D%G;I=M%(int(B,16)^int(H,16));J=m_hash([A for A in bytes.fromhex(D%(K,B,L))])
|
||||
if A.mode:return bytes.fromhex(D%(F,J,I))
|
||||
else:return bytes.fromhex(D%(F,I,J))
|
||||
N=binascii.a2b_hex(A._m_z(data).encode(_B));return A.verify(sign,N)
|
||||
IV=[1937774191,1226093241,388252375,3666478592,2842636476,372324522,3817729613,2969243214]
|
||||
T_j=[2043430169,2043430169,2043430169,2043430169,2043430169,2043430169,2043430169,2043430169,2043430169,2043430169,2043430169,2043430169,2043430169,2043430169,2043430169,2043430169,2055708042,2055708042,2055708042,2055708042,2055708042,2055708042,2055708042,2055708042,2055708042,2055708042,2055708042,2055708042,2055708042,2055708042,2055708042,2055708042,2055708042,2055708042,2055708042,2055708042,2055708042,2055708042,2055708042,2055708042,2055708042,2055708042,2055708042,2055708042,2055708042,2055708042,2055708042,2055708042,2055708042,2055708042,2055708042,2055708042,2055708042,2055708042,2055708042,2055708042,2055708042,2055708042,2055708042,2055708042,2055708042,2055708042,2055708042,2055708042]
|
||||
def m_ff_j(x,y,z,j):
|
||||
if 0<=j and j<16:A=x^y^z
|
||||
elif 16<=j and j<64:A=x&y|x&z|y&z
|
||||
return A
|
||||
def m_gg_j(x,y,z,j):
|
||||
if 0<=j and j<16:A=x^y^z
|
||||
elif 16<=j and j<64:A=x&y|~x&z
|
||||
return A
|
||||
def m_p_0(x):return x^rotl(x,9%32)^rotl(x,17%32)
|
||||
def m_p_1(x):return x^rotl(x,15%32)^rotl(x,23%32)
|
||||
def m_cf(v_i,b_i):
|
||||
B=[]
|
||||
for N in range(16):
|
||||
L=16777216;M=0
|
||||
for P in range(N*4,(N+1)*4):M=M+b_i[P]*L;L=int(L/256)
|
||||
B.append(M)
|
||||
for A in range(16,68):B.append(0);B[A]=m_p_1(B[A-16]^B[A-9]^rotl(B[A-3],15%32))^rotl(B[A-13],7%32)^B[A-6];Q=_F%B[A]
|
||||
K=[]
|
||||
for A in range(0,64):K.append(0);K[A]=B[A]^B[A+4];Q=_F%K[A]
|
||||
C,E,F,I,D,G,H,J=v_i
|
||||
for A in range(0,64):O=rotl(rotl(C,12%32)+D+rotl(T_j[A],A%32)&4294967295,7%32);R=O^rotl(C,12%32);S=m_ff_j(C,E,F,A)+I+R+K[A]&4294967295;T=m_gg_j(D,G,H,A)+J+O+B[A]&4294967295;I=F;F=rotl(E,9%32);E=C;C=S;J=H;H=rotl(G,19%32);G=D;D=m_p_0(T);C,E,F,I,D,G,H,J=map(lambda x:x&4294967295,[C,E,F,I,D,G,H,J])
|
||||
U=[C,E,F,I,D,G,H,J];return[U[A]^v_i[A]for A in range(8)]
|
||||
def m_hash(msg):
|
||||
B=msg;H=len(B);C=H%64;B.append(128);C=C+1;D=56
|
||||
if C>D:D=D+64
|
||||
for A in range(C,D):B.append(0)
|
||||
E=H*8;I=[E%256]
|
||||
for A in range(7):E=int(E/256);I.append(E%256)
|
||||
for A in range(8):B.append(I[7-A])
|
||||
J=round(len(B)/64);K=[]
|
||||
for A in range(0,J):K.append(B[A*64:(A+1)*64])
|
||||
F=[];F.append(IV)
|
||||
for A in range(0,J):F.append(m_cf(F[A],K[A]))
|
||||
L=F[A+1];G=''
|
||||
for A in L:G='%s%08x'%(G,A)
|
||||
return G
|
||||
def m_kdf(z,klen):
|
||||
A=klen;A=int(A);C=1;D=ceil(A/32);E=[A for A in bytes.fromhex(z.decode(_E))];B=''
|
||||
for G in range(D):F=E+[A for A in binascii.a2b_hex((_F%C).encode(_E))];B=B+m_hash(F);C+=1
|
||||
return B[0:A*2]
|
||||
SM4_BOXES_TABLE=[214,144,233,254,204,225,61,183,22,182,20,194,40,251,44,5,43,103,154,118,42,190,4,195,170,68,19,38,73,134,6,153,156,66,80,244,145,239,152,122,51,84,11,67,237,207,172,98,228,179,28,169,201,8,232,149,128,223,148,250,117,143,63,166,71,7,167,252,243,115,23,186,131,89,60,25,230,133,79,168,104,107,129,178,113,100,218,139,248,235,15,75,112,86,157,53,30,36,14,94,99,88,209,162,37,34,124,59,1,33,120,135,212,0,70,87,159,211,39,82,76,54,2,231,160,196,200,158,234,191,138,210,64,199,56,181,163,247,242,206,249,97,21,161,224,174,93,164,155,52,26,85,173,147,50,48,245,140,177,227,29,246,226,46,130,102,202,96,192,41,35,171,13,83,78,111,213,219,55,69,222,253,142,47,3,255,106,114,109,108,91,81,141,27,175,146,187,221,188,127,17,217,92,65,31,16,90,216,10,193,49,136,165,205,123,189,45,116,208,18,184,229,180,176,137,105,151,74,12,150,119,126,101,185,241,9,197,110,198,132,24,240,125,236,58,220,77,32,121,238,95,62,215,203,57,72]
|
||||
SM4_FK=[2746333894,1453994832,1736282519,2993693404]
|
||||
SM4_CK=[462357,472066609,943670861,1415275113,1886879365,2358483617,2830087869,3301692121,3773296373,4228057617,404694573,876298825,1347903077,1819507329,2291111581,2762715833,3234320085,3705924337,4177462797,337322537,808926789,1280531041,1752135293,2223739545,2695343797,3166948049,3638552301,4110090761,269950501,741554753,1213159005,1684763257]
|
||||
SM4_ENCRYPT=0
|
||||
SM4_DECRYPT=1
|
||||
NoPadding=0
|
||||
ZERO=1
|
||||
ISO9797M2=2
|
||||
PKCS7=3
|
||||
PBOC=4
|
||||
class AA:
|
||||
def __init__(A,mode=SM4_ENCRYPT,padding_mode=PKCS7):A.sk=[0]*32;A.mode=mode;A.padding_mode=padding_mode
|
||||
@classmethod
|
||||
def _round_key(E,ka):A=[0,0,0,0];B=put_uint32_be(ka);A[0]=SM4_BOXES_TABLE[B[0]];A[1]=SM4_BOXES_TABLE[B[1]];A[2]=SM4_BOXES_TABLE[B[2]];A[3]=SM4_BOXES_TABLE[B[3]];C=get_uint32_be(A[0:4]);D=C^rotl(C,13)^rotl(C,23);return D
|
||||
@classmethod
|
||||
def _f(B,x0,x1,x2,x3,rk):
|
||||
def A(ka):A=[0,0,0,0];C=put_uint32_be(ka);A[0]=SM4_BOXES_TABLE[C[0]];A[1]=SM4_BOXES_TABLE[C[1]];A[2]=SM4_BOXES_TABLE[C[2]];A[3]=SM4_BOXES_TABLE[C[3]];B=get_uint32_be(A[0:4]);D=B^rotl(B,2)^rotl(B,10)^rotl(B,18)^rotl(B,24);return D
|
||||
return x0^A(x1^x2^x3^rk)
|
||||
def set_key(B,key,mode):
|
||||
D=key;D=bytes_to_list(D);E=[0,0,0,0];C=[0]*36;E[0]=get_uint32_be(D[0:4]);E[1]=get_uint32_be(D[4:8]);E[2]=get_uint32_be(D[8:12]);E[3]=get_uint32_be(D[12:16]);C[0:4]=xor(E[0:4],SM4_FK[0:4])
|
||||
for A in range(32):C[A+4]=C[A]^B._round_key(C[A+1]^C[A+2]^C[A+3]^SM4_CK[A]);B.sk[A]=C[A+4]
|
||||
B.mode=mode
|
||||
if mode==SM4_DECRYPT:
|
||||
for F in range(16):G=B.sk[F];B.sk[F]=B.sk[31-F];B.sk[31-F]=G
|
||||
def one_round(E,sk,in_put):
|
||||
D=in_put;C=[];A=[0]*36;A[0]=get_uint32_be(D[0:4]);A[1]=get_uint32_be(D[4:8]);A[2]=get_uint32_be(D[8:12]);A[3]=get_uint32_be(D[12:16])
|
||||
for B in range(32):A[B+4]=E._f(A[B],A[B+1],A[B+2],A[B+3],sk[B])
|
||||
C+=put_uint32_be(A[35]);C+=put_uint32_be(A[34]);C+=put_uint32_be(A[33]);C+=put_uint32_be(A[32]);return C
|
||||
def crypt_ecb(A,input_data):
|
||||
B=input_data
|
||||
if A.mode==SM4_ENCRYPT:
|
||||
if A.padding_mode==NoPadding:0
|
||||
if A.padding_mode==ZERO:B=zero_padding(bytes_to_list(B))
|
||||
if A.padding_mode==ISO9797M2:B=iso9797m2_padding(B)
|
||||
if A.padding_mode==PKCS7:B=pkcs7_padding(bytes_to_list(B))
|
||||
if A.padding_mode==PBOC:B=pboc_padding(B)
|
||||
E=len(B);D=0;C=[]
|
||||
while E>0:C+=A.one_round(A.sk,B[D:D+16]);D+=16;E-=16
|
||||
if A.mode==SM4_DECRYPT:
|
||||
if A.padding_mode==NoPadding:0
|
||||
if A.padding_mode==ZERO:return list_to_bytes(zero_unpadding(C))
|
||||
if A.padding_mode==ISO9797M2:return list_to_bytes(iso9797m2_unpadding(C))
|
||||
if A.padding_mode==PKCS7:return list_to_bytes(pkcs7_unpadding(C))
|
||||
if A.padding_mode==PBOC:return list_to_bytes(pboc_unpadding(C))
|
||||
return list_to_bytes(C)
|
||||
def crypt_cbc(A,iv,input_data):
|
||||
E=iv;C=input_data;B=0;D=[];G=[0]*16;E=bytes_to_list(E)
|
||||
if A.mode==SM4_ENCRYPT:
|
||||
if A.padding_mode==NoPadding:0
|
||||
if A.padding_mode==ZERO:C=zero_padding(bytes_to_list(C))
|
||||
if A.padding_mode==ISO9797M2:C=iso9797m2_padding(C)
|
||||
if A.padding_mode==PKCS7:C=pkcs7_padding(bytes_to_list(C))
|
||||
if A.padding_mode==PBOC:C=pboc_padding(C)
|
||||
F=len(C)
|
||||
while F>0:G[0:16]=xor(C[B:B+16],E[0:16]);D+=A.one_round(A.sk,G[0:16]);E=copy.deepcopy(D[B:B+16]);B+=16;F-=16
|
||||
return list_to_bytes(D)
|
||||
else:
|
||||
F=len(C)
|
||||
while F>0:D+=A.one_round(A.sk,C[B:B+16]);D[B:B+16]=xor(D[B:B+16],E[0:16]);E=copy.deepcopy(C[B:B+16]);B+=16;F-=16
|
||||
if A.padding_mode==NoPadding:0
|
||||
if A.padding_mode==ZERO:return list_to_bytes(zero_unpadding(D))
|
||||
if A.padding_mode==ISO9797M2:return list_to_bytes(iso9797m2_unpadding(D))
|
||||
if A.padding_mode==PKCS7:return list_to_bytes(pkcs7_unpadding(D))
|
||||
if A.padding_mode==PBOC:return list_to_bytes(pboc_unpadding(D))
|
||||
return list_to_bytes(D)
|
||||
@@ -0,0 +1,5 @@
|
||||
import logging
|
||||
|
||||
from ..const import PACKAGE_NAME
|
||||
|
||||
LOGGER: logging.Logger = logging.getLogger(PACKAGE_NAME)
|
||||
@@ -0,0 +1,64 @@
|
||||
from homeassistant.helpers.json import JSONEncoder
|
||||
from homeassistant.helpers.storage import Store
|
||||
from homeassistant.util import json as json_util
|
||||
|
||||
from ..const import VERSION_STORAGE
|
||||
from .logger import LOGGER
|
||||
|
||||
_LOGGER = LOGGER
|
||||
|
||||
class StateGridStore(Store):
|
||||
"""A subclass of Store that allows multiple loads in the executor."""
|
||||
|
||||
def load(self):
|
||||
"""Load the data from disk if version matches."""
|
||||
try:
|
||||
data = json_util.load_json(self.path)
|
||||
except (
|
||||
BaseException # lgtm [py/catch-base-exception] pylint: disable=broad-except
|
||||
) as exception:
|
||||
_LOGGER.critical(
|
||||
"Could not load '%s', restore it from a backup or delete the file: %s",
|
||||
self.path,
|
||||
exception,
|
||||
)
|
||||
if data == {} or data["version"] != self.version:
|
||||
return None
|
||||
return data["data"]
|
||||
|
||||
|
||||
def _get_store_for_key(hass, key, encoder):
|
||||
"""Create a Store object for the key."""
|
||||
return StateGridStore(hass, VERSION_STORAGE, key, encoder=encoder, atomic_writes=True)
|
||||
|
||||
|
||||
def get_store_for_key(hass, key):
|
||||
"""Create a Store object for the key."""
|
||||
return _get_store_for_key(hass, key, JSONEncoder)
|
||||
|
||||
|
||||
async def async_load_from_store(hass, key):
|
||||
"""Load the retained data from store and return de-serialized data."""
|
||||
return await get_store_for_key(hass, key).async_load() or {}
|
||||
|
||||
|
||||
async def async_save_to_store(hass, key, data):
|
||||
"""Generate dynamic data to store and save it to the filesystem.
|
||||
|
||||
The data is only written if the content on the disk has changed
|
||||
by reading the existing content and comparing it.
|
||||
|
||||
If the data has changed this will generate two executor jobs
|
||||
|
||||
If the data has not changed this will generate one executor job
|
||||
"""
|
||||
current = await async_load_from_store(hass, key)
|
||||
if current is None or current != data:
|
||||
await get_store_for_key(hass, key).async_save(data)
|
||||
|
||||
|
||||
async def async_remove_store(hass, key):
|
||||
"""Remove a store element that should no longer be used."""
|
||||
if "/" not in key:
|
||||
return
|
||||
await get_store_for_key(hass, key).async_remove()
|
||||
Reference in New Issue
Block a user