とりあえず、hems のデータを gatewayから取得できることがわかったので、pythonのscriptで influxdbにデータを送り込むスクリプトを作成し、5分毎に起動することに。
#
#
# https://echonet.jp/wp/wp-content/uploads/pdf/General/Standard/Release/Release_R/Appendix_Release_R_r2.pdf
# 住宅用太陽光発電クラス規定: 3-206
# クラスグループコード: 0x02
# クラスコード : 0x79
# インスタンスコード : 0x01 - 0x7F
#
import asyncio
import netifaces
import socket
import sys
#
# for influxdb
#
from influxdb_client import InfluxDBClient,Point,WritePrecision
from influxdb_client.client.write_api import SYNCHRONOUS
#
UDP_RESES = []
UDP_PORT = 3610 # for UDP
GWIP = "xxx.xxx.xxx.xxx" # Gateway IP address
INTERVAL = 0.03 # udp 送信のインターバル (sec)
#
# for influxdb2
#
org = "homemetrics"
bucket = "environment"
token = "api token"
client = InfluxDBClient(url="http://xxx.xxx.xxx.xxx:8086",token=token,org=org)
write_api = client.write_api(write_options=SYNCHRONOUS)
query_api = client.query_api()
#
async def main():
# 自身のipアドレスを探索
local_ip = UdpSender.find_local_ip_addr()
if local_ip is None:
print("ローカルIPアドレスが見つかりませんでした。")
return
#print(f"ローカルIPアドレス: {local_ip}")
# echonetのコマンドをudp送信
sender = UdpSender(local_ip)
# echonetのコマンド受信用
recv = UdpReceiver()
await recv.start()
echonet_cmd = echonet_cmd_pro("80") # 0x80: 動作状態 :unsigned char: 1 Byte
await sender.send_msg(GWIP, echonet_cmd) # 動作状態を確認
await asyncio.sleep(INTERVAL)
echonet_cmd = echonet_cmd_pro("89") # 89: 異常内容 :unsigend short: 2 Byte
await sender.send_msg(GWIP, echonet_cmd) # 動作状態を確認
await asyncio.sleep(INTERVAL)
echonet_cmd = echonet_cmd_pro("97") # 97: 現在時刻設定 :unsigend char: 2 Byte
await sender.send_msg(GWIP, echonet_cmd) # 動作状態を確認
await asyncio.sleep(INTERVAL)
echonet_cmd = echonet_cmd_pro("98") # 98: 現在年月日設定 :unsigend char: 4 Byte
await sender.send_msg(GWIP, echonet_cmd) # 動作状態を確認
await asyncio.sleep(INTERVAL)
echonet_cmd = echonet_cmd_pro("E0") # E0: 瞬時発電電力計測値(W) :unsigend short : 2 Byte
await sender.send_msg(GWIP, echonet_cmd) # 動作状態を確認
await asyncio.sleep(INTERVAL)
echonet_cmd = echonet_cmd_pro("E1") # E1: 積算発電電力量計測値(kWh) :unsigend short : 4 Byte
await sender.send_msg(GWIP, echonet_cmd) # 動作状態を確認
await asyncio.sleep(INTERVAL)
sender.close()
# 複数機器からレスポンスがある場合があるので、少々、待つ
await asyncio.sleep(5)
# UDP_RESES に各機器からのレスポンスが、echonet電文とIPで入ってます
for udp_res in UDP_RESES:
echonet_res = parse_echonet_res(udp_res[0])
#print(udp_res)
print(echonet_res)
await recv.stop()
print("end")
for udp_res in UDP_RESES:
echonet_res = parse_echonet_res(udp_res[0]) #
match echonet_res[6]:
case "80": # 動作状態: 0x30 on, 0x31 off
print("80 start")
data80 = echonet_res[8]
print(data80)
case "89": # 異常内容 : 上位1バイト 小分類 、下位1バイト 大分類
print("89 start")
data89 = echonet_res[8]
print(data89)
case "97": # 現在時刻設定 0x00-0x17=0-23, 0x00-0x3B = 0-59
print("97 start")
data97 = str(int(echonet_res[8][:2],16)).zfill(2) + ':' + str(int(echonet_res[8][2:],16)).zfill(2)
print(data97)
case "98": # 現在年月日設定 1-0x270F = 1-9999, 1-0x0C = 1-12, 1-0x1F = 1-31
print("98 start")
data98 = str(int(echonet_res[8][:4],16)) + '-' + str(int(echonet_res[8][4:6],16)).zfill(2) + '-' + str(int(echonet_res[8][6:],16)).zfill(2)
print(data98)
case "e0": # 瞬時発電電力量計測値(W) 0x0000-0xfffd = 0-65533
print("e0 start")
datae0 = int(echonet_res[8],16)
print(datae0)
case "e1": # 積算発電電力量計測値(0.001kWh) 0x00000000-0x3b9ac9ff = 0 - 999,999.999 kWh
print("e1 start")
datae1 = float(int(echonet_res[8],16)/1000)
print(datae1)
#
# for influxdb
#
p = Point("hems_02791f")\
.tag("location","home")\
.field("data80",data80)\
.field("data89",data89)\
.field("data97",data97)\
.field("data98",data98)\
.field("datae0",datae0)\
.field("datae1",datae1)
write_api.write(bucket=bucket,record=p,write_precision=WritePrecision.S)
def parse_echonet_res(echonet_res):
res_cols = [echonet_res[0:4], # echonetであることの宣言
echonet_res[4:8], # 自由欄
echonet_res[8:14], # SEOJ(送信元機器) 0ef001=ノード
echonet_res[14:20], # DEOJ(送信先機器) 05ff01=コントローラ
echonet_res[20:22], # 応答code. 71=set 72=get
echonet_res[22:24], # 処理プロパティ数
echonet_res[24:26], # プロパティ名 d6=自ノードlist.
echonet_res[26:28], # PDC. 後のbyte数
echonet_res[28:] # 0105ff01 = 1個(01)x05ff01
]
return res_cols
def echonet_cmd_pro(property):
cmd_cols = ["1081", # echonetであることの宣言
"0000", # 自由欄
"05ff01", # SEOJ(送信元機器) 05ff01=コントローラ
"02791f", # DEOJ(送信先機器) 02791f=太陽光発電?
"62", # 60=set, 61=set(要:応答), 62=get
"01", # 処理プロパティ数
property, #
"00"]
echonet_cmd = "".join(cmd_cols)
return echonet_cmd
class UdpSender():
def __init__(self, local_ip):
self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.sock.setsockopt(socket.IPPROTO_IP,
socket.IP_MULTICAST_IF,
socket.inet_aton(local_ip))
async def send_msg(self, ip, message):
msg = bytes.fromhex(message)
loop = asyncio.get_running_loop()
await loop.run_in_executor(None, self.sock.sendto, msg, (ip, UDP_PORT))
def close(self):
self.sock.close()
@staticmethod
def find_local_ip_addr(find_iface_name=None):
for iface_name in netifaces.interfaces():
iface_data = netifaces.ifaddresses(iface_name)
af_inet = iface_data.get(netifaces.AF_INET)
if not af_inet:
continue
ip_addr = af_inet[0]["addr"]
if find_iface_name is None:
return ip_addr
elif iface_name == find_iface_name:
return ip_addr
return None
class UdpReceiver():
def __init__(self):
self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.sock.bind(("0.0.0.0", UDP_PORT))
self.sock.settimeout(5.0)
self.task = asyncio.create_task(self.receive_loop())
self.running = True
async def start(self):
loop = asyncio.get_running_loop()
self.task = loop.create_task(self.receive_loop())
async def receive_loop(self):
loop = asyncio.get_running_loop()
while self.running:
try:
data, addr = await asyncio.get_running_loop().run_in_executor(None, self.sock.recvfrom, 4096)
global UDP_RESES
UDP_RESES.append([data.hex(), addr[0]])
#print(f"データ受信: {data.hex()}, 送信元アドレス: {addr[0]}")
except socket.timeout:
print("socket timeout")
continue
except (OSError, asyncio.CancelledError):
break
async def stop(self):
self.running = False
self.sock.close()
self.task.cancel()
try:
await self.task
except asyncio.CancellerdError:
pass
if __name__ == '__main__':
asyncio.run(main())
UDPの受けのアドレスは '0.0.0.0' を明示的に指定しないと受信してくれなかった。ちょっとはまったかも。
あと、influxdb 側のデータ受信日時が、どこかで丸められていて 分単位になっていることがちょっとあったので、
明示的に、WritePrecision.S で秒単位で記録させることに。さすがに、ms,ns,us レベルは必要ないだろう。
コメントする