This commit is contained in:
HassBox
2024-07-12 09:44:26 +08:00
commit 3f3f7c0bcc
17 changed files with 1597 additions and 0 deletions
+36
View File
@@ -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
+15
View File
@@ -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
+1
View File
@@ -0,0 +1 @@
# State Grid
+17
View File
@@ -0,0 +1,17 @@
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)
hass.async_create_task(
hass.config_entries.async_forward_entry_setup(config_entry, Platform.SENSOR)
)
return True
+270
View File
@@ -0,0 +1,270 @@
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)
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)
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
)
+4
View File
@@ -0,0 +1,4 @@
DOMAIN = "state_grid"
PACKAGE_NAME = "custom_components.state_grid"
VERSION = "0.1.3"
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()
+392
View File
@@ -0,0 +1,392 @@
_B4='get_door_daily_bill_api'
_B3='startTime'
_B2='constType'
_B1='provinceCode'
_B0='access_token'
_A_='businessType'
_Az='qrCodeSerial'
_Ay='redirect_url'
_Ax='_access_token'
_Aw='dataVersion'
_Av='refresh_interval'
_Au='doorAccountDict'
_At='refreshToken'
_As='accessToken'
_Ar='/osg-web0004/member/c24/f01'
_Aq='BCP_00026'
_Ap='serviceCode_smt'
_Ao='WEBA10070900'
_An='serviceType'
_Am='jM_custType'
_Al='jM_busiTypeCode'
_Ak='doorNumberManeger'
_Aj='month_t_ele_num'
_Ai='month_n_ele_num'
_Ah='month_v_ele_num'
_Ag='month_p_ele_num'
_Af='month_ele_num'
_Ae='thisTPq'
_Ad='thisNPq'
_Ac='thisVPq'
_Ab='thisPPq'
_Aa='%Y%m%d'
_AZ='params4'
_AY='params3'
_AX='params1'
_AW='queryYear'
_AV='userAccountId'
_AU='powerUserList'
_AT='publicKey'
_AS='WEBA10070800'
_AR='timeDay'
_AQ='WEBA10070700'
_AP='channelNo'
_AO='dayElePq'
_AN='sevenEleList'
_AM='month'
_AL='yearTotalCost'
_AK='consType'
_AJ='0000'
_AI='refresh_token'
_AH='skey'
_AG='userInfo'
_AF='0101046'
_AE='billYear'
_AD='proCode'
_AC='loginAccount'
_AB='list'
_AA='keyCode'
_A9='querytypeCode'
_A8='01010049'
_A7='account'
_A6='daily_bill_list'
_A5='account_balance'
_A4='provinceId'
_A3='userName'
_A2='acctId'
_A1='resultCode'
_A0='BCP_000026'
_z='app'
_y='WEBALIPAY_01'
_x='order'
_w='state_grid'
_v='latestBillMonth'
_u='quInfo'
_t='authFlag'
_s='09'
_r='0101183'
_q='stepelect'
_p='consNo'
_o=True
_n='0101154'
_m='monthBillList'
_l='bizrt'
_k='token'
_j='timestamp'
_i=False
_h='month_ele'
_g='errmsg'
_f='clearCache'
_e='SGAPP'
_d='orgNo'
_c='consNo_dst'
_b='promotCode'
_a='01'
_Z='devciceId'
_Y='devciceIp'
_X='proNo'
_W='tenant'
_V='member'
_U='promotType'
_T='getday'
_S='srvrt'
_R='userId'
_Q='subBusiTypeCode'
_P='target'
_O='serCat'
_N='serialNo'
_M='0902'
_L='srvCode'
_K='busiTypeCode'
_J='uscInfo'
_I='channelCode'
_H='code'
_G='1'
_F=None
_E='source'
_D='funcCode'
_C='errcode'
_B='serviceCode'
_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={_J:{_V:_M,_Y:'',_Z:'',_W:_w},_E:_e,_P:'32101',_I:_M,_AP:_M,'toPublish':_a,'siteId':'2012000000033700',_L:'',_N:'',_D:'',_B:{_x:_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',_P:'-1'},'userInformation':{_B:'01008183',_E:_e},'userInform':{_B:_r,_E:_e},'elesum':{_I:_M,_D:_y,_b:_G,_U:_G,_B:'0101143',_E:_z},_A7:{_I:_M,_D:'WEBA1007200'},_Ak:{_E:_M,_P:'-1',_I:_s,_AP:_s,_B:_A8,_D:'WEBA40050000',_J:{_V:_M,_Y:'',_Z:'',_W:_w}},'doorAuth':{_E:_e,_B:'f04'},'xinZ':{_O:'101',_Al:'101','fJ_busiTypeCode':'102',_Am:'03','fJ_custType':'02',_An:_a,_Q:'',_D:_AQ,_x:_n,_E:_e,_A9:_G},'onedo':{_B:_AF,_E:_e,_D:_AQ,'queryType':'03'},'xinHuTongDian':{_O:'110',_K:'211',_Q:'21102',_D:'WEBA10071200',_I:_M,_E:_s,_B:_r},'company':{_O:'104',_D:_AQ,_An:'02',_A9:_G,_t:_G,_E:_e,_x:_n},'charge':{_I:_s,_D:'WEBA10071300',_AP:'0901',_O:'102',_Am:_a,_Al:'102'},'other':{_I:_s,_D:'WEBA10079700',_O:'129',_K:'999',_Q:'21501',_B:_A0,_L:'',_N:''},'vatchange':{'submit':'0101003',_K:'320',_Q:'',_O:'115',_D:'WEBA10074000',_t:_G},'bill':{_f:_G,_D:_y,_U:_G,_B:_A0},_q:{_I:_M,_D:_y,_U:_G,_f:_s,_B:_A0,_E:_z},_T:{_I:_M,_f:'11',_D:_y,_b:_G,_U:_G,_B:_A0,_E:_z},'mouthOut':{_I:_M,_f:'11',_D:_y,_b:_G,_U:_G,_B:_A0,_E:_z},'meter':{_O:'114',_K:'304',_D:'WEBA10071000',_Q:'',_B:_AF,_N:''},'complaint':{_K:'005','srvMode':_M,'anonymousFlag':'0','replyMode':_a,'retvisitFlag':_a},'report':{_K:'006'},'tradewinds':{_K:'019'},'somesay':{_K:'091'},'faultrepair':{_D:_Ao,_B:_r,_O:'111',_K:'001',_Q:'21505'},'electronicInvoice':{_O:'105',_K:'0'},'rename':{_B:_AF,_D:'WEBA10076100',_K:'210',_O:'109',_t:_G,'gh_busiTypeCode':'211','gh_subusi':'21101',_N:'',_L:''},'pause':{_Q:'',_B:_A8,_D:'WEBA10073600',_O:'107',_K:'203','jr_busi':'201',_N:'',_L:''},'capacityRecovery':{_B:_A8,_E:_e,_L:'',_N:'',_D:'WEBA10073700','busiTypeCode_stop':'204','busiTypeCode_less':'202',_K:'202',_Q:'',_O:'108',_AR:'5',_t:_G},'electricityPriceChange':{_B:_r,_K:'215',_Q:'21502',_O:'113',_t:_G,_AR:'15',_D:'WEBA10073900WEB',_L:'',_N:''},'electricityPriceStrategyChange':{_B:'01008183',_K:'215',_Q:'21506',_O:'160',_D:'WEBV00000517WEB',_L:'',_N:''},'eemandValueAdjustment':{_B:_r,_L:'',_N:'',_O:'112',_D:'WEBA10073800',_K:'215',_Q:'21504',_t:_G,_AR:'5','getMonthServiceCode':_AF},'businessProgress':{_B:_r,_L:_a,_D:'WEB01'},'increase':{_E:_e,_N:'',_L:'',_Ap:_A8,_B:_n,_x:_n,_D:_AS,_A9:_G,_O:'106',_K:'111',_Q:''},'fjincrea':{_O:'105',_K:'110',_Q:'',_E:_e,_D:_AS,_N:'',_L:'',_Ap:_A8,_B:_n,_x:_n,_A9:_G},'persIncrea':{_O:'105',_K:'109',_x:_n,_Q:'',_E:_e,_D:_AS,_A9:_G},'fgdChange':{_B:_r,_L:_a,_I:_s,_D:_Ao,_K:'215',_Q:'21505',_O:'111',_t:_G},'createOrder':{_I:_M,_D:_y,_L:'BCP_000001','chargeMode':'02','conType':_a,'bizTypeId':'BT_ELEC'},'largePopulation':{_K:'383',_D:'WEBA10076800',_Q:'',_L:'',_U:'',_b:'',_I:'0901',_O:'383',_B:'',_N:''},'biaoJiCode':{_B:'0104507',_E:'1704',_I:'1704'},'twoGuar':{_K:'402',_Q:'40201',_D:'web_twoGuar'},'electTrend':{_B:_Aq,_I:_M},'emergency':{_B:_Aq,_D:'A10000000',_I:_M},'infoPublic':{_B:'2545454',_E:_z}}
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=_Ar
get_door_daily_bill_api=_Ar
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;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[_AT];A.accessToken=B[_As];A.refreshToken=B[_At];A.token=B[_k];A.userInfo=B[_AG];A.powerUserList=B[_AU];A.doorAccountDict=B[_Au];A.refresh_interval=B[_Av];A.is_debug=B['is_debug'];A.dataVersion=B[_Aw]
except Exception as E:LOGGER.error(E)
async def save_data(B):A={};A[_AA]=B.keyCode;A[_AT]=B.publicKey;A[_As]=B.accessToken;A[_At]=B.refreshToken;A[_k]=B.token;A[_AG]=B.userInfo;A[_AU]=B.powerUserList;A[_Au]=B.doorAccountDict;A[_Av]=B.refresh_interval;A['is_debug']=B.is_debug;A[_Aw]=VERSION;await async_save_to_store(B.hass,'state_grid.config',A)
def encrypt_post_data(A,data):B={_Ax: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)),_AH:d(A.keyCode,A.publicKey),_j:str(A.timestamp)}
def handle_request_result_message(E,api,result):
D='message';C='resultMessage';A=result;B=_F
if _A in A and _S in A[_A]and C in A[_A][_S]:B=A[_A][_S][C]
elif _S in A and C in A[_S]:B=A[_S][C]
elif D in A:B=A[D]
else:B=json_dumps(A)
if E.is_debug:LOGGER.error(api+': '+B);LOGGER.error(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)),_AH:d(F,'042BC7AD510BF9793B7744C8854C56A8C95DD1027EE619247A332EC6ED5B279F435A23D62441FE861F4B0C963347ECD5792F380B64CA084BE8BE41151F8B8D19C8'),I:appKey,_j:str(D)}
elif E==get_qr_code_api:A={_Ax:'','_t':'','_data':A,_j:D}
elif E==get_request_authorize_api:
A={I:appKey,'response_type':_H,_Ay:'/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)),_AH:d(F,B.publicKey),_j:str(D)}
elif E==refresh_web_token_api:A={O:_AI,P:c(appKey+str(D)),K:appSecret,Q:S,R:F,I:appKey,_j:D,_AI:B.refreshToken};H=a(json_dumps(A),F);A={_A:H+c(H+str(D)),_AH: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][_AT];return{_C:0}
return{_C:1,_g:C}
async def __get_qr_code(B):
C={_J:{_Y:'',_W:_w,_V:_M,_Z:''},_u:{'optType':_a,_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][_S]and A[_A][_S][_A1]==_AJ:B.qrCodeSerial=A[_A][_l][_Az];E=A[_A][_l]['qrCode'];return{_C:0,_A:E}
return{_C:1,_g:D}
async def __get_qr_code_status(B):
C={_l:{_Az:B.qrCodeSerial}};D={_k:'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{_C:0}
else:return{_C:1,_g:'未使用网上国网 App 扫码或确认登录'}
return{_C:1,_g:E}
async def __get_qr_code_token(B):
C={_J:{_W:_w,_V:_M,'isEncrypt':_o},_k: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 _S in A and _A1 in A[_S]and A[_S][_A1]==_AJ:B.userInfo=A[_l][_AG];return{_C:0}
return{_C:1,_g:D}
async def __send_code(B,phone):
C=phone;B.phone=C;D={_J:{_Y:'',_W:_w,_V:_M,_Z:''},_u:{'sendType':'0',_A7:C,_A_:'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][_S]and A[_A][_S][_A1]==_AJ:B.codeKey=A[_A][_l]['codeKey'];return{_C:0}
return{_C:1,_g:E}
async def __verfiy_code(A,code):
C={_J:{_Y:'',_W:_w,_V:_M,_Z:''},_u:{_A7:A.phone,_A_:'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 _S in B and _A1 in B[_S]and B[_S][_A1]==_AJ:A.token=B[_l][_k];A.userInfo=B[_l][_AG][0];return{_C:0}
return{_C:1,_g: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][_Ay];D=C.rfind('code=');B.authorizeCode=C[D+5:D+5+32];return{_C:0}
return{_C:1,_g: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][_B0];A.refreshToken=B[_A][_AI];return{_C:0}
return{_C:1,_g: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][_B0];B.refreshToken=A[_A][_AI];return{_C:0}
return{_C:1,_g:C}
async def __get_door_number(A):
B=configuration[_Ak];G={_B:B[_B],_E:B[_E],_P:B[_P],_J:{_V:B[_J][_V],_Y:B[_J][_Y],_Z:B[_J][_Z],_W:B[_J][_W]},_u:{_R:A.userInfo[_R]},_k: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 _l in C[_A]:
D={}
if A.powerUserList is not _F:D={A[_c]:A for A in A.powerUserList}
E=[]
for F in C[_A][_l][_AU]:
if F[_c]in D:E.append(D[F[_c]])
else:E.append(F)
A.powerUserList=E;return{_C:0}
return{_C:1,_g:H}
async def __get_door_balance(B,door_account):
A=door_account;E={_A:{_L:'',_N:'',_I:configuration[_A7][_I],_D:configuration[_A7][_D],_A2:B.userInfo[_R],_A3:B.userInfo.get(_AC,B.userInfo.get('nickname',_F)),_U:_G,_b:_G,_AV:B.userInfo[_R],_AB:[{'consNoSrc':A[_c],_AD:A.get(_X,A.get(_A4,_F)),'sceneType':A.get('consSortCode',A.get('elecTypeCode',_F)),_p:A[_p],_d:A[_d]}]},_B:'0101143',_E:configuration[_E],_P:A.get(_X,A.get(_A4,_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 _AB in C[_A]:
D=C[_A][_AB]
if len(D)!=0:A[_A5]=D[0]
async def __get_door_bill(C,door_account,monthDate):
I='dataInfo';G='mothEleList';E=monthDate;A=door_account;J={_A:{_A2:C.userInfo[_R],_I:configuration[_I],_f:'11',_AK:A[_B2],_D:'ALIPAY_01',_d:A[_d],_AD:A[_X],_b:_G,_U:_G,_N:'',_L:'',_A3:'',_B1:A[_X],_AV:C.userInfo[_R],_p:A[_p],_AW:E.year},_B:_A0,_E:_z,_P:A[_X]};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[_AL]=B[_A][I]
if G in B[_A]:
if _AE not in A or A[_AE]!=E.year:A[_m]=B[_A][G];A[_AE]=E.year
else:
F={A.month:A for A in A[_m]};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[_m]=H
if len(A[_m])>0:A[_v]=A[_m][-1]
async def __get_door_ladder(C,door_account,monthBill):
E=monthBill;A=door_account;I=A[_c];F=datetime.datetime.strptime(E[_AM],'%Y%m');G=f"{F.year}-{F.month:02d}";H={_A:{_I:configuration[_q][_I],_D:configuration[_q][_D],_U:configuration[_q][_U],_f:configuration[_q][_f],_p:A[_c],_b:A[_X],_d:A[_d],'queryDate':G,_B1:A[_X],_AK:A[_B2],_AV:C.userInfo[_R],_N:'',_L:'',_A3:C.userInfo[_AC],_A2:C.userInfo[_R]},_B:configuration[_q][_B],_E:configuration[_q][_E],_P:A[_X]};B=await C.__fetch(get_door_ladder_api,H);J=C.handle_request_result_message('get_door_ladder_api',B)
if _H in B and B[_H]==1 and _A in B and _AB in B[_A]:
D=B[_A][_AB]
if len(D)!=0:D=D[0];E['ladder']=D
async def __get_door_mouth_daily_bill(B,door_account,monthBill):
F=monthBill;A=door_account;N=A[_c];L=datetime.datetime.strptime(F[_AM],'%Y%m');M={_AX:{_B:configuration[_B],_E:configuration[_E],_P:configuration[_P],_J:{_V:configuration[_J][_V],_Y:configuration[_J][_Y],_Z:configuration[_J][_Z],_W:configuration[_J][_W]},_u:{_R:B.userInfo[_R]},_k:B.token},_AY:{_A:{_A2:B.userInfo[_R],_p:A[_c],_AK:_a,'endTime':F['endDate'],_d:A[_d],_AW:str(L.year),_AD:A.get(_X,A.get(_A4,_F)),_N:'',_L:'',_B3:F['begDate'],_A3:B.userInfo[_AC],_D:configuration[_T][_D],_I:configuration[_T][_I],_f:configuration[_T][_f],_b:configuration[_T][_b],_U:configuration[_T][_U]},_B:configuration[_T][_B],_E:configuration[_T][_E],_P:A.get(_X,A.get(_A4,_F))},_AZ:'010103'};C=await B.__fetch_safe(get_door_daily_bill_api,M);O=B.handle_request_result_message(_B4,C)
if _H in C and C[_H]==1 and _A in C and _AN in C[_A]:
G=0;H=0;I=0;J=0;K=0
for D in C[_A][_AN]:P=datetime.datetime.strptime(D['day'],_Aa);G+=catchFloat(D,_AO);H+=catchFloat(D,_Ab);I+=catchFloat(D,_Ac);J+=catchFloat(D,_Ad);K+=catchFloat(D,_Ae)
E={};E[_Af]=normal_round(G,2);E[_Ag]=normal_round(H,2);E[_Ah]=normal_round(I,2);E[_Ai]=normal_round(J,2);E[_Aj]=normal_round(K,2);F[_h]=E
async def __get_door_daily_bill(B,door_account,year,start_date,end_date):
A=door_account;D={_AX:{_B:configuration[_B],_E:configuration[_E],_P:configuration[_P],_J:{_V:configuration[_J][_V],_Y:configuration[_J][_Y],_Z:configuration[_J][_Z],_W:configuration[_J][_W]},_u:{_R:B.userInfo[_R]},_k:B.token},_AY:{_A:{_A2:B.userInfo[_R],_p:A[_c],_AK:_a,'endTime':end_date,_d:A[_d],_AW:year,_AD:A.get(_X,A.get(_A4,_F)),_N:'',_L:'',_B3:start_date,_A3:B.userInfo[_AC],_D:configuration[_T][_D],_I:configuration[_T][_I],_f:configuration[_T][_f],_b:configuration[_T][_b],_U:configuration[_T][_U]},_B:configuration[_T][_B],_E:configuration[_T][_E],_P:A.get(_X,A.get(_A4,_F))},_AZ:'010103'};C=await B.__fetch_safe(get_door_daily_bill_api,D);E=B.handle_request_result_message(_B4,C)
if _H in C and C[_H]==1 and _A in C and _AN in C[_A]:A[_A6]=C[_A][_AN]
async def __get_door_pay_record(A,door_account):B=door_account;D=B[_c];C={_AX:{_B:configuration[_B],_E:configuration[_E],_P:configuration[_P],_J:{_V:configuration[_J][_V],_Y:configuration[_J][_Y],_Z:configuration[_J][_Z],_W:configuration[_J][_W]},_u:{_R:A.userInfo[_R]},_k:A.token},_AY:{_A:{_A2:A.userInfo[_R],'bgnPayDate':'2023-04-24',_I:configuration[_I],_p:B[_c],'endPayDate':'2024-04-24',_D:'webALIPAY_01','number':100,_d:B[_d],'page':_G,_AD:B[_X],_b:_G,_U:_G,_N:'',_L:'',_A3:A.userInfo[_AC]},_B:'0101051',_E:_a,_P:B[_X]},_AZ:'010104'};E=await A.__fetch(getJiaoFeiRecordApi,C)
async def get_qr_code(B):
A=await B.__get_request_key()
if _C in A and A[_C]!=0:return A
return await B.__get_qr_code()
async def check_qr_code(B):
A=await B.__get_qr_code_status()
if _C in A and A[_C]!=0:return A
A=await B.__get_qr_code_token()
if _C in A and A[_C]!=0:return A
return await B.__get_token()
async def send_phone_code(B,phone):
A=await B.__get_request_key()
if _C in A and A[_C]!=0:return A
return await B.__send_code(phone)
async def verfiy_phone_code(B,code):
A=await B.__verfiy_code(code)
if _C in A and A[_C]!=0:return A
return await B.__get_token()
async def __get_token(B):
A=await B.__get_request_key()
if _C in A and A[_C]!=0:return A
A=await B.__get_request_authorize()
if _C in A and A[_C]!=0:return A
A=await B.__get_web_token()
if _C in A and A[_C]!=0:return A
A=await B.__get_door_number()
if _C in A and A[_C]!=0:return A
B.need_login=_i;await B.save_data();return{_C:0,_A:B.powerUserList}
async def __refresh_token(A):
B=await A.__get_request_key()
if _C in B and B[_C]!=0:return
B=await A.__refresh_web_token()
if _C in B and B[_C]==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):
p='last_month_ele_cost';o='year_ele_cost';n='%Y-%m-%d %H:%M:%S';b=setup;a='last_month_ele_num';Z='daily_lasted_date';Y='refresh_time';U='year_ele_num';G='balance'
if b is _o:
if B.dataVersion!=VERSION:B.powerUserList=_F
c=await B.__get_door_number()
if _C in c and c[_C]!=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[Y]='Token刷新失败,请重新登录'
LOGGER.error('国家电网 - Token刷新失败,请重新登录!');return
q=b or force_refresh or int(time.time()*1000)-B.timestamp>B.refresh_interval*3600*1000
if q is _i:return
H=datetime.datetime.now();F=H-datetime.timedelta(days=1);d=f"{F.year}-{F.month:02d}-{F.day:02d}";V=F-datetime.timedelta(days=40);r=f"{V.year}-{V.month:02d}-{V.day:02d}"
for A in B.powerUserList:
s=A[_c];B.doorAccountDict[s]=A
if Z in A and A[Z]==d:A[Y]=datetime.datetime.strftime(H,n);continue
await B.__get_door_balance(A)
if B.retry_times!=0:return
if _A5 in A:
t=catchFloat(A[_A5],'estiAmt');W=catchFloat(A[_A5],'prepayBal');e=catchFloat(A[_A5],'sumMoney')
if W==0 or W==e:A[G]=e
else:A[G]=W-t
f=catchFloat(A[_A5],'historyOwe')
if f>0:A[G]=-f
else:LOGGER.error('国家电网账户余额获取失败!')
if G not in A:A[G]=0
await B.__get_door_daily_bill(A,H.year,r,d)
if _A6 not in A:LOGGER.error('国家电网无法获取日用电数据!');return
X=0;I=_i
for g in range(10):
D=A[_A6][g]
try:float(D[_AO]);I=_o;break
except:X=X+1
h=0;i=0;j=0;k=0;l=0
if I:
for g in range(X):A[_A6].pop(0)
D=A[_A6][0];E=datetime.datetime.strptime(D['day'],_Aa);A[Z]=f"{E.year}-{E.month:02d}-{E.day:02d}";h=catchFloat(D,_AO);i=catchFloat(D,_Ab);j=catchFloat(D,_Ac);k=catchFloat(D,_Ad);l=catchFloat(D,_Ae)
A['daily_ele_num']=normal_round(h,2);A['daily_p_ele_num']=normal_round(i,2);A['daily_v_ele_num']=normal_round(j,2);A['daily_n_ele_num']=normal_round(k,2);A['daily_t_ele_num']=normal_round(l,2);J=0;K=0;L=0;M=0;N=0
if I:
for C in A[_A6]:
u=datetime.datetime.strptime(C['day'],_Aa)
if u.month!=E.month:break
J+=catchFloat(C,_AO);K+=catchFloat(C,_Ab);L+=catchFloat(C,_Ac);M+=catchFloat(C,_Ad);N+=catchFloat(C,_Ae)
A[_Af]=normal_round(J,2);A[_Ag]=normal_round(K,2);A[_Ah]=normal_round(L,2);A[_Ai]=normal_round(M,2);A[_Aj]=normal_round(N,2)
if I:
O=E-datetime.timedelta(days=E.day);v=f"{O.year}{O.month:02d}"
if _AE not in A or A[_AE]!=O.year or _v not in A or A[_v][_AM]!=v:await B.__get_door_bill(A,O)
if _m in A:
for C in A[_m]:
if _h not in C:await B.__get_door_mouth_daily_bill(A,C)
if _AL in A:A[U]=catchFloat(A[_AL],'totalEleNum');A[o]=catchFloat(A[_AL],'totalEleCost')
if U not in A:A[U]=0;A[o]=0
if _v in A:A[a]=catchFloat(A[_v],'monthEleNum');A[p]=catchFloat(A[_v],'monthEleCost');m=datetime.datetime.strptime(A[_v][_AM],'%Y%m')
if a not in A:A[a]=0;A[p]=0;m=F.month
P=0;Q=0;R=0;S=0;T=0
if m.month==12:P=J;Q=K;R=L;S=M;T=N
else:
if _m in A:
for C in A[_m]:
if _h in C:P+=C[_h][_Af];Q+=C[_h][_Ag];R+=C[_h][_Ah];S+=C[_h][_Ai];T+=C[_h][_Aj]
P+=J;Q+=K;R+=L;S+=M;T+=N
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[Y]=datetime.datetime.strftime(H,n)
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.3"
}
+227
View File
@@ -0,0 +1,227 @@
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:
coordinator = StateGridCoordinator(hass)
await coordinator.async_config_entry_first_refresh()
data_client: StateGridDataClient = hass.data[DOMAIN]
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"]]
+74
View File
@@ -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}"
}
}
}
+232
View File
@@ -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()
+75
View File
@@ -0,0 +1,75 @@
#!/bin/bash
# curl -fsSL get.hassbox.cn/state_grid | bash
set -e
export LC_ALL=en_US.UTF-8
RED_COLOR='\033[0;31m'
GREEN_COLOR='\033[0;32m'
GREEN_YELLOW='\033[1;33m'
NO_COLOR='\033[0m'
declare haPath
declare -a paths=(
"$PWD"
"$PWD/config"
"/config"
"$HOME/.homeassistant"
"/usr/share/hassio/homeassistant"
)
function info () { echo -e "${GREEN_COLOR}INFO: $1${NO_COLOR}";}
function warn () { echo -e "${GREEN_YELLOW}WARN: $1${NO_COLOR}";}
function error () { echo -e "${RED_COLOR}ERROR: $1${NO_COLOR}";}
function checkRequirement () {
if [ -z "$(command -v "$1")" ]; then
warn "'$1' 未安装,准备安装..."
apt install $1
fi
}
info "检查依赖项"
checkRequirement "wget"
checkRequirement "unzip"
for path in "${paths[@]}"; do
if [ -n "$haPath" ]; then
break
fi
if [ -f "$path/.HA_VERSION" ]; then
haPath="$path"
fi
done
if [ -z "$haPath" ]; then
echo
error "找不到 Home Assistant 根目录"
exit 1
fi
cd "$haPath"
if [ ! -d "$haPath/custom_components" ]; then
mkdir "$haPath/custom_components"
fi
cd "$haPath/custom_components"
info "检查依赖项 ok"
info "下载 state_grid 集成包"
wget "https://get.hasss.cn/state_grid.zip" >/dev/null 2>&1
info "下载 HassBox 集成包 ok"
info "state_grid 集成包解压"
if [ -d "$haPath/custom_components/state_grid" ]; then
rm -R "$haPath/custom_components/state_grid"
fi
mkdir "$haPath/custom_components/state_grid"
unzip "$haPath/custom_components/state_grid.zip" -d "$haPath/custom_components/state_grid" >/dev/null 2>&1
rm "$haPath/custom_components/state_grid.zip"
info "HassBox 集成包解压 ok"
info "安装成功!请重启 Home Assistant!如需其他帮助,可至 HassBox全屋智能 微信公众号联系客服"