214 lines
8.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import paho.mqtt.client as mqtt
import json
import logging
import taosrest
from datetime import datetime
# 配置日志
logging.basicConfig(
filename='charge_jiuxing.log',
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
encoding='utf-8'
)
class ChargeJiuxingParser:
def __init__(self, mqtt_host="123.6.102.119", mqtt_port=1883, mqtt_username="emqx_test", mqtt_password="emqx_test",
tdengine_url="http://123.6.102.119:6041", tdengine_user="root", tdengine_password="taosdata"):
# MQTT 配置
self.mqtt_client = mqtt.Client(client_id="ChargeJiuxingParser", protocol=mqtt.MQTTv311,
callback_api_version=mqtt.CallbackAPIVersion.VERSION2)
self.mqtt_client.username_pw_set(mqtt_username, mqtt_password)
self.mqtt_client.on_connect = self.on_connect
self.mqtt_client.on_message = self.on_message
self.mqtt_host = mqtt_host
self.mqtt_port = mqtt_port
self.connected = False
# TDengine 配置
self.tdengine_url = tdengine_url
self.tdengine_user = tdengine_user
self.tdengine_password = tdengine_password
self.tdengine_conn = None
def connect(self):
"""连接 MQTT 和 TDengine"""
# 连接 MQTT
try:
self.mqtt_client.connect(self.mqtt_host, self.mqtt_port, 60)
self.mqtt_client.loop_start()
logging.info("Connected to MQTT broker")
except Exception as e:
logging.error(f"MQTT connection error: {str(e)}")
raise
# 连接 TDengine
try:
self.tdengine_conn = taosrest.connect(
url=self.tdengine_url,
user=self.tdengine_user,
password=self.tdengine_password,
database="tms_design"
)
logging.info("Connected to TDengine via REST")
except Exception as e:
logging.error(f"TDengine connection error: {str(e)}")
raise
def on_connect(self, client, userdata, flags, rc, properties=None):
if rc == 0:
self.connected = True
self.mqtt_client.subscribe("hejin/charging/log", qos=1)
logging.info("Subscribed to hejin/charging/log")
else:
logging.error(f"Failed to connect to MQTT broker with code: {rc}")
def on_message(self, client, userdata, msg, properties=None):
"""处理接收到的 MQTT 消息"""
try:
payload = msg.payload.decode('utf-8')
data = json.loads(payload)
logging.info(f"Received message: {data}")
# 解析报文并存储
sql = self.parse_and_generate_sql(data)
if sql:
self.tdengine_conn.execute(sql)
logging.info(f"Inserted into TDengine: {sql}")
except Exception as e:
logging.error(f"Error processing message: {str(e)}")
def hex_to_ascii(self, hex_str):
"""将 HEX 字符串转为 ASCII"""
try:
return bytes.fromhex(hex_str).decode('ascii').strip('\x00')
except:
return ""
def parse_bcd_time(self, bcd):
"""解析 BCD 码时间格式YYMMDDHHMMSS"""
try:
year = f"20{bcd[0:2]}" # 假设 19 表示 2019
month = bcd[2:4]
day = bcd[4:6]
hour = bcd[6:8]
minute = bcd[8:10]
second = bcd[10:12]
return f"{year}-{month}-{day}T{hour}:{minute}:{second}+08:00"
except:
return None
def parse_and_generate_sql(self, data):
"""解析报文并生成插入 SQL"""
hex_data = data[3].replace(" ", "") # 移除空格
pile_id = data[4] # 桩号
time = data[2].replace(" ", "T") + "+08:00" # 时间
# 提取基本字段
if len(hex_data) < 30: # 最小长度14字节HEX表示为28字符+校验码(2)
logging.warning(f"数据包长度不足: {hex_data}")
return None
company = hex_data[0:4] # 4A58
if company != "4A58":
logging.warning(f"无效帧起始: {company}")
return None
cmd = hex_data[4:6] # 命令码
length_str = hex_data[24:28] # 数据域长度
length = int(length_str, 16) * 2 # HEX字符数
data_domain = hex_data[28:28 + length] if length > 0 else ""
# 从桩号提取信息
operator_id = pile_id[0:4] # 运营商编号
station_id = pile_id[6:12] # 站点编号
charger_no = pile_id[12:16] # 站内桩地址(作为充电桩编号)
# 初始化默认值
battery_bun_id = "unknown"
org_code = "unknown"
merchant_id = "unknown"
chg_run_status = 0 # 默认待机
chg_fau_lts = ""
charger_name = "unknown"
charger_power = 0.0
created_at = time
updated_at = time
battery_bun_no = 0
# 根据命令码解析数据域
if cmd == "0B": # 平台心跳
if len(data_domain) >= 12: # 6字节时间 + 1字节超时次数
time_str = data_domain[0:12] # BCD码时间
parsed_time = self.parse_bcd_time(time_str)
if parsed_time:
created_at = parsed_time
updated_at = parsed_time
chg_run_status = 0 # 心跳表示待机
elif cmd == "25": # 充电信息(表 3.9.9
if len(data_domain) >= 92: # 确保数据域足够长
# 充电用户编号(字节 0-15ASCII
battery_bun_id = self.hex_to_ascii(data_domain[0:32])
# 充电电压(字节 16-17HEX单位 0.1V
charger_power = int(data_domain[32:36], 16) / 10.0 # 电压作为功率(示例)
# 充电电流(字节 18-19HEX单位 0.1A
current = int(data_domain[36:40], 16) / 10.0
# 充电电量(字节 20-21HEX单位 0.01kWh
energy = int(data_domain[40:44], 16) / 100.0
# SOC字节 22HEX单位 %
soc = int(data_domain[44:46], 16)
# 故障状态(字节 23-26HEX
chg_fau_lts = data_domain[46:54]
# 充电状态(字节 27HEX
charge_status = int(data_domain[54:56], 16)
# 映射充电状态
if charge_status == 0:
chg_run_status = 0 # 待机
elif charge_status == 1:
chg_run_status = 1 # 充电中
elif charge_status == 2:
chg_run_status = 2 # 充电结束
elif charge_status == 3:
chg_run_status = 3 # 充电异常
else:
chg_run_status = 4 # 故障
else:
logging.info(f"未处理的命令码: {cmd}")
return None # 跳过未处理的命令
# 构建插入 SQL
sql = (
"INSERT INTO charge_jiuxing (charger_no, battery_bun_id, station_id, operator_id, org_code, "
"merchant_id, chg_run_status, chg_fau_lts, charger_name, charger_power, created_at, updated_at, battery_bun_no) VALUES "
f"('{charger_no}', '{battery_bun_id}', '{station_id}', '{operator_id}', '{org_code}', "
f"'{merchant_id}', {chg_run_status}, '{chg_fau_lts}', '{charger_name}', {charger_power}, "
f"'{created_at}', '{updated_at}', {battery_bun_no})"
)
return sql
def run(self):
"""启动程序"""
self.connect()
try:
while True:
pass # 保持程序运行
except KeyboardInterrupt:
self.mqtt_client.loop_stop()
self.mqtt_client.disconnect()
if self.tdengine_conn:
self.tdengine_conn.close()
logging.info("Program stopped")
if __name__ == "__main__":
parser = ChargeJiuxingParser(
mqtt_host="123.6.102.119",
mqtt_port=1883,
mqtt_username="emqx_test",
mqtt_password="emqx_test",
tdengine_url="http://localhost:6041", # 根据实际环境修改
tdengine_user="root",
tdengine_password="taosdata"
)
parser.run()