This commit is contained in:
HassBox
2024-06-14 21:11:09 +08:00
commit 45d9570f5e
17 changed files with 1540 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
+29
View File
@@ -0,0 +1,29 @@
# 国家电网
集成国家电网每日用电等数据,支持多户号,阶梯用电。
## 安装/更新
#### 方法 1: 通过`Samba`或`SFTP`手动安装
下载并复制`custom_components/state_gird`文件夹到 Home Assistant 根目录下的`custom_components`文件夹
#### 方法 2: 通过`SSH`或`Terminal & SSH`加载项执行一键安装命令
```shell
curl -fsSL get.hassbox.cn/state_grid | bash
```
#### 方法 3: 通过`shell_command`服务
1. 复制下面的代码到 Home Assistant 配置文件`configuration.yaml`
```yaml
shell_command:
install_state_grid: |-
curl -fsSL get.hassbox.cn/state_grid | bash
```
2. 重启 Home Assistant
3. 在 Home Assistant 开发者工具中调用此服务[`service: shell_command.install_state_grid`](https://my.home-assistant.io/redirect/developer_call_service/?service=shell_command.install_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"]}
)
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
)
+4
View File
@@ -0,0 +1,4 @@
DOMAIN = "state_grid"
PACKAGE_NAME = "custom_components.state_grid"
VERSION = "0.1.2"
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()
+355
View File
@@ -0,0 +1,355 @@
_Al='constType'
_Ak='queryYear'
_Aj='provinceCode'
_Ai='access_token'
_Ah='businessType'
_Ag='qrCodeSerial'
_Af='redirect_url'
_Ae='_access_token'
_Ad='refresh_interval'
_Ac='doorAccountDict'
_Ab='refreshToken'
_Aa='accessToken'
_AZ='/osg-web0004/member/c24/f01'
_AY='BCP_00026'
_AX='serviceCode_smt'
_AW='WEBA10070900'
_AV='serviceType'
_AU='jM_custType'
_AT='jM_busiTypeCode'
_AS='doorNumberManeger'
_AR='month'
_AQ='consType'
_AP='userAccountId'
_AO='powerUserList'
_AN='publicKey'
_AM='WEBA10070800'
_AL='timeDay'
_AK='WEBA10070700'
_AJ='channelNo'
_AI='monthBillList'
_AH='yearTotalCost'
_AG='provinceId'
_AF='proCode'
_AE='loginAccount'
_AD='0000'
_AC='refresh_token'
_AB='skey'
_AA='userInfo'
_A9='0101046'
_A8='list'
_A7='userName'
_A6='acctId'
_A5='keyCode'
_A4='querytypeCode'
_A3='01010049'
_A2='account'
_A1='account_balance'
_A0='resultCode'
_z='quInfo'
_y='BCP_000026'
_x='app'
_w='WEBALIPAY_01'
_v='order'
_u='state_grid'
_t='latestBillMonth'
_s='consNo_dst'
_r='consNo'
_q=True
_p='authFlag'
_o='09'
_n='0101183'
_m='stepelect'
_l='daily_bill_list'
_k='token'
_j=False
_i='0101154'
_h='getday'
_g='bizrt'
_f='clearCache'
_e='orgNo'
_d='timestamp'
_c='promotCode'
_b='errmsg'
_a='01'
_Z='SGAPP'
_Y='devciceId'
_X='devciceIp'
_W='proNo'
_V='tenant'
_U='member'
_T='promotType'
_S='userId'
_R='target'
_Q='srvrt'
_P='subBusiTypeCode'
_O='serialNo'
_N='serCat'
_M='srvCode'
_L='0902'
_K='uscInfo'
_J='busiTypeCode'
_I='channelCode'
_H='code'
_G=None
_F='1'
_E='source'
_D='funcCode'
_C='serviceCode'
_B='errcode'
_A='data'
import json,time,aiohttp,urllib.parse,datetime
from.utils.logger import LOGGER
from.utils.store import async_save_to_store
from.utils.crypt import a,b,c,d,e
configuration={_K:{_U:_L,_X:'',_Y:'',_V:_u},_E:_Z,_R:'32101',_I:_L,_AJ:_L,'toPublish':_a,'siteId':'2012000000033700',_M:'',_O:'',_D:'',_C:{_v:_i,'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:_L},'subscriptionList':{_M:'APP_SGPMS_05_030',_O:'22',_I:_L,_D:'22',_R:'-1'},'userInformation':{_C:'01008183',_E:_Z},'userInform':{_C:_n,_E:_Z},'elesum':{_I:_L,_D:_w,_c:_F,_T:_F,_C:'0101143',_E:_x},_A2:{_I:_L,_D:'WEBA1007200'},_AS:{_E:_L,_R:'-1',_I:_o,_AJ:_o,_C:_A3,_D:'WEBA40050000',_K:{_U:_L,_X:'',_Y:'',_V:_u}},'doorAuth':{_E:_Z,_C:'f04'},'xinZ':{_N:'101',_AT:'101','fJ_busiTypeCode':'102',_AU:'03','fJ_custType':'02',_AV:_a,_P:'',_D:_AK,_v:_i,_E:_Z,_A4:_F},'onedo':{_C:_A9,_E:_Z,_D:_AK,'queryType':'03'},'xinHuTongDian':{_N:'110',_J:'211',_P:'21102',_D:'WEBA10071200',_I:_L,_E:_o,_C:_n},'company':{_N:'104',_D:_AK,_AV:'02',_A4:_F,_p:_F,_E:_Z,_v:_i},'charge':{_I:_o,_D:'WEBA10071300',_AJ:'0901',_N:'102',_AU:_a,_AT:'102'},'other':{_I:_o,_D:'WEBA10079700',_N:'129',_J:'999',_P:'21501',_C:_y,_M:'',_O:''},'vatchange':{'submit':'0101003',_J:'320',_P:'',_N:'115',_D:'WEBA10074000',_p:_F},'bill':{_f:_F,_D:_w,_T:_F,_C:_y},_m:{_I:_L,_D:_w,_T:_F,_f:_o,_C:_y,_E:_x},_h:{_I:_L,_f:'11',_D:_w,_c:_F,_T:_F,_C:_y,_E:_x},'mouthOut':{_I:_L,_f:'11',_D:_w,_c:_F,_T:_F,_C:_y,_E:_x},'meter':{_N:'114',_J:'304',_D:'WEBA10071000',_P:'',_C:_A9,_O:''},'complaint':{_J:'005','srvMode':_L,'anonymousFlag':'0','replyMode':_a,'retvisitFlag':_a},'report':{_J:'006'},'tradewinds':{_J:'019'},'somesay':{_J:'091'},'faultrepair':{_D:_AW,_C:_n,_N:'111',_J:'001',_P:'21505'},'electronicInvoice':{_N:'105',_J:'0'},'rename':{_C:_A9,_D:'WEBA10076100',_J:'210',_N:'109',_p:_F,'gh_busiTypeCode':'211','gh_subusi':'21101',_O:'',_M:''},'pause':{_P:'',_C:_A3,_D:'WEBA10073600',_N:'107',_J:'203','jr_busi':'201',_O:'',_M:''},'capacityRecovery':{_C:_A3,_E:_Z,_M:'',_O:'',_D:'WEBA10073700','busiTypeCode_stop':'204','busiTypeCode_less':'202',_J:'202',_P:'',_N:'108',_AL:'5',_p:_F},'electricityPriceChange':{_C:_n,_J:'215',_P:'21502',_N:'113',_p:_F,_AL:'15',_D:'WEBA10073900WEB',_M:'',_O:''},'electricityPriceStrategyChange':{_C:'01008183',_J:'215',_P:'21506',_N:'160',_D:'WEBV00000517WEB',_M:'',_O:''},'eemandValueAdjustment':{_C:_n,_M:'',_O:'',_N:'112',_D:'WEBA10073800',_J:'215',_P:'21504',_p:_F,_AL:'5','getMonthServiceCode':_A9},'businessProgress':{_C:_n,_M:_a,_D:'WEB01'},'increase':{_E:_Z,_O:'',_M:'',_AX:_A3,_C:_i,_v:_i,_D:_AM,_A4:_F,_N:'106',_J:'111',_P:''},'fjincrea':{_N:'105',_J:'110',_P:'',_E:_Z,_D:_AM,_O:'',_M:'',_AX:_A3,_C:_i,_v:_i,_A4:_F},'persIncrea':{_N:'105',_J:'109',_v:_i,_P:'',_E:_Z,_D:_AM,_A4:_F},'fgdChange':{_C:_n,_M:_a,_I:_o,_D:_AW,_J:'215',_P:'21505',_N:'111',_p:_F},'createOrder':{_I:_L,_D:_w,_M:'BCP_000001','chargeMode':'02','conType':_a,'bizTypeId':'BT_ELEC'},'largePopulation':{_J:'383',_D:'WEBA10076800',_P:'',_M:'',_T:'',_c:'',_I:'0901',_N:'383',_C:'',_O:''},'biaoJiCode':{_C:'0104507',_E:'1704',_I:'1704'},'twoGuar':{_J:'402',_P:'40201',_D:'web_twoGuar'},'electTrend':{_C:_AY,_I:_L},'emergency':{_C:_AY,_D:'A10000000',_I:_L},'infoPublic':{_C:'2545454',_E:_x}}
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=_AZ
get_door_daily_bill_api=_AZ
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=_j)
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=_G;session=_G;keyCode=_G;publicKey=_G;need_login=_j;phone=_G;codeKey=_G;serialNo=_G;qrCodeSerial=_G;userInfo=_G;accountInfo=_G;powerUserList=_G;doorAccountDict={};cookie=[];timestamp=int(time.time()*1000);accessToken=_G;refreshToken=_G;token=_G;expirationDate=_G;refresh_interval=6;is_debug=_j
def __init__(A,hass,config=_G):
B=config;A.hass=hass;C=aiohttp.TCPConnector(ssl=_j);D=aiohttp.CookieJar(quote_cookie=_q);A.session=aiohttp.ClientSession(cookie_jar=D,connector=C)
if B is not _G:
try:A.keyCode=B[_A5];A.publicKey=B[_AN];A.accessToken=B[_Aa];A.refreshToken=B[_Ab];A.token=B[_k];A.userInfo=B[_AA];A.powerUserList=B[_AO];A.doorAccountDict=B[_Ac];A.refresh_interval=B[_Ad];A.is_debug=B['is_debug']
except Exception as E:LOGGER.error(E)
async def save_data(A):B={};B[_A5]=A.keyCode;B[_AN]=A.publicKey;B[_Aa]=A.accessToken;B[_Ab]=A.refreshToken;B[_k]=A.token;B[_AA]=A.userInfo;B[_AO]=A.powerUserList;B[_Ac]=A.doorAccountDict;B[_Ad]=A.refresh_interval;B['is_debug']=A.is_debug;await async_save_to_store(A.hass,'state_grid.config',B)
def encrypt_post_data(A,data):B={_Ae:A.accessToken[len(A.accessToken)//2:]if A.accessToken else'','_t':A.token[len(A.token)//2:]if A.token else'','_data':data,_d: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)),_AB:d(A.keyCode,A.publicKey),_d:str(A.timestamp)}
def handle_request_result_message(E,api,result):
D='message';C='resultMessage';A=result;B=_G
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)
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 _q:return B
else:return await C.__fetch(api,data)
else:return B
async def __fetch(B,api,data,header=_G):
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 _G: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',_d: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)),_AB:d(F,'042BC7AD510BF9793B7744C8854C56A8C95DD1027EE619247A332EC6ED5B279F435A23D62441FE861F4B0C963347ECD5792F380B64CA084BE8BE41151F8B8D19C8'),I:appKey,_d:str(D)}
elif E==get_qr_code_api:A={_Ae:'','_t':'','_data':A,_d:D}
elif E==get_request_authorize_api:
A={I:appKey,'response_type':_H,_Af:'/test',_d:D,'rsi':B.token};A=urllib.parse.urlencode(A);G[M]='application/x-www-form-urlencoded; charset=UTF-8';G[_A5]=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,_d:D,_H:A[_H]};H=a(json_dumps(A),F);A={_A:H+c(H+str(D)),_AB:d(F,B.publicKey),_d:str(D)}
elif E==refresh_web_token_api:A={O:_AC,P:c(appKey+str(D)),K:appSecret,Q:S,R:F,I:appKey,_d:D,_AC:B.refreshToken};H=a(json_dumps(A),F);A={_A:H+c(H+str(D)),_AB:d(F,B.publicKey),_d:str(D)};E=get_web_token_api
else:A=B.encrypt_post_data(A)
if L is not _G:G.update(L)
if E in sessionIdControlApiList:G['sessionId']='web'+str(D)
if E in keyCodeControlApiList:G[_A5]=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=_G;B=await A.__fetch(get_request_key_api,{});C=A.handle_request_result_message('get_request_key_api',B)
if B[_H]==_F:A.keyCode=B[_A][_A5];A.publicKey=B[_A][_AN];return{_B:0}
return{_B:1,_b:C}
async def __get_qr_code(B):
C={_K:{_X:'',_V:_u,_U:_L,_Y:''},_z:{'optType':_a,_O: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][_A0]==_AD:B.qrCodeSerial=A[_A][_g][_Ag];E=A[_A][_g]['qrCode'];return{_B:0,_A:E}
return{_B:1,_b:D}
async def __get_qr_code_status(B):
C={_g:{_Ag: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{_B:0}
else:return{_B:1,_b:'未使用网上国网 App 扫码或确认登录'}
return{_B:1,_b:E}
async def __get_qr_code_token(B):
C={_K:{_V:_u,_U:_L,'isEncrypt':_q},_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 _Q in A and _A0 in A[_Q]and A[_Q][_A0]==_AD:B.userInfo=A[_g][_AA];return{_B:0}
return{_B:1,_b:D}
async def __send_code(B,phone):
C=phone;B.phone=C;D={_K:{_X:'',_V:_u,_U:_L,_Y:''},_z:{'sendType':'0',_A2:C,_Ah:'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][_A0]==_AD:B.codeKey=A[_A][_g]['codeKey'];return{_B:0}
return{_B:1,_b:E}
async def __verfiy_code(A,code):
C={_K:{_X:'',_V:_u,_U:_L,_Y:''},_z:{_A2:A.phone,_Ah:'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 _A0 in B[_Q]and B[_Q][_A0]==_AD:A.token=B[_g][_k];A.userInfo=B[_g][_AA][0];return{_B:0}
return{_B:1,_b: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]==_F:C=A[_A][_Af];D=C.rfind('code=');B.authorizeCode=C[D+5:D+5+32];return{_B:0}
return{_B:1,_b: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]==_F:A.accessToken=B[_A][_Ai];A.refreshToken=B[_A][_AC];return{_B:0}
return{_B:1,_b: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]==_F:B.accessToken=A[_A][_Ai];B.refreshToken=A[_A][_AC];return{_B:0}
return{_B:1,_b:C}
async def __get_door_number(C):
A=configuration[_AS];D={_C:A[_C],_E:A[_E],_R:A[_R],_K:{_U:A[_K][_U],_X:A[_K][_X],_Y:A[_K][_Y],_V:A[_K][_V]},_z:{_S:C.userInfo[_S]},_k:C.token};B=await C.__fetch_safe(get_door_number_api,D);E=C.handle_request_result_message('get_door_number_api',B)
if _H in B and B[_H]==1 and _A in B and _g in B[_A]:C.powerUserList=B[_A][_g][_AO];return{_B:0}
return{_B:1,_b:E}
async def __get_door_balance(B,door_account):
A=door_account;E={_A:{_M:'',_O:'',_I:configuration[_A2][_I],_D:configuration[_A2][_D],_A6:B.userInfo[_S],_A7:B.userInfo.get(_AE,B.userInfo.get('nickname',_G)),_T:_F,_c:_F,_AP:B.userInfo[_S],_A8:[{'consNoSrc':A[_s],_AF:A.get(_W,A.get(_AG,_G)),'sceneType':A.get('consSortCode',A.get('elecTypeCode',_G)),_r:A[_r],_e:A[_e]}]},_C:'0101143',_E:configuration[_E],_R:A.get(_W,A.get(_AG,_G))};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 _A8 in C[_A]:
D=C[_A][_A8]
if len(D)!=0:A[_A1]=D[0]
async def __get_door_bill(C,door_account,year):
E='dataInfo';D='mothEleList';B=door_account;F={_A:{_A6:C.userInfo[_S],_I:configuration[_I],_f:'11',_AQ:B[_Al],_D:'ALIPAY_01',_e:B[_e],_AF:B[_W],_c:_F,_T:_F,_O:'',_M:'',_A7:'',_Aj:B[_W],_AP:C.userInfo[_S],_r:B[_r],_Ak:year},_C:_y,_E:_x,_R:B[_W]};A=await C.__fetch_safe(get_door_bill_api,F);C.handle_request_result_message('get_door_bill_api',A)
if _H in A and A[_H]==1 and _A in A:
if E in A[_A]:B[_AH]=A[_A][E]
if D in A[_A]:
B[_AI]=A[_A][D]
if len(A[_A][D])!=0:B[_t]=A[_A][D][-1]
async def __get_door_ladder(B,door_account,month):
F='ladder_flag';E=month;A=door_account;G=A[_s];H={_A:{_I:configuration[_m][_I],_D:configuration[_m][_D],_T:configuration[_m][_T],_f:configuration[_m][_f],_r:A[_s],_c:A[_W],_e:A[_e],'queryDate':E,_Aj:A[_W],_AQ:A[_Al],_AP:B.userInfo[_S],_O:'',_M:'',_A7:B.userInfo[_AE],_A6:B.userInfo[_S]},_C:configuration[_m][_C],_E:configuration[_m][_E],_R:A[_W]};C=await B.__fetch(get_door_ladder_api,H);I=B.handle_request_result_message('get_door_ladder_api',C)
if _H in C and C[_H]==1 and _A in C and _A8 in C[_A]:
D=C[_A][_A8]
if len(D)!=0:
D=D[0];D[_AR]=E
if D['electricParticulars']['levelFlag']=='2':A[F]=1
else:A[F]=0
B.doorAccountDict[G]['ladder']=D
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],_R:configuration[_R],_K:{_U:configuration[_K][_U],_X:configuration[_K][_X],_Y:configuration[_K][_Y],_V:configuration[_K][_V]},_z:{_S:B.userInfo[_S]},_k:B.token},'params3':{_A:{_A6:B.userInfo[_S],_r:A[_s],_AQ:_a,'endTime':end_date,_e:A[_e],_Ak:year,_AF:A.get(_W,A.get(_AG,_G)),_O:'',_M:'','startTime':start_date,_A7:B.userInfo[_AE],_D:configuration[_h][_D],_I:configuration[_h][_I],_f:configuration[_h][_f],_c:configuration[_h][_c],_T:configuration[_h][_T]},_C:configuration[_h][_C],_E:configuration[_h][_E],_R:A.get(_W,A.get(_AG,_G))},'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[_l]=C[_A][D]
async def __get_door_pay_record(A,door_account):B=door_account;D=B[_s];C={'params1':{_C:configuration[_C],_E:configuration[_E],_R:configuration[_R],_K:{_U:configuration[_K][_U],_X:configuration[_K][_X],_Y:configuration[_K][_Y],_V:configuration[_K][_V]},_z:{_S:A.userInfo[_S]},_k:A.token},'params3':{_A:{_A6:A.userInfo[_S],'bgnPayDate':'2023-04-24',_I:configuration[_I],_r:B[_s],'endPayDate':'2024-04-24',_D:'webALIPAY_01','number':100,_e:B[_e],'page':_F,_AF:B[_W],_c:_F,_T:_F,_O:'',_M:'',_A7:A.userInfo[_AE]},_C:'0101051',_E:_a,_R:B[_W]},'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=_j;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=_j;await A.save_data()
else:A.need_login=_q;LOGGER.error('国家电网刷新 Token 失败')
async def refresh_data(C,setup=_j):
l='last_month_ele_cost';k='monthEleNum';j='year_ele_cost';i='thisTPq';h='thisNPq';g='thisVPq';f='thisPPq';e='refresh_time';U=setup;T='ladder_level_num';S='ladder_level';R='last_month_ele_num';Q='year_ele_num';L='%Y%m%d';K='day';I='dayElePq';H='balance'
if U is _q:
V=await C.__get_door_number()
if _B in V and V[_B]!=0:C.need_login=_q
if C.need_login is _q:
if C.powerUserList is not _G:
for A in C.get_door_account_list():A[e]='刷新 Token 失败,需重新登录!'
LOGGER.error('国家电网需要重新登录!');return
m=U or int(time.time()*1000)-C.timestamp>C.refresh_interval*3600*1000
if m is _j:return
M=datetime.datetime.now();J=M-datetime.timedelta(days=1);n=f"{J.year}-{J.month:02d}-{J.day:02d}";N=J-datetime.timedelta(days=40);o=f"{N.year}-{N.month:02d}-{N.day:02d}"
for A in C.powerUserList:
p=A[_s];C.doorAccountDict[p]=A;await C.__get_door_balance(A)
if _A1 in A:
q=catchFloat(A[_A1],'estiAmt');W=catchFloat(A[_A1],'prepayBal');r=catchFloat(A[_A1],'sumMoney')
if W==0:A[H]=r
else:A[H]=W-q
X=catchFloat(A[_A1],'historyOwe')
if X>0:A[H]=-X
else:LOGGER.error('国家电网账户余额获取失败!')
if H not in A:A[H]=0
await C.__get_door_daily_bill(A,M.year,o,n)
if _l not in A:LOGGER.error('国家电网无法获取日用电数据!');return
O=0
for Y in range(10):
D=A[_l][Y]
try:float(D[I]);break
except:O=O+1
for Y in range(O):A[_l].pop(0)
D=A[_l][0];E=datetime.datetime.strptime(D[K],L);A['daily_ele_num']=catchFloat(D,I);A['daily_p_ele_num']=catchFloat(D,f);A['daily_v_ele_num']=catchFloat(D,g);A['daily_n_ele_num']=catchFloat(D,h);A['daily_t_ele_num']=catchFloat(D,i);Z=0;a=0;b=0;c=0;d=0
for B in A[_l]:
G=datetime.datetime.strptime(B[K],L)
if G.month!=E.month:break
Z+=catchFloat(B,I);a+=catchFloat(B,f);b+=catchFloat(B,g);c+=catchFloat(B,h);d+=catchFloat(B,i)
A['month_ele_num']=normal_round(Z,2);A['month_p_ele_num']=normal_round(a,2);A['month_v_ele_num']=normal_round(b,2);A['month_n_ele_num']=normal_round(c,2);A['month_t_ele_num']=normal_round(d,2);P=E-datetime.timedelta(days=E.day);s=f"{P.year}-{P.month:02d}"
if _t not in A or A[_t][_AR]!=s:await C.__get_door_bill(A,P.year)
if _t not in A:LOGGER.error('国家电网无法获取年度用电数据!');return
if _AH in A:A[Q]=catchFloat(A[_AH],'totalEleNum');A[j]=catchFloat(A[_AH],'totalEleCost')
if Q not in A:A[Q]=0;A[j]=0
if _AI in A:A[R]=catchFloat(A[_t],k);A[l]=catchFloat(A[_t],'monthEleCost')
if R not in A:A[R]=0;A[l]=0
t=datetime.datetime.strptime(A[_t][_AR],'%Y%m')
if t.month==12:
F=0
for B in A[_l]:
G=datetime.datetime.strptime(B[K],L)
if G.month!=12:break
F+=catchFloat(B,I)
else:
F=0
for B in A[_AI]:F+=catchFloat(B,k)
u=len(A[_AI])
for B in A[_l]:
G=datetime.datetime.strptime(B[K],L)
if G.month<=u:break
F+=catchFloat(B,I)
if F<=2760:A[S]='第一阶梯';A[T]=1
elif F<=4800:A[S]='第二阶梯';A[T]=2
else:A[S]='第三阶梯';A[T]=3
A['daily_lasted_date']=f"{E.year}-{E.month:02d}-{E.day:02d}";A[e]=datetime.datetime.strftime(M,'%Y-%m-%d %H:%M:%S')
await C.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.2"
}
+179
View File
@@ -0,0 +1,179 @@
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_DU = ""
UNIT_YUAN = ""
ENTITY_ID_SENSOR_FORMAT = SENSOR_DOMAIN + ".state_grid_"
SENSOR_TYPES = [
{
"key":"balance",
"name": "账户余额",
"unit_of_measurement": UNIT_YUAN,
"native_unit_of_measurement": UNIT_YUAN
},
{
"key": "year_ele_num",
"name": "年度累计用电",
"unit_of_measurement": UNIT_DU,
"native_unit_of_measurement": UNIT_DU
},
{
"key": "year_ele_cost",
"name": "年度累计电费",
"unit_of_measurement": UNIT_YUAN,
"native_unit_of_measurement": UNIT_YUAN
},
{
"key": "last_month_ele_num",
"name": "上个月用电",
"unit_of_measurement": UNIT_DU,
"native_unit_of_measurement": UNIT_DU
},
{
"key": "last_month_ele_cost",
"name": "上个月电费",
"unit_of_measurement": UNIT_YUAN,
"native_unit_of_measurement": UNIT_YUAN
},
{
"key": "month_ele_num",
"name": "当月累计用电",
"unit_of_measurement": UNIT_DU,
"native_unit_of_measurement": UNIT_DU
},
{
"key": "month_p_ele_num",
"name": "当月累计峰用电",
"unit_of_measurement": UNIT_DU,
"native_unit_of_measurement": UNIT_DU
},
{
"key": "month_v_ele_num",
"name": "当月累计谷用电",
"unit_of_measurement": UNIT_DU,
"native_unit_of_measurement": UNIT_DU
},
{
"key": "month_n_ele_num",
"name": "当月累计平用电",
"unit_of_measurement": UNIT_DU,
"native_unit_of_measurement": UNIT_DU
},
{
"key": "month_t_ele_num",
"name": "当月累计尖用电",
"unit_of_measurement": UNIT_DU,
"native_unit_of_measurement": UNIT_DU
},
{
"key": "daily_ele_num",
"name": "日总用电",
"unit_of_measurement": UNIT_DU,
"native_unit_of_measurement": UNIT_DU
},
{
"key": "daily_p_ele_num",
"name": "日峰用电",
"unit_of_measurement": UNIT_DU,
"native_unit_of_measurement": UNIT_DU
},
{
"key": "daily_v_ele_num",
"name": "日谷用电",
"unit_of_measurement": UNIT_DU,
"native_unit_of_measurement": UNIT_DU
},
{
"key": "daily_n_ele_num",
"name": "日平用电",
"unit_of_measurement": UNIT_DU,
"native_unit_of_measurement": UNIT_DU
},
{
"key": "daily_t_ele_num",
"name": "日尖用电",
"unit_of_measurement": UNIT_DU,
"native_unit_of_measurement": UNIT_DU
},
{
"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 "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全屋智能 微信公众号联系客服"