0.前言介绍
之前一直想把水电燃气都接入HA,水和燃气都接入了,电费一直没有接入。一开始尝试网上抓包,有个变量没摸透它的规律,所以没办法入手。后面发现小程序也可以查电费,所以准备从小程序入手,抓包看看能不能够有什么发现。
1.抓包准备
搜索微信小程序 国网江苏电力营业厅 抓下包。抓包的工具可以使用 Fiddler 苹果手机上可以使用 Stream 都是挺好用的。
2.抓包
在抓包的结果中找找有没有哪个返回金额等相关数据的。找了找,找到了 https://weixin.js.sgcc.com.cn/wxapp_dlsh/wx/oauth_executeNewMini.do?openid=xxxxxxxxx×tamp=xxxxx&noncestr=xxxxxxx&sign=xxxxxxxx&unionid=xxxxxxx&userInfo=null 这个地址。通过多次对比这个链接,发现
openid=xxxxxxxxx //固定的
timestamp=xxxxx // 变动的
noncestr=xxxxxxx //变动的
sign=xxxxxxxx // 变动的
unionid=xxxxxxx //固定的
timestamp noncestr sign 这三个参数是有关联关系,不能够随意改动。这个地址的返回结果中也会返回这三个字段,所以我们不必考虑他们之间的关系,再次调用是,把之前的参数替换成现在最新的即可。
3.编写插件
分析到上面那步之后插件写起来就简单了,其实就是解析参数赋值的事情了。代码如下:
"""
Support for guojiadianwang
# Author:
freefitter
# Created:
2020/9/2
"""
import logging
import json
import configparser
import time, datetime
from dateutil.relativedelta import relativedelta
from homeassistant.const import (
CONF_API_KEY, CONF_NAME, ATTR_ATTRIBUTION, CONF_ID
)
from homeassistant.helpers.entity import Entity
import homeassistant.helpers.config_validation as cv
import voluptuous as vol
from homeassistant.components.sensor import PLATFORM_SCHEMA
import requests
from bs4 import BeautifulSoup
_Log=logging.getLogger(__name__)
DEFAULT_NAME = 'gjdw'
OPENID = 'openid'
NONCESTR = 'noncestr'
SIGN = 'sign'
UNIONID = 'unionid'
TIMESTAMP1 = 'timestamp'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(OPENID): cv.string,
vol.Required(NONCESTR): cv.string,
vol.Required(SIGN): cv.string,
vol.Required(UNIONID): cv.string,
vol.Required(TIMESTAMP1): cv.string,
vol.Optional(CONF_NAME, default= DEFAULT_NAME): cv.string,
})
HEADERS = {
'Host': 'weixin.js.sgcc.com.cn',
'Connection': 'keep-alive',
'Content-Length': '0',
'content-type': 'application/json',
'Accept-Encoding': 'gzip,compress,br,deflate',
'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 14_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 MicroMessenger/8.0.0(0x18000026) NetType/WIFI Language/zh_CN',
'Referer': 'https://servicewechat.com/wx203b37ad2ad5d2a6/32/page-frame.html',
}
API_URL = "https://weixin.js.sgcc.com.cn/wxapp_dlsh/wx/oauth_executeNewMini.do"
def setup_platform(hass, config, add_devices, discovery_info=None):
sensor_name = config.get(CONF_NAME)
openid = config.get(OPENID)
unionid = config.get(UNIONID)
cfg = configparser.ConfigParser()
cfg.read('param.ini')
timestamp1 = cfg.get('Param', 'timestamp')
noncestr = cfg.get('Param', 'noncestr')
sign = cfg.get('Param', 'sign')
url = API_URL + "?openid=" + openid +"×tamp=" + timestamp1 + "&noncestr=" + noncestr + "&sign=" + sign + "&unionid=" + unionid + "&userInfo=null"
_Log.info("url:" + url )
add_devices([GJDW(sensor_name,url,openid,unionid,cfg)])
class GJDW(Entity):
"""Representation of a guojiadianwang """
def __init__(self,sensor_name,url,openid,unionid,cfg):
self.attributes = {}
self._state = None
self._name = sensor_name
self._url = url
self._openid = openid
self._unionid = unionid
self._cfg = cfg
@property
def name(self):
"""Return the name of the sensor."""
return self._name
@property
def state(self):
"""Return the state of the sensor."""
return self._state
@property
def icon(self):
""" mdi ."""
return 'mdi:flash'
@property
def device_state_attributes(self):
"""Return the state attributes."""
return self.attributes
@property
def unit_of_measurement(self):
"""Return the unit this state is expressed in."""
return "元"
def update(self):
try:
"""_Log.error("url:" + self._url)"""
response = requests.post(self._url,headers = HEADERS)
except ReadTimeout:
_Log.error("Connection timeout....")
except ConnectionError:
_Log.error("Connection Error....")
except RequestException:
_Log.error("Unknown Error")
res = response.content.decode('utf-8')
if res.find("html") >= 0 :
_Log.error("参数存在问题,请重新抓包填写")
return
"""_Log.error(res)"""
ret = json.loads(res)
if ret["errcode"] == "0000":
if "owe_amt" in ret["yeModel"] :
self._state = round(float(ret["yeModel"]["owe_amt"]), 2)
self.attributes['rca_flag'] = ret["yeModel"]["rca_flag"]
self.attributes['cur_amt'] = ret["yeModel"]["cur_amt"]
curfeeMsg = json.loads(ret["curfee"])
self.attributes['ymd'] = curfeeMsg["ymd"]
self.attributes['totalMoney'] = curfeeMsg["totalMoney"]
self.attributes['powerSum'] = curfeeMsg["powerSum"]
dbObjMsg = json.loads(ret["dbObj"])
self.attributes['stepNo'] = dbObjMsg["stepNo"]
self.attributes['yearAmount'] = dbObjMsg["yearAmount"]
self.attributes['isUpdated'] = 'yes'
else:
self.attributes['rca_flag'] = '-1'
self.attributes['cur_amt'] = '-1'
self.attributes['isUpdated'] = 'no'
#_Log.error("yeModel is null !")
# 更新url参数
url = API_URL + "?openid=" + self._openid +"×tamp=" + ret["timestamp"] + "&noncestr=" + ret["noncestr"] + "&sign=" + ret["sign"] + "&unionid=" + self._unionid + "&userInfo=null"
self._url = url
# 保存参数至文本
self._cfg.set("Param", "timestamp", ret["timestamp"])
self._cfg.set("Param", "noncestr", ret["noncestr"])
self._cfg.set("Param", "sign", ret["sign"])
self._cfg.write(open('param.ini','w'))
else:
_Log.error("send request error....")
如果重启会导致参数失效,所以插件把变动的参数保存了下来,每次插件更新的时候将相关的参数更新进去,这样重启时间不长的话就不需要重新抓包了。
4.HA配置
在配置文件中加上如下内容:
- platform: gjdw
name: '电费'
openid: 'xxxxxxxxx-xxxxxxxxx'
noncestr: 'xxxxxxxxx'
sign: 'xxxxxxxxx'
unionid: 'xxxxxxxxx'
timestamp: 'xxxxxxxxx'
增加 param.ini 配置文件,文件内容如下
[Param]
timestamp = xxxxxxxxx
noncestr = xxxxxxxxx
sign = xxxxxxxxx
5.成果展示
6.附件下载
点击下载 插件
或者去论坛我发的 电费插件-这次应该全国通用了吧 帖子下载。
7.结语
自己写写插件还是挺好玩的,最近玩了玩Node-Red和MQTT,感觉这个搞插件还是快的。有几个写好的插件可以先看看,抓了美食杰和微博热搜的数据