Compare commits

28 Commits

Author SHA1 Message Date
何 泽隆
67b238b56a 修复备份程序打包功能缺陷; 2025-01-22 19:14:01 +08:00
何 泽隆
7cf26cf898 添加备份程序打包功能; 2025-01-21 19:45:18 +08:00
何 泽隆
404fea7b3b 添加备份程序升级包生成测试; 2025-01-21 10:09:58 +08:00
何 泽隆
34c9c67a00 主站分析脚本; 2025-01-21 10:07:18 +08:00
何 泽隆
f3847f4f34 积累修改; 2025-01-20 11:13:57 +08:00
何 泽隆
52406c45b9 积累修改; 2025-01-13 09:10:47 +08:00
何 泽隆
5022bf802e 积累修改; 2025-01-04 21:20:27 +08:00
何 泽隆
00ddd6d68c 积累更新 2024-12-27 01:41:47 +08:00
何 泽隆
810bac464f 积累修改; 2024-12-12 22:02:09 +08:00
38d6ff30a3 添加绘图逻辑; 2024-11-25 01:45:49 +08:00
何 泽隆
5cca8a0862 主站数据分析脚本; 2024-11-23 17:39:54 +08:00
何 泽隆
9a01153f3c 添加MQTT主站连接脚本; 2024-11-21 18:05:47 +08:00
何 泽隆
32a2f59c33 修改报文生成与解析调用为保护函数; 2024-11-21 01:27:39 +08:00
10885c9101 重构设备类定义, 使用继承简化代码; 2024-11-18 02:49:30 +08:00
何 泽隆
628ce8bf27 添加SLCP102打包逻辑; 2024-11-16 18:57:23 +08:00
何 泽隆
272b7c68b2 更新SLCP102程序打包流程; 2024-11-12 10:49:49 +08:00
何 泽隆
8a963c2983 积累更新; 2024-11-02 22:51:13 +08:00
何 泽隆
73a36c35bb 积累更新 2024-10-31 09:01:49 +08:00
何 泽隆
eeb476a538 重构代码. 2024-10-25 09:32:53 +08:00
何 泽隆
b6aa0e8b75 修复适配器通信函数调用错误; 2024-10-12 00:14:58 +08:00
何 泽隆
53a773155e 更新参数测试脚本; 2024-10-09 00:35:22 +08:00
何 泽隆
1ddde4855a 为录波状态添加固定通信配置;
添加录波数据回复数据帧数校验;
添加阻止读取报文打印输出逻辑;
增加读写报文间隔时间, 避免回复报文接收失败;
2024-10-08 16:36:18 +08:00
14f9b48a76 添加环境依赖配置;
添加测试用例生成;
2024-10-08 02:53:41 +08:00
7056b73237 替换旧版帧处理函数; 2024-10-05 02:21:38 +08:00
何 泽隆
3706a51c6b 添加AI生成函数, 准备制作map文件解析功能; 2024-10-04 21:21:32 +08:00
何 泽隆
8fa7c8f40b 重构叠光控制器脚本文件; 2024-10-04 21:20:49 +08:00
何 泽隆
b3835de75a 添加包初始化文件; 2024-10-04 21:19:42 +08:00
何 泽隆
9876c81d5c 重构帧处理函数;
修改Hex字符显示函数;
添加Bin文件版本信息解析函数;(需要特殊代码段结构)
2024-10-04 21:18:55 +08:00
33 changed files with 53219 additions and 2217 deletions

BIN
requirements.txt Normal file

Binary file not shown.

45
resource/Untitled-1.py Normal file
View File

@@ -0,0 +1,45 @@
# %%
from paho.mqtt import client as mqtt_client
broker = '123.249.75.235'
port = 1883
topic = "python/mqtt"
client_id = f'python-mqtt-{random.randint(0, 1000)}'
username = 'TTE0101TC2311000003'
password = 'qh10579lcb7au8o2'
# %%
import random
from paho.mqtt import client as mqtt_client
broker = '123.249.75.235'
port = 1883
topic = "python/mqtt"
client_id = f'python-mqtt-{random.randint(0, 1000)}'
username = 'TTE0101TC2311000003'
password = 'qh10579lcb7au8o2'
# %%
def connect_mqtt():
def on_connect(client, userdata, flags, rc):
# For paho-mqtt 2.0.0, you need to add the properties parameter.
# def on_connect(client, userdata, flags, rc, properties):
if rc == 0:
print("Connected to MQTT Broker!")
else:
print("Failed to connect, return code %d\n", rc)
# Set Connecting Client ID
client = mqtt_client.Client(client_id)
# For paho-mqtt 2.0.0, you need to set callback_api_version.
# client = mqtt_client.Client(client_id=client_id, callback_api_version=mqtt_client.CallbackAPIVersion.VERSION2)
# client.username_pw_set(username, password)
client.on_connect = on_connect
client.connect(broker, port)
return client

1644
source/Interactive-1.ipynb Normal file

File diff suppressed because it is too large Load Diff

35739
source/Interactive-2.ipynb Normal file

File diff suppressed because it is too large Load Diff

637
source/data_analysis.py Normal file
View File

@@ -0,0 +1,637 @@
import time
import logging
import datetime
import requests
import numpy as np
import pandas as pd
from pathlib import Path
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.colors as mcolors
from sqlalchemy import create_engine
from sqlalchemy import MetaData, Table, Column, String, Float, Integer, DateTime
API_URL = "https://energy-iot.chinatowercom.cn/api/device/device/historyPerformance"
API_HEADER = {
"accept": "application/json, text/plain, */*",
"Accept-Encoding": "gzip, deflate, br, zstd",
"accept-language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6",
"Connection": "keep-alive",
"Content-Length": "170",
"content-type": "application/json;charset=UTF-8",
"Cookie": "HWWAFSESID=455f2793ca86a3aaf0; HWWAFSESTIME=1734509454212; dc04ed2361044be8a9355f6efb378cf2=WyIyODM4MDM2MDcxIl0",
"authorization": "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiIl0sInVzZXJfbmFtZSI6IndlYl9tYW5hZ2V8d2FuZ2xlaTQiLCJzY29wZSI6WyJhbGwiXSwiZXhwIjoxNzM0NjYzNDQ5LCJ1c2VySWQiOjI0Mjg1LCJqdGkiOiJhZmZhNmY1My05ZDA4LTQ2ODUtODU3MS05YzA5ODAxMGJjZWYiLCJjbGllbnRfaWQiOiJ3ZWJfbWFuYWdlIn0.q0X4qrgL4wRUTZL8c_5oTIUGW0Lsivxw8pYQ1iMIqLnyJrUeS7IQKNRavMhc4NEdQ9uG6ZgFVHIj80HbzH8DHCxssCPLdv9_TXksI5podU2aU6Vjh6AaN1THFYAE2uflj1saBnQ5_gKiK0-cAcXDeJNSt_u6Cd9hI1ejEUPPzO_hLg-NLzch7yIB-HPvhoDNnl0n5pSYoQpT8XaKT14HezL3VQrLII69Vme38S2dMmmkiAeIyhHQi56kXZ11K45Lu5bHXv6fDg2Mfr9VgVuTleZldiO69BAmG0h1-HqTQuGE39jtGWrrCnFduRZR6VsaOWWJy3qyqUbXWMOli1Yy1g",
"Host": "energy-iot.chinatowercom.cn",
"Origin": "https://energy-iot.chinatowercom.cn",
"Sec-Fetch-Dest": "empty",
"Sec-Fetch-Mode": "cors",
"Sec-Fetch-Site": "same-origin",
"sec-ch-ua": "\"Microsoft Edge\";v=\"131\", \"Chromium\";v=\"131\", \"Not_A Brand\";v=\"24\"",
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": "Windows",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 Edg/131.0.0.0",
}
SemaMap_adapter = {
'apt_facturer': ('0305113001', 'adapter', False, "厂家"),
'apt_version': ('0305114001', 'adapter', False, "软件版本"),
'apt_model': ('0305115001', 'adapter', False, "型号"),
'apt_status': ('0305116001', 'adapter', False, "开关机状态"),
'apt_temp': ('0305117001', 'adapter', True, "温度"),
'apt_volt_in': ('0305118001', 'adapter', True, "输入电压"),
'apt_curr_in': ('0305119001', 'adapter', True, "输入电流"),
'apt_volt_out': ('0305120001', 'adapter', True, "输出电压"),
'apt_curr_out': ('0305121001', 'adapter', True, "输出电流"),
'apt_power_out': ('0305122001', 'adapter', True, "输出功率"),
}
semamap_combiner = {
'cbr_IMSI': ('0305102001', 'combiner', False, "IMSI"),
'cbr_ICCID': ('0305103001', 'combiner', False, "SIM卡ICCID"),
'cbr_MSISDN': ('0305104001', 'combiner', False, "MSISDN"),
'cbr_dev_type': ('0305101001', 'combiner', False, "系统类型"),
'cbr_facturer': ('0305107001', 'combiner', False, "汇流箱厂家"),
'cbr_model': ('0305108001', 'combiner', False, "汇流箱型号"),
'cbr_ver_software': ('0305105001', 'combiner', False, "软件版本"),
'cbr_ver_hardware': ('0305106001', 'combiner', False, "硬件版本"),
'cbr_power_total': ('0305109001', 'combiner', True, "系统总功率"),
'cbr_energy_total': ('0305110001', 'combiner', True, "系统累计发电量"),
'cbr_energy_daily': ('0305111001', 'combiner', True, "系统日发电量"),
}
SemaMap_meter = {
'mtr_id': ('0305123001', 'meter', False, "电表号"),
'mtr_volt': ('0305124001', 'meter', True, "直流电压"),
'mtr_curr': ('0436101001', 'meter', True, "直流总电流"),
'mtr_power': ('0436102001', 'meter', True, "总有功功率"),
'mtr_energy_total': ('0305125001', 'meter', True, "总有功电能"),
'mtr_energy_daily': ('0305126001', 'meter', True, "日有功电能"),
'mtr_energy_total_T': ('0305127001', 'meter', True, "尖时段总正向有功电能"),
'mtr_energy_total_P': ('0305128001', 'meter', True, "峰时段总正向有功电能"),
'mtr_energy_total_F': ('0305129001', 'meter', True, "平时段总正向有功电能"),
'mtr_energy_total_V': ('0305130001', 'meter', True, "谷时段总正向有功电能"),
'mtr_energy_daily_T': ('0305131001', 'meter', True, "尖时段日正向有功电能"),
'mtr_energy_daily_P': ('0305132001', 'meter', True, "峰时段日正向有功电能"),
'mtr_energy_daily_F': ('0305133001', 'meter', True, "平时段日正向有功电能"),
'mtr_energy_daily_V': ('0305134001', 'meter', True, "谷时段日正向有功电能"),
}
Sema_Map = {
'cbr_IMSI': ('0305102001', 'TTE0101', False, "IMSI"),
'cbr_ICCID': ('0305103001', 'TTE0101', False, "SIM卡ICCID"),
'cbr_MSISDN': ('0305104001', 'TTE0101', False, "MSISDN"),
'cbr_dev_type': ('0305101001', 'TTE0101', False, "系统类型"),
'cbr_facturer': ('0305107001', 'TTE0101', False, "汇流箱厂家"),
'cbr_model': ('0305108001', 'TTE0101', False, "汇流箱型号"),
'cbr_ver_software': ('0305105001', 'TTE0101', False, "软件版本"),
'cbr_ver_hardware': ('0305106001', 'TTE0101', False, "硬件版本"),
'cbr_power_total': ('0305109001', 'TTE0101', True, "系统总功率"),
'cbr_energy_total': ('0305110001', 'TTE0101', True, "系统累计发电量"),
'cbr_energy_daily': ('0305111001', 'TTE0101', True, "系统日发电量"),
'apt_facturer': ('0305113001', 'TTE0102', False, "厂家"),
'apt_version': ('0305114001', 'TTE0102', False, "软件版本"),
'apt_model': ('0305115001', 'TTE0102', False, "型号"),
'apt_status': ('0305116001', 'TTE0102', False, "开关机状态"),
'apt_volt_in': ('0305118001', 'TTE0102', True, "输入电压"),
'apt_curr_in': ('0305119001', 'TTE0102', True, "输入电流"),
'apt_volt_out': ('0305120001', 'TTE0102', True, "输出电压"),
'apt_curr_out': ('0305121001', 'TTE0102', True, "输出电流"),
'apt_power_out': ('0305122001', 'TTE0102', True, "输出功率"),
'apt_temp': ('0305117001', 'TTE0102', True, "温度"),
'mtr_id': ('0305123001', 'TTE0103', False, "电表号"),
'mtr_volt': ('0305124001', 'TTE0103', True, "直流电压"),
'mtr_curr': ('0436101001', 'TTE0103', True, "直流总电流"),
'mtr_energy_total': ('0305125001', 'TTE0103', True, "总有功电能"),
'mtr_energy_daily': ('0305126001', 'TTE0103', True, "日有功电能"),
'mtr_power': ('0436102001', 'TTE0103', True, "总有功功率"),
'mtr_energy_total_T': ('0305127001', 'TTE0103', True, "尖时段总正向有功电能"),
'mtr_energy_total_P': ('0305128001', 'TTE0103', True, "峰时段总正向有功电能"),
'mtr_energy_total_F': ('0305129001', 'TTE0103', True, "平时段总正向有功电能"),
'mtr_energy_total_V': ('0305130001', 'TTE0103', True, "谷时段总正向有功电能"),
'mtr_energy_daily_T': ('0305131001', 'TTE0103', True, "尖时段日正向有功电能"),
'mtr_energy_daily_P': ('0305132001', 'TTE0103', True, "峰时段日正向有功电能"),
'mtr_energy_daily_F': ('0305133001', 'TTE0103', True, "平时段日正向有功电能"),
'mtr_energy_daily_V': ('0305134001', 'TTE0103', True, "谷时段日正向有功电能"),
}
API_Map = {
'refreshToken': ['https://energy-iot.chinatowercom.cn/api/auth/refreshToken', None],
'search_stn': ['https://energy-iot.chinatowercom.cn/api/device/station/list', None],
'search_dev': ['https://energy-iot.chinatowercom.cn/api/device/device/page', None],
'dev_info': ['https://energy-iot.chinatowercom.cn/api/device/device/devInfo', None],
'perf_real': ['https://energy-iot.chinatowercom.cn/api/device/device/perfReal', None],
'history': ['https://energy-iot.chinatowercom.cn/api/device/device/historyPerformance', [SemaMap_adapter, SemaMap_meter]],
'page': ['https://energy-iot.chinatowercom.cn/api/device/device/page', None],
'station': ['https://energy-iot.chinatowercom.cn/api/device/station/detail/', None],
}
class Lamina_Data(object):
""" 叠光主站数据分析 """
def __init__(self, database="sqlite:///:memory", header=None):
""" 初始化 """
self.engine = create_engine(database)
metadata = MetaData()
metadata.reflect(bind=self.engine)
if 'history' not in metadata.tables:
history_table = Table(
'history', metadata,
Column('dev', String(50)),
Column('mid', String(50)),
Column('time', DateTime),
Column('value', Float)
)
metadata.create_all(self.engine)
if 'log' not in metadata.tables:
log_table = Table(
'log', metadata,
Column('dev', String(50)),
Column('mid', String(50)),
Column('Timestamp_start', DateTime),
Column('Timestamp_end', DateTime),
)
metadata.create_all(self.engine)
self.data = {
'history': pd.read_sql_table('history', self.engine),
}
self.api_origin = {
'domain': 'https://energy-iot.chinatowercom.cn/api',
'header': API_HEADER,
}
def save_history_data(self):
""" 保存历史数据 """
data_memory = self.data['history']
data_file = pd.read_sql_table('history', self.engine)
merged_df = pd.merge(data_memory, data_file[['dev', 'mid', 'time']], on=['dev', 'mid', 'time'], how='left', indicator=True)
filtered_data_memory = merged_df[merged_df['_merge'] == 'left_only'].drop(columns='_merge')
filtered_data_memory.to_sql('history', self.engine, if_exists='append', index=False)
logging.critical(f"成功插入 {len(filtered_data_memory)} 条数据")
return len(filtered_data_memory)
def save_data(func):
""" 保存函数返回数据 """
def wrapper(*args, **kwds):
self: Lamina_Data = args[0]
result = func(*args, **kwds)
if isinstance(result, pd.DataFrame):
if result.shape[0] != 0:
self.data['history'] = pd.concat([self.data['history'], result], ignore_index=True)
return result
return wrapper
@save_data
def get_history_data_by_net(self, device_id, data_type, time_start:int, time_end:int, header=None):
""" 读取信号量历史数据, 返回接口json数据 """
if header is None:
header = self.api_origin['header']
body = {
"businessType": "7",
"startTimestamp": int(time_start * 1000),
"endTimestamp": int(time_end * 1000),
"deviceCode": f"{device_id}",
"mid": f"{data_type[0]}",
"pageNum": 1,
"pageSize": 10,
"total": 0
}
req = requests.post(API_URL, json=body, headers=header)
json_data = req.json()
if json_data['code'] == 200:
""" 数据读取成功 """
print(f"Get data success, mid={data_type[0]}, len={len(json_data['data'])}")
table_data = pd.DataFrame(json_data['data'], columns=['collectTime', 'mid', 'midName', 'value'])
table_data['dev'] = device_id
table_data['time'] = pd.to_datetime(table_data.collectTime)
table_data['value'] = pd.to_numeric(table_data.value)
return table_data[['dev', 'mid', 'time', 'value']]
else:
print(f"Get data fail, code={json_data['code']}, msg=\n\t{json_data['message']}")
raise ValueError(f"{json_data['message']}")
def get_real_data_by_net(self, device_id, fsu_id=None, header=None):
""" 读取设备当前数据, 返回接口json数据 """
if header is None:
header = self.api_origin['header']
body = {
"businessType": "7",
"devType": device_id[3:7],
"deviceCodes": device_id,
"type": "遥测"
}
if device_id[3:7] != "0101":
if fsu_id is None:
raise ValueError(f"Missing required parameters: fsu_id={fsu_id}")
body["fsuCode"] = fsu_id
req = requests.post(API_Map['perf_real'][0], json=body, headers=header)
json_data = req.json()
if json_data['code'] == 200:
""" 数据读取成功 """
print(f"Get data success, len={len(json_data['data'])}")
table_data = pd.DataFrame(json_data['data'])
column_name = sorted(table_data.columns)
table_data['dev'] = device_id
table_data['time'] = table_data['updateTime'].apply(lambda x: int(time.mktime(time.strptime(x, r"%Y-%m-%d %H:%M:%S"))))
table_data = table_data[['time', 'dev', *column_name]].drop(columns='updateTime')
return table_data
else:
print(f"Get data fail, code={json_data['code']}, msg=\n\t{json_data['message']}")
raise ValueError(f"{json_data['message']}")
def get_devinfo_data_by_net(self, device_id, data_type, time_start:int, time_end:int, header=None):
""" 读取设备信息, 返回接口json数据 """
if header is None:
header = self.api_origin['header']
body = {
"businessType": "7",
"id": int(data_type),
}
req = requests.post(API_Map['dev_info'][0], json=body, headers=header)
json_data = req.json()
if json_data['code'] == 200:
""" 数据读取成功 """
print(f"Get data success, len={len(json_data['data'])}")
table_data = pd.DataFrame(json_data['data'])
return table_data
else:
print(f"Get data fail, code={json_data['code']}, msg=\n\t{json_data['message']}")
raise ValueError(f"{json_data['message']}")
def spider_device(self, device_id:str, time_start:int, time_end:int):
""" 爬取设备数据 """
result = {}
key_list = list(filter(lambda x: Sema_Map[x][1] == device_id[:7] and Sema_Map[x][2], Sema_Map.keys()))
data_device = pd.DataFrame([], columns=['time', 'device', *map(lambda s: s.split('_', 1)[1], key_list)])
for key in key_list:
result[key] = self.get_history_data_by_net(device_id, Sema_Map[key], time_start, time_end)
if data_device.empty:
data_device.time = result[key].time
data_device.device = device_id
data_device[key[4:]] = result[key].value.apply(float)
return data_device
def spider_search_devices(self, device_id:str, header=None):
if header is None:
header = self.api_origin['header']
body = {
"devType": "",
"accessPointId": "",
"pageNum": 1,
"pageSize": 10,
"businessType": "7",
"devCode": device_id,
"deptIds": []
}
req = requests.post(API_Map['search_dev'][0], json=body, headers=header)
json_data = req.json()
if json_data['code'] != 200:
""" 数据读取失败 """
print(f"Get data fail, code={json_data['code']}, msg=\n\t{json_data['message']}")
return ""
elif search_dev := json_data['rows']:
print(f"Search device success, len={len(search_dev)}")
return search_dev[0]['stationCode']
else:
print(f"Search device fail.")
return ""
def spider_search_station(self, name:str, header=None):
if header is None:
header = self.api_origin['header']
body = {
"pageNum": 1,
"pageSize": 10,
"provinceId": "",
"cityId": "",
"countId": "",
"name": name,
"code": "",
"rsSource": "",
"businessType": "7",
"status": "",
"onlineStatus": "",
"maintenancePerson": "",
"deptIds": []
}
req = requests.post(API_Map['search_stn'][0], json=body, headers=header)
json_data = req.json()
if json_data['code'] != 200:
""" 数据读取失败 """
print(f"Get data fail, code={json_data['code']}, msg=\n\t{json_data['message']}")
return ""
elif search_stn := json_data['rows']:
print(f"Search station success, len={len(search_stn)}")
return search_stn[0]['code']
else:
print(f"Search station fail.")
return ""
def spider_station(self, search:str, time_start:int, time_end:int, header=None):
""" 爬取站点数据 """
if header is None:
header = self.api_origin['header']
if search[:3] == "TTE":
""" 设备编号 """
station_id = self.spider_search_devices(search, header=header)
else:
""" 站点名称 """
station_id = self.spider_search_station(search, header=header)
if station_id == "":
print(f"Search station fail.")
return {'result': False}
body = {
"businessType": "7",
"stationCode": station_id,
}
time.sleep(0.5)
print(f"Get Data for Station: {station_id}")
req = requests.post(API_Map['page'][0], json=body, headers=header)
json_data = req.json()
if json_data['code'] != 200:
""" 数据读取失败 """
print(f"Get data fail, code={json_data['code']}, msg=\n\t{json_data['message']}")
return ""
dev_meter = []
dev_adapter = []
dev_combiner = []
dev_info = []
try:
for dev in sorted(json_data['rows'], key=lambda x: x['devCode']):
print(f"Dev: {dev['devTypeName']}, id={dev['devCode']}")
time.sleep(0.5)
fsu_id = dev['parentCode'] if 'parentCode' in dev.keys() else None
dev_info.append(self.get_real_data_by_net(dev['devCode'], fsu_id, header=header))
time.sleep(0.5)
match dev['devType']:
case "0101":
fsu_id = dev['devCode']
dev_combiner.append(self.spider_device(dev['devCode'], time_start, time_end))
case "0102":
dev_adapter.append(self.spider_device(dev['devCode'], time_start, time_end))
case "0103":
dev_meter.append(self.spider_device(dev['devCode'], time_start, time_end))
self.save_history_data()
except Exception as e:
print(f"Get data fail, msg=\n\t{e}")
return {'result': False, 'token': e.args[0]}
result = {
'result': True,
'station': station_id,
'information': pd.concat(dev_info, ignore_index=True),
'combiner': pd.concat(dev_combiner, ignore_index=True),
'adapter': pd.concat(dev_adapter, ignore_index=True),
'meter': pd.concat(dev_meter, ignore_index=True),
}
print(f"Station Done.")
return result
def save_station_by_file1(data_lamina: Lamina_Data):
""" 依据文件爬取所需站点数据 """
time_start = datetime.datetime(2024, 12, 24, 0, 0, 0)
time_end = datetime.datetime(2024, 12, 26, 0, 0, 0)
time_start_timestamp = time.mktime(time_start.timetuple())
time_end_timestamp = time.mktime(time_end.timetuple())
stations = pd.read_excel(Path(r'C:\Users\wrqal\Documents\Obsidian Vault\附件\25号0发电适配器.xlsx'))
output_file = Path(r'result/output.xlsx')
if output_file.exists():
finished_station = pd.read_excel(output_file, sheet_name=None)
finished_station["Station"]['station'] = finished_station["Station"]['station'].astype('str')
finished_station["Adatper"]['station'] = finished_station["Adatper"]['station'].astype('str')
finished_station["Meter"]['station'] = finished_station["Meter"]['station'].astype('str')
merged_df = pd.merge(stations['点位名称'], finished_station['Station']['点位名称'], how='left', indicator=True)
remain_station = merged_df[merged_df['_merge'] == 'left_only'].drop(columns='_merge')
else:
remain_station = stations['点位名称']
dataset = []
for name in remain_station['点位名称']:
print(f"Station: {name}")
data = data_lamina.spider_station(name, time_start_timestamp, time_end_timestamp)
if data['result']:
dataset.append(data)
print(f"Done.")
# 使用 ExcelWriter 将多个 DataFrame 保存到不同的工作表中
df_station = pd.DataFrame([], columns=['station', '点位名称'])
df_station.station = [data['station'] for data in dataset]
df_station.点位名称 = remain_station['点位名称'][:len(dataset)].values
df_adapter = pd.concat([data['adapter'].assign(station=data['station']) for data in dataset], ignore_index=True)
df_meter = pd.concat([data['meter'].assign(station=data['station']) for data in dataset], ignore_index=True)
column_adapter = ['time', 'station', *df_adapter.columns[1:-1]]
column_meter = ['time', 'station', *df_meter.columns[1:-1]]
if output_file.exists():
""" 连接文件 """
df_station = pd.concat([finished_station['Station'], df_station], ignore_index=True)
df_adapter = pd.concat([finished_station['Adatper'], df_adapter], ignore_index=True)
df_meter = pd.concat([finished_station['Meter'], df_meter], ignore_index=True)
with pd.ExcelWriter(output_file) as writer:
df_station.to_excel(writer, sheet_name='Station', index=False)
df_adapter.to_excel(writer, sheet_name='Adatper', index=False, columns=column_adapter)
df_meter.to_excel(writer, sheet_name='Meter', index=False, columns=column_meter)
print(f"数据已成功保存到 {output_file}")
def save_station_by_file2(data_lamina: Lamina_Data, file_path):
""" 依据文件爬取所需站点数据 """
file_input = Path(file_path)
file_output = file_input.parent / (file_input.stem + '_output.xlsx')
df_input = pd.read_excel(file_input)
if file_output.exists():
finished_station = pd.read_excel(file_output, sheet_name=None)
finished_station["Station"]['station'] = finished_station["Station"]['station'].astype('str')
finished_station["Adatper"]['station'] = finished_station["Adatper"]['station'].astype('str')
finished_station["Meter"]['station'] = finished_station["Meter"]['station'].astype('str')
merged_df = pd.merge(df_input['点位名称'], finished_station['Station']['点位名称'], how='left', indicator=True)
remain_station = merged_df[merged_df['_merge'] == 'left_only'].drop(columns='_merge')
else:
remain_station = df_input
dataset = []
df_input = df_input.set_index('点位名称')
for name in remain_station['点位名称']:
logging.info(f"Station: {name}")
time_start_timestamp = df_input['开始时间'][name].tz_localize('Asia/Shanghai').timestamp()
time_end_timestamp = df_input['结束时间'][name].tz_localize('Asia/Shanghai').timestamp()
data = data_lamina.spider_station(name, time_start_timestamp, time_end_timestamp)
if data['result']:
dataset.append(data)
analysis_info1(data)
plt.waitforbuttonpress()
elif data['token']:
""" Token 失效 """
data_lamina.api_origin['header']['authorization'] = data['token']
logging.info(f"All Station Done.")
# 使用 ExcelWriter 将多个 DataFrame 保存到不同的工作表中
df_station = pd.DataFrame([], columns=['station', '点位名称'])
df_station.station = [data['station'] for data in dataset]
df_station.点位名称 = remain_station['点位名称'][:len(dataset)].values
df_adapter = pd.concat([data['adapter'].assign(station=data['station']) for data in dataset], ignore_index=True)
df_meter = pd.concat([data['meter'].assign(station=data['station']) for data in dataset], ignore_index=True)
column_adapter = ['time', 'station', *df_adapter.columns[1:-1]]
column_meter = ['time', 'station', *df_meter.columns[1:-1]]
if file_output.exists():
""" 连接文件 """
df_station = pd.concat([finished_station['Station'], df_station], ignore_index=True)
df_adapter = pd.concat([finished_station['Adatper'], df_adapter], ignore_index=True)
df_meter = pd.concat([finished_station['Meter'], df_meter], ignore_index=True)
with pd.ExcelWriter(file_output) as writer:
df_station.to_excel(writer, sheet_name='Station', index=False)
df_adapter.to_excel(writer, sheet_name='Adatper', index=False, columns=column_adapter)
df_meter.to_excel(writer, sheet_name='Meter', index=False, columns=column_meter)
logging.info(f"数据已成功保存到 {file_output}")
return result
def analysis_info(df_station: pd.DataFrame):
""" 站点Log数据分析 """
map_mid = {}
for k, v in SemaMap_adapter.items():
map_mid[v[0]] = v[3]
for k, v in SemaMap_meter.items():
map_mid[v[0]] = v[3]
map_dev = {
'TTE0101': 'Combiner',
'TTE0102': 'Adapter',
'TTE0103': 'Meter',
}
data = df_station.assign(
timestamp = lambda df: pd.to_datetime(df['time'], unit='s', utc=True).apply(lambda x: x.tz_convert('Asia/Shanghai')),
type = lambda df: df['dev'].apply(lambda x: map_dev[x[:7]]),
date = lambda df: df['timestamp'].apply(lambda x: x.date()),
name = lambda df: df['mid'].map(map_mid),
value = lambda df: pd.to_numeric(df['value'])
)
data_daliy = data.loc[(data['dev'] == 'TTE0102DX2406272727') & (data['date'] == np.datetime64('2024-12-25')) & (data['type'] == 'Adapter')]
fig, axes = plt.subplots(3, 2)
axes = axes.flatten()
i = 0
for name, df_plot in data_daliy.set_index('timestamp').sort_index()[['name', 'value']].groupby('name'):
df_plot.plot(ax=axes[i], title=name)
i += 1
plt.show()
def analysis_info1(data_station: dict):
""" 站点spider返回数据分析 """
define_dev = {
'TTE0101': ('combiner', (3,1)),
'TTE0102': ('adapter', (3,2)),
'TTE0103': ('meter', (3,2)),
}
# 创建双色颜色过渡
color_map = mcolors.LinearSegmentedColormap.from_list("mycmap", ["blue", "red"])
alpha = 0.5
for dev_id in data_station['information']['dev'].unique():
data_dev = data_station['information'].loc[data_station['information']['dev'] == dev_id]
history_dev = data_station[define_dev[dev_id[:7]][0]].loc[
lambda df: df['device'] == dev_id
].assign(
date = lambda df: df['time'].apply(lambda x: x.date()),
id_group = lambda df: df['date'].apply(lambda x: x.toordinal()).diff().fillna(0).cumsum(),
)
logging.debug(f"Device: {dev_id}")
logging.debug(history_dev.head())
fig, axs = plt.subplots(*define_dev[dev_id[:7]][1])
axs = axs.flatten()
for date, group in history_dev.groupby('date'):
# 计算当天的起始时间
start_time = pd.Timestamp(date)
# 调整时间索引,使其从当天的起始时间开始
adjusted_time = group['time'] - start_time
# 计算颜色和不透明度
color = color_map(group['id_group'].min() / history_dev['id_group'].max())
for index, key in enumerate(history_dev.columns[2:-2]):
if index >= len(axs):
break
group.set_index(adjusted_time)[key].plot(ax=axs[index], label=str(date), color=color, alpha=alpha)
axs[index-2].set_title(f"{key.replace('_', ' ').title()}")
fig.suptitle(f"{data_station['station']}_{define_dev[dev_id[:7]][0].title()} Device: {dev_id}", fontsize=16)
plt.show()
plt.savefig(Path(f"result\Analysis\{data_station['station']}_{dev_id}.png"))
if __name__=='__main__':
""" 主体调用流程 """
# plt中文显示
plt.rcParams['font.sans-serif'] = ['SimHei']
# 坐标轴负数显示
plt.rcParams['axes.unicode_minus'] = False
if not hasattr(__builtins__,"__IPYTHON__") and 0:
import pickle
path_data1 = Path(r"result\Analysis\station_data2.pkl")
with open(path_data1, 'rb') as f:
loaded_data = pickle.load(f)
analysis_info1(loaded_data)
if hasattr(__builtins__,"__IPYTHON__"):
path_db = '../result/chinatowercom.db'
else:
path_db = 'result/chinatowercom.db'
if not (file_db:= Path(path_db)).exists():
file_db.touch()
API_HEADER['Cookie'] = "HWWAFSESTIME=1737167522632; HWWAFSESID=6cb0288b7bc75e5a66; dc04ed2361044be8a9355f6efb378cf2=WyIzNTI0NjE3OTgzIl0"
API_HEADER['authorization'] = 'Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiIl0sInVzZXJfbmFtZSI6IndlYl9tYW5hZ2V8d2FuZ2xlaTQiLCJzY29wZSI6WyJhbGwiXSwiZXhwIjoxNzM3NDI3OTAwLCJ1c2VySWQiOjI0Mjg1LCJqdGkiOiIwYzliOTk2ZC01OTU3LTQ5MjMtOTAzNC03YzlkMDQ4YWU3MzQiLCJjbGllbnRfaWQiOiJ3ZWJfbWFuYWdlIn0.JcPqvOoVv06gi7l_ownVl1ubwDn1dYgkqB082gjrQlHqveXpyqeiF6MUUjlhcUFgNArttog9ZnI82jqmiBfSOkc-gdjvM-AHUvXc3DRN4dvlY9eXdeDeMTLFQh5rfmlYHEd9fgI7eRBvLAiUbDpiNuxyU2N2VV72vxGdvp5f1GeUPEmLj6lwBch5L2sWSYi2p9PwCBYX0sm5EwnL--nui1Iv2PHNos02y4h_m2C-x96L3chXV-h_vKoRWrztiEX6O40zaNwzlIcm_rSmX6GEOF4darGB9hU7aFzKBfM4wTcj-ZKae7dx3ttkuc1HD_eFL8xpDr0pvWycFzrgSlLtkw'
data_lamina = Lamina_Data('sqlite:///' + path_db)
# 依据站点内设备爬取整个站点的实时与历史数据
# today = datetime.datetime.today()
# yesterday = today - datetime.timedelta(days=30)
# today_midnight = today.replace(hour=0, minute=0, second=0, microsecond=0)
# yesterday_midnight = yesterday.replace(hour=0, minute=0, second=0, microsecond=0)
# today_midnight_timestamp = time.mktime(today_midnight.timetuple())
# yesterday_midnight_timestamp = time.mktime(yesterday_midnight.timetuple())
# data = data_lamina.spider_station("乐亭后庞河村", yesterday_midnight_timestamp, today_midnight_timestamp)
# 读取站点历史数据
# save_station_by_file1(data_lamina)
result = save_station_by_file2(data_lamina, "result\station_Q0120.xlsx")
# 网站令牌更新
body = {
"appId": "pjxNHUmFrMuj82pJenTmEc3Uvzj1cAO/qXs3zKMTjsG7Quk59cyjBCQM4miupyXv1At4e3deTn1cF9c4/WveDaeJCwEB+Dslom9yufrVPziOmRrQj1iAo8QVWSUnT1k70soDst+JN6japzOt7vjibru0uS/xezHrhuLSyNxkqzs=",
"refreshToken": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiIl0sInVzZXJfbmFtZSI6IndlYl9tYW5hZ2V8d2FuZ2xlaTQiLCJzY29wZSI6WyJhbGwiXSwiYXRpIjoiNGU4NTY1NTAtZDE5Ni00YjY4LWI1OGYtMzBkOTY0YjIyOGNkIiwiZXhwIjoxNzM1OTcyNTA0LCJ1c2VySWQiOjI0Mjg1LCJqdGkiOiIwMTY1NDg1MC1mZjIwLTRkMzQtYTQ4ZC03NmRiZTk3MmQ3YWQiLCJjbGllbnRfaWQiOiJ3ZWJfbWFuYWdlIn0.PFT8JlTvWay1GUI5TC2Ht25rZWkAnQT3nxs-dOcAVIN9To06rG8EDspZ5eFxmNuEraurNxHCOLPfQZ-bCzJ8ywlA747PyJxyMPBhRhgXSDHYHX7ZqHEUdQdQo_Wkf75I8ko8_szchyhItjtgDUCzud9TlxKeuBQuerpYV8tkUVWobp4ulnnHEg0kqZFDeXrI-84Lyy-kodCDI-r3KuMBC5Rvbce0hqMcs2l-2U7M-V7LUT2VhBEvQd8l_Agx8hqWcK-d-dMVhlNjcvcb0AKmcX845D0bD5tKVKim_5JX4Er9-NANzSmgO0SRnsFVuxHhXiNqSkTB7pIdyi9r-ui23Q",
"accessToken": API_HEADER['authorization']
}
body1 = {
"appId": "ePUxoRrHClb7+Wxk7NAJpiJhoVAcJbZ5NPJEak8ZTFrETrfA0JAIjbqiDuaow1Jdyg1FLjUAwlBXrLoKh514oTTZSp1U91ewVj+8ZvNi2vtbQkU03WdyxyHXiyTNjC88O1JRm13hRnIm1vRMoxsudm8CPCpUIsU9yYABZ+/w3A4=",
"refreshToken": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiIl0sInVzZXJfbmFtZSI6IndlYl9tYW5hZ2V8d2FuZ2xlaTQiLCJzY29wZSI6WyJhbGwiXSwiYXRpIjoiNGU4NTY1NTAtZDE5Ni00YjY4LWI1OGYtMzBkOTY0YjIyOGNkIiwiZXhwIjoxNzM1OTcyNTA0LCJ1c2VySWQiOjI0Mjg1LCJqdGkiOiIwMTY1NDg1MC1mZjIwLTRkMzQtYTQ4ZC03NmRiZTk3MmQ3YWQiLCJjbGllbnRfaWQiOiJ3ZWJfbWFuYWdlIn0.PFT8JlTvWay1GUI5TC2Ht25rZWkAnQT3nxs-dOcAVIN9To06rG8EDspZ5eFxmNuEraurNxHCOLPfQZ-bCzJ8ywlA747PyJxyMPBhRhgXSDHYHX7ZqHEUdQdQo_Wkf75I8ko8_szchyhItjtgDUCzud9TlxKeuBQuerpYV8tkUVWobp4ulnnHEg0kqZFDeXrI-84Lyy-kodCDI-r3KuMBC5Rvbce0hqMcs2l-2U7M-V7LUT2VhBEvQd8l_Agx8hqWcK-d-dMVhlNjcvcb0AKmcX845D0bD5tKVKim_5JX4Er9-NANzSmgO0SRnsFVuxHhXiNqSkTB7pIdyi9r-ui23Q",
"accessToken": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiIl0sInVzZXJfbmFtZSI6IndlYl9tYW5hZ2V8d2FuZ2xlaTQiLCJzY29wZSI6WyJhbGwiXSwiZXhwIjoxNzM1OTA5NTE1LCJ1c2VySWQiOjI0Mjg1LCJqdGkiOiI0ZTg1NjU1MC1kMTk2LTRiNjgtYjU4Zi0zMGQ5NjRiMjI4Y2QiLCJjbGllbnRfaWQiOiJ3ZWJfbWFuYWdlIn0.KxGBpvuPIP3CHfVT41wxE_v9vlHNC9GL6sfaIta8cI2qlMpTCVg9dg-4DgPlXuMrtI0YzrSbAywCQmFLGcBgh3HD_UuIAH-k3Y8__osZEgc4bUcJ58W-uukuEu3MEwbV6ZcxTq7dxf3iqu9aXGrawYY_iL-jIRH1v8Zcr4qUPA9Mlzl8LvZdzZ05XgntbxE8IQRmt1M5rWdWLV4tvbUEYR5eDGs3az0w-MFXQ8qNHo8KLJc68WvbilmOMWkhK2k_xQQTdNx_jPktjYfClZa6l9-6rYAb5MMqwt77fY0_JE87u3w5YbU_GRyBI2mjnJe1qKdMjUEpQwWqt3DLJWLe7Q"
}
req = requests.post(API_Map['refreshToken'][0], json=body1, headers=API_HEADER)
# data = sim_data_apt(('2024-10-1 00:00:00', '2024-10-1 12:00:00'))
# chart_apt(data)
if not hasattr(__builtins__,"__IPYTHON__"):
table_apt = data_lamina.graphs_adapter('TTE0102DX2406180988', '2024-11-23 00:00:00', '2024-11-26 00:00:00')
while True:
plt.waitforbuttonpress()

89
source/data_inverter.py Normal file
View File

@@ -0,0 +1,89 @@
import numpy as np
import pandas as pd
from pathlib import Path
import matplotlib.pyplot as plt
def fft_anlyies(data, time_step=0.0002) -> pd.DataFrame:
""" 生成FFT数据 """
frequencies = np.fft.fftfreq(data.shape[0], d=time_step) # 计算频率轴
result = pd.DataFrame([], index=frequencies)
for col_name in data:
""" 分列处理 """
fft_result = np.fft.fft(data[col_name])
fft_magnitude = np.abs(fft_result) # 获取幅度谱
result[col_name + '频谱'] = fft_magnitude
return result
def RecordingAnalysis(data_origin: pd.DataFrame):
""" 光伏逆变器录波数据分析 """
graph_map = {
'电网电压': ['A相电网电压', 'B相电网电压', 'C相电网电压'],
'滤波电容电压': ['A相滤波电容电压', 'B相滤波电容电压', 'C相滤波电容电压'],
'母线电压': ['上母线电压', '下母线电压'],
'母线电流': ['上母线电流', '下母线电流'],
'逆变电流': ['A相逆变电流', 'B相逆变电流', 'C相逆变电流'],
'负载电流': ['A相负载电流', 'B相负载电流', 'C相负载电流'],
'调制比': ['A相调制比', 'B相调制比', 'C相调制比'],
'电压前馈': ['A相电压前馈', 'B相电压前馈', 'C相电压前馈'],
'逆变电流参考值': ['A相逆变电流参考值', 'B相逆变电流参考值', 'C相逆变电流参考值'],
'谐振控制输出': ['谐振控制输出'],
'有源阻尼': ['有源阻尼'],
'零序电压': ['零序电压'],
'相角': ['相角'],
'故障状态': ['状态标签', '故障录波标志位'],
}
data_origin.index = data_origin.index * time_step
for key, value in graph_map.items():
ax = data_origin[value].plot(title=key)
if key == '故障状态':
threshold = 1e-5
diff = data_origin[value].iloc[:-1].reset_index(drop=True) - data_origin[value].iloc[1:].reset_index(drop=True)
diff = diff.replace(0, pd.NA).dropna(how='all')
for x, d in diff.T.items():
if threshold is None or d > threshold:
Y = data_origin.loc[x, value]
for y in Y:
ax.annotate(f'{y:.2f}', (x, y), textcoords="jump points", xytext=(0,5), ha='center')
if key == '电网电压' or key == '滤波电容电压':
""" 三相运行数据 """
data_fft = fft_anlyies(data_origin[value], time_step=time_step)
data_fft.plot(title=(key + ' FFT频谱图'), xlabel = '频率 (Hz)', ylabel = '幅度')
ax_fft = data_fft.plot(title=(key + ' FFT频谱图'), xlabel = '频率 (Hz)', ylabel = '幅度', xlim=(-200, 200))
if __name__ == "__main__":
# plt中文显示
plt.rcParams['font.sans-serif'] = ['SimHei']
# 坐标轴负数显示
plt.rcParams['axes.unicode_minus'] = False
time_step = 1 / 5000
data = pd.read_excel(Path(r"D:\WorkSpace\UserTool\SelfTool\FrameParser\test\pv_inviter\19__24_12_06_15_19_03.xlsx"))
# Index([
# 'A相电网电压', 'B相电网电压', 'C相电网电压',
# 'A相滤波电容电压', 'B相滤波电容电压', 'C相滤波电容电压',
# '上母线电压', '下母线电压',
# '上母线电流', '下母线电流',
# 'A相逆变电流', 'B相逆变电流', 'C相逆变电流',
# 'A相负载电流', 'B相负载电流', 'C相负载电流',
# 'A相调制比', 'B相调制比', 'C相调制比',
# 'A相电压前馈', 'B相电压前馈', 'C相电压前馈',
# 'A相逆变电流参考值', 'B相逆变电流参考值', 'C相逆变电流参考值',
# '谐振控制输出',
# '有源阻尼',
# '零序电压',
# '相角',
# '状态标签', '故障录波标志位'
# ],
# dtype='object')
RecordingAnalysis(data)
if not hasattr(__builtins__,"__IPYTHON__"):
plt.waitforbuttonpress()

File diff suppressed because it is too large Load Diff

View File

@@ -1,388 +1,32 @@
import time
import warnings
from pathlib import Path
from serial import Serial
from tools.ByteConv import trans_list_to_str
from func_frame import make_frame_dlt645, check_frame_dlt645
from func_upgrade import GeneratePackage_SLCP101_p460
modbus_map = {
# 1 - Hex
# 2 - Int16
# 3 - lnt32
# 4 - str
# 5 - addr
# 6 - float
0x0E: ["故障字1", 1],
0x0F: ["故障字2", 1],
0x10: ["MPPT工作状态", 1],
0x11: ["系统工作状态", 1],
0x12: ["系统工作模式", 1],
0x13: ["输入电压", 2],
0x14: ["电感电流", 2],
0x15: ["12V电压", 2],
0x16: ["输出电压", 2],
0x17: ["输入电流", 2],
0x18: ["温度1", 2],
0x19: ["温度2", 2],
0x1A: ["输入功率", 3],
0x1C: ["设备温度", 2],
0x1D: ["开关机状态", 1],
0x1E: ["电池电压", 2],
0x1F: ["并机功率限值", 3],
0x60: ["光伏通道使能", 1],
0x61: ["最小启动输入电压", 2],
0x62: ["最大启动输入电压", 2],
0x63: ["最小停止输入电压", 2],
0x64: ["最大停止输入电压", 2],
0x65: ["最小MPPT电压", 2],
0x66: ["最大MPPT电压", 2],
0x67: ["最小启动输出电压", 2],
0x68: ["最大启动输出电压", 2],
0x69: ["最小停止输出电压", 2],
0x6A: ["最大停止输出电压", 2],
0x6B: ["输入过压保护值", 2],
0x6C: ["输出过压保护值", 2],
0x6D: ["输出欠压保护值", 2],
0x6E: ["电感过流保护值", 2],
0x6F: ["输入过流保护值", 2],
0x70: ["最小电感电流限值", 2],
0x71: ["最大电感电流限值", 2],
0x72: ["浮充电压阈值", 2],
0x73: ["三点法中间阈值", 2],
0x74: ["恒压充电电压", 2],
0x75: ["过温故障值", 2],
0x76: ["过温告警值", 2],
0x77: ["温度恢复值", 2],
0x78: ["最低满载电压", 2],
0x79: ["最高满载电压", 2],
0x7A: ["输入过载保护值", 3],
0x7C: ["最小功率限值", 3],
0x7E: ["最大功率限值", 3],
0x80: ["最大功率限值存储值", 3],
0x82: ["载波通信地址", 5, 3],
0x85: ["电压环out_max", 2],
0x86: ["电压环out_min", 2],
0x87: ["电流环out_max", 2],
0x88: ["电流环out_min", 2],
0x89: ["MPPT扰动系数k_d_vin", 2],
0x8A: ["dmin", 2],
0x8B: ["dmax", 2],
0x8C: ["扫描电压偏移scanvolt_offset", 2],
0x8D: ["电压环Kp", 3],
0x8F: ["电压环Ki", 3],
0x91: ["电流环Kp", 3],
0x93: ["电流环Ki", 3],
0x95: ["日志级别", 1],
0x96: ["日志输出方式", 1],
0x97: ["采样校准volt_in_a", 2],
0x98: ["采样校准volt_in_b", 2],
0x99: ["采样校准volt_out_a", 2],
0x9A: ["采样校准volt_out_b", 2],
0x9B: ["采样校准curr_in_a", 2],
0x9C: ["采样校准curr_in_b", 2],
0x9D: ["采样校准curr_induc_a", 2],
0x9E: ["采样校准curr_induc_b", 2],
0x9F: ["采样校准volt_12V_a", 2],
0xA0: ["采样校准volt_12V_b", 2],
0xA1: ["温度补偿temp1_b", 2],
0xA2: ["温度补偿temp2_b", 2],
0xA3: ["系统工作模式", 2],
0xA4: ["电感电流给定值curr_set", 2],
0xA5: ["抖动频率上限", 2],
0xA6: ["抖动频率下限", 2],
0xA7: ["电池电压判断限值", 2],
0xA8: ["MPPT追踪模式", 1],
0xA9: ["ADC参考电压", 2],
0xAA: ["保留", 1],
0xAB: ["保留", 1],
0xAC: ["保留", 1],
0xAD: ["保留", 1],
0xAE: ["保留", 1],
0xAF: ["保留", 1],
0x100: ["版本", 4, 16],
0x110: ["型号", 4, 16],
0x120: ["载波芯片地址", 4, 16],
0x130: ["厂商", 4, 8],
0x138: ["保留", 4, 8],
0x140: ["保留", 4, 16],
0x150: ["保留", 4, 16],
0x160: ["硬件", 4, 16],
0x170: ["SN", 4, 16],
0x180: ["MES", 4, 16],
0x190: ["Datetime", 4, 16],
}
class LaminaAdapter:
def __init__(self, com_name="COM16", addr_645=[0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA], addr_modbus=0x01, **kwargs):
# 初始化串口通信
if com_name is not None:
com_config = {}
com_config['baudrate'] = kwargs['baudrate'] if 'baudrate' in kwargs.keys() else 115200
com_config['parity'] = kwargs['parity'] if 'parity' in kwargs.keys() else 'N'
com_config['bytesize'] = kwargs['bytesize'] if 'bytesize' in kwargs.keys() else 8
com_config['stopbits'] = kwargs['stopbits'] if 'stopbits' in kwargs.keys() else 1
self.__com = Serial(com_name, **com_config)
else:
self.__com =None
self.flag_print = 'frame_print' in kwargs.keys()
self.time_out = kwargs['time_out'] if 'time_out' in kwargs.keys() else 1
self.retry = kwargs['retry'] if 'retry' in kwargs.keys() else 1
self.retry_sub = kwargs['retry_sub'] if 'retry_sub' in kwargs.keys() else 1
self.block = {
'addr' : addr_645,
'type' : 'modbus',
'data' : {
'addr_dev' : addr_modbus,
'data_define': modbus_map,
},
}
self.output = {}
def __transfer_data(self, frame):
""" 报文数据传输 """
if self.__com is None:
print(trans_list_to_str(frame))
return False
cnt = 0
while cnt < self.retry:
self.__com.read_all()
self.__com.write(bytearray(frame))
cnt_sub = 0
frame_recv = None
while not frame_recv:
time.sleep(self.time_out)
frame_recv = self.__com.read_all()
cnt_sub += 1
if cnt_sub >= self.retry_sub:
break
try:
output_text = check_frame_dlt645(frame_recv, block=self.block)
except Exception as e:
print(e)
cnt += 1
continue
print(output_text)
for line in output_text.split('\n'):
line = line.strip()
line_info = line.split('\t')
if line_info[0] == '0x0100':
self.output['version'] = line_info[2].rstrip('\x00')
elif line_info[0] == '0x0082':
self.output['address'] = line_info[2].rstrip('\x00')
break
if self.flag_print:
print(trans_list_to_str(frame))
print(trans_list_to_str(frame_recv))
return cnt < self.retry
def frame_read(self, daddr=0x60, dlen=0x50):
self.block['data']['type'] = 'read'
self.block['data']['data_addr'] = daddr
self.block['data']['data_len'] = dlen
frame = make_frame_dlt645(self.block)
return self.__transfer_data(frame)
def frame_write_one(self, daddr=0x85, dval=-900):
self.block['data']['type'] = 'write_one'
self.block['data']['data_addr'] = daddr
self.block['data']['data_val'] = dval
frame = make_frame_dlt645(self.block)
return self.__transfer_data(frame)
def frame_write_dual(self, daddr=0x91, dval=600):
self.block['data']['type'] = 'write_dual'
self.block['data']['data_addr'] = daddr
self.block['data']['data_val'] = dval
frame = make_frame_dlt645(self.block)
return self.__transfer_data(frame)
def frame_write_str(self, daddr=0x82, dval=[0x06, 0x05, 0x04, 0x03, 0x02, 0x01]):
self.block['data']['type'] = 'write_str'
self.block['data']['data_addr'] = daddr
self.block['data']['data_val'] = dval
frame = make_frame_dlt645(self.block)
return self.__transfer_data(frame)
def frame_update(self, path_bin):
""" 程序升级
注意: 在使用单板升级测试时, 需要关闭低电压检测功能, 否则无法启动升级流程;
"""
self.block['data']['type'] = 'update'
self.block['data']['step'] = 'start'
self.block['data']['index'] = 0
self.block['data']['file'] = Path(path_bin).read_bytes()
self.block['data']['header_offset'] = 184
# 启动帧
frame_master = bytearray(make_frame_dlt645(self.block))
# 等待擦除完成返回
try_times = 30
self.__com.read_all()
while try_times:
time.sleep(self.time_out)
self.__com.write(frame_master)
frame_slave = self.__com.read_all()
if not frame_slave:
try_times -= 1
continue
_, _, self.block["data"]['file_block_size'] = check_frame_dlt645(frame_slave, self.block)
break
if self.block["data"]['file_block_size'] == 0:
raise Exception("Error slave response.")
# 避免接收到延迟返回报文
time.sleep(self.time_out)
# 文件传输
self.block["data"]['step'] = 'trans'
data_remain = len(self.block["data"]['file']) - self.block['data']['header_offset']
while data_remain > 0:
frame_master = bytearray(make_frame_dlt645(self.block))
cnt = 0
while cnt < self.retry:
self.__com.read_all()
self.__com.write(frame_master)
cnt_sub = 0
frame_slave = None
while not frame_slave:
time.sleep(self.time_out)
frame_slave = self.__com.read_all()
cnt_sub += 1
if cnt_sub >= self.retry_sub:
break
try:
ret = check_frame_dlt645(frame_slave, self.block)
except Exception as e:
print(e)
ret = False, 0, 0
if ret[0]:
break
else:
cnt += 1
if cnt >= self.retry:
raise Exception("Error, Retry failed.")
self.block["data"]['index'] += 1
data_remain -= self.block["data"]['file_block_size']
# 结束升级
self.block["data"]['step'] = 'end'
frame_master = bytearray(make_frame_dlt645(self.block))
cnt = 0
while cnt < self.retry:
self.__com.read_all()
self.__com.write(frame_master)
cnt_sub = 0
frame_slave = None
while not frame_slave:
time.sleep(self.time_out)
frame_slave = self.__com.read_all()
cnt_sub += 1
if cnt_sub >= self.retry_sub:
break
try:
ret = check_frame_dlt645(frame_slave, self.block)
except Exception as e:
print(e)
ret = False, 0, 0
if ret[0]:
break
else:
cnt += 1
if cnt >= self.retry:
raise Exception("Error, Retry failed.")
from device.LaminaAdapter import LaminaAdapter
from device.tools.ByteConv import trans_list_to_str, trans_str_to_list
def test_communication(time_out=2):
""" 通信成功率测试 """
log_success = 0
log_failed = 0
log_failedseries = 0
cnt_failedseries = 0
time_start = time.time()
saveconfig_print = dev_lamina.flag_print
param_saved = dev_lamina.flag_print, dev_lamina.retry, dev_lamina.time_out
dev_lamina.flag_print = False
dev_lamina.retry = 1
try:
while 1:
if dev_lamina.frame_read(0x0E, 0x13):
log_success += 1
cnt_failedseries = 0
else:
log_failed += 1
cnt_failedseries += 1
if log_failedseries <= cnt_failedseries:
log_failedseries = cnt_failedseries
while True:
dev_lamina.frame_read(0x0C, 0x20)
print(f"Time Stamp: {time.ctime()}")
print(f"Success Frame: {log_success}")
print(f"Failed Frame: {log_failed}")
print(f"Max Series Failed Frame: {log_failedseries}")
print(f"Success Frame: {dev_lamina.log['read']}")
print(f"Failed Frame: {dev_lamina.log['send'] - dev_lamina.log['read']}")
print(f"Max Series Failed Frame: {dev_lamina.log['keep-fail']}")
time.sleep(time_out)
finally:
time_end = time.time()
print("Test Result: ")
print(f"Time Start: {time.strftime(r'%Y-%m-%d %H:%M:%S', time.localtime(time_start))}, \tTime End: {time.strftime(r'%Y-%m-%d %H:%M:%S', time.localtime(time_end))}")
print(f"Time Elapsed: {time_end - time_start}")
print(f"Success Rate: {log_success / (log_success + log_failed) * 100}%")
dev_lamina.flag_print = saveconfig_print
def make_Pakeage(fp: Path):
""" 生成升级包 """
hex_update = fp
file_package = fp.parent / f'{hex_update.stem}.dat'
file_update_bin = fp.parent / f'{hex_update.stem}.bin'
data_package, data_update_bin = GeneratePackage_SLCP101_p460(hex_update)
file_package.write_bytes(data_package)
file_update_bin.write_bytes(data_update_bin)
return file_package
if __name__=='__main__':
mode_config = {
"Log": {'com_name': None,
# 'addr_645': [0x01, 0x00, 0x00, 0x00, 0x00, 0x40],
},
"Debug": {'com_name': 'COM8', 'baudrate': 115200, 'parity': 'N', 'bytesize': 8, 'stopbits': 1,
# 'addr_645': [0x01, 0x02, 0x03, 0x04, 0x05, 0x06],
'frame_print': True,
'time_out': 0.1, 'retry': 1, 'retry_sub': 10},
"HPLC": {'com_name': 'COM10', 'baudrate': 9600, 'parity': 'E', 'bytesize': 8, 'stopbits': 1,
# 'addr_645': [0x01, 0x02, 0x03, 0x04, 0x05, 0x06],
'frame_print': True,
'time_out': 0.5, 'retry': 3, 'retry_sub': 10},
}
dev_lamina = LaminaAdapter(**mode_config['Debug'])
dev_lamina.frame_read(0x0100, 0x20)
print(f"Success Rate: {dev_lamina.log['read'] / dev_lamina.log['send'] * 100}%")
dev_lamina.flag_print, dev_lamina.retry, dev_lamina.time_out = param_saved
def test():
if 0:
dev_lamina.frame_read(0xA9, 1) # 读ADC参考电压
dev_lamina.frame_write_one(0xA9, 2000) # 写ADC参考电压:2.0V
@@ -398,7 +42,6 @@ if __name__=='__main__':
dev_lamina.frame_write_one(0x99, 1500) # 写校准参数Vout_a: 1.500
dev_lamina.frame_write_one(0x9A, 1000) # 写校准参数Vout_a: 1.00
dev_lamina.frame_write_one(0x9A, 1500) # 写校准参数Vout_a: 1.50
if 0:
dev_lamina.frame_write_str(0x82, [0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA])
dev_lamina.frame_write_str(0x82, [0x00, 0x00, 0x00, 0x00, 0x00, 0x00])
@@ -438,7 +81,6 @@ if __name__=='__main__':
dev_lamina.frame_read(0x0E, 0x13)
time.sleep(0.5)
dev_lamina.frame_write_one(0x50, 0x00)
if 0:
dev_lamina.frame_write_one(0x0054, 0x01)
dev_lamina.frame_read(0x000E, 0x02)
@@ -471,43 +113,173 @@ if __name__=='__main__':
time.sleep(2)
dev_lamina.frame_read(0x0170, 0x30)
if not hasattr(__builtins__,"__IPYTHON__"):
# file_package = Path(r"D:\WorkSpace\UserTool\SelfTool\FrameParser\test\p460_o1\result\lamina_optimizer_t1.dat")
# path_bin = Path(r"D:\WorkingProject\LightStackAdapter\software\lamina_adapter\tools\upgrade\SLCP001_240520_0000_T1.11.bin")
# 工程-即时转换
if 0:
dev_lamina.flag_print = False
dev_lamina.frame_write_one(0x52, 0x01)
time.sleep(6)
dev_lamina.frame_read(0x69, 0x02)
for value in [40, 41.9, 42, 42.1, 56.5, 57, 57.5, 57.9, 58, 58.1, 59]:
time.sleep(0.5)
result = dev_lamina.frame_write_one(0x21, int(value * 10))
print(f"Write Value: {value}, result: {'Seccusss' if result else 'Fail'}.")
time.sleep(0.5)
dev_lamina.frame_read(0x21,1)
time.sleep(0.5)
value = 46
result = dev_lamina.frame_write_one(0x69, int(value * 10))
print(f"Write Value: {value} in Addr: {0x69}, result: {'Seccusss' if result else 'Fail'}.")
time.sleep(0.5)
value = 60
result = dev_lamina.frame_write_one(0x6A, int(value * 10))
print(f"Write Value: {value} in Addr: {0x6A}, result: {'Seccusss' if result else 'Fail'}.")
for value in [45, 45.9, 46, 46.1, 56.5, 57, 57.5, 58, 59.9, 60, 60.1, 61]:
time.sleep(0.5)
result = dev_lamina.frame_write_one(0x21, int(value * 10))
print(f"Write Value: {value}, result: {'Seccusss' if result else 'Fail'}.")
time.sleep(0.5)
dev_lamina.frame_read(0x21,1)
time.sleep(0.5)
dev_lamina.frame_write_one(0x53, 0x01)
time.sleep(4.5)
dev_lamina.frame_read(0x21, 1)
time.sleep(0.5)
dev_lamina.frame_read(0x69, 2)
dev_lamina.flag_print = True
if 0: # 并机功率限值测试
dev_lamina.flag_print = False
step = 0
time_start = time.time()
time_interval = 120
list_power_limit = [650, 300, 200, 150, 120, 100, 80, 70, 50, 25, 10, 5, 1, 0.1, 0, 650]
while True:
time.sleep(1)
print(time.ctime())
dev_lamina.frame_read(0x0E, 0x20)
if time.time() - time_start > time_interval:
if step >= len(list_power_limit):
break
time.sleep(0.5)
time_start = time.time()
value = list_power_limit[step]
result = dev_lamina.frame_write_dual(0x1F, int(value * 1000))
print(f"Write Value: {value} in Addr: 0x1F by Time: {time.ctime(time_start)}. \n\tresult: {'Seccusss' if result else 'Fail'}.")
step += 1
dev_lamina.flag_print = True
if 0: # 启停机条件测试
dev_lamina.flag_print = False
dev_lamina.frame_read(0x60, 0x0B)
while True:
time.sleep(1)
print(time.ctime())
dev_lamina.frame_read(0x0E, 0x20)
dev_lamina.flag_print = True
if 0: # 程序升级
dev_lamina.frame_update(file_hex, makefile=True)
time.sleep(4.5)
dev_lamina.frame_read(0x100, 0x20)
if 0: # 曲线扫描
dev_lamina.flag_print = False
action_list = [(0x50, 1), (0x50, 0), (0xA8, 0), (0x50, 1)]
dev_lamina.frame_write_one(0x50, 0)
time.sleep(0.5)
dev_lamina.frame_write_one(0x52, 1)
time.sleep(4.5)
dev_lamina.frame_read(0x60, 0x60)
step = 0
time_start = time.time()
time_interval = 120
while True:
time.sleep(1)
print(time.ctime())
dev_lamina.frame_read(0x0E, 0x20)
if time.time() - time_start > time_interval:
if step >= len(action_list):
break
time.sleep(0.5)
time_start = time.time()
result = dev_lamina.frame_write_one(*action_list[step])
print(f"Write Value: {action_list[step][1]} in Addr: 0x{action_list[step][0]:x} by Time: {time.ctime(time_start)}. \n\tresult: {'Seccusss' if result else 'Fail'}.")
step += 1
dev_lamina.flag_print = True
if 0: # 数据读写验证
addr, value = 0x61, 29.9
dlen = 1
result = dev_lamina.frame_write(addr, dlen, value)
print(f"Write Result: \n0x{addr:04x}:\t{value}\t{'Seccusss' if result else 'Fail'}.")
time.sleep(0.5)
dev_lamina.frame_read(addr, dlen)
if __name__=='__main__':
mode_config = {
"Log": {'com_name': None,
# 'addr_645': [0x01, 0x00, 0x00, 0x00, 0x00, 0x40],
},
"Debug": {'com_name': 'COM3', 'baudrate': 115200, 'parity': 'N', 'bytesize': 8, 'stopbits': 1,
# 'addr_645': [0x01, 0x02, 0x03, 0x04, 0x05, 0x06],
'frame_print': True,
'time_out': 0.5, 'retry': 1, 'retry_sub': 10},
"HPLC": {'com_name': 'COM8', 'baudrate': 9600, 'parity': 'E', 'bytesize': 8, 'stopbits': 1,
'addr_645': trans_str_to_list("02 01 00 00 24 20"),
'frame_print': True,
'time_out': 3, 'time_gap': 0.1, 'retry': 3, 'retry_sub': 10},
}
dev_lamina = LaminaAdapter(type_dev="SLCP101", **mode_config['Debug'])
dev_lamina.frame_read(0x0100, 0x30)
# 工程-即时转换
if dev_lamina.device == 'SLCP001':
file_hex = Path(r"D:\WorkingProject\LightStackAdapter\software\lamina_adapter\release\device_V1\lamina_adapter\Debug\lamina_adapter.hex")
elif dev_lamina.device == 'SLCP101':
file_hex = Path(r"D:\WorkingProject\LightStackAdapter\software\lamina_adapter\release\device_V2.03\lamina_adapter\Debug\lamina_adapter.hex")
file_dat_back = Path(r"D:\WorkingProject\LightStackAdapter\software\tools\hex_history_SLCP101\生产镜像\SLCP101_V2.03\SLCP101_250111_0800_B2.03.dat")
elif dev_lamina.device == 'SLCP102':
file_hex = Path(r"D:\WorkingProject\LightStackAdapter\software\lamina_adapter\release\device_V3.01\lamina_adapter\Debug\lamina_adapter.hex")
elif dev_lamina.device == 'DLSY001':
file_hex = Path(r"D:\WorkingProject\LightStackOptimizer\software\lamina_optimizer\lamina_optimizer\Debug\lamina_optimizer.hex")
else:
file_hex = Path(r"D:\WorkingProject\LightStackAdapter\software\lamina_adapter\lamina_adapter\Debug\lamina_adapter.hex")
if not file_hex.exists():
raise Exception("工程编译目标文件不存在.")
file_package = make_Pakeage(file_hex)
if not file_hex.exists():
warnings.warn("工程编译目标文件不存在.", UserWarning)
if dev_lamina.output['Regs'][0x100][1].split('_')[0] != dev_lamina.device:
warnings.warn("设备型号不匹配.", UserWarning)
print(dev_lamina.device)
print(file_hex)
# 江苏发货产品灌装版本
# path_bin = Path(r"D:\WorkingProject\LightStackAdapter\software\lamina_adapter\tools\upgrade\SLCP001_240603_2100_V1.18.bin")
if not hasattr(__builtins__,"__IPYTHON__") and 0:
""" 测试备份程序升级 """
dev_lamina.frame_update(file_dat_back)
time.sleep(3)
ret = dev_lamina.frame_read(0x0100, 0x20)
version = "DLSY001_240911_1600_V1.01"
if not hasattr(__builtins__,"__IPYTHON__"):
version = "SLCP101_241030_2000_V2.03"
addr = [0x24, 0x09, 0x12, 0x00, 0x00, 0x00]
while True:
""" 自动检测升级流程 """
ret = False
while not ret or ('version' not in dev_lamina.output.keys()) or (version == dev_lamina.output['version']):
dev_lamina.frame_read(0x82, 3)
while not ret or ('Regs' not in dev_lamina.output.keys()) or (version == dev_lamina.output['Regs'][0x0100][1].strip('\000')):
# dev_lamina.frame_read(0x82, 3)
ret = dev_lamina.frame_read(0x0100, 0x20)
time.sleep(1)
dev_lamina.frame_update(file_package)
dev_lamina.frame_update(file_hex, makefile=True)
time.sleep(6)
time.sleep(3)
ret = dev_lamina.frame_read(0x0100, 0x20)
if ret and (version == dev_lamina.output['version']):
if ret and (version == dev_lamina.output['Regs'][0x0100][1]):
dev_lamina.frame_write_one(0x52, 0x01)
print(f"address: {' '.join(map(lambda x: ('000' + hex(x)[2:])[-2:], addr))}")
dev_lamina.frame_write_str(0x82, addr)
dev_lamina.frame_read(0x82, 3)
addr[5] += 1
if addr[5] & 0x0F >= 10:
addr[5] += 0x10 - 10
# print(f"address: {' '.join(map(lambda x: ('000' + hex(x)[2:])[-2:], addr))}")
# dev_lamina.frame_write_str(0x82, addr)
# dev_lamina.frame_read(0x82, 3)
# addr[5] += 1
# if addr[5] & 0x0F >= 10:
# addr[5] += 0x10 - 10

File diff suppressed because it is too large Load Diff

View File

@@ -1,562 +1,48 @@
import time
from pathlib import Path
from serial import Serial
from datetime import datetime
from tools.ByteConv import trans_list_to_str
from tools.IntelHex import file_Bin_to_IntelHex
from func_frame import make_frame_modbus, check_frame_modbus
from func_upgrade import GenerateImage_DLSP001_p280039, GeneratePackage_DLSP001_p280039
modbus_map = {
# 1 - Hex
# 2 - Int16
# 3 - lnt32
# 4 - str
# 5 - addr
# 6 - float
0x0B: ["事件标志", 1],
0x0C: ["告警字1", 1],
0x0D: ["告警字2", 1],
0x0E: ["故障字1", 1],
0x0F: ["故障字2", 1],
0x10: ["系统工作状态" , 1],
0x11: ["Boost1工作状态" , 1],
0x12: ["Boost2工作状态" , 1],
0x13: ["开关机状态" , 1],
0x14: ["光伏组串1输入电压" , 2],
0x15: ["光伏组串2输入电压" , 2],
0x16: ["Boost1电感电流" , 2],
0x17: ["Boost2电感电流" , 2],
0x18: ["Boost输出电压" , 2],
0x19: ["Boost输出总电流" , 2],
0x1A: ["Boost1功率" , 3],
0x1C: ["Boost2功率" , 3],
0x1E: ["输入总功率" , 3],
0x20: ["LLC输出电压" , 2],
0x21: ["端口输出电压" , 2],
0x22: ["LLC输出电流均值" , 2],
0x23: ["LLC输出电流峰值" , 2],
0x24: ["绝缘检测电压" , 2],
0x25: ["散热片温度" , 2],
0x26: ["腔体1温度" , 2],
0x27: ["腔体2温度" , 2],
0x28: ["设备温度" , 2],
0x50: ["启停控制命令" , 2],
0x51: ["故障清除命令" , 2],
0x52: ["参数还原命令" , 2],
0x53: ["设备复位命令" , 2],
0x54: ["模式更改命令" , 2],
0x55: ["短时停机命令(未启用)" , 2],
0x56: ["手动录波命令" , 2],
0x57: ["时间配置命令" , 7, 3],
0x60: ["整机运行使能", 1],
0x61: ["最小启动允许输入电压", 2],
0x62: ["最大启动允许输入电压", 2],
0x63: ["最小停机输入电压", 2],
0x64: ["最大停机输入电压", 2],
0x65: ["最小启动允许输出电压", 2],
0x66: ["最大启动允许输出电压", 2],
0x67: ["最小停止允许输出电压", 2],
0x68: ["最大停止允许输出电压", 2],
0x69: ["最小MPPT电流限值", 2],
0x6A: ["最大MPPT电流限值", 2],
0x6B: ["保留数据项", 2],
0x6C: ["最大功率限值", 3],
0x6E: ["最大功率限值存储值", 3],
0x70: ["Boost输入过压保护值", 2],
0x71: ["Boost输出过压保护值", 2],
0x72: ["LLC输出过压保护值", 2],
0x73: ["LLC输出欠压保护值", 2],
0x74: ["Boost电感过流保护值", 2],
0x75: ["LLC输出电流均值保护值", 2],
0x76: ["LLC输出电流峰值保护值", 2],
0x77: ["保留数据项", 2],
0x78: ["过载保护值", 3],
0x7A: ["过温故障值", 2],
0x7B: ["过温告警值", 2],
0x7C: ["过温恢复值", 2],
0x7D: ["输出继电器故障判断差值", 2],
0x7E: ["LLC输出电压给定值", 2],
0x7F: ["Boost输出电压给定值", 2],
0x80: ["三点法中间阈值", 2],
0x81: ["浮充电压", 2],
0x82: ["恒压充电电压", 2],
0x83: ["llc软起开始电压", 2],
0x84: ["boost开始运行电压", 2],
0x85: ["boost停止运行电压", 2],
0x86: ["绝缘检测正阻抗限值", 3],
0x88: ["绝缘检测负阻抗限值", 3],
0x8A: ["保留地址项", 2],
0x8B: ["保留地址项", 2],
0x8C: ["保留地址项", 2],
0x8D: ["保留地址项", 2],
0x8E: ["保留地址项", 2],
0x8F: ["保留地址项", 2],
0x100: ["程序版本字符串", 4, 16],
0x110: ["设备型号字符串", 4, 16],
0x120: ["保留地址项", 4, 16],
0x130: ["生产厂家字符串", 4, 8],
0x138: ["保留地址项", 4, 8],
0x140: ["保留地址项", 4, 16],
0x150: ["保留地址项", 4, 16],
0x160: ["硬件版本字符串", 4, 16],
0x170: ["设备序列号", 4, 16],
0x180: ["设备MES码", 4, 16],
0x190: ["出厂日期批次", 4, 16],
}
class LaminaController:
def __init__(self, com_name="COM16", addr_645=[0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA], addr_modbus=0x01, **kwargs):
# 初始化串口通信
if com_name is not None:
com_config = {}
com_config['baudrate'] = kwargs['baudrate'] if 'baudrate' in kwargs.keys() else 115200
com_config['parity'] = kwargs['parity'] if 'parity' in kwargs.keys() else 'N'
com_config['bytesize'] = kwargs['bytesize'] if 'bytesize' in kwargs.keys() else 8
com_config['stopbits'] = kwargs['stopbits'] if 'stopbits' in kwargs.keys() else 1
self.__com = Serial(com_name, **com_config)
else:
self.__com =None
self.flag_print = 'frame_print' in kwargs.keys()
self.time_out = kwargs['time_out'] if 'time_out' in kwargs.keys() else 1
self.retry = kwargs['retry'] if 'retry' in kwargs.keys() else 1
self.retry_sub = kwargs['retry_sub'] if 'retry_sub' in kwargs.keys() else 1
self.block = {
'addr' : addr_645,
'type' : 'modbus',
'data' : {
'addr_dev' : addr_modbus,
'data_define': modbus_map,
},
}
def __transfer_data(self, frame):
""" 报文数据传输 """
if self.__com is None:
print(trans_list_to_str(frame))
return False
cnt = 0
while cnt < self.retry:
self.__com.read_all()
self.__com.write(bytearray(frame))
cnt_sub = 0
frame_recv = None
while not frame_recv:
time.sleep(self.time_out)
frame_recv = self.__com.read_all()
cnt_sub += 1
if cnt_sub >= self.retry_sub:
break
try:
output_text = check_frame_modbus(frame_recv, self.block['data'])
except Exception as e:
print(e)
cnt += 1
continue
print(output_text)
break
if self.flag_print:
print(trans_list_to_str(frame))
print(trans_list_to_str(frame_recv))
return cnt < self.retry
def frame_read(self, daddr=0x60, dlen=0x30):
self.block['data']['type'] = 'read'
self.block['data']['data_addr'] = daddr
self.block['data']['data_len'] = dlen
frame = make_frame_modbus(self.block['data'])
return self.__transfer_data(frame)
def frame_write_one(self, daddr=0x85, dval=-900):
self.block['data']['type'] = 'write_one'
self.block['data']['data_addr'] = daddr
self.block['data']['data_val'] = dval
frame = make_frame_modbus(self.block['data'])
return self.__transfer_data(frame)
def frame_write_dual(self, daddr=0x91, dval=600):
self.block['data']['type'] = 'write_dual'
self.block['data']['data_addr'] = daddr
self.block['data']['data_val'] = dval
frame = make_frame_modbus(self.block['data'])
return self.__transfer_data(frame)
def frame_write_str(self, daddr=0x82, dval=[0x06, 0x05, 0x04, 0x03, 0x02, 0x01]):
self.block['data']['type'] = 'write_str'
self.block['data']['data_addr'] = daddr
self.block['data']['data_val'] = dval
frame = make_frame_modbus(self.block['data'])
return self.__transfer_data(frame)
def frame_record(self):
""" 读取录波数据
"""
self.block['data']['file_block_size'] = 240
# 读取config
self.block['data']['type'] = 'record_cfg'
self.block['data']['step'] = 'start'
frame_master = make_frame_modbus(self.block['data'])
ret, cnt, cnt_sub = True, 0, 0
frame_data_cfg = []
while ret:
self.__com.read_all()
self.__com.write(frame_master)
frame_slave = None
while not frame_slave:
time.sleep(0.1)
frame_slave = self.__com.read_all()
cnt_sub += 1
if cnt_sub >= self.retry_sub:
break
try:
ret, *block_cfg = check_frame_modbus(frame_slave, self.block['data'])
print(f"{trans_list_to_str(frame_slave[:9])}")
except Exception as ex:
print(ex)
cnt += 1
ret = False
if ret:
cnt = 0
frame_data_cfg.append(block_cfg[0])
if block_cfg[0]['seq'] == 0:
self.block['data']['step'] = 'next'
frame_master = make_frame_modbus(self.block['data'])
elif (block_cfg[0]['seq']) >= block_cfg[0]['total']:
ret = False
elif cnt < self.retry:
ret = True
# 读取data
self.block['data']['type'] = 'record_data'
self.block['data']['step'] = 'start'
frame_master = make_frame_modbus(self.block['data'])
ret, cnt, cnt_sub = True, 0, 0
frame_data_record = []
while ret:
self.__com.read_all()
self.__com.write(frame_master)
time.sleep(0.3)
frame_slave = None
while not frame_slave:
time.sleep(0.1)
frame_slave = self.__com.read_all()
cnt_sub += 1
if cnt_sub >= self.retry_sub:
break
try:
ret, *block_record = check_frame_modbus(frame_slave, self.block['data'])
print(f"{trans_list_to_str(frame_slave[:9])}")
except Exception as ex:
print(ex)
cnt += 1
ret = False
if ret:
cnt = 0
frame_data_record.append(block_record[0])
if block_record[0]['seq'] == 0:
self.block['data']['step'] = 'next'
frame_master = make_frame_modbus(self.block['data'])
elif (block_record[0]['seq']) >= block_record[0]['total']:
ret = False
elif cnt < self.retry:
ret = True
if len(frame_data_record) == 0:
raise Exception("未取得录波数据.")
# 处理配置信息
data_cfg_bare = b"".join([x['data'] for x in frame_data_cfg])
config_record = {'LinePos': []}
pos = 0
if data_cfg_bare[pos+1] != 0xF1 or data_cfg_bare[pos] != 0xF1:
Warning("config 配置文件格式异常")
pos += 2
config_record['LinePos'].append(pos)
len_faultword = (data_cfg_bare[3] * 0x100 + data_cfg_bare[2])
pos += 2
config_record['FaultWord'] = []
for i in range(0, len_faultword, 2):
faultword = data_cfg_bare[pos+i+1] * 0x100 + data_cfg_bare[pos+i]
config_record['FaultWord'].append(faultword)
pos += len_faultword
config_record['SysStatus'] = data_cfg_bare[pos+1] * 0x100 + data_cfg_bare[pos]
pos += 2
config_record['FaultRecordPosition'] = data_cfg_bare[pos+1] * 0x100 + data_cfg_bare[pos]
pos += 2
config_record['FaultNum'] = data_cfg_bare[pos+1] * 0x100 + data_cfg_bare[pos]
pos += 2
config_record['Standard'] = data_cfg_bare[pos+1] * 0x100 + data_cfg_bare[pos]
pos += 2
if data_cfg_bare[pos+1] != 0xF2 or data_cfg_bare[pos] != 0xF2:
Warning("config 配置文件格式异常")
pos += 2
config_record['LinePos'].append(pos)
config_record['ChannelNum'] = data_cfg_bare[pos+1] * 0x100 + data_cfg_bare[pos]
pos += 2
config_record['ChNum_Alg'] = data_cfg_bare[pos+1] * 0x100 + data_cfg_bare[pos]
pos += 2
config_record['ChNum_Dgt'] = data_cfg_bare[pos+1] * 0x100 + data_cfg_bare[pos]
pos += 2
if data_cfg_bare[pos+1] != 0xF3 or data_cfg_bare[pos] != 0xF3:
Warning("config 配置文件格式异常")
pos += 2
config_record['LinePos'].append(pos)
config_record['ChannelDescription'] = []
config_record['ChannelCoefficient'] = []
for i in range(config_record['ChannelNum']):
len_str = data_cfg_bare[pos+1] * 0x100 + data_cfg_bare[pos]
pos += 2
ch_desc = []
for j in range(0, len_str, 2):
ch_desc.append(data_cfg_bare[pos + j])
pos += len_str
ch_coe = data_cfg_bare[pos+1] * 0x100 + data_cfg_bare[pos]
pos += 2
config_record['ChannelDescription'].append(bytearray(ch_desc).decode())
config_record['ChannelCoefficient'].append(ch_coe)
if data_cfg_bare[pos+1] != 0xF4 or data_cfg_bare[pos] != 0xF4:
Warning("config 配置文件格式异常")
pos += 2
config_record['LinePos'].append(pos)
config_record['SysFreq'] = data_cfg_bare[pos+1] * 0x100 + data_cfg_bare[pos]
pos += 2
config_record['FreqNum'] = data_cfg_bare[pos+1] * 0x100 + data_cfg_bare[pos]
pos += 2
config_record['SampleFreq'] = data_cfg_bare[pos+1] * 0x100 + data_cfg_bare[pos]
pos += 2
config_record['SamplePoint'] = data_cfg_bare[pos+1] * 0x100 + data_cfg_bare[pos]
pos += 2
config_record['DataType'] = data_cfg_bare[pos+1] * 0x100 + data_cfg_bare[pos]
pos += 2
config_record['TimeFactor'] = data_cfg_bare[pos+1] * 0x100 + data_cfg_bare[pos]
pos += 2
if data_cfg_bare[pos+1] != 0xF5 or data_cfg_bare[pos] != 0xF5:
Warning("config 配置文件格式异常")
pos += 2
config_record['LinePos'].append(pos)
time_stamp = {
'year': data_cfg_bare[pos+1] * 0x100 + data_cfg_bare[pos],
'month': data_cfg_bare[pos+3] * 0x100 + data_cfg_bare[pos+2],
'day': data_cfg_bare[pos+5] * 0x100 + data_cfg_bare[pos+4],
'hour': data_cfg_bare[pos+7] * 0x100 + data_cfg_bare[pos+6],
'minute': data_cfg_bare[pos+9] * 0x100 + data_cfg_bare[pos+8],
'second': data_cfg_bare[pos+11] * 0x100 + data_cfg_bare[pos+10] +
(data_cfg_bare[pos+13] * 0x100 + data_cfg_bare[pos+12]) * 0.001,
}
config_record['StartTime'] = time_stamp
pos += 14
time_stamp = {
'year': data_cfg_bare[pos+1] * 0x100 + data_cfg_bare[pos],
'month': data_cfg_bare[pos+3] * 0x100 + data_cfg_bare[pos+2],
'day': data_cfg_bare[pos+5] * 0x100 + data_cfg_bare[pos+4],
'hour': data_cfg_bare[pos+7] * 0x100 + data_cfg_bare[pos+6],
'minute': data_cfg_bare[pos+9] * 0x100 + data_cfg_bare[pos+8],
'second': data_cfg_bare[pos+11] * 0x100 + data_cfg_bare[pos+10] +
(data_cfg_bare[pos+13] * 0x100 + data_cfg_bare[pos+12]) * 0.001,
}
config_record['TriggerTime'] = time_stamp
# 处理录波数据
data_record_bare = b"".join([x['data'] for x in frame_data_record])
data_record = []
pos = 0
while pos < len(data_record_bare):
record_point = {
'index': data_record_bare[pos+3] * 0x1000 +
data_record_bare[pos+2] * 0x100 +
data_record_bare[pos+1] * 0x10 +
data_record_bare[pos],
'timestamp': data_record_bare[pos+7] * 0x1000 +
data_record_bare[pos+6] * 0x100 +
data_record_bare[pos+5] * 0x10 +
data_record_bare[pos + 4],
'ChAlg': [],
'ChDgt': [],
}
pos += 8
for i in range(config_record['ChNum_Alg']):
point_data_alg = data_record_bare[pos+1] * 0x100 + data_record_bare[pos]
if data_record_bare[pos+1] & 0x80:
point_data_alg -= 0x10000
record_point['ChAlg'].append(point_data_alg)
pos += 2
for i in range(config_record['ChNum_Dgt']):
point_data_dgt = (data_record_bare[pos+(i // 8)] & (0x01 << (i % 8))) == (0x01 << (i % 8))
record_point['ChDgt'].append(point_data_dgt)
pos += 2
data_record.append(record_point)
return config_record, data_record
def frame_update(self, path_bin):
""" 程序升级
注意: 在使用单板升级测试时, 需要关闭低电压检测功能, 否则无法启动升级流程;
"""
self.block['data']['type'] = 'update'
self.block['data']['step'] = 'start'
self.block['data']['index'] = 0
self.block['data']['file'] = Path(path_bin).read_bytes()
self.block['data']['header_offset'] = 184
# 启动帧
frame_master = bytearray(make_frame_modbus(self.block['data']))
# 等待擦除完成返回
try_times = 30
self.__com.read_all()
while try_times:
time.sleep(self.time_out)
self.__com.write(frame_master)
frame_slave = self.__com.read_all()
if not frame_slave:
try_times -= 1
continue
try:
ret, _, self.block["data"]['file_block_size'] = check_frame_modbus(frame_slave, self.block['data'])
break
except Exception as e:
print(e)
if self.block["data"]['file_block_size'] == 0:
raise Exception("Error slave response.")
# 避免接收到延迟返回报文
time.sleep(self.time_out)
# 文件传输
self.block["data"]['step'] = 'trans'
data_remain = len(self.block["data"]['file']) - self.block['data']['header_offset']
while data_remain > 0:
frame_master = bytearray(make_frame_modbus(self.block['data']))
cnt = 0
while cnt < self.retry:
self.__com.read_all()
self.__com.write(frame_master)
cnt_sub = 0
frame_slave = None
while not frame_slave:
time.sleep(self.time_out)
frame_slave = self.__com.read_all()
cnt_sub += 1
if cnt_sub >= self.retry_sub:
break
try:
ret = check_frame_modbus(frame_slave, self.block['data'])
except Exception as e:
print(e)
ret = False, 0, 0
if ret[0]:
break
else:
cnt += 1
if cnt >= self.retry:
raise Exception("Error, Retry failed.")
self.block["data"]['index'] += 1
data_remain -= self.block["data"]['file_block_size']
# 结束升级
self.block["data"]['step'] = 'end'
frame_master = bytearray(make_frame_modbus(self.block['data']))
cnt = 0
while cnt < self.retry:
self.__com.read_all()
self.__com.write(frame_master)
cnt_sub = 0
frame_slave = None
while not frame_slave:
time.sleep(self.time_out)
frame_slave = self.__com.read_all()
cnt_sub += 1
if cnt_sub >= self.retry_sub:
break
try:
ret = check_frame_modbus(frame_slave, self.block['data'])
except Exception as e:
print(e)
ret = False, 0, 0
if ret[0]:
break
else:
cnt += 1
if cnt >= self.retry:
raise Exception("Error, Retry failed.")
from tenacity import retry, stop_after_attempt, wait_fixed
from device.LaminaController import LaminaController, LaminaController_new
from device.LaminaController import GeneratePackage_DLSP001_p280039
from device.LaminaController import GenerateImage_DLSP001_p280039
from device.tools.ByteConv import trans_list_to_str
from device.tools.IntelHex import file_Bin_to_IntelHex
def test_communication(time_out=2):
""" 通信成功率测试 """
log_success = 0
log_failed = 0
log_failedseries = 0
cnt_failedseries = 0
time_start = time.time()
saveconfig_print = dev_lamina.flag_print
param_saved = dev_lamina.flag_print, dev_lamina.retry, dev_lamina.time_out
dev_lamina.flag_print = False
dev_lamina.retry = 1
try:
while 1:
if dev_lamina.frame_read(0x0C, 0x20):
log_success += 1
cnt_failedseries = 0
else:
log_failed += 1
cnt_failedseries += 1
if log_failedseries <= cnt_failedseries:
log_failedseries = cnt_failedseries
while True:
dev_lamina.frame_read(0x0C, 0x20)
print(f"Time Stamp: {time.ctime()}")
print(f"Success Frame: {log_success}")
print(f"Failed Frame: {log_failed}")
print(f"Max Series Failed Frame: {log_failedseries}")
print(f"Success Frame: {dev_lamina.log['read']}")
print(f"Failed Frame: {dev_lamina.log['send'] - dev_lamina.log['read']}")
print(f"Max Series Failed Frame: {dev_lamina.log['keep-fail']}")
time.sleep(time_out)
finally:
time_end = time.time()
print("Test Result: ")
print(f"Time Start: {time.strftime(r'%Y-%m-%d %H:%M:%S', time.localtime(time_start))}, \tTime End: {time.strftime(r'%Y-%m-%d %H:%M:%S', time.localtime(time_end))}")
print(f"Time Elapsed: {time_end - time_start}")
print(f"Success Rate: {log_success / (log_success + log_failed) * 100}%")
dev_lamina.flag_print = saveconfig_print
print(f"Success Rate: {dev_lamina.log['read'] / dev_lamina.log['send'] * 100}%")
dev_lamina.flag_print, dev_lamina.retry, dev_lamina.time_out = param_saved
def test_record(path_CFG=None, path_data=None):
""" 执行录波数据读取流程 """
if path_CFG is None: path_CFG = Path(r"D:\WorkSpace\UserTool\SelfTool\FrameParser\test\p280039\record") / f"record_{'-'.join(time.ctime().replace(':', '').split(' '))}.cfg"
if path_data is None: path_data = Path(r"D:\WorkSpace\UserTool\SelfTool\FrameParser\test\p280039\record") / f"record_{'-'.join(time.ctime().replace(':', '').split(' '))}.dat"
config, data = dev_lamina.frame_record()
if not dev_lamina.frame_record():
return
config = dev_lamina.log['record']['config']
data = dev_lamina.log['record']['data']
# Header File
# Header File
# Config File
# Config File
text_cfg = ""
text_cfg += f"LaminaController,1,{config['Standard']}\r\n"
text_cfg += f"{config['ChannelNum']},{config['ChNum_Alg']}A,{config['ChNum_Dgt']}D\r\n"
@@ -581,10 +67,10 @@ def test_record(path_CFG=None, path_data=None):
text_cfg += f"1\r\n"
path_CFG.write_text(text_cfg)
# Data File
# Data File
text_record = ""
for point in data:
# line_record = f"{point['index']},{point['timestamp'] * 1000},"
# line_record = f"{point['index']},{point['timestamp'] * 1000},"
line_record = f"{point['index']},{30},"
for data_alg in point['ChAlg']:
line_record += f"{data_alg},"
@@ -593,6 +79,10 @@ def test_record(path_CFG=None, path_data=None):
text_record += line_record + "\r\n"
path_data.write_text(text_record)
print(f"Saved Path:")
print(f"\tconfig: {path_CFG}")
print(f"\tdata: {path_data}")
def make_Image():
""" 叠光控制器DSP镜像与升级包生成流程 """
@@ -740,18 +230,20 @@ if __name__=='__main__':
"Log": {'com_name': None,
# 'addr_645': [0x01, 0x00, 0x00, 0x00, 0x00, 0x40],
},
"Debug": {'com_name': 'COM3', 'baudrate': 115200, 'parity': 'N', 'bytesize': 8, 'stopbits': 1,
"Debug": {'com_name': 'COM8', 'baudrate': 115200, 'parity': 'N', 'bytesize': 8, 'stopbits': 1,
# 'addr_645': [0x01, 0x02, 0x03, 0x04, 0x05, 0x06],
'frame_print': True,
'time_out': 0.2, 'retry': 3, 'retry_sub': 10},
'time_out': 0.5, 'time_gap': 0.02, 'retry': 3},
}
dev_lamina = LaminaController(**mode_config['Debug'])
dev_lamina = LaminaController_new(**mode_config['Debug'])
dev_lamina.frame_read(0x0100, 0x20)
# dev_lamina.frame_read(0x0A, 0x20)
# dev_lamina.frame_read(0x60, 0x60)
if not hasattr(__builtins__,"__IPYTHON__") and 0: #
if not hasattr(__builtins__,"__IPYTHON__"): # and 0
""" 读取故障录波数据 """
path_CFG = Path(r"D:\WorkSpace\UserTool\SelfTool\FrameParser\test\p280039\result\record4.cfg")
path_data = Path(r"D:\WorkSpace\UserTool\SelfTool\FrameParser\test\p280039\result\record4.dat")
@@ -764,21 +256,14 @@ if __name__=='__main__':
""" 升级流程 """
path_project = Path("D:\WorkingProject\LightStackOptimizer\software\lamina_controller_dsp\lamina_controller_dsp")
file_hex = path_project / "DEBUG\lamina_controller_dsp.hex"
# file_hex = Path(r"C:\Users\wrqal\Documents\WXWork\1688856624403708\Cache\File\2024-09\lamina_controller_dsp(1).hex")
if not file_hex.exists():
raise Exception("工程编译目标文件不存在.")
file_package = make_Pakeage(file_hex)
dev_lamina.frame_write_one(0x60, 0)
time.sleep(1)
dev_lamina.frame_update(file_package)
time.sleep(6)
dev_lamina.frame_read(0x0100, 0x20)
# dev_lamina.frame_write_one(0x52, 0x01)
if dev_lamina.frame_update(file_hex, makefile=True):
time.sleep(2)
dev_lamina.frame_read(0x0100, 0x20)
# dev_lamina.frame_write_one(0x52, 0x01)

1280
source/dev_inverter.ipynb Normal file

File diff suppressed because it is too large Load Diff

306
source/dev_station.py Normal file
View File

@@ -0,0 +1,306 @@
""" 主站通信脚本
mqtt协议
"""
import time
import random
import logging
from paho.mqtt import client as mqtt_client
from device.LaminaAdapter import ParamMap_LaminaAdapter
from device.DeviceMQTT import DeviceMQTT
from device.function import protocols
MainStation = {
"broker": '123.249.75.235',
"port": 1883,
"account": ('TTE0101TC2311000003', 'qh10579lcb7au8o2')
}
ParamMap_LaminaCombiner = {
# 1 - Hex
# 2 - Int16[ratio]
# 3 - lnt32[ratio]
# 4 - str
# 5 - addr
# 6 - float
0x000: ["系统型号", 4, 16],
0x010: ["程序版本", 4, 16],
0x100: ["主板温度" , 2, 10],
0x101: ["输入电流1" , 2, 100],
0x102: ["输入电流2" , 2, 100],
0x103: ["输入电流3" , 2, 100],
0x104: ["输入电流4" , 2, 100],
0x105: ["输入电流5" , 2, 100],
0x106: ["输入电流6" , 2, 100],
0x107: ["输入电流7" , 2, 100],
0x108: ["输入电流8" , 2, 100],
0x11A: ["适配器01交互滴答", 2, 1],
0x11B: ["适配器02交互滴答", 2, 1],
0x11C: ["适配器03交互滴答", 2, 1],
0x11D: ["适配器04交互滴答", 2, 1],
0x11E: ["适配器05交互滴答", 2, 1],
0x11F: ["适配器06交互滴答", 2, 1],
0x120: ["适配器07交互滴答", 2, 1],
0x121: ["适配器08交互滴答", 2, 1],
0x122: ["适配器09交互滴答", 2, 1],
0x123: ["适配器10交互滴答", 2, 1],
0x124: ["适配器11交互滴答", 2, 1],
0x125: ["适配器12交互滴答", 2, 1],
0x126: ["适配器13交互滴答", 2, 1],
0x127: ["适配器14交互滴答", 2, 1],
0x128: ["适配器15交互滴答", 2, 1],
0x129: ["适配器16交互滴答", 2, 1],
0x12A: ["路由拓扑从节点个数", 2, 1],
0x12B: ["路由管理状态机状态", 2, 1],
0x12C: ["正在升级的适配器序号", 2, 1],
0x200: ["从板温度" , 2, 10],
0x201: ["输入电流9" , 2, 100],
0x202: ["输入电流10" , 2, 100],
0x203: ["输入电流11" , 2, 100],
0x204: ["输入电流12" , 2, 100],
0x205: ["输入电流13" , 2, 100],
0x206: ["输入电流14" , 2, 100],
0x207: ["输入电流15" , 2, 100],
0x208: ["输入电流16" , 2, 100],
0x800: ["设备地址", 5, 3],
0x803: ["时间", 5, 3],
0x806: ["文件记录日志级别", 1],
0x807: ["串口输出日志级别", 1],
0x808: ["日志输出控制字", 1],
0x809: ["mqtt设备编码(deviceID)", 4, 25],
0x822: ["mqtt直流表子设备编码(deviceID)", 4, 25],
0x83B: ["mqtt适配器子设备编码(deviceID)", 4, 25],
0x854: ["报文控制字", 1],
0x855: ["汇流箱类型", 1],
0x856: ["待升级适配器映射", 1],
0x857: ["适配器1地址", 5, 3],
0x85A: ["适配器1线路ID", 1],
0x85B: ["适配器2地址", 5, 3],
0x85E: ["适配器2线路ID", 1],
0x85F: ["适配器3地址", 5, 3],
0x862: ["适配器3线路ID", 1],
0x863: ["适配器4地址", 5, 3],
0x866: ["适配器4线路ID", 1],
0x867: ["适配器5地址", 5, 3],
0x86A: ["适配器5线路ID", 1],
0x86B: ["适配器6地址", 5, 3],
0x86E: ["适配器6线路ID", 1],
0x86F: ["适配器7地址", 5, 3],
0x872: ["适配器7线路ID", 1],
0x873: ["适配器8地址", 5, 3],
0x876: ["适配器8线路ID", 1],
0x877: ["适配器9地址", 5, 3],
0x87A: ["适配器9线路ID", 1],
0x87B: ["适配器10地址", 5, 3],
0x87E: ["适配器10线路ID", 1],
0x87F: ["适配器11地址", 5, 3],
0x882: ["适配器11线路ID", 1],
0x883: ["适配器12地址", 5, 3],
0x886: ["适配器12线路ID", 1],
0x887: ["适配器13地址", 5, 3],
0x88A: ["适配器13线路ID", 1],
0x88B: ["适配器14地址", 5, 3],
0x88E: ["适配器14线路ID", 1],
0x88F: ["适配器15地址", 5, 3],
0x892: ["适配器15线路ID", 1],
0x893: ["适配器16地址", 5, 3],
0x896: ["适配器16线路ID", 1],
0x897: ["档案自适用使能", 1],
0x898: ["档案自适用收集地址时间", 2, 1],
0x899: ["适配器开机时间", 2, 1],
0x89A: ["适配器关机时间", 2, 1],
0x89B: ["有流阈值", 2, 1],
0x89C: ["无流阈值", 2, 1],
0x89D: ["路由组网时间", 2, 1],
0x89E: ["并发抄读最大并发数", 2, 1],
0x89F: ["并发抄读等待回复超时时间", 2, 1],
0x8A0: ["并发抄读数据有效维持时间", 2, 1],
0x8A1: ["输出母线过压阈值", 2, 1],
0x8A2: ["默认电池基准电压", 2, 1],
0x8A3: ["实际电池基准电压", 2, 1],
0x8A4: ["功率限制功能关闭的适配器个数", 2, 1],
0x8A5: ["功率限制功能使关闭的适配器映射", 2, 1],
0x8A6: ["功率限制比率", 2, 1],
0x8A7: ["下挂电表类型", 1],
0x8A8: ["功率限制电池电压回执(差值)", 2, 1],
0x8A9: ["功率恢复电池电压回执(差值)", 2, 1],
0x8AA: ["是否ODM", 1],
0x8AB: ["系统类型", 4, 16],
0x8BB: ["控制器生产厂商", 4, 16],
0x8CB: ["控制器型号", 4, 16],
0x8DB: ["厂家缩写", 4, 4],
0x8DF: ["汇流箱软件版本前缀", 4, 8],
0x8E7: ["适配器软件版本前缀", 4, 8],
0x8EF: ["适配器生产厂商", 4, 8],
0x8F7: ["适配器型号", 4, 16],
}
class LaminaStation(DeviceMQTT):
def __init__(self, device_id, station=None, **kwargs):
""" 设备初始化 """
def check_frame_modbus_MultiDevice(frame):
""" 多设备帧报文检测 """
frame_block = self.block.copy()
if (0x4000 < frame_block['data_addr']) and (frame_block['data_addr'] < 0x6000):
""" 报文来自于适配器 """
dev_id = (frame_block['data_addr'] - 0x4000) // 0x200
frame_block['data_addr'] -= 0x4000 + 0x200 * dev_id
frame_block['data_define'] = ParamMap_LaminaAdapter
if ((frame_block['data_addr'] + frame_block['data_len']) >= 0x200):
raise ValueError("Data addresses across boundaries")
return protocols.check_frame_modbus(frame, frame_block)
if station is None:
station = MainStation
super().__init__(**station, device_id=device_id,
callbacks=(lambda : protocols.make_frame_modbus(self.block),
check_frame_modbus_MultiDevice),
**kwargs)
self.block = {
'addr_dev' : 0x00,
'data_define': ParamMap_LaminaCombiner,
}
def frame_read_adapter(self, id, daddr, dlen) -> bool:
""" 读取适配器数据 """
return self.frame_read(0x4000 + id * 0x200 + daddr, dlen)
def frame_read(self, daddr=0x60, dlen=0x30) -> bool:
self.block['type'] = 'read'
self.block['data_addr'] = daddr
self.block['data_len'] = dlen
return self._transfer_data()
def frame_write_one(self, daddr=0x85, dval=-900) -> bool:
self.block['type'] = 'write_one'
self.block['data_addr'] = daddr
# item_coff = self.block['data_define'][daddr][2] if len(self.block['data_define'][daddr]) > 2 else 1
self.block['data_val'] = int(dval) # * item_coff
return self._transfer_data()
def frame_write_dual(self, daddr=0x91, dval=600) -> bool:
self.block['type'] = 'write_dual'
self.block['data_addr'] = daddr
# item_coff = self.block['data_define'][daddr][2] if len(self.block['data_define'][daddr]) > 2 else 1
self.block['data_val'] = int(dval) # * item_coff
return self._transfer_data()
def frame_write_str(self, daddr=0x82, dval=[0x06, 0x05, 0x04, 0x03, 0x02, 0x01]) -> bool:
self.block['type'] = 'write_str'
self.block['data_addr'] = daddr
self.block['data_val'] = dval
return self._transfer_data()
if __name__ == '__main__':
mode_config = {
"dev1": {'device_id': 'TTE0101DX2406140046', # 张家港鹿苑北单管塔
'frame_print': True,
'time_out': 4, 'retry': 1},
"dev2": {'device_id': 'TTE0101DX2409230113', # 常来东-光伏
'frame_print': True,
'time_out': 4, 'retry': 1},
"dev3": {'device_id': 'TTE0101DX2406270041', # 大丰市镇区补点139
'frame_print': True,
'time_out': 4, 'retry': 1},
"dev4": {'device_id': 'TTE0101DX2407020114', # 大丰大龙南
'frame_print': True,
'time_out': 4, 'retry': 1},
"dev5": {'device_id': 'TTE0101DX2407010082', # 大丰草堰镇双垛村
'frame_print': True,
'time_out': 4, 'retry': 1},
"dev6": {'device_id': 'TTE0101DX2406140040', # 张家港泗港公落地内爬单管塔
'frame_print': True,
'time_out': 4, 'retry': 1},
"dev7": {'device_id': 'TTE0101DX2406260040', # 常熟河坝落地外爬单管塔
'frame_print': True,
'time_out': 4, 'retry': 1},
"dev8": {'device_id': 'TTE0101DX2407080036', # 昆山好孩子西落地外爬单管塔
'frame_print': True,
'time_out': 4, 'retry': 1},
"dev9": {'device_id': 'TTE0101DX2406140009', # 市区东山镇
'frame_print': True,
'time_out': 4, 'retry': 1},
"dev10": {'device_id': 'TTE0101DX2406270018', # 张家港兆丰北单管塔
'frame_print': True,
'time_out': 4, 'retry': 1},
"dev11": {'device_id': 'TTE0101DX2407290011', # 昆山市吴淞江污水厂基站机房
'frame_print': True,
'time_out': 4, 'retry': 1},
"dev12": {'device_id': 'TTE0101DX2409210071', # 丰顺村铁路搬迁-光伏
'frame_print': True,
'time_out': 6, 'retry': 1},
"dev13": {'device_id': 'TTE0101DX2410110017', # 张家港市福源纺织基站机房
'frame_print': True,
'time_out': 6, 'retry': 1},
"dev14": {'device_id': 'TTE0101DX2406140008', # 张家港乐余良种场落地外爬单管塔
'frame_print': True,
'time_out': 6, 'retry': 1},
"dev15": {'device_id': 'TTE0101DX2406270099', # 张家港塘市东落地景观塔
'frame_print': True,
'time_out': 6, 'retry': 1},
"dev16": {'device_id': 'TTE0101DX2409210027', # 2017-YD定州北只东
'frame_print': True,
'time_out': 6, 'retry': 1},
"dev17": {'device_id': 'TTE0101DX2406280009', # 壮志村委会-光伏
'frame_print': True,
'time_out': 6, 'retry': 1},
"dev18": {'device_id': 'TTE0101DX2406260013', # 开发区竹行机房-光伏
'frame_print': True,
'time_out': 6, 'retry': 1},
"dev19": {'device_id': 'TTE0101DX2409210093', # 2016-YD:定州北木庄
'frame_print': True,
'time_out': 6, 'retry': 1},
"dev20": {'device_id': 'TTE0101DX2409270062', # 内丘中张村北
'frame_print': True,
'time_out': 6, 'retry': 1},
"dev21": {'device_id': 'TTE0101DX2407080037', # 市区临湖东吴德生
'frame_print': True,
'time_out': 6, 'retry': 1},
"dev22": {'device_id': 'TTE0101DX2406300067', # (新版限功率升级)
'frame_print': True,
'time_out': 6, 'retry': 1},
"dev23": {'device_id': 'TTE0101HP2411260059', # 孟村董林小区/移动
'frame_print': True,
'time_out': 6, 'retry': 1},
"dev24": {'device_id': 'TTE0101DX2406280016', # 复兴村-光伏
'frame_print': True,
'time_out': 6, 'retry': 1},
"dev25": {'device_id': 'TTE0101HP2411180035', # 霸州-纸房头
'frame_print': True,
'time_out': 6, 'retry': 1},
"dev26": {'device_id': 'TTE0101DX2408010159', # 句容市小衣庄基站机房
'frame_print': True,
'time_out': 6, 'retry': 1},
"dev27": {'device_id': 'TTE0101HP2411180003', # 文安何庄村新建1
'frame_print': True,
'time_out': 6, 'retry': 1},
"dev28": {'device_id': 'TTE0101DX2406240043', # 樟山村
'frame_print': True,
'time_out': 6, 'retry': 1},
"dev29": {'device_id': 'TTE0101DX2409270044', # 行唐西口头
'frame_print': True,
'time_out': 6, 'retry': 1},
}
dev_lamina = LaminaStation(**mode_config["dev29"])
dev_lamina.frame_read(0x0000, 0x20)
time.sleep(2)
dev_lamina.frame_read(0x857, 0x40)
dev_lamina.frame_read_adapter(0, 0x0E, 0x20)
if not hasattr(__builtins__,"__IPYTHON__"):
pass
dev_lamina.frame_read(0x400E + 0x200 * (6-1), 0x20)
dev_lamina.frame_read(0x4072 + 0x200 * (6-1), 0x03)

198
source/device/DeviceMQTT.py Normal file
View File

@@ -0,0 +1,198 @@
import time
import json
import random
import logging
from paho.mqtt import client as mqtt_client
from .tools import ByteConv
from .function import protocols
# 通信重连参数
FIRST_RECONNECT_DELAY = 1
RECONNECT_RATE = 2
MAX_RECONNECT_COUNT = 12
MAX_RECONNECT_DELAY = 60
class DeviceMQTT:
""" MQTT通信设备原型
Note: 配置通道及订阅主题
"""
def __init__(self, broker, port, account=None, device_id=None, callbacks=None, **kwargs):
""" 设备初始化 """
self.topic = None
self.client_id = f'python-DeviceMQTT-{random.randint(0, 10000):04d}'
self.client = self.open_connection(broker, port, account, **kwargs)
self._subscribe(device_id)
self.flag_print = kwargs['frame_print'] if 'frame_print' in kwargs.keys() else False
self.time_out = kwargs['time_out'] if 'time_out' in kwargs.keys() else 1
self.time_gap = kwargs['time_gap'] if 'time_gap' in kwargs.keys() else 0.01
self.retry = kwargs['retry'] if 'retry' in kwargs.keys() else 1
match callbacks:
case (maker, parser):
self._frame_maker = maker if maker is not None else lambda self: ''
self._frame_parser = parser if parser is not None else lambda self, frame: ''
case _:
self._frame_maker = lambda self: ''
self._frame_parser = lambda self, frame: ''
self._message = []
self.output = {
'result': False,
'code_func': 0x00,
}
self.log = {
'send': 0,
'read': 0,
'keep-fail': 0,
'record': {
'config': None,
'data': None,
},
}
def open_connection(self, broker, port, account=None, **kwargs):
""" 创建链接 """
def on_connect(client, userdata, flags, rc, properties):
""" 回调函数-创建链接 """
if rc == 0:
print("Connected to MQTT Broker!")
else:
print("Failed to connect, return code %d\n", rc)
def on_disconnect(client, userdata, rc):
""" 回调函数-断开链接 """
print("Disconnected with result code: %s", rc)
reconnect_count, reconnect_delay = 0, FIRST_RECONNECT_DELAY
while reconnect_count < MAX_RECONNECT_COUNT:
print("Reconnecting in %d seconds...", reconnect_delay)
time.sleep(reconnect_delay)
try:
client.reconnect()
print("Reconnected successfully!")
return
except Exception as err:
logging.error("%s. Reconnect failed. Retrying...", err)
reconnect_delay *= RECONNECT_RATE
reconnect_delay = min(reconnect_delay, MAX_RECONNECT_DELAY)
reconnect_count += 1
print("Reconnect failed after %s attempts. Exiting...", reconnect_count)
def on_message(client, userdata, msg):
self._message.append(msg)
print(f"Received message from `{msg.topic}` topic")
client = mqtt_client.Client(client_id=self.client_id, callback_api_version=mqtt_client.CallbackAPIVersion.VERSION2)
if account is not None:
client.username_pw_set(account[0], account[1])
client.on_connect = on_connect if 'func_on_connect' not in kwargs.keys() else kwargs['func_on_connect']
client.on_disconnect = on_disconnect if 'func_on_disconnect' not in kwargs.keys() else kwargs['func_on_disconnect']
client.on_message = on_message if 'func_on_message' not in kwargs.keys() else kwargs['func_on_message']
client.connect(broker, port)
client.loop_start()
return client
def close_connection(self) ->bool:
""" 关闭连接 """
self.client.loop_close()
self.client.disconnect()
return True
def _subscribe(self, device_id):
""" 订阅主题 """
if self.topic is not None:
self.client.unsubscribe(self.topic[1])
topic_send = f"ctiot/download/7/0101/{device_id}/function/invoke"
topic_read = f"ctiot/upload/7/0101/{device_id}/#"
self.client.subscribe(topic_read)
self.device_id = device_id
self.topic = (topic_send, topic_read)
def __send(self, msg: bytearray, topic=None) -> bool:
""" 发布消息 """
if topic is None:
topic = self.topic[0]
message = {
"deviceId": self.device_id,
"isSubDevice": 0,
"requestType": 0,
"messageId": f"debug-{self.client_id[18:]}",
"timestamp": int(time.time()),
"functionId": "168493059",
"inputs": [
{
"modbus_msg": ByteConv.trans_list_to_str(msg).replace(" ", '')
}
]
}
result = self.client.publish(topic, str(message).replace('\'', '\"'))
return result[0] == 1
def __read(self, timeout=None) -> bytes:
self._message.clear()
timeout = timeout if timeout is not None else 1
self.client.loop_start()
time.sleep(timeout)
self.client.loop_stop()
for message in self._message:
message_data = json.loads(message.payload)
if message_data['messageId'] == f"debug-{self.client_id[18:]}":
""" 报文接收成功 """
frame = " ".join((message_data['modbus_msg'][2*i:2*(i+1)] for i in range(len(message_data['modbus_msg'])//2)))
frame = ByteConv.trans_str_to_list(frame)
return bytearray(frame)
return b''
def __read_frame(self) ->bool:
""" 读取报文并解析帧 """
frame_recv = b''
try:
frame_recv = self.__read(timeout=self.time_out)
self.output = self._frame_parser(frame_recv)
if self.flag_print:
print("Read Frame: ", ByteConv.trans_list_to_str(frame_recv))
except Exception as ex:
print("Error Info: ", ex)
if self.flag_print and frame_recv:
print("Fail Data: " , ByteConv.trans_list_to_str(frame_recv))
self.output['result'] = False
return self.output['result']
def _transfer_data(self) -> bool:
""" 串口收发报文, 包含重试逻辑与数据打印 """
# 生成发送帧
frame: bytearray = self._frame_maker()
# if not self.client.is_connected():
# """ 无效通信接口, 打印报文后返回 """
# print(ByteConv.trans_list_to_str(frame))
# return False
fail_count = 0
while fail_count < self.retry:
frame_discard = self.__read(timeout=0)
self.__send(frame)
self.log['send'] += 1
if self.flag_print and frame_discard:
print("Discard Data: " , frame_discard)
if self.flag_print:
print("Send Frame: ", ByteConv.trans_list_to_str(frame))
if self.__read_frame():
if (self.flag_print is not None) and 'Regs' in self.output.keys():
protocols.print_display(self.output['Regs'])
self.log['read'] += 1
break
fail_count += 1
self.log['keep-fail'] = fail_count if fail_count >= self.log['keep-fail'] else self.log['keep-fail']
time.sleep(2 * self.time_out)
return fail_count < self.retry

View File

@@ -0,0 +1,199 @@
import time
from pathlib import Path
from serial import Serial
from serial.tools import list_ports
from abc import ABC, abstractmethod
from .tools import ByteConv
from .function import protocols
class DeviceSerial:
""" 串口通信设备原型
Note: 串口资源释放与重复开启
"""
def __init__(self, com_name, callbacks, **kwargs):
""" 初始化设备 """
self.__com = None
if com_name is not None:
self.open_connection(com_name, **kwargs)
self.flag_print = kwargs['frame_print'] if 'frame_print' in kwargs.keys() else False
self.time_out = kwargs['time_out'] if 'time_out' in kwargs.keys() else 1
self.time_gap = kwargs['time_gap'] if 'time_gap' in kwargs.keys() else 0.01
self.retry = kwargs['retry'] if 'retry' in kwargs.keys() else 1
match callbacks:
case (maker, parser):
self._frame_maker = maker if maker is not None else lambda self: ''
self._frame_parser = parser if parser is not None else lambda self, frame: ''
case _:
self._frame_maker = lambda self: ''
self._frame_parser = lambda self, frame: ''
self.output = {
'result': False,
'code_func': 0x00,
}
self.log = {
'send': 0,
'read': 0,
'keep-fail': 0,
'record': {
'config': None,
'data': None,
},
}
def __read_frame(self) -> bool:
""" 使用帧字节超时策略读报文帧, 并进行解析数据, 打印异常 """
frame_recv = b''
time_start, time_current, flag_frame = time.time(), time.time(), False
while (time_current - time_start) < self.time_out:
time.sleep(self.time_gap)
time_current = time.time()
bytes_read = self.__com.read_all()
if flag_frame and len(bytes_read) == 0:
break
elif len(bytes_read):
flag_frame = True
frame_recv += bytes_read
try:
self.output = self._frame_parser(frame_recv)
if self.flag_print:
print("Read Frame: ", ByteConv.trans_list_to_str(frame_recv))
except Exception as ex:
print("Error Info: ", ex)
if self.flag_print and frame_recv:
print("Fail Data: " , ByteConv.trans_list_to_str(frame_recv))
self.output['result'] = False
return self.output['result']
def _transfer_data(self) -> bool:
""" 串口收发报文, 包含重试逻辑与数据打印 """
# 生成发送帧
frame: bytearray = self._frame_maker()
if self.__com is None:
""" 无效通信接口, 打印报文后返回 """
print(ByteConv.trans_list_to_str(frame))
return False
fail_count = 0
while fail_count < self.retry:
frame_discard = self.__com.read_all()
self.__com.write(frame)
self.log['send'] += 1
if self.flag_print and frame_discard:
print("Discard Data: " , frame_discard)
if self.flag_print:
print("Send Frame: ", ByteConv.trans_list_to_str(frame))
time.sleep(2 * self.time_gap)
if self.__read_frame():
if (self.flag_print is not None) and 'Regs' in self.output.keys():
protocols.print_display(self.output['Regs'])
self.log['read'] += 1
break
fail_count += 1
self.log['keep-fail'] = fail_count if fail_count >= self.log['keep-fail'] else self.log['keep-fail']
time.sleep(2 * self.time_out)
return fail_count < self.retry
def close_connection(self) -> bool:
""" 关闭连接, 释放通信资源
"""
if self.__com is not None:
self.__com.close()
return self.__com is None or (not self.__com.is_open)
def open_connection(self, port=None, **kwargs) -> bool:
""" 打开连接, 更新或重新配置通信资源
"""
com_config = {
'baudrate': 115200,
'parity': 'N',
'bytesize': 8,
'stopbits': 1,
}
kwargs = kwargs if kwargs else None
serial_close = lambda com: com.close() if com.isOpen() else None
serial_port_check = lambda port: port.upper() in (com.name for com in list_ports.comports())
match (self.__com, port, kwargs):
case (None, str() as port, None):
""" 使用默认参数打开串口 """
if not serial_port_check(port):
raise ValueError("无效串口端口: %s" % port)
self.__com = Serial(port, timeout=0, **com_config)
case (None, str() as port, dict() as kwargs):
""" 使用指定参数打开串口 """
if not serial_port_check(port):
raise ValueError("无效串口端口: %s" % port)
com_config['baudrate'] = kwargs['baudrate'] if 'baudrate' in kwargs.keys() else com_config['baudrate']
com_config['parity'] = kwargs['parity'] if 'parity' in kwargs.keys() else com_config['parity']
com_config['bytesize'] = kwargs['bytesize'] if 'bytesize' in kwargs.keys() else com_config['bytesize']
com_config['stopbits'] = kwargs['stopbits'] if 'stopbits' in kwargs.keys() else com_config['stopbits']
self.__com = Serial(port, timeout=0, **com_config)
case (Serial() as com, None, None):
""" 无参数重开串口 """
serial_close(com)
com.open()
case (Serial() as com, port, None):
""" 重新指定端口号并打开串口 """
serial_close(com)
if not serial_port_check(port):
raise ValueError("无效串口端口: %s" % port)
com.port = port
com.open()
case (Serial() as com, str() as port, dict() as kwargs):
""" 重新指定端口号与配置并打开串口 """
serial_close(com)
if not serial_port_check(port):
raise ValueError("无效串口端口: %s" % port)
com.port = port
com.baudrate = kwargs['baudrate'] if 'baudrate' in kwargs.keys() else com_config['baudrate']
com.parity = kwargs['parity'] if 'parity' in kwargs.keys() else com_config['parity']
com.bytesize = kwargs['bytesize'] if 'bytesize' in kwargs.keys() else com_config['bytesize']
com.stopbits = kwargs['stopbits'] if 'stopbits' in kwargs.keys() else com_config['stopbits']
com.open()
case _:
""" 匹配失败, 报错 """
raise ValueError("Invalid config.")
return self.__com.is_open
@abstractmethod
def frame_read(self, daddr=0x60, dlen=0x50) -> bool:
pass
@abstractmethod
def frame_write(self, daddr, dlen=1, dval=None) -> bool:
pass
@abstractmethod
def frame_update(self, path_file: Path, makefile: bool = False) -> bool:
pass

View File

@@ -1,69 +1,98 @@
import time
import socket
import random
import hashlib
from pathlib import Path
from tools.ByteConv import trans_list_to_str
from func_frame import make_frame_modbus, check_frame_modbus
modbus_map = {
from . import tools
from . import function
ParamMap_EnergyRouter = {
0x00: ['编译日期', 4, 6],
0x06: ['编译时间', 4, 5],
}
class EnergyRouter:
def __init__(self, ip="192.168.100.10", port=7, adddr_modbus=0x01):
""" 能量路由器远程升级测试(未完成)
"""
def __init__(self, ip="192.168.100.10", port=7, adddr_modbus=0x01, **kwargs):
self._ip = ip
self._port = port
self._addr_modbus =adddr_modbus
self.flag_print = 'frame_print' in kwargs.keys()
self.time_out = kwargs['time_out'] if 'time_out' in kwargs.keys() else 1
self.retry = kwargs['retry'] if 'retry' in kwargs.keys() else 1
self.tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.tcp_socket.connect((self._ip, self._port))
self.block = {
'addr_dev' : self._addr_modbus,
'data_define': modbus_map,
'addr_dev' : adddr_modbus,
'data_define': ParamMap_EnergyRouter,
}
self.output = {
'result': False,
'code_func': 0x00,
}
self.log = {
'send': 0,
'read': 0,
'keep-fail': 0,
'record': {
'config': None,
'data': None,
},
}
def __transfer_data(self, frame: bytes) -> bool:
""" 数据传输处理函数 """
if self.tcp_socket is None:
print(tools.ByteConv.trans_list_to_str(frame))
return False
try:
self.tcp_socket.send(frame)
time.sleep(self.time_out)
frame_recv = self.tcp_socket.recv(128)
self.output = function.protocols.check_frame_modbus(frame_recv, self.block)
if self.flag_print:
print("Read Frame: ", tools.ByteConv.trans_list_to_str(frame_recv))
if 'Regs' in self.output.keys():
function.protocols.print_display(self.output['Regs'])
except Exception as ex:
print("Error Info: ", ex)
if self.flag_print and frame_recv:
print("Fail Data: " , tools.ByteConv.trans_list_to_str(frame_recv))
self.output['result'] = False
return self.output['result']
def frame_read(self, daddr=0x00, dlen=0x10):
self.block['type'] = 'read'
self.block['data_addr'] = daddr
self.block['data_len'] = dlen
frame = make_frame_modbus(self.block)
if self.tcp_socket is None:
print(trans_list_to_str(frame))
return
# self.tcp_socket.recv(8)
self.tcp_socket.send(bytearray(frame))
time.sleep(0.5)
frame_recv = self.tcp_socket.recv(128)
output_text = check_frame_modbus(frame_recv, self.block)
print(output_text)
frame = function.protocols.make_frame_modbus(self.block)
return self.__transfer_data(frame)
def frame_update(self, path_bin):
""" 程序升级
"""
param_saved = self.flag_print, self.retry, self.time_out
self.block['type'] = 'update'
self.block['step'] = 'start'
self.block['file'] = Path(path_bin).read_bytes()
self.block['header_offset'] = 128
# 启动帧
frame_master = bytearray(make_frame_modbus(self.block))
frame_master = function.protocols.make_frame_modbus(self.block)
# 等待擦除完成返回
time.sleep(0.4)
self.tcp_socket.send(frame_master)
frame_slave = self.tcp_socket.recv(32)
if frame_slave == '':
raise Exception("TCP closed.")
self.block['file_block_size'] = check_frame_modbus(frame_slave, self.block)
if self.block['file_block_size'] == 0:
raise Exception("Error slave response.")
if not self.__transfer_data(frame_master):
self.flag_print, self.retry, self.time_out = param_saved
print('Upgrade Fail: start')
return False
self.block['file_block_size'] = self.output['upgrade']['length']
# 避免接收到延迟返回报文
time.sleep(0.4)
@@ -87,7 +116,7 @@ class EnergyRouter:
continue
seq_window[i] = 1
self.block['index'] = seq_offset + i
seq_frame_master[i] = bytearray(make_frame_modbus(self.block))
seq_frame_master[i] = function.protocols.make_frame_modbus(self.block)
self.tcp_socket.send(seq_frame_master[i])
# 接收帧回复
tmp = list(zip(range(len(seq_window)), seq_window))
@@ -97,10 +126,11 @@ class EnergyRouter:
# 接收到空数据, 对端已关闭连接
if seq_frame_slave[i] == '':
raise Exception("TCP closed.")
result, seq_current, seq_hope = check_frame_modbus(seq_frame_slave[i], None)
self.output = function.protocols.check_frame_modbus(seq_frame_slave[i], None)
seq_current, seq_hope = self.output['upgrade']['index'], self.output['upgrade']['hope']
if seq_current < seq_offset:
raise Exception("Error.")
elif result:
elif self.output['result']:
seq_window[seq_current - seq_offset] = 2
data_remain -= self.block['file_block_size']
if seq_hope is not None and seq_hope < seq_offset:
@@ -121,16 +151,60 @@ class EnergyRouter:
seq_window[i] = 0
# 结束升级
ret = 0
self.block['step'] = 'end'
frame_master = bytearray(make_frame_modbus(self.block))
frame_master = function.protocols.make_frame_modbus(self.block)
while ret == 0:
while self.output['result'] is False:
self.tcp_socket.send(frame_master)
frame_slave = self.tcp_socket.recv(8)
if frame_slave == '':
raise Exception("TCP closed.")
ret = check_frame_modbus(frame_slave[:18], self.block)
self.output = function.protocols.check_frame_modbus(frame_slave[:18], self.block)
def GeneratePackage_Demo_Xilinx(path_bin: Path):
""" 完整升级包生成测试 """
config = {
'file_type': [0x10, 0x01], # Xilinx-Demo 自机升级文件
'file_version': [0x00, 0x00], # 文件版本-00 用于兼容文件格式升级
# 'file_length': [], # 文件长度(自动生成)
# 'md5': [], # 文件MD5(自动生成)
'encrypt': [0x01], # 默认加密算法
'update_type': [0x01], # APP升级
'update_spec': [0x00, 0x00, 0x00, 0x00], # 升级特征字
'update_verison': [0x02, 0x00, 0x00, 0x01], # 升级版本号
'update_date': [0x22, 0x04, 0x24], # 升级版本日期
# 'area_code': [], # 省份特征
# 'uptate_str': [], # 升级段描述
# 'device_str': [], # 设备特征描述
# 'hex_name': [], # Hex文件名(自动读取)
# 文件Hex结构信息
# 'flash_addr': 0x3E8020, # 程序起始地址
# 'flash_size': 0x005FC0, # 程序空间大小
}
data_bin = path_bin.read_bytes()
md5_ctx = hashlib.md5()
md5_ctx.update(data_bin)
config["md5"] = list(md5_ctx.digest())
config['file_length'] = tools.ByteConv.conv_int_to_array(len(data_bin))
config['hex_name'] = list(path_bin.name.encode())[:80]
if (header:= function.file_upgrade.build_header(config, 128)) is None:
raise Exception("Header tag oversize. ")
if (header_512:= function.file_upgrade.build_header(config, 512)) is None:
raise Exception("Header tag oversize. ")
data_encrypt = function.file_upgrade.file_encryption(data_bin)
print("Upgrade file generated successfully.")
print(f"\t header_length={len(header)}, bin_length={len(data_bin)}[{hex(len(data_bin))}]")
print(f"\t file md5: {tools.ByteConv.trans_list_to_str(config['md5'])}")
file1 = path_bin.parent / (path_bin.stem + '.dat')
file1.write_bytes(header + data_bin)
file2 = path_bin.parent / (path_bin.stem + '_h512.dat')
file2.write_bytes(header_512 + data_bin)
if __name__ == "__main__":

View File

@@ -0,0 +1,540 @@
import time
import warnings
import hashlib
from math import ceil
from tqdm import tqdm
from pathlib import Path
from . import tools
from .tools import ByteConv, IntelHex
from .function import protocols, file_upgrade
from .DeviceSerial import DeviceSerial
ParamMap_LaminaAdapter = {
# 1 - Hex
# 2 - Int16
# 3 - lnt32
# 4 - str
# 5 - addr
# 6 - float
# 8 - func_callback
0x00: ["硬件版本识别电压", 2, 1000],
0x01: ["输出电容电电压", 2, 10],
0x02: ["参考电压", 2, 10],
0x0E: ["故障字1", 1],
0x0F: ["故障字2", 1],
0x10: ["MPPT工作状态", 1],
0x11: ["系统工作状态", 1],
0x12: ["系统工作模式", 1],
0x13: ["输入电压", 2, 10],
0x14: ["电感电流", 2, 100],
0x15: ["12V电压", 2, 10],
0x16: ["输出电压", 2, 10],
0x17: ["输入电流", 2, 100],
0x18: ["温度1", 2, 10],
0x19: ["温度2", 2, 10],
0x1A: ["输入功率", 3, 1000],
0x1C: ["设备温度", 2, 10],
0x1D: ["开关机状态", 1],
0x1E: ["电池电压", 2, 10],
0x1F: ["并机功率限值", 3, 1000],
0x21: ["输出限功率电压阈值", 2, 10],
0x50: ["启停控制命令" , 2],
0x51: ["故障清除命令" , 2],
0x52: ["参数还原命令" , 2],
0x53: ["设备复位命令" , 2],
0x54: ["主动故障命令" , 2],
0x55: ["短时停机命令" , 2],
0x5E: ["抖动频率上限", 2, 100],
0x5F: ["抖动频率下限", 2, 100],
0x60: ["光伏通道使能", 1],
0x61: ["最小启动输入电压", 2, 10],
0x62: ["最大启动输入电压", 2, 10],
0x63: ["最小停止输入电压", 2, 10],
0x64: ["最大停止输入电压", 2, 10],
0x65: ["最小MPPT电压", 2, 10],
0x66: ["最大MPPT电压", 2, 10],
0x67: ["最小启动输出电压", 2, 10],
0x68: ["最大启动输出电压", 2, 10],
0x69: ["最小停止输出电压", 2, 10],
0x6A: ["最大停止输出电压", 2, 10],
0x6B: ["输入过压保护值", 2, 10],
0x6C: ["输出过压保护值", 2, 10],
0x6D: ["输出欠压保护值", 2, 10],
0x6E: ["电感过流保护值", 2, 100],
0x6F: ["输入过流保护值", 2, 100],
0x70: ["最小电感电流限值", 2, 100],
0x71: ["最大电感电流限值", 2, 100],
0x72: ["浮充电压阈值", 2, 10],
0x73: ["三点法中间阈值", 2, 10],
0x74: ["恒压充电电压", 2, 10],
0x75: ["过温故障值", 2, 10],
0x76: ["过温告警值", 2, 10],
0x77: ["温度恢复值", 2, 10],
0x78: ["最低满载电压", 2, 10],
0x79: ["最高满载电压", 2, 10],
0x7A: ["输入过载保护值", 3, 1000],
0x7C: ["最小功率限值", 3, 1000],
0x7E: ["最大功率限值", 3, 1000],
0x80: ["最大功率限值存储值", 3, 1000],
0x82: ["载波通信地址", 5, 3],
0x85: ["电压环out_max", 2, 100],
0x86: ["电压环out_min", 2, 100],
0x87: ["电流环out_max", 2, 100],
0x88: ["电流环out_min", 2, 100],
0x89: ["MPPT扰动系数k_d_vin", 2, 100],
0x8A: ["dmin", 2, 1000],
0x8B: ["dmax", 2, 1000],
0x8C: ["扫描电压偏移scanvolt_offset", 2, 10],
0x8D: ["电压环Kp", 3, 100000],
0x8F: ["电压环Ki", 3, 100000],
0x91: ["电流环Kp", 3, 100000],
0x93: ["电流环Ki", 3, 100000],
0x95: ["日志级别", 1],
0x96: ["日志输出方式", 1],
0x97: ["采样校准volt_in_a", 2, 1000],
0x98: ["采样校准volt_in_b", 2, 100],
0x99: ["采样校准volt_out_a", 2, 1000],
0x9A: ["采样校准volt_out_b", 2, 100],
0x9B: ["采样校准curr_in_a", 2, 1000],
0x9C: ["采样校准curr_in_b", 2, 100],
0x9D: ["采样校准curr_induc_a", 2, 1000],
0x9E: ["采样校准curr_induc_b", 2, 100],
0x9F: ["采样校准volt_12V_a", 2, 1000],
0xA0: ["采样校准volt_12V_b", 2, 100],
0xA1: ["温度补偿temp1_b", 2, 10],
0xA2: ["温度补偿temp2_b", 2, 10],
0xA3: ["系统工作模式", 2],
0xA4: ["电感电流给定值curr_set", 2, 100],
0xA5: ["抖动频率上限", 2, 100],
0xA6: ["抖动频率下限", 2, 100],
0xA7: ["电池电压判断限值", 2, 10],
0xA8: ["MPPT追踪模式", 1],
0xA9: ["ADC参考电压", 2, 1000],
0xAA: ["硬件版本", 1],
0xAB: ["保留", 1],
0xAC: ["保留", 1],
0xAD: ["保留", 1],
0xAE: ["保留", 1],
0xAF: ["保留", 1],
0xB0: ["版本", 4, 16],
0x100: ["版本(ODM)", 4, 16],
0x110: ["型号", 4, 16],
0x120: ["载波芯片地址", 4, 16],
0x130: ["厂商", 4, 8],
0x138: ["保留", 4, 8],
0x140: ["保留", 4, 16],
0x150: ["保留", 4, 16],
0x160: ["硬件", 4, 16],
0x170: ["SN", 4, 16],
0x180: ["MES", 4, 16],
0x190: ["Datetime", 4, 16],
0x1A0: ["事件记录1", 8, 16],
0x1B0: ["事件记录2", 8, 16],
0x1C0: ["事件记录3", 8, 16],
0x1D0: ["事件记录4", 8, 16],
0x1E0: ["事件记录5", 8, 16],
0x1F0: ["事件记录6", 8, 16],
0x200: ["事件记录-Log", 8, 16],
}
MemoryMap_SLCP001 = {
'image_size': 0x100000, # 镜像文件大小
'app_size': 0x040000, # 应用程序大小
'boot_size': 0x010000, # Boot程序大小
'boot_addr': 0x000000, # Boot程序地址
'main_addr': 0x010000, # main程序地址
'back_addr': 0x0BC000, # back程序地址
'main_header': 0x0FC000, # main信息地址
'back_header': 0x0FE000, # back信息地址
}
MemoryMap_460 = {
'image_size': 0x058000, # 镜像文件大小
'app_size': 0x024000, # 应用程序大小
'boot_size': 0x010000, # Boot程序大小
'boot_addr': 0x000000, # Boot程序地址
'main_addr': 0x00C000, # main程序地址
'back_addr': 0x030000, # back程序地址
'main_header': 0x054000, # main信息地址
'back_header': 0x056000, # back信息地址
}
class LaminaAdapter(DeviceSerial):
""" 叠光适配器\优化器
使用继承方式实现功能
"""
def __init__(self, com_name,
addr_645=[0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA],
addr_modbus=0x01, type_dev='SLCP101', **kwargs):
""" 调用超类实现函数初始化 """
super().__init__(com_name,
callbacks=(lambda : protocols.make_frame_dlt645(self.block),
lambda frame: protocols.check_frame_dlt645(frame, self.block)),
**kwargs)
self.device = type_dev
match type_dev:
case 'SLCP001':
self.make_package = lambda *args, **kwargs: GeneratePackage('SLCP001', *args, **kwargs)
self.make_image = lambda *args, **kwargs: GenerateImage('SLCP001', *args, **kwargs)
case 'SLCP101':
self.make_package = lambda *args, **kwargs: GeneratePackage('SLCP101', *args, **kwargs)
self.make_image = lambda *args, **kwargs: GenerateImage('SLCP101', *args, **kwargs)
case 'SLCP102':
self.make_package = lambda *args, **kwargs: GeneratePackage('SLCP102', *args, **kwargs)
self.make_image = lambda *args, **kwargs: GenerateImage('SLCP102', *args, **kwargs)
case 'DLSY001':
self.make_package = lambda *args, **kwargs: GeneratePackage('DLSY001', *args, **kwargs)
self.make_image = lambda *args, **kwargs: GenerateImage('DLSY001', *args, **kwargs)
case _:
self.make_package = None
self.make_image = None
self.block = {
'addr' : addr_645,
'type' : 'modbus',
'data' : {
'addr_dev' : addr_modbus,
'data_define': ParamMap_LaminaAdapter,
},
}
def frame_read(self, daddr=0x60, dlen=0x50) -> bool:
""" 通用参数读取 """
self.block['data']['type'] = 'read'
self.block['data']['data_addr'] = daddr
self.block['data']['data_len'] = dlen
return self._transfer_data()
def frame_write(self, daddr, dlen=1, dval=None) -> bool:
""" 通用参数写入 """
if daddr in self.block['data']['data_define'].keys():
param_define = self.block['data']['data_define'][daddr]
else:
param_define = [f'未知参数{daddr}', 1]
match param_define[1]:
case 1 | 2:
return self.frame_write_one(daddr, 0 if dval is None else dval)
case 3:
return self.frame_write_dual(daddr, 0 if dval is None else dval)
case 4 | 5 | 6:
return self.frame_write_str(daddr, [] if dval is None else dval)
case 6 | 7 | 8:
type_param_define = next(v for k, v in protocols.modbus_map.items() if v == param_define[1])
warnings.warn(f"DataType unsupport write: {type_param_define}")
return False
case _:
return self.frame_write_one(daddr, 0 if dval is None else dval)
def frame_write_one(self, daddr=0x85, dval=-900) -> bool:
self.block['data']['type'] = 'write_one'
self.block['data']['data_addr'] = daddr
item_coff = self.block['data']['data_define'][daddr][2] if len(self.block['data']['data_define'][daddr]) > 2 else 1
self.block['data']['data_val'] = int(dval * item_coff)
return self._transfer_data()
def frame_write_dual(self, daddr=0x91, dval=600) -> bool:
self.block['data']['type'] = 'write_dual'
self.block['data']['data_addr'] = daddr
item_coff = self.block['data']['data_define'][daddr][2] if len(self.block['data']['data_define'][daddr]) > 2 else 1
self.block['data']['data_val'] = int(dval * item_coff)
return self._transfer_data()
def frame_write_str(self, daddr=0x82, dval=[0x06, 0x05, 0x04, 0x03, 0x02, 0x01]) -> bool:
self.block['data']['type'] = 'write_str'
self.block['data']['data_addr'] = daddr
self.block['data']['data_val'] = dval
return self._transfer_data()
def frame_read_log(self, step='next') -> bool:
self.block['data']['type'] = 'log'
self.block['data']['step'] = step
return self._transfer_data()
def frame_update(self, path_file: Path, makefile: bool = False, savefile: bool = False, **kwargs) -> bool:
""" 程序升级
注意: 在使用单板升级测试时, 需要关闭低电压检测功能, 否则无法启动升级流程;
"""
param_saved = self.flag_print, self.retry, self.time_out
self.flag_print = False
try:
status = 'init' # 初始化
if not path_file.exists():
raise Exception("工程编译目标文件不存在.")
if makefile and self.make_package is not None:
self.block['data']['file'] = self.make_package(path_file, **kwargs)
if savefile:
path_file.parent.joinpath(path_file.stem + '.dat').write_bytes(self.block['data']['file'])
else:
self.block['data']['file'] = path_file.read_bytes()
self.block['data']['type'] = 'update'
self.block['data']['header_offset'] = 184
status = 'start' # 启动帧
self.block['data']['step'] = 'start'
self.block['data']['index'] = 0
assert self._transfer_data()
self.block["data"]['file_block_size'] = self.output['upgrade']['length']
# 避免接收到延迟返回报文
time.sleep(self.time_out)
status = 'trans' # 文件传输
self.retry = 3
self.time_out = 1.5
self.block["data"]['step'] = 'trans'
self.block['data']['index'] = 0
frame_total = ceil((len(self.block["data"]['file']) - self.block['data']['header_offset']) / self.block["data"]['file_block_size'])
for idx in tqdm(range(frame_total), desc="File Transmitting"):
self.block["data"]['index'] = idx
assert self._transfer_data()
status = 'end' # 结束升级
self.time_out = 1
self.block["data"]['step'] = 'end'
self.block["data"]['index'] += 1
assert self._transfer_data()
except Exception as ex:
""" 通用异常处理 """
self.flag_print, self.retry, self.time_out = param_saved
report = f'Upgrade Fail: {status}'
report += f', Frame in {self.block["data"]["index"]}' if status == 'trans' else ''
report += f'\n Error by {ex}' if not isinstance(ex, AssertionError) else ''
print(report)
return False
self.flag_print, self.retry, self.time_out = param_saved
time.sleep(6)
self.frame_read(0x100, 0x20)
return True
def GeneratePackage(type_dev: str, path_hex: Path, **kwargs) -> bytearray:
""" 生成升级包 """
upgrade_backup = kwargs['upgrade_backup'] if 'upgrade_backup' in kwargs.keys() else None
config = {
'prod_type': [0x45, 0x00], # 产品类型
'method_compress': False, # 文件压缩
'prog_id': list(type_dev.encode('ascii')), # 程序识别号
'prog_type': 'app', # 程序类型
'area_code': [0x00, 0x00], # 地区
}
match type_dev:
case 'SLCP001':
MemoryMap = MemoryMap_SLCP001
case 'SLCP101':
MemoryMap = MemoryMap_460
case 'SLCP102':
MemoryMap = MemoryMap_460
case 'DLSY001':
MemoryMap = MemoryMap_460
case _:
raise Exception("Unknow device.")
bin_main = IntelHex.file_IntelHex_to_Bin(path_hex.read_text(), len_max=MemoryMap['app_size'])
encrypt_main = file_upgrade.file_encryption(bin_main)
md5_ctx = hashlib.md5()
md5_ctx.update(bin_main)
config["md5"] = list(md5_ctx.digest())
config['file_length'] = ByteConv.conv_int_to_array(len(bin_main))
config['hex_name'] = list(path_hex.name.encode())[:64]
config['hex_name'] += [0] * (64 - len(config['hex_name']))
if upgrade_backup:
config['prog_id'][:4] = b'BACK'
if (main_header:=file_upgrade.build_header_new(config)) is None:
raise Exception("Header tag oversize. ")
print("Package generated successfully.")
print(f"File name: {path_hex.name}")
print(f"File Info:")
print(f"\t header_length={len(main_header)}, bin_length={len(bin_main)}[{hex(len(bin_main))}]")
# 组装镜像
Image = [0xFF] * (len(main_header) + len(encrypt_main))
offset_image = 0
Image[offset_image: offset_image + len(main_header)] = main_header
offset_image += len(main_header)
Image[offset_image: offset_image + len(encrypt_main)] = encrypt_main
# 额外处理
if 'output_bin' in kwargs.keys() and kwargs['output_bin']:
(path_hex / (path_hex.stem + '.bin')).write_bytes(bin_main)
if 'output_dat' in kwargs.keys() and kwargs['output_dat']:
(path_hex / (path_hex.stem + '.dat')).write_bytes(bytearray(Image))
return bytearray(Image)
def GenerateImage(type_dev: str, path_boot: Path, path_main: Path, path_back: Path, **kwargs) ->bytearray:
""" 镜像生成 """
config = {
'prod_type': [0x45, 0x00], # 产品类型
'method_compress': False, # 文件压缩
'prog_id': list(type_dev.encode('ascii')), # 程序识别号
'prog_type': 'app', # 程序类型
'area_code': [0x00, 0x00], # 地区
}
match type_dev:
case 'SLCP001':
MemoryMap = MemoryMap_SLCP001
case 'SLCP101':
MemoryMap = MemoryMap_460
case 'SLCP102':
MemoryMap = MemoryMap_460
case 'DLSY001':
MemoryMap = MemoryMap_460
case _:
raise Exception("Unknow device.")
bin_boot = IntelHex.file_IntelHex_to_Bin(path_boot.read_text(), len_max=MemoryMap['boot_size'])
bin_main = IntelHex.file_IntelHex_to_Bin(path_main.read_text(), len_max=MemoryMap['app_size'])
bin_back = IntelHex.file_IntelHex_to_Bin(path_back.read_text(), len_max=MemoryMap['app_size'])
md5_ctx = hashlib.md5()
md5_ctx.update(bin_main)
config["md5"] = list(md5_ctx.digest())
config['file_length'] = ByteConv.conv_int_to_array(len(bin_main))
config['hex_name'] = list(path_main.name.encode())[:64]
config['hex_name'] += [0] * (64 - len(config['hex_name']))
if (main_header:=file_upgrade.build_header_new(config)) is None:
raise Exception("Header tag oversize. ")
md5_ctx = hashlib.md5()
md5_ctx.update(bin_back)
config["md5"] = list(md5_ctx.digest())
config['file_length'] = ByteConv.conv_int_to_array(len(bin_back))
config['hex_name'] = list(path_back.name.encode())[:64]
config['hex_name'] += [0] * (64 - len(config['hex_name']))
config['prog_id'][:4] = b'BACK'
if (back_header:=file_upgrade.build_header_new(config)) is None:
raise Exception("Header tag oversize. ")
# 组装镜像
Image = [0xFF] * MemoryMap['image_size']
Image[MemoryMap['boot_addr']: MemoryMap['boot_addr'] + len(bin_boot)] = bin_boot
Image[MemoryMap['main_addr']: MemoryMap['main_addr'] + len(bin_main)] = bin_main
Image[MemoryMap['back_addr']: MemoryMap['back_addr'] + len(bin_back)] = bin_back
Image[MemoryMap['main_header']: MemoryMap['main_header'] + len(main_header)] = main_header
Image[MemoryMap['back_header']: MemoryMap['back_header'] + len(back_header)] = back_header
# Log打印
print("Merge Image generated successfully.")
print(f"Main File:")
print(f"\t header_length={len(main_header)}, bin_length={len(bin_main)}[{hex(len(bin_main))}]")
print(f"Back File:")
print(f"\t header_length={len(back_header)}, bin_length={len(bin_back)}[{hex(len(bin_back))}]")
# 额外文件
if 'output_header' in kwargs.keys() and kwargs['output_header']:
(path_main.parent / (path_main.stem + '.header')).write_bytes(main_header)
(path_back.parent / (path_back.stem + '.header')).write_bytes(back_header)
if 'output_bin' in kwargs.keys() and kwargs['output_bin']:
(path_main.parent / (path_main.stem + '.bin')).write_bytes(bin_main)
(path_back.parent / (path_back.stem + '.bin')).write_bytes(bin_back)
if 'output_encrypt' in kwargs.keys() and kwargs['output_encrypt']:
encrypt_main = file_upgrade.file_encryption(bin_main)
encrypt_back = file_upgrade.file_encryption(bin_back)
(path_main.parent / (path_main.stem + '.encrypt')).write_bytes(encrypt_main)
(path_back.parent / (path_back.stem + '.encrypt')).write_bytes(encrypt_back)
return bytearray(Image)
def GeneratePackage_DLSY001_p460(path_hex: Path):
""" 叠光优化器-460平台版本 生成升级包 """
config = {
'prod_type': [0x45, 0x00], # 产品类型
'method_compress': False, # 文件压缩
'prog_id': list(b"DLSY001"), # 程序识别号
'prog_type': 'app', # 程序类型
'area_code': [0x00, 0x00], # 地区
}
bin_main = IntelHex.file_IntelHex_to_Bin(path_hex.read_text(), len_max=0x024000)
encrypt_main = file_upgrade.file_encryption(bin_main)
md5_ctx = hashlib.md5()
md5_ctx.update(bin_main)
config["md5"] = list(md5_ctx.digest())
config['file_length'] = ByteConv.conv_int_to_array(len(bin_main))
config['hex_name'] = list(path_hex.name.encode())[:64]
config['hex_name'] += [0] * (64 - len(config['hex_name']))
if (main_header:=file_upgrade.build_header_new(config)) is None:
raise Exception("Header tag oversize. ")
print("Package generated successfully.")
print(f"File name: {path_hex.name}")
print(f"File Info:")
print(f"\t header_length={len(main_header)}, bin_length={len(bin_main)}[{hex(len(bin_main))}]")
# 组装镜像
Image = [0xFF] * (len(main_header) + len(encrypt_main))
offset_image = 0
Image[offset_image: offset_image + len(main_header)] = main_header
offset_image += len(main_header)
Image[offset_image: offset_image + len(encrypt_main)] = encrypt_main
return bytearray(Image), bin_main
def GenerateImage_DLSY001_p460(path_boot: Path, path_main: Path, path_back: Path):
""" 叠光优化器-460平台版本 镜像生成 """
config = {
'prod_type': [0x45, 0x00], # 产品类型
'method_compress': False, # 文件压缩
'prog_id': list(b"DLSY001"), # 程序识别号
'prog_type': 'app', # 程序类型
'area_code': [0x00, 0x00], # 地区
}
bin_boot = IntelHex.file_IntelHex_to_Bin(path_boot.read_text(), len_max=0x00C000)
bin_main = IntelHex.file_IntelHex_to_Bin(path_main.read_text(), len_max=0x024000)
bin_back = IntelHex.file_IntelHex_to_Bin(path_back.read_text(), len_max=0x024000)
encrypt_main = file_upgrade.file_encryption(bin_main)
encrypt_back = file_upgrade.file_encryption(bin_back)
md5_ctx = hashlib.md5()
md5_ctx.update(bin_main)
config["md5"] = list(md5_ctx.digest())
config['file_length'] = ByteConv.conv_int_to_array(len(bin_main))
config['hex_name'] = list(path_main.name.encode())[:64]
config['hex_name'] += [0] * (64 - len(config['hex_name']))
if (main_header:=file_upgrade.build_header_new(config)) is None:
raise Exception("Header tag oversize. ")
md5_ctx = hashlib.md5()
md5_ctx.update(bin_back)
config["md5"] = list(md5_ctx.digest())
config['file_length'] = ByteConv.conv_int_to_array(len(bin_back))
config['hex_name'] = list(path_back.name.encode())[:64]
config['hex_name'] += [0] * (64 - len(config['hex_name']))
if (back_header:=file_upgrade.build_header_new(config)) is None:
raise Exception("Header tag oversize. ")
print("Merge Image generated successfully.")
print(f"Main File:")
print(f"\t header_length={len(main_header)}, bin_length={len(bin_main)}[{hex(len(bin_main))}]")
print(f"Back File:")
print(f"\t header_length={len(back_header)}, bin_length={len(bin_back)}[{hex(len(bin_back))}]")
# 组装镜像
Image = [0xFF] * 0x058000
offset_image = 0
Image[offset_image: offset_image + len(bin_boot)] = bin_boot
offset_image = 0x00C000
Image[offset_image: offset_image + len(bin_main)] = bin_main
offset_image = 0x030000
Image[offset_image: offset_image + len(bin_back)] = bin_back
offset_image = 0x054000
Image[offset_image: offset_image + len(main_header)] = main_header
offset_image = 0x056000
Image[offset_image: offset_image + len(back_header)] = back_header
return bytearray(Image), main_header, back_header

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,5 @@
from . import tools
from . import function
from . import EnergyRouter
from . import LaminaAdapter
from . import LaminaController

View File

@@ -0,0 +1,2 @@
from . import protocols
from . import file_upgrade

View File

@@ -0,0 +1,225 @@
from datetime import datetime
from crc import Calculator, Crc16
Header_Tag = {
'file_type': [0x00, 2], # 0 - 文件类型; 2byte
'file_version': [0x01, 2], # 1 - 文件版本; 2byte
'file_length': [0x02, 4], # 2 - 文件长度; 4byte
'md5': [0x03, 16], # 3 - 文件MD5; 16byte
'encrypt': [0x04, 1], # 4 - 加密算法; 1byte
'update_type': [0x05, 1], # 5 - 升级文件类别; 1byte
'update_spec': [0x06, 4], # 6 - 升级特征字; 4byte
'update_verison': [0x07, 4], # 7 - 升级版本号; 4byte
'update_date': [0x08, 3], # 8 - 升级版本日期; 3byte
'area_code': [0x09, 4], # 9 - 省份特征; 4byte
'uptate_str': [0x0A, -1, 64], # 10 - 升级段描述; less than 64byte
'device_str': [0x0D, -1, 64], # 13 - 设备特征描述; less than 64byte
'hex_name': [0xFF, -1, 80], # 255 - Hex文件名; less than 80byte
}
def file_encryption(buffer):
""" 文件加密算法 """
pwd_idx = 0
pwd = b'moc.mmocspot.www'
pwd = list(map(lambda x: (x - 0x30 + 0x100) % 0x100, pwd))
result = bytearray(len(buffer))
for i in range(len(buffer)):
k = i
k |= i >> 8
k |= i >> 16
k |= i >> 24
result[i] = buffer[i] ^ pwd[pwd_idx] ^ (k & 0xFF)
pwd_idx = (pwd_idx + 1) % len(pwd)
return result
def build_header(config: dict, len_max=512):
"""
基于配置参数, 生成文件信息头;
V1版本, 依据字典生成tag标签组;
"""
# 定义文件头
m_file_header = bytearray(len_max)
header_len = 11
tag_num = 0
for tag, value in config.items():
if tag in Header_Tag.keys():
if tag == 'hex_name' and len_max < 256:
""" 当文件头长度不足时, 跳过文件名标签 """
continue
elif Header_Tag[tag][1] == -1:
tag_len = min(len(value), Header_Tag[tag][2])
else:
tag_len = Header_Tag[tag][1]
tag_date = [Header_Tag[tag][0], tag_len] + value[:tag_len]
m_file_header[header_len: header_len + tag_len + 2] = bytearray(tag_date)
tag_num += 1
header_len += 2 + tag_len
m_file_header[0:8] = b"TOPSCOMM"
m_file_header[8] = ((header_len - 10) % 0x100)
m_file_header[9] = ((header_len - 10) // 0x100)
m_file_header[10] = tag_num
m_file_header[header_len] = sum(m_file_header[:header_len]) % 0x100
m_file_header[header_len+1] = sum(m_file_header[:header_len]) // 0x100
if header_len+2 > len_max:
return None
else:
return m_file_header
def build_header_lite(config: dict):
"""
基于配置参数, 生成文件信息头;
V2版本, 仅提供必要信息;
"""
# 定义文件头
m_file_header = bytearray(64)
m_file_header[0:4] = bytearray(config["update_verison"])
m_file_header[4:8] = bytearray(config["file_length"])
m_file_header[8:24] = bytearray(config["md5"])
return m_file_header
def build_header_new(config: dict):
"""
基于配置参数, 生成新版文件信息头;
V3版本, 依据新版格式填充数据;
"""
# 定义文件头
m_file_header = [0xFF] * 184
m_file_header[0:8] = list(b"TOPSCOMM")
m_file_header[8:10] = config['prod_type']
m_file_header[10:22] = config['prog_id'] + [0] * (12 - len(config['prog_id']))
if config['method_compress'] == True:
m_file_header[23] = 0x01
else:
m_file_header[23] = 0x00
if 'crc32' in config.keys():
m_file_header[22] = 0x00
m_file_header[24: 40] = config['crc32'] + [0x00] * 12
elif 'md5' in config.keys():
m_file_header[22] = 0x01
m_file_header[24: 40] = config['md5']
else:
raise Exception("Error, Unknown method verify.")
# 时间戳生成
time_now = datetime.now()
time_stamp = list(map(lambda x: int(x),
time_now.strftime("%Y-%m-%d-%H-%M").split('-')))
time_stamp.insert(1, time_stamp[0] // 0x100)
time_stamp[0] = time_stamp[0] % 0x100
m_file_header[40: 46] = time_stamp
m_file_header[46: 50] = config['file_length']
# Cpu1
m_file_header[50: 54] = [0x00] * 4
m_file_header[54: 70] = [0x00] * 16
# Cpu2
m_file_header[70: 74] = [0x00] * 4
m_file_header[74: 90] = [0x00] * 16
if config['prog_type'] == 'app':
m_file_header[90: 92] = [0x00, 0x00]
elif config['prog_type'] == 'boot':
m_file_header[90: 92] = [0x01, 0x00]
elif config['prog_type'] == 'diff':
m_file_header[90: 92] = [0x02, 0x00]
elif config['prog_type'] == 'font':
m_file_header[90: 92] = [0x03, 0x00]
elif config['prog_type'] == 'config':
m_file_header[90: 92] = [0x04, 0x00]
elif config['prog_type'] == 'data':
m_file_header[90: 92] = [0x05, 0x00]
elif config['prog_type'] == 'test':
m_file_header[90: 92] = [0x06, 0x00]
else:
raise Exception("Error, Unknown Program Type.")
m_file_header[92: 94] = config['area_code']
m_file_header[94: 158] = config['hex_name']
if 'upgrade_type' in config.keys():
m_file_header[158: 160] = config['upgrade_type']
m_file_header[160: 182] = [0x00] * 22
else:
m_file_header[158: 182] = [0x00] * 24
m_file_header = bytearray(m_file_header)
calculator = Calculator(Crc16.MODBUS)
code_crc16 = calculator.checksum(m_file_header[:-2])
m_file_header[182: 184] = [code_crc16 // 0x100, code_crc16 % 0x100]
return m_file_header
def parser_version_info(file_bin: bytearray, base_addr:int):
""" 解析Bin文件内的版本信息结构体 """
address_block = (file_bin[6] << 24) + (file_bin[7] << 16) + (file_bin[4] << 8) + file_bin[5]
address_block -= base_addr
address_block *= 2
offset = address_block
block_version = {}
block_version['name'] = file_bin[offset + 1 : offset + 64 : 2]
offset += 64
block_version['device'] = file_bin[offset + 1 : offset + 64 : 2]
offset += 64
block_version['factory'] = file_bin[offset + 1 : offset + 32 : 2]
offset += 32
block_version['hardware'] = file_bin[offset + 1 : offset + 64 : 2]
offset += 64
return block_version
def test2():
""" 校验, 加密测试 """
# Header - Crc16
bin_offcial = Path(r"D:\WorkSpace\UserTool\SelfTool\FrameParser\test\p460\result\lamina_adapter_t1.dat")
data_offcial = bin_offcial.read_bytes()
byte_data = data_offcial[:182]
crc = data_offcial[182:184]
# data = "54 4F 50 53 43 4F 4D 4D 45 00 53 4C 43 50 30 30 31 00 00 00 00 00 01 00 B6 61 A8 73 BF 82 9E A7 4C 79 F6 BB 94 E2 A5 18 E8 07 05 18 0B 17 18 4C 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 6C 61 6D 69 6E 61 5F 61 64 61 70 74 65 72 5F 6D 61 69 6E 02 00 00 00 00 20 10 53 06 00 00 00 00 80 A5 8F 02 00 00 00 00 EC 1F 40 00 00 00 00 00 00 00 00 00 00 00 00 00 7A CA 61 0A FD 7F 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00"
# byte_data = list(map(lambda x: int(x, 16), data.split(' ')))
# crc = "99 CB"
calculator = Calculator(Crc16.MODBUS)
code_crc16 = calculator.checksum(bytearray(byte_data))
print(f"Result = {hex(code_crc16)}, Offcial Result = {crc}")
# File - md5
data_bin = '123456 123456 '.encode()
md5_ctx = hashlib.md5()
md5_ctx.update(data_bin)
hash = md5_ctx.hexdigest()
# File - encrypt
buffer1 = data_bin[:]
buffer1_en = file_encryption(buffer1)
buffer2 = buffer1_en[:6] + buffer1[6:]
buffer2_de = file_encryption(buffer2)
pass
def task5():
""" 文件缓冲区对比测试 """
file_dat = Path(r"test\p280039\result\lamina_controller_dsp_t1.dat")
file_dat_buffer = Path(r"test\p280039\result\lamina_controller_dsp_buffer.bin")
file_dat_buffer.exists(), file_dat.exists()
data_dat = file_dat.read_bytes()
data_dat_buffer = file_dat_buffer.read_bytes()
for i in range(len(data_dat)):
if data_dat[i] != data_dat_buffer[2*i]:
print(f"Diff in {hex(i)}, Data: {data_dat[i]}!={data_dat_buffer[2*i]}")
if __name__ == "__main__":
import hashlib
from pathlib import Path
pass

View File

@@ -0,0 +1,450 @@
import struct
from crc import Calculator, Crc16
from .. import tools
modbus_map = {
# 1 - Hex
# 2 - Int16
# 3 - lnt32
# 4 - str
# 5 - addr
# 6 - float
# 7 - numberList
0x01: ["Hex示例", 1],
0x02: ["Int示例", 2],
0x03: ["Int32示例", 3],
0x05: ["str示例", 4, 16],
0x15: ["addr示例", 5, 6],
0x1B: ["Float示例", 6],
0x1D: ["numberList示例", 3],
0x20: ["callback示例", 16],
}
frame_modbus = {
# func: len_base, type, idx
# type = 0, fixed length
# type = 1, add uint8 length by idx
# type = 2, add uint16 length by idx
# type = 3, check sub func code by idx, choose fixed length
0x03: [5, 1, 2],
0x04: [5, 1, 2],
0x06: [8, 0],
0x07: [8, 3, 2, {0x01: 2, 0x02: 0, 0x03: 0}],
0x08: [5, 3, 2, {0x01: 1, 0x02: 0x22, 0x03: 1}],
0x10: [8, 0],
0x11: [11, 2, 7]
}
def make_frame_modbus(block:dict) -> bytearray:
""" modbus 生成函数"""
frame = []
calculator = Calculator(Crc16.MODBUS)
frame.append(block['addr_dev'])
if block['type'] == 'update':
""" 升级系列报文 """
frame.append(0x07)
if len(block['file']) <= block["header_offset"]:
raise Exception("Modbus Update error, file too small.")
if block['step'] == 'start':
frame.append(0x01)
frame.append(0x00)
frame.append(0x00)
frame.append(0x00)
frame.append(block['header_offset'] // 256)
frame.append(block['header_offset'] % 256)
frame += list(block['file'][:block['header_offset']])
elif block['step'] == 'trans':
file_offset = block["header_offset"] + block['index'] * block['file_block_size']
file_block = block['file'][file_offset: file_offset + block['file_block_size']]
frame.append(0x02)
frame.append(0x00)
frame.append(block['index'] // 256)
frame.append(block['index'] % 256)
frame.append(len(file_block) // 256)
frame.append(len(file_block) % 256)
frame += list(file_block)
elif block['step'] == 'end':
frame.append(0x03)
frame.append(0x00)
frame.append(block['index'] // 256)
frame.append(block['index'] % 256)
frame.append(0x00)
frame.append(0x00)
else:
raise Exception("Modbus Update Frame Step Error.")
elif block['type'][:6] == 'record':
""" 录波系列报文 """
frame.append(0x11)
if block['type'][7:10] == 'cfg':
frame.append(0x01)
elif block['type'][7:11] == 'data':
frame.append(0x02)
else:
raise Exception("Modbus Record Frame Type Error.")
if block['step'] == 'start':
frame.append(0x00)
elif block['step'] == 'next':
frame.append(0x01)
elif block['step'] == 'repeat':
frame.append(0x02)
else:
raise Exception("Modbus Record Frame Step Error.")
frame.append(block['file_block_size'] // 256)
frame.append(block['file_block_size'] % 256)
elif block['type'] == 'log':
""" 事件记录系列报文 """
frame.append(0x08)
if block['step'] == 'start':
frame.append(0x01)
frame.append(block['data_len'] // 0x100 % 0x100)
frame.append(block['data_len'] % 0x100)
elif block['step'] == 'next':
frame.append(0x02)
elif block['step'] == 'end':
frame.append(0x03)
else:
raise Exception("Modbus Log Frame Step Error.")
else:
""" 数据读取系列报文 """
data_addr = block['data_addr']
if block['type'] == "read":
frame.append(0x03)
data_len = block['data_len']
frame.append(data_addr // 256 % 256)
frame.append(data_addr % 256)
frame.append(data_len // 256 % 256)
frame.append(data_len % 256)
elif block['type'] == "write_one":
frame.append(0x06)
data_val = block['data_val']
if data_val < 0:
data_val += 0x1_0000
frame.append(data_addr // 256 % 256)
frame.append(data_addr % 256)
frame.append(data_val // 256 % 256)
frame.append(data_val % 256)
elif block['type'] == "write_dual":
frame.append(0x10)
data_val = block['data_val']
if data_val < 0:
data_val += 0x1_0000_0000
frame.append(data_addr // 256 % 256)
frame.append(data_addr % 256)
frame.append(0x00)
frame.append(0x02)
frame.append(0x04)
frame.append(data_val // 256 % 256)
frame.append(data_val % 256)
data_val //= 0x1_0000
frame.append(data_val // 256 % 256)
frame.append(data_val % 256)
elif block['type'] == "write_str":
frame.append(0x10)
data_len = len(block['data_val'])
item_len = 2 * block['data_define'][data_addr][2] if data_addr in block['data_define'].keys() else data_len
data_val = block['data_val']
if data_len > item_len:
raise Exception("Modbus data len oversize.")
elif data_len < item_len:
data_val += b'\000' * (item_len - data_len)
data_len = len(data_val)
frame.append(data_addr // 256 % 256)
frame.append(data_addr % 256)
frame.append(0x00)
frame.append(data_len // 2)
frame.append(data_len)
if type(data_val[0]) == str:
data_val = list(map(lambda x: x.encode()[0], data_val))
for i in range(data_len//2):
frame.append(data_val[2*i + 1])
frame.append(data_val[2*i])
else:
raise Exception("Modbus Frame Type Error.")
crc = calculator.checksum(bytearray(frame))
frame.append(crc % 256)
frame.append(crc // 256)
return bytearray(frame)
def make_frame_dlt645(block:dict) -> bytearray:
""" dlt645 生成函数"""
frame = []
if block['type'] == 'modbus':
""" Modbus 透传帧 """
ctrl_code = 0x1F # Modbus 透传帧功能码
data_frame = make_frame_modbus(block["data"])
len_data = len(data_frame)
else:
raise Exception("Unknown dlt645 frame type.")
frame.append(0x68)
frame += block['addr'][:6]
frame.append(0x68)
frame.append(ctrl_code)
frame.append(len_data)
frame += data_frame
frame.append(sum(frame) % 256)
frame.append(0x16)
return bytearray(frame)
def check_frame_modbus(frame:bytes, block:dict) -> dict:
""" 校验modbus帧回复 """
if len(frame:=find_frame_modbus(frame, block['addr_dev'])) == 0:
raise Exception("No frame data")
output = {
'result': False,
'code_func': frame[1],
'code_sub': frame[2],
}
if output['code_func'] == 0x07:
""" 升级回复帧 """
output['upgrade'] = {}
output['upgrade']['index'] = frame[4] * 256 + frame[5]
if output['code_sub'] == 0x01:
""" 升级开始, 处理文件头 """
output['upgrade']['length'] = frame[6] * 256 + frame[7]
elif output['code_sub'] == 0x02:
""" 升级传输, 解析帧序号及返回值, 不做序列号校验 """
pass
elif output['code_sub'] == 0x03:
""" 升级结束 """
pass
else:
raise Exception(" Upgrade SubFunc code error.")
output['result'] = True if frame[3] == 0x00 else False
output['code_error'] = frame[3]
elif output['code_func'] == 0x03 or output['code_func'] == 0x04:
""" 数据读取帧 """
if frame[2] == len(frame[3:-2]):
output['Regs'] = display_data(block['data_addr'], frame[3:-2], block['data_define'])
else:
raise Exception("Frame read data length error.")
output['result'] = True
elif output['code_func'] == 0x08:
if frame[2] == 0x02:
output['Regs'] = display_data(0x200, frame[5:-2], block['data_define'])
else:
raise Exception(" Log SubFunc code error.")
output['result'] = True
elif output['code_func'] == 0x06:
""" 单个数据写入帧 """
output['result'] = True
elif output['code_func'] == 0x10:
""" 多个数据写入帧 """
output['result'] = True
elif output['code_func'] == 0x11:
""" 录波功能帧 """
data_record = {}
if frame[2] == 0x01:
data_record['type'] = 'config'
elif frame[2] == 0x02:
data_record["type"] = 'data'
else:
raise Exception("Unknow data type")
data_record['seq'] = frame[5] * 0x100 + frame[6]
data_record['total'] = frame[3] * 0x100 + frame[4]
data_record['data'] = frame[9:-2]
output['record'] = data_record
output['result'] = True
elif output['code_func'] & 0x80:
""" 错误返回帧 """
output['code_error'] = frame[2] if output['code_func'] == 0x87 else frame[3]
else:
raise Exception(f"Frame Date error. func={output['code_func']}, func_sub={output['code_sub']}, len={len(frame)}")
return output
def check_frame_dlt645(frame:bytes, block:dict) -> dict:
""" 校验dlt645帧回复 """
if len(frame:=find_frame_dlt645(frame, block['addr'])) == 0:
raise Exception("No frame data")
code_func = frame[8]
if code_func == 0x9F:
return check_frame_modbus(frame[10:-2], block['data'])
else:
raise Exception("DLT645 Frame type error.")
def find_frame_modbus(buffer:bytes, address:int, frame_defines: dict=frame_modbus) -> bytes:
""" 搜索合法modbus帧子串 """
len_buffer = len(buffer)
pos_frame, len_frame = 0, 0
calculator = Calculator(Crc16.MODBUS)
for i in range(len_buffer):
if buffer[i] != address:
continue
if (buffer[i+1] & 0x7F) not in frame_defines.keys():
continue
frame_define = frame_defines[buffer[i+1] & 0x7F]
j = frame_define[0]
if buffer[i+1] & 0x80:
j = 5
elif frame_define[1] == 0:
pass
elif frame_define[1] == 1:
j += buffer[i+frame_define[2]]
elif frame_define[1] == 2:
j += buffer[i+frame_define[2]] * 0x100 + buffer[i+frame_define[2]+1]
elif frame_define[1] == 3:
if buffer[i+frame_define[2]] not in frame_define[3].keys():
continue
j += frame_define[3][buffer[i+frame_define[2]]]
else:
raise Exception("Unknow Modbus Define type.")
if ((i+j) <= len_buffer) and calculator.checksum(buffer[i:i+j-2]) == (buffer[i+j-1] * 0x100 + buffer[i+j-2]):
pos_frame, len_frame = i, j
break
return buffer[pos_frame: pos_frame+len_frame]
def find_frame_dlt645(buffer:bytes, address: list) -> bytes:
""" 搜索合法645帧子串 """
len_buffer = len(buffer)
pos_frame, len_frame = 0, 0
for i in range(len_buffer):
if buffer[i] != 0x68 or buffer[i+7] != 0x68:
continue
if address[0] != 0xAA and buffer[i+1:i+7] != bytes(address):
continue
j = buffer[i+9] + 12
if sum(buffer[i:i+j-2]) % 0x100 == buffer[i+j-2]:
pos_frame, len_frame = i, j
break
return buffer[pos_frame: pos_frame+len_frame]
def display_data(address: int, data: bytes, modbus_map: dict=modbus_map) -> dict:
""" 格式化表示数据, 得到显示数据字典 """
def swapping_words(data:bytes, length:int) -> bytearray:
item = bytearray(data[:2 * length])
for i in range(length):
item[2*i], item[2*i+1] = item[2*i+1], item[2*i]
return item
def convert_regs_to_int(data:bytes, length:int, signed: bool=True) -> int:
result = 0
for idx_data, byte_data in enumerate(swapping_words(data, length)):
result += byte_data * 2 ** (8 * idx_data)
result -= 2 ** (16 * length) if signed and result >= (2 ** (16 * length - 1)) else 0
return result
output_data = {}
idx = address
while data:
data_label, data_len = "未知数据", 1
if idx not in modbus_map.keys():
item = convert_regs_to_int(data, 1, signed=False)
item = tools.ByteConv.display_hex(item, 4)
else:
current_map = modbus_map[idx]
data_label = current_map[0]
if current_map[1] == 1:
""" Hex字符表示 """
item = convert_regs_to_int(data, 1, signed=False)
item = tools.ByteConv.display_hex(item, 4)
elif current_map[1] == 2:
""" 16位数值表示 """
item = convert_regs_to_int(data, 1)
if len(current_map) > 2:
item /= current_map[2]
elif current_map[1] == 3:
""" 32位数值表示 """
data_len = 2
item = convert_regs_to_int(data, 2)
if len(current_map) > 2:
item /= current_map[2]
elif current_map[1] == 4:
""" 字符串表示 """
data_len = current_map[2]
item = swapping_words(data, data_len)
try:
item = item.decode()
except Exception as ex:
item_len = sum([any(item[i:]) for i in range(len(item))])
item = tools.ByteConv.trans_list_to_str(item[:item_len])
elif current_map[1] == 5:
""" 载波地址表示 """
data_len = current_map[2]
item = swapping_words(data, data_len)
item = tools.ByteConv.trans_list_to_str(item)
elif current_map[1] == 6:
""" 浮点数值表示 """
data_len = 2
item = struct.unpack('>f', bytes([data[2], data[3], data[0], data[1]]))[0]
elif current_map[1] == 7:
""" 正序数值表示 """
data_len = current_map[2]
item = list(map(lambda x: x, data[:2 * data_len]))
elif current_map[1] == 8:
""" 事件记录解析 """
data_len = current_map[2]
time_stamp, event_id, event_num = struct.unpack('<IBB', data[:6])
item = f"Event: {event_id}\n"
item += f"\ttime: {time_stamp}\n"
item += f"\tnumber: {event_num}\n"
if event_id == 2:
""" 故障记录事件 """
info_len, status, flag_fault, flag_alarm, volt_in, volt_out, curr_in, curr_induc, cbox_power_limit, flag_control = struct.unpack('<BBHHhhhhhH', data[6:6+18])
info = data[6+18:6+18+info_len].decode().strip('\x00')
item += f"\tData: {info}\n"
item += f"\t\tstatus: {status:x}\n"
item += f"\t\tflag_fault: {flag_fault:x}\n"
item += f"\t\tflag_alarm: {flag_alarm:x}\n"
item += f"\t\tvolt_in: {volt_in / 10}\n"
item += f"\t\tvolt_out: {volt_out / 10}\n"
item += f"\t\tcurr_in: {curr_in / 100}\n"
item += f"\t\tcurr_induc: {curr_induc / 100}\n"
item += f"\t\tcbox_power_limit: {cbox_power_limit / 10}\n"
item += f"\t\tflag_control: {flag_control:b}\n"
else:
info_len, = struct.unpack('<B', data[6:6+1])
info = data[7:7+info_len].decode().strip('\x00')
item += f"\tData: {info}\n"
output_data[idx] = data_label, item
idx += data_len
data = data[2*data_len:]
return output_data
def print_display(output_data: dict):
""" 格式化表示输出数据 """
bank_chars = ' \t\000'
print("Parse Result:")
label_len_max = max(map(lambda x: len(x[0]), output_data.values()))
data_len_max = max(map(lambda x: len(str(x[1])), output_data.values()))
for key, value in output_data.items():
match value:
case (str() as label, list() as data):
print(f"{tools.ByteConv.display_hex(key, 4)}: {'-'.join(map(str, data)):<{data_len_max}} {label:<{label_len_max}}")
case (str() as label, str() as data):
print(f"{tools.ByteConv.display_hex(key, 4)}: {data.rstrip(bank_chars):<{data_len_max}} {label:<{label_len_max}}")
case (str() as label, data):
print(f"{tools.ByteConv.display_hex(key, 4)}: {data:<{data_len_max}} {label:<{label_len_max}}")
case _:
raise ValueError(f"Key: {key}, Unkown Value: {value}")

View File

@@ -0,0 +1,39 @@
def trans_list_to_str(data: list, word_len=2, prefix='') -> str:
""" 标准串口字符串表示 """
func_trans_word = lambda x: prefix + ('0' * word_len + hex(x % (0x10 ** word_len))[2:])[-word_len:].upper()
return " ".join(map(func_trans_word, data))
def trans_str_to_list(data: str) -> list:
""" 标准串口字符串转换列表 """
func_trans = lambda x: int(x, 16)
return list(map(func_trans, data.strip().split(" ")))
def conv_int_to_array(num: int, big_end=False):
""" 数值转字节数组 """
result = [0, 0, 0, 0]
if big_end:
indexlist = reversed(range(len(result)))
else:
indexlist = range(len(result))
for i in indexlist:
result[i] = int(num % 0x100)
num //= 0x100
return result
def display_hex(data:int, length:int=2) -> str:
""" Hex字符固定最小长度表示 """
return f"0x{data:0{length}X}"
def trans_list_to_str_by_word(data: list, big_end=True) -> str:
""" 将数据以word格式显示 """
s = [data[i+1] * 0x100 + data[i] for i in range(0, len(data), 2)]
r = " ".join(map(hex, s))
print(data)
print(trans_list_to_str(data))
print(r)
return r

View File

@@ -1,3 +1,6 @@
from pathlib import Path
def calculate_checksum(data):
""" 计算校验域 """
checksum = 0
@@ -12,7 +15,7 @@ def file_IntelHex_to_Bin(file_data, base_address=0, len_max=1, **kwargs):
"""
if base_address == 0:
if file_data[8] == '2':
base_address = int(file_data[9: 13], 16) * 0x100
base_address = int(file_data[9: 13], 16) * 0x10
offset_begin = 16
elif file_data[8] == '4':
base_address = int(file_data[9: 13], 16) * 0x10000
@@ -140,3 +143,18 @@ def file_Bin_to_IntelHex(file_data: bytearray, base_address=0, **kwargs):
result += hex_record
return result
def test1(fp: Path):
""" bin文件生成测试 """
file1 = Path(fp)
path1 = file1.parent / (file1.stem + ".bin")
data1 = file1.read_text()
data2 = file_IntelHex_to_Bin(data1, 0x3F0100)
path1.write_bytes(data2)
# 测试Bin到IntelHex
bin = Path(fp).read_bytes()
result = file_Bin_to_IntelHex(bin, 0x80000, memory_width=2)
(fp.parent / (fp.stem + ".hex")).write_text(result)

View File

@@ -0,0 +1,2 @@
from . import IntelHex
from . import ByteConv

View File

@@ -1,489 +0,0 @@
import struct
from crc import Calculator, Crc16
from tools.ByteConv import trans_list_to_str, trans_str_to_list, display_hex
modbus_map = {
# 1 - Hex
# 2 - Int16
# 3 - lnt32
# 4 - str
# 5 - addr
# 6 - float
0x01: ["Hex示例", 1],
0x02: ["Int示例", 2],
0x03: ["Int32示例", 3],
0x04: ["str示例", 4, 16],
0x10: ["addr示例", 5, 6],
0x20: ["Float示例", 6],
}
frame_modbus = {
# func: len_base, type, idx
# type = 0, fixed length
# type = 1, add uint8 length by idx
# type = 2, add uint16 length by idx
# type = 3, check sub func code by idx, choose fixed length
0x03: [5, 1, 2],
0x04: [5, 1, 2],
0x06: [8, 0],
0x07: [8, 3, 2, {0x01: 2, 0x02: 0, 0x03: 0}],
0x10: [8, 0],
0x11: [11, 2, 7]
}
def make_frame_modbus(block:dict):
""" modbus 生成函数"""
frame = []
calculator = Calculator(Crc16.MODBUS)
frame.append(block['addr_dev'])
if block['type'] == 'update':
""" 升级系列报文 """
frame.append(0x07)
if len(block['file']) <= block["header_offset"]:
raise Exception("Modbus Update error, file too small.")
if block['step'] == 'start':
frame.append(0x01)
frame.append(0x00)
frame.append(0x00)
frame.append(0x00)
frame.append(block['header_offset'] // 256)
frame.append(block['header_offset'] % 256)
frame += list(block['file'][:block['header_offset']])
elif block['step'] == 'trans':
file_offset = block["header_offset"] + block['index'] * block['file_block_size']
file_block = block['file'][file_offset: file_offset + block['file_block_size']]
frame.append(0x02)
frame.append(0x00)
frame.append(block['index'] // 256)
frame.append(block['index'] % 256)
frame.append(len(file_block) // 256)
frame.append(len(file_block) % 256)
frame += list(file_block)
elif block['step'] == 'end':
frame.append(0x03)
frame.append(0x00)
frame.append(block['index'] // 256)
frame.append(block['index'] % 256)
frame.append(0x00)
frame.append(0x00)
else:
raise Exception("Modbus Update Frame Step Error.")
elif block['type'][:6] == 'record':
""" 录波系列报文 """
frame.append(0x11)
frame_types = block['type'].split('_')
if block['type'][7:10] == 'cfg':
frame.append(0x01)
elif block['type'][7:11] == 'data':
frame.append(0x02)
else:
raise Exception("Modbus Record Frame Type Error.")
if block['step'] == 'start':
frame.append(0x00)
elif block['step'] == 'next':
frame.append(0x01)
elif block['step'] == 'repeat':
frame.append(0x02)
else:
raise Exception("Modbus Record Frame Step Error.")
frame.append(block['file_block_size'] // 256)
frame.append(block['file_block_size'] % 256)
else:
""" 数据读取系列报文 """
data_addr = block['data_addr']
if block['type'] == "read":
frame.append(0x03)
data_len = block['data_len']
frame.append(data_addr // 256 % 256)
frame.append(data_addr % 256)
frame.append(data_len // 256 % 256)
frame.append(data_len % 256)
elif block['type'] == "write_one":
frame.append(0x06)
data_val = block['data_val']
if data_val < 0:
data_val += 0x1_0000
frame.append(data_addr // 256 % 256)
frame.append(data_addr % 256)
frame.append(data_val // 256 % 256)
frame.append(data_val % 256)
elif block['type'] == "write_dual":
frame.append(0x10)
data_val = block['data_val']
if data_val < 0:
data_val += 0x1_0000_0000
frame.append(data_addr // 256 % 256)
frame.append(data_addr % 256)
frame.append(0x00)
frame.append(0x02)
frame.append(0x04)
frame.append(data_val // 256 % 256)
frame.append(data_val % 256)
data_val //= 0x1_0000
frame.append(data_val // 256 % 256)
frame.append(data_val % 256)
elif block['type'] == "write_str":
frame.append(0x10)
data_len = len(block['data_val'])
item_len = 2 * block['data_define'][data_addr][2]
data_val = block['data_val']
if data_len > item_len:
raise Exception("Modbus data len oversize.")
elif data_len < item_len:
data_val += '\000' * (item_len - data_len)
data_len = len(data_val)
frame.append(data_addr // 256 % 256)
frame.append(data_addr % 256)
frame.append(0x00)
frame.append(data_len // 2)
frame.append(data_len)
if type(data_val[0]) == str:
data_val = list(map(lambda x: x.encode()[0], data_val))
for i in range(data_len//2):
frame.append(data_val[2*i + 1])
frame.append(data_val[2*i])
else:
raise Exception("Modbus Frame Type Error.")
crc = calculator.checksum(bytearray(frame))
frame.append(crc % 256)
frame.append(crc // 256)
return frame
def make_frame_dlt645(block:dict):
""" dlt645 生成函数"""
frame = []
if block['type'] == 'modbus':
""" Modbus 透传帧 """
ctrl_code = 0x1F # Modbus 透传帧功能码
data_frame = make_frame_modbus(block["data"])
len_data = len(data_frame)
else:
raise Exception("Unknown dlt645 frame type.")
frame.append(0x68)
frame += block['addr'][:6]
frame.append(0x68)
frame.append(ctrl_code)
frame.append(len_data)
frame += data_frame
frame.append(sum(frame) % 256)
frame.append(0x16)
return frame
def display_data(modbus_map: dict, address: int, data: list):
""" 格式化表示数据 """
def display_str(data, len_data):
item = bytearray(data[:2 * len_data])
for i in range(len_data):
t = item[2*i]
item[2*i] = item[2*i+1]
item[2*i+1] = t
return item
output = "Parse Result: \n"
idx = address
len_max = 0
while data:
data_len = 1
data_label = "未知数据"
if idx not in modbus_map.keys():
item = data[0] * 0x0100 + data[1]
item = display_hex(item, 4)
else:
current_map = modbus_map[idx]
data_label = current_map[0]
if current_map[1] == 1:
""" Hex字符表示 """
item = data[0] * 0x0100 + data[1]
item = display_hex(item, 4)
elif current_map[1] == 2:
""" 16位数值表示 """
item = data[0] * 0x0100 + data[1]
if item > 0x8000:
item -= 0x1_0000
elif current_map[1] == 3:
""" 32位数值表示 """
item = data[2] * 0x0100 + data[3]
item *= 0x10000
item += data[0] * 0x0100 + data[1]
if data[2] > 0x80:
item -= 0x1_0000_0000
data_len = 2
elif current_map[1] == 4:
""" 字符串表示 """
data_len = current_map[2]
item = display_str(data, data_len)
item = item.replace(b'\xff', b' 0xFF')
item = item.decode()
elif current_map[1] == 5:
""" 载波地址表示 """
data_len = current_map[2]
item = display_str(data, data_len)
item = trans_list_to_str(item)
elif current_map[1] == 6:
""" 浮点数值表示 """
temp = [data[2], data[3], data[0], data[1]]
item = struct.unpack('>f', bytes(temp))[0]
data_len = 2
elif current_map[1] == 7:
""" 正序数值表示 """
data_len = current_map[2]
item = " ".join(map(lambda x: str(x), data[:2 * data_len]))
if len_max < len(data_label):
len_max = len(data_label)
len_remain = len_max - len(data_label)
output_line = "\t".join([display_hex(idx, 4), data_label + " " * len_remain, str(item)])
idx += data_len
del data[0:data_len * 2]
output += output_line + "\n"
return output
def find_frame_modbus(buffer, address, frame_defines: dict=frame_modbus):
""" 搜索合法modbus帧子串 """
len_buffer = len(buffer)
pos_frame, len_frame = 0, 0
calculator = Calculator(Crc16.MODBUS)
for i in range(len_buffer):
if buffer[i] != address:
continue
if (buffer[i+1] & 0x7F) not in frame_defines.keys():
continue
frame_define = frame_defines[buffer[i+1] & 0x7F]
j = frame_define[0]
if buffer[i+1] & 0x80:
j = 5
elif frame_define[1] == 0:
pass
elif frame_define[1] == 1:
j += buffer[i+frame_define[2]]
elif frame_define[1] == 2:
j += buffer[i+frame_define[2]] * 0x100 + buffer[i+frame_define[2]+1]
elif frame_define[1] == 3:
if buffer[i+frame_define[2]] not in frame_define[3].keys():
continue
j += frame_define[3][buffer[i+frame_define[2]]]
else:
raise Exception("Unknow Modbus Define type.")
if ((i+j) <= len_buffer) and calculator.checksum(buffer[i:i+j-2]) == (buffer[i+j-1] * 0x100 + buffer[i+j-2]):
pos_frame, len_frame = i, j
break
return buffer[pos_frame: pos_frame+len_frame]
def find_frame_dlt645(buffer, address: list):
""" 搜索合法645帧子串 """
len_buffer = len(buffer)
pos_frame, len_frame = 0, 0
for i in range(len_buffer):
if buffer[i] != 0x68 or buffer[i+7] != 0x68:
continue
if address[0] != 0xAA and buffer[i+1:i+7] == bytes(address):
continue
j = buffer[i+9] + 12
if sum(buffer[i:i+j-2]) % 0x100 == buffer[i+j-2]:
pos_frame, len_frame = i, j
break
return buffer[pos_frame: pos_frame+len_frame]
def check_frame_modbus(frame, block=None):
""" 校验modbus帧回复 """
if len(frame:=find_frame_modbus(frame, block['addr_dev'])) == 0:
raise Exception("No frame data")
code_func = frame[1]
code_subfunc = frame[2]
if code_func == 0x07:
""" 升级回复帧 """
if frame[3] == 0x00:
check_result = True
else:
check_result = False
update_index = frame[4] * 256 + frame[5]
check_hope = 0
if code_subfunc == 0x01:
""" 处理帧头 """
check_hope = frame[6] * 256 + frame[7]
elif code_subfunc == 0x02:
""" 解析帧序号及返回值, 不做序列号校验 """
elif code_subfunc == 0x03:
""" 升级结束 """
else:
raise Exception("Func code or Return code error.")
return check_result, update_index, check_hope
elif code_func == 0x03 or code_func == 0x04:
""" 数据读取帧 """
if frame[2] == len(frame[3:-2]):
if type(block) is dict:
data_addr = block['data_addr']
return display_data(block['data_define'], data_addr, list(frame[3:-2]))
else:
return trans_list_to_str(frame[3:-2])
else:
raise Exception("Frame read error.")
elif code_func == 0x06:
""" 单个数据写入帧 """
return trans_list_to_str(frame[2:-2])
elif code_func == 0x10:
""" 多个数据写入帧 """
return trans_list_to_str(frame[2:-2])
elif code_func == 0x11:
""" 录波功能帧 """
frame_data = {
'seq': frame[5] * 0x100 + frame[6],
'total': frame[3] * 0x100 + frame[4],
'data': frame[9:-2]
}
if frame[2] == 0x01:
frame_data['type'] = 'config'
elif frame[2] == 0x02:
frame_data["type"] = 'data'
else:
raise Exception("Unknow data type")
return True, frame_data
elif code_func & 0x80:
""" 错误返回帧 """
if code_func & 0x7F == 0x07:
return False, frame[3], code_func, code_subfunc
return False, frame[2], code_func
else:
raise Exception(f"Frame Date error. func={code_func}, func_sub={code_subfunc}, len={len(frame)}")
def check_frame_dlt645(frame, block=None):
""" 校验dlt645帧回复 """
if len(frame:=find_frame_dlt645(frame, block['addr'])) == 0:
raise Exception("No frame data")
code_func = frame[8]
if code_func == 0x9F:
block = block['data']
return check_frame_modbus(frame[10:-2], block)
else:
raise Exception("DLT645 Frame type error.")
if __name__ == "__main__":
frame_slave = '01 07 02 00 00 A7 00 F0 A1 A4 1F DC 0B 1D 27 08 0F CB A7 49 03 99 09 C6 87 10 FB 08 86 71 9F 2A 2F BB 8F 69 4D 5C 9F 90 51 8A 8B D3 E0 85 9F 2B C1 9A 7E A3 22 9B 29 EE 6B 70 28 D3 31 F6 EF 78 43 8A DF FC F3 8C 13 02 31 F4 65 B5 EE 46 80 F2 E9 D4 E9 C8 F2 84 13 3A DF 50 1D 45 53 52 1D 89 6F F8 CB 7F 56 28 DF A2 11 D4 47 93 04 04 FF AB 35 1F C3 BA D2 F0 65 F2 F6 A3 AC A5 A2 AF AF 3E 88 65 EC 7B 35 62 DA 4A CF A4 69 A5 9E C7 70 E6 DC DD BE 49 DD DA 09 CE 42 18 5C 57 86 12 E0 A0 74 5F 5C F7 35 B3 FE 7C 51 94 5C 57 28 1A 86 1E 9F DE F6 B2 4B A9 29 E6 30 EA F2 BB E6 72 81 E9 80 3A DE FC DC C2 F8 8E 30 F4 66 B3 25 12 30 FA 90 09 3C 8C 1D FD 49 8E 4A 58 1C 17 48 54 CF 6A DE B4 05 7F 3D DD 68 F2 7E C2 CE 01 11 D6 DE 5E 7C 27 10 FE 28 28 3E 06 4D C8 01 07 02 00 00 A7 F4 08'
buffer_test = trans_str_to_list("62 01 03 25 1C 00 01 03 3A 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 10 01 F3 00 06 00 12 01 DB 00 19 00 BA 00 00 24 62 00 00 25 1C 00 00 00 0F 00 01 00 07 00 2B 02 4D FD 6C FD 6C 01 20 01 20 73 08 4D FD 6C FD")
buffer_test1 = trans_str_to_list("01 03 3A 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 11 01 F4 00 06 00 13 01 DB 00 1A 00 D5 00 00 25 AE 00 00 26 83 00 00 00 0C FF FF 00 56 00 72 02 1D FD 6C FD 6C 01 26 01 26 94 E3")
buffer_test2 = trans_str_to_list("01 06 00 60 00 00 89 D4 94 E3")
buffer_test3 = trans_str_to_list(frame_slave)
modbus_map = {
# 1 - Hex
# 2 - Int16
# 3 - lnt32
# 4 - str
# 5 - addr
# 6 - float
0x0C: ["告警字1", 1],
0x0D: ["告警字2", 1],
0x0E: ["故障字1", 1],
0x0F: ["故障字2", 1],
0x10: ["系统工作状态" , 1],
0x11: ["Boost1工作状态" , 1],
0x12: ["Boost2工作状态" , 1],
0x13: ["开关机状态" , 1],
0x14: ["光伏组串1输入电压" , 2],
0x15: ["光伏组串2输入电压" , 2],
0x16: ["Boost1电感电流" , 2],
0x17: ["Boost2电感电流" , 2],
0x18: ["Boost输出电压" , 2],
0x19: ["Boost输出总电流" , 2],
0x1A: ["Boost1功率" , 3],
0x1C: ["Boost2功率" , 3],
0x1E: ["输入总功率" , 3],
0x20: ["LLC输出电压" , 2],
0x21: ["端口输出电压" , 2],
0x22: ["LLC输出电流均值" , 2],
0x23: ["LLC输出电流峰值" , 2],
0x24: ["绝缘检测电压" , 2],
0x25: ["散热片温度" , 2],
0x26: ["腔体1温度" , 2],
0x27: ["腔体2温度" , 2],
0x28: ["设备温度" , 2],
0x50: ["启停控制命令" , 2],
0x51: ["故障清除命令" , 2],
0x52: ["参数还原命令" , 2],
0x53: ["设备复位命令" , 2],
0x60: ["整机运行使能", 1],
0x61: ["最小启动允许输入电压", 2],
0x62: ["最大启动允许输入电压", 2],
0x63: ["最小停机输入电压", 2],
0x64: ["最大停机输入电压", 2],
0x65: ["最小启动允许输出电压", 2],
0x66: ["最大启动允许输出电压", 2],
0x67: ["最小停止允许输出电压", 2],
0x68: ["最大停止允许输出电压", 2],
0x69: ["最小MPPT电流限值", 2],
0x6A: ["最大MPPT电流限值", 2],
0x6B: ["保留数据项", 2],
0x6C: ["最大功率限值", 3],
0x6E: ["最大功率限值存储值", 3],
0x70: ["Boost输入过压保护值", 2],
0x71: ["Boost输出过压保护值", 2],
0x72: ["LLC输出过压保护值", 2],
0x73: ["LLC输出欠压保护值", 2],
0x74: ["Boost电感过流保护值", 2],
0x75: ["LLC输出电流均值保护值", 2],
0x76: ["LLC输出电流峰值保护值", 2],
0x77: ["保留数据项", 2],
0x78: ["过载保护值", 3],
0x7A: ["过温故障值", 2],
0x7B: ["过温告警值", 2],
0x7C: ["过温恢复值", 2],
0x7D: ["输出继电器故障判断差值", 2],
0x7E: ["保留数据项", 1],
0x7F: ["保留数据项", 1],
0x80: ["三点法中间阈值", 2],
0x81: ["浮充电压", 2],
0x82: ["恒压充电电压", 2],
0x83: ["llc软起开始电压", 2],
0x84: ["boost开始运行电压", 2],
0x85: ["boost停止运行电压", 2],
0x86: ["绝缘检测正阻抗限值", 3],
0x88: ["绝缘检测负阻抗限值", 3],
0x8A: ["保留地址项", 2],
0x8B: ["保留地址项", 2],
0x8C: ["保留地址项", 2],
0x8D: ["保留地址项", 2],
0x8E: ["保留地址项", 2],
0x8F: ["保留地址项", 2],
0x100: ["程序版本字符串", 4, 16],
0x110: ["设备型号字符串", 4, 16],
0x120: ["保留地址项", 4, 16],
0x130: ["生产厂家字符串", 4, 8],
0x138: ["保留地址项", 4, 8],
0x140: ["保留地址项", 4, 16],
0x150: ["保留地址项", 4, 16],
0x160: ["硬件版本字符串", 4, 16],
0x170: ["设备序列号", 4, 16],
0x180: ["设备MES码", 4, 16],
0x190: ["出厂日期批次", 4, 16],
}
block = {
'addr_dev' : 0x01,
'data_define': modbus_map,
}
check_frame_modbus(bytearray(buffer_test3), block)

View File

@@ -1,704 +0,0 @@
# 升级包生成脚本
import hashlib
from pathlib import Path
from datetime import datetime
from crc import Calculator, Crc16
from tools.ByteConv import trans_list_to_str, conv_int_to_array
from tools.IntelHex import file_IntelHex_to_Bin, file_Bin_to_IntelHex
Header_Tag = {
'file_type': [0x00, 2], # 0 - 文件类型; 2byte
'file_version': [0x01, 2], # 1 - 文件版本; 2byte
'file_length': [0x02, 4], # 2 - 文件长度; 4byte
'md5': [0x03, 16], # 3 - 文件MD5; 16byte
'encrypt': [0x04, 1], # 4 - 加密算法; 1byte
'update_type': [0x05, 1], # 5 - 升级文件类别; 1byte
'update_spec': [0x06, 4], # 6 - 升级特征字; 4byte
'update_verison': [0x07, 4], # 7 - 升级版本号; 4byte
'update_date': [0x08, 3], # 8 - 升级版本日期; 3byte
'area_code': [0x09, 4], # 9 - 省份特征; 4byte
'uptate_str': [0x0A, -1, 64], # 10 - 升级段描述; less than 64byte
'device_str': [0x0D, -1, 64], # 13 - 设备特征描述; less than 64byte
'hex_name': [0xFF, -1, 80], # 255 - Hex文件名; less than 80byte
}
def file_encryption(buffer):
""" 文件加密算法 """
pwd_idx = 0
pwd = b'moc.mmocspot.www'
pwd = list(map(lambda x: (x - 0x30 + 0x100) % 0x100, pwd))
result = bytearray(len(buffer))
for i in range(len(buffer)):
k = i
k |= i >> 8
k |= i >> 16
k |= i >> 24
result[i] = buffer[i] ^ pwd[pwd_idx] ^ (k & 0xFF)
pwd_idx = (pwd_idx + 1) % len(pwd)
return result
def build_header(config: dict, len_max=512):
"""
基于配置参数, 生成文件信息头;
V1版本, 依据字典生成tag标签组;
"""
# 定义文件头
m_file_header = bytearray(len_max)
header_len = 11
tag_num = 0
for tag, value in config.items():
if tag in Header_Tag.keys():
if tag == 'hex_name' and len_max < 256:
""" 当文件头长度不足时, 跳过文件名标签 """
continue
elif Header_Tag[tag][1] == -1:
tag_len = min(len(value), Header_Tag[tag][2])
else:
tag_len = Header_Tag[tag][1]
tag_date = [Header_Tag[tag][0], tag_len] + value[:tag_len]
m_file_header[header_len: header_len + tag_len + 2] = bytearray(tag_date)
tag_num += 1
header_len += 2 + tag_len
m_file_header[0:8] = b"TOPSCOMM"
m_file_header[8] = ((header_len - 10) % 0x100)
m_file_header[9] = ((header_len - 10) // 0x100)
m_file_header[10] = tag_num
m_file_header[header_len] = sum(m_file_header[:header_len]) % 0x100
m_file_header[header_len+1] = sum(m_file_header[:header_len]) // 0x100
if header_len+2 > len_max:
return None
else:
return m_file_header
def build_header_lite(config: dict):
"""
基于配置参数, 生成文件信息头;
V2版本, 仅提供必要信息;
"""
# 定义文件头
m_file_header = bytearray(64)
m_file_header[0:4] = bytearray(config["update_verison"])
m_file_header[4:8] = bytearray(config["file_length"])
m_file_header[8:24] = bytearray(config["md5"])
return m_file_header
def build_header_new(config: dict):
"""
基于配置参数, 生成新版文件信息头;
V3版本, 依据新版格式填充数据;
"""
# 定义文件头
m_file_header = [0xFF] * 184
m_file_header[0:8] = list(b"TOPSCOMM")
m_file_header[8:10] = config['prod_type']
m_file_header[10:22] = config['prog_id'] + [0] * (12 - len(config['prog_id']))
if config['method_compress'] == True:
m_file_header[23] = 0x01
else:
m_file_header[23] = 0x00
if 'crc32' in config.keys():
m_file_header[22] = 0x00
m_file_header[24: 40] = config['crc32'] + [0x00] * 12
elif 'md5' in config.keys():
m_file_header[22] = 0x01
m_file_header[24: 40] = config['md5']
else:
raise Exception("Error, Unknown method verify.")
# 时间戳生成
time_now = datetime.now()
time_stamp = list(map(lambda x: int(x),
time_now.strftime("%Y-%m-%d-%H-%M").split('-')))
time_stamp.insert(1, time_stamp[0] // 0x100)
time_stamp[0] = time_stamp[0] % 0x100
m_file_header[40: 46] = time_stamp
m_file_header[46: 50] = config['file_length']
# Cpu1
m_file_header[50: 54] = [0x00] * 4
m_file_header[54: 70] = [0x00] * 16
# Cpu2
m_file_header[70: 74] = [0x00] * 4
m_file_header[74: 90] = [0x00] * 16
if config['prog_type'] == 'app':
m_file_header[90: 92] = [0x00, 0x00]
elif config['prog_type'] == 'boot':
m_file_header[90: 92] = [0x01, 0x00]
elif config['prog_type'] == 'diff':
m_file_header[90: 92] = [0x02, 0x00]
elif config['prog_type'] == 'font':
m_file_header[90: 92] = [0x03, 0x00]
elif config['prog_type'] == 'config':
m_file_header[90: 92] = [0x04, 0x00]
elif config['prog_type'] == 'data':
m_file_header[90: 92] = [0x05, 0x00]
elif config['prog_type'] == 'test':
m_file_header[90: 92] = [0x06, 0x00]
else:
raise Exception("Error, Unknown Program Type.")
m_file_header[92: 94] = config['area_code']
m_file_header[94: 158] = config['hex_name']
if 'upgrade_type' in config.keys():
m_file_header[158: 160] = config['upgrade_type']
m_file_header[160: 182] = [0x00] * 22
else:
m_file_header[158: 182] = [0x00] * 24
m_file_header = bytearray(m_file_header)
calculator = Calculator(Crc16.MODBUS)
code_crc16 = calculator.checksum(m_file_header[:-2])
m_file_header[182: 184] = [code_crc16 // 0x100, code_crc16 % 0x100]
return m_file_header
def test1(fp: Path):
""" bin文件生成测试 """
file1 = Path(fp)
path1 = file1.parent / (file1.stem + ".bin")
data1 = file1.read_text()
data2 = file_IntelHex_to_Bin(data1, 0x3F0100)
path1.write_bytes(data2)
# 测试Bin到IntelHex
bin = Path(fp).read_bytes()
result = file_Bin_to_IntelHex(bin, 0x80000, memory_width=2)
(fp.parent / (fp.stem + ".hex")).write_text(result)
def test2():
""" 校验, 加密测试 """
# Header - Crc16
bin_offcial = Path(r"D:\WorkSpace\UserTool\SelfTool\FrameParser\test\p460\result\lamina_adapter_t1.dat")
data_offcial = bin_offcial.read_bytes()
byte_data = data_offcial[:182]
crc = data_offcial[182:184]
# data = "54 4F 50 53 43 4F 4D 4D 45 00 53 4C 43 50 30 30 31 00 00 00 00 00 01 00 B6 61 A8 73 BF 82 9E A7 4C 79 F6 BB 94 E2 A5 18 E8 07 05 18 0B 17 18 4C 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 6C 61 6D 69 6E 61 5F 61 64 61 70 74 65 72 5F 6D 61 69 6E 02 00 00 00 00 20 10 53 06 00 00 00 00 80 A5 8F 02 00 00 00 00 EC 1F 40 00 00 00 00 00 00 00 00 00 00 00 00 00 7A CA 61 0A FD 7F 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00"
# byte_data = list(map(lambda x: int(x, 16), data.split(' ')))
# crc = "99 CB"
calculator = Calculator(Crc16.MODBUS)
code_crc16 = calculator.checksum(bytearray(byte_data))
print(f"Result = {hex(code_crc16)}, Offcial Result = {crc}")
# File - md5
data_bin = '123456 123456 '.encode()
md5_ctx = hashlib.md5()
md5_ctx.update(data_bin)
hash = md5_ctx.hexdigest()
# File - encrypt
buffer1 = data_bin[:]
buffer1_en = file_encryption(buffer1)
buffer2 = buffer1_en[:6] + buffer1[6:]
buffer2_de = file_encryption(buffer2)
pass
def task5():
""" 文件缓冲区对比测试 """
file_dat = Path(r"test\p280039\result\lamina_controller_dsp_t1.dat")
file_dat_buffer = Path(r"test\p280039\result\lamina_controller_dsp_buffer.bin")
file_dat_buffer.exists(), file_dat.exists()
data_dat = file_dat.read_bytes()
data_dat_buffer = file_dat_buffer.read_bytes()
for i in range(len(data_dat)):
if data_dat[i] != data_dat_buffer[2*i]:
print(f"Diff in {hex(i)}, Data: {data_dat[i]}!={data_dat_buffer[2*i]}")
def GeneratePackage_Demo_Xilinx(path_bin: Path):
""" 完整升级包生成测试 """
config = {
'file_type': [0x10, 0x01], # Xilinx-Demo 自机升级文件
'file_version': [0x00, 0x00], # 文件版本-00 用于兼容文件格式升级
# 'file_length': [], # 文件长度(自动生成)
# 'md5': [], # 文件MD5(自动生成)
'encrypt': [0x01], # 默认加密算法
'update_type': [0x01], # APP升级
'update_spec': [0x00, 0x00, 0x00, 0x00], # 升级特征字
'update_verison': [0x02, 0x00, 0x00, 0x01], # 升级版本号
'update_date': [0x22, 0x04, 0x24], # 升级版本日期
# 'area_code': [], # 省份特征
# 'uptate_str': [], # 升级段描述
# 'device_str': [], # 设备特征描述
# 'hex_name': [], # Hex文件名(自动读取)
# 文件Hex结构信息
# 'flash_addr': 0x3E8020, # 程序起始地址
# 'flash_size': 0x005FC0, # 程序空间大小
}
data_bin = path_bin.read_bytes()
md5_ctx = hashlib.md5()
md5_ctx.update(data_bin)
config["md5"] = list(md5_ctx.digest())
config['file_length'] = conv_int_to_array(len(data_bin))
config['hex_name'] = list(path_bin.name.encode())[:80]
if (header:= build_header(config, 128)) is None:
raise Exception("Header tag oversize. ")
if (header_512:= build_header(config, 512)) is None:
raise Exception("Header tag oversize. ")
data_encrypt = file_encryption(data_bin)
print("Upgrade file generated successfully.")
print(f"\t header_length={len(header)}, bin_length={len(data_bin)}[{hex(len(data_bin))}]")
print(f"\t file md5: {trans_list_to_str(config['md5'])}")
file1 = path_bin.parent / (path_bin.stem + '.dat')
file1.write_bytes(header + data_bin)
file2 = path_bin.parent / (path_bin.stem + '_h512.dat')
file2.write_bytes(header_512 + data_bin)
def GenerateImage_SLCP001_p4a0(path_boot: Path, path_main: Path, path_back: Path):
""" 叠光适配器-4A0平台版本 镜像生成 """
config = {
'prod_type': [0x45, 0x00], # 产品类型
'method_compress': False, # 文件压缩
'prog_id': list(b"SLCP001"), # 程序识别号
'prog_type': 'app', # 程序类型
'area_code': [0x00, 0x00], # 地区
}
bin_boot = file_IntelHex_to_Bin(path_boot.read_text(), len_max=0x010000)
bin_main = file_IntelHex_to_Bin(path_main.read_text(), len_max=0x0CC000)
bin_back = file_IntelHex_to_Bin(path_back.read_text(), len_max=0x040000)
encrypt_main = file_encryption(bin_main)
encrypt_back = file_encryption(bin_back)
md5_ctx = hashlib.md5()
md5_ctx.update(bin_main)
config["md5"] = list(md5_ctx.digest())
config['file_length'] = conv_int_to_array(len(bin_main))
config['hex_name'] = list(path_main.name.encode())[:64]
config['hex_name'] += [0] * (64 - len(config['hex_name']))
if (main_header:=build_header_new(config)) is None:
raise Exception("Header tag oversize. ")
md5_ctx = hashlib.md5()
md5_ctx.update(bin_back)
config["md5"] = list(md5_ctx.digest())
config['file_length'] = conv_int_to_array(len(bin_back))
config['hex_name'] = list(path_back.name.encode())[:64]
config['hex_name'] += [0] * (64 - len(config['hex_name']))
if (back_header:=build_header_new(config)) is None:
raise Exception("Header tag oversize. ")
print("Merge Image generated successfully.")
print(f"Main File:")
print(f"\t header_length={len(main_header)}, bin_length={len(bin_main)}[{hex(len(bin_main))}]")
print(f"Back File:")
print(f"\t header_length={len(back_header)}, bin_length={len(bin_back)}[{hex(len(bin_back))}]")
# 组装镜像
Image = [0xFF] * 0x100000
offset_image = 0
Image[offset_image: offset_image + len(bin_boot)] = bin_boot
offset_image = 0x010000
Image[offset_image: offset_image + len(bin_main)] = bin_main
offset_image = 0x0BC000
Image[offset_image: offset_image + len(bin_back)] = bin_back
offset_image = 0x0FC000
Image[offset_image: offset_image + len(main_header)] = main_header
offset_image = 0x0FE000
Image[offset_image: offset_image + len(back_header)] = back_header
return bytearray(Image), main_header, back_header
def GenerateImage_SLCP101_p460(path_boot: Path, path_main: Path, path_back: Path):
""" 叠光适配器-460平台版本 镜像生成 """
config = {
'prod_type': [0x45, 0x00], # 产品类型
'method_compress': False, # 文件压缩
'prog_id': list(b"SLCP101"), # 程序识别号
'prog_type': 'app', # 程序类型
'area_code': [0x00, 0x00], # 地区
}
bin_boot = file_IntelHex_to_Bin(path_boot.read_text(), len_max=0x00C000)
bin_main = file_IntelHex_to_Bin(path_main.read_text(), len_max=0x024000)
bin_back = file_IntelHex_to_Bin(path_back.read_text(), len_max=0x024000)
encrypt_main = file_encryption(bin_main)
encrypt_back = file_encryption(bin_back)
md5_ctx = hashlib.md5()
md5_ctx.update(bin_main)
config["md5"] = list(md5_ctx.digest())
config['file_length'] = conv_int_to_array(len(bin_main))
config['hex_name'] = list(path_main.name.encode())[:64]
config['hex_name'] += [0] * (64 - len(config['hex_name']))
if (main_header:=build_header_new(config)) is None:
raise Exception("Header tag oversize. ")
md5_ctx = hashlib.md5()
md5_ctx.update(bin_back)
config["md5"] = list(md5_ctx.digest())
config['file_length'] = conv_int_to_array(len(bin_back))
config['hex_name'] = list(path_back.name.encode())[:64]
config['hex_name'] += [0] * (64 - len(config['hex_name']))
if (back_header:=build_header_new(config)) is None:
raise Exception("Header tag oversize. ")
print("Merge Image generated successfully.")
print(f"Main File:")
print(f"\t header_length={len(main_header)}, bin_length={len(bin_main)}[{hex(len(bin_main))}]")
print(f"Back File:")
print(f"\t header_length={len(back_header)}, bin_length={len(bin_back)}[{hex(len(bin_back))}]")
# 组装镜像
Image = [0xFF] * 0x058000
offset_image = 0
Image[offset_image: offset_image + len(bin_boot)] = bin_boot
offset_image = 0x00C000
Image[offset_image: offset_image + len(bin_main)] = bin_main
offset_image = 0x030000
Image[offset_image: offset_image + len(bin_back)] = bin_back
offset_image = 0x054000
Image[offset_image: offset_image + len(main_header)] = main_header
offset_image = 0x056000
Image[offset_image: offset_image + len(back_header)] = back_header
return bytearray(Image), main_header, back_header
def GeneratePackage_SLCP101_p460(path_hex: Path):
""" 叠光适配器-460平台版本 生成升级包 """
config = {
'prod_type': [0x45, 0x00], # 产品类型
'method_compress': False, # 文件压缩
'prog_id': list(b"SLCP101"), # 程序识别号
'prog_type': 'app', # 程序类型
'area_code': [0x00, 0x00], # 地区
}
bin_main = file_IntelHex_to_Bin(path_hex.read_text(), len_max=0x024000)
encrypt_main = file_encryption(bin_main)
md5_ctx = hashlib.md5()
md5_ctx.update(bin_main)
config["md5"] = list(md5_ctx.digest())
config['file_length'] = conv_int_to_array(len(bin_main))
config['hex_name'] = list(path_hex.name.encode())[:64]
config['hex_name'] += [0] * (64 - len(config['hex_name']))
if (main_header:=build_header_new(config)) is None:
raise Exception("Header tag oversize. ")
print("Package generated successfully.")
print(f"File name: {path_hex.name}")
print(f"File Info:")
print(f"\t header_length={len(main_header)}, bin_length={len(bin_main)}[{hex(len(bin_main))}]")
# 组装镜像
Image = [0xFF] * (len(main_header) + len(encrypt_main))
offset_image = 0
Image[offset_image: offset_image + len(main_header)] = main_header
offset_image += len(main_header)
Image[offset_image: offset_image + len(encrypt_main)] = encrypt_main
return bytearray(Image), bin_main
def GenerateImage_DLSY001_p460(path_boot: Path, path_main: Path, path_back: Path):
""" 叠光优化器-460平台版本 镜像生成 """
config = {
'prod_type': [0x45, 0x00], # 产品类型
'method_compress': False, # 文件压缩
'prog_id': list(b"DLSY001"), # 程序识别号
'prog_type': 'app', # 程序类型
'area_code': [0x00, 0x00], # 地区
}
bin_boot = file_IntelHex_to_Bin(path_boot.read_text(), len_max=0x00C000)
bin_main = file_IntelHex_to_Bin(path_main.read_text(), len_max=0x024000)
bin_back = file_IntelHex_to_Bin(path_back.read_text(), len_max=0x024000)
encrypt_main = file_encryption(bin_main)
encrypt_back = file_encryption(bin_back)
md5_ctx = hashlib.md5()
md5_ctx.update(bin_main)
config["md5"] = list(md5_ctx.digest())
config['file_length'] = conv_int_to_array(len(bin_main))
config['hex_name'] = list(path_main.name.encode())[:64]
config['hex_name'] += [0] * (64 - len(config['hex_name']))
if (main_header:=build_header_new(config)) is None:
raise Exception("Header tag oversize. ")
md5_ctx = hashlib.md5()
md5_ctx.update(bin_back)
config["md5"] = list(md5_ctx.digest())
config['file_length'] = conv_int_to_array(len(bin_back))
config['hex_name'] = list(path_back.name.encode())[:64]
config['hex_name'] += [0] * (64 - len(config['hex_name']))
if (back_header:=build_header_new(config)) is None:
raise Exception("Header tag oversize. ")
print("Merge Image generated successfully.")
print(f"Main File:")
print(f"\t header_length={len(main_header)}, bin_length={len(bin_main)}[{hex(len(bin_main))}]")
print(f"Back File:")
print(f"\t header_length={len(back_header)}, bin_length={len(bin_back)}[{hex(len(bin_back))}]")
# 组装镜像
Image = [0xFF] * 0x058000
offset_image = 0
Image[offset_image: offset_image + len(bin_boot)] = bin_boot
offset_image = 0x00C000
Image[offset_image: offset_image + len(bin_main)] = bin_main
offset_image = 0x030000
Image[offset_image: offset_image + len(bin_back)] = bin_back
offset_image = 0x054000
Image[offset_image: offset_image + len(main_header)] = main_header
offset_image = 0x056000
Image[offset_image: offset_image + len(back_header)] = back_header
return bytearray(Image), main_header, back_header
def GeneratePackage_DLSY001_p460(path_hex: Path):
""" 叠光优化器-460平台版本 生成升级包 """
config = {
'prod_type': [0x45, 0x00], # 产品类型
'method_compress': False, # 文件压缩
'prog_id': list(b"DLSY001"), # 程序识别号
'prog_type': 'app', # 程序类型
'area_code': [0x00, 0x00], # 地区
}
bin_main = file_IntelHex_to_Bin(path_hex.read_text(), len_max=0x024000)
encrypt_main = file_encryption(bin_main)
md5_ctx = hashlib.md5()
md5_ctx.update(bin_main)
config["md5"] = list(md5_ctx.digest())
config['file_length'] = conv_int_to_array(len(bin_main))
config['hex_name'] = list(path_hex.name.encode())[:64]
config['hex_name'] += [0] * (64 - len(config['hex_name']))
if (main_header:=build_header_new(config)) is None:
raise Exception("Header tag oversize. ")
print("Package generated successfully.")
print(f"File name: {path_hex.name}")
print(f"File Info:")
print(f"\t header_length={len(main_header)}, bin_length={len(bin_main)}[{hex(len(bin_main))}]")
# 组装镜像
Image = [0xFF] * (len(main_header) + len(encrypt_main))
offset_image = 0
Image[offset_image: offset_image + len(main_header)] = main_header
offset_image += len(main_header)
Image[offset_image: offset_image + len(encrypt_main)] = encrypt_main
return bytearray(Image), bin_main
def GenerateImage_DLSP001_p280039(path_boot: Path, path_main: Path, path_back: Path):
""" 叠光适配器-460平台版本 镜像生成 """
config = {
'prod_type': [0x46, 0x00], # 产品类型
'method_compress': False, # 文件压缩
'prog_id': list(b"DLSP001"), # 程序识别号
'prog_type': 'app', # 程序类型
'area_code': [0x00, 0x00], # 地区
'upgrade_type': [0x00, 0x00], # 升级方式(0-片外缓冲, 1-片内缓冲, 2-升级备份)
}
bin_boot = file_IntelHex_to_Bin(path_boot.read_text(), len_max=2 * 0x004000, conv_end=False)
bin_main = file_IntelHex_to_Bin(path_main.read_text(), len_max=2 * 0x014000, conv_end=False)
bin_back = file_IntelHex_to_Bin(path_back.read_text(), len_max=2 * 0x014000, conv_end=False)
md5_ctx = hashlib.md5()
md5_ctx.update(bin_main)
config["md5"] = list(md5_ctx.digest())
config['upgrade_type'] = [0x00, 0x00] # 主程序-片外缓冲
config['file_length'] = conv_int_to_array(len(bin_main))
config['hex_name'] = list(path_main.name.encode())[:64]
config['hex_name'] += [0] * (64 - len(config['hex_name']))
if (main_header:=build_header_new(config)) is None:
raise Exception("Header tag oversize. ")
md5_ctx = hashlib.md5()
md5_ctx.update(bin_back)
config["md5"] = list(md5_ctx.digest())
config['upgrade_type'] = [0x02, 0x00] # 备份程序
config['file_length'] = conv_int_to_array(len(bin_back))
config['hex_name'] = list(path_back.name.encode())[:64]
config['hex_name'] += [0] * (64 - len(config['hex_name']))
if (back_header:=build_header_new(config)) is None:
raise Exception("Header tag oversize. ")
main_header_buffer = bytearray()
for byte_org in main_header:
main_header_buffer.extend(bytearray([0x00, byte_org]))
back_header_buffer = bytearray()
for byte_org in back_header:
back_header_buffer.extend(bytearray([0x00, byte_org]))
main_encrypt = file_encryption(bin_main)
back_encrypt = file_encryption(bin_back)
print("Merge Image generated successfully.")
print(f"Main File:")
print(f"\t header_length={len(main_header)}, bin_length={len(bin_main)}[{hex(len(bin_main))}]")
print(f"Back File:")
print(f"\t header_length={len(back_header)}, bin_length={len(bin_back)}[{hex(len(bin_back))}]")
# 组装镜像
Image = [0xFF] * 2 * 0x030000
offset_image = 0
Image[offset_image: offset_image + len(bin_boot)] = bin_boot
offset_image = 2 * 0x006000
Image[offset_image: offset_image + len(main_header_buffer)] = main_header_buffer
offset_image = 2 * 0x007000
Image[offset_image: offset_image + len(back_header_buffer)] = back_header_buffer
offset_image = 2 * 0x008000
Image[offset_image: offset_image + len(bin_main)] = bin_main
offset_image = 2 * 0x01C000
Image[offset_image: offset_image + len(bin_back)] = bin_back
return bytearray(Image), main_header, back_header
def GeneratePackage_DLSP001_p280039(path_hex: Path):
""" 叠光控制器DSP-280039平台版本 生成升级包 """
config = {
'prod_type': [0x46, 0x00], # 产品类型
'method_compress': False, # 文件压缩
'prog_id': list(b"DLSP001"), # 程序识别号
'prog_type': 'app', # 程序类型
'area_code': [0x00, 0x00], # 地区
'upgrade_type': [0x00, 0x00], # 升级方式(0-片外缓冲, 1-片内缓冲, 2-升级备份)
}
bin_main = file_IntelHex_to_Bin(path_hex.read_text(), len_max=2 * 0x014000, conv_end=False)
encrypt_main = file_encryption(bin_main)
md5_ctx = hashlib.md5()
md5_ctx.update(bin_main)
config["md5"] = list(md5_ctx.digest())
config['file_length'] = conv_int_to_array(len(bin_main))
config['hex_name'] = list(path_hex.name.encode())[:64]
config['hex_name'] += [0] * (64 - len(config['hex_name']))
if (main_header:=build_header_new(config)) is None:
raise Exception("Header tag oversize. ")
print("Package generated successfully.")
print(f"File name: {path_hex.name}")
print(f"File Info:")
print(f"\t header_length={len(main_header)}, bin_length={len(bin_main)}[{hex(len(bin_main))}]")
# 组装镜像
Image = [0xFF] * (len(main_header) + len(encrypt_main))
offset_image = 0
Image[offset_image: offset_image + len(main_header)] = main_header
offset_image += len(main_header)
Image[offset_image: offset_image + len(encrypt_main)] = encrypt_main
return bytearray(Image), bin_main
def Process1():
""" 镜像生成流程 """
root = Path(r"test\p460")
result = Path(r"test\p460\result")
# 正常启动镜像
hex_boot = root / r"bootloader.hex"
hex_main = root / r"lamina_adapter.hex"
hex_back = root / r"lamina_adapter_back.hex"
hex_update = root / r"lamina_adapter_t1.hex"
file_image = result / f'{hex_main.stem[:-6]}_ROM.bin'
file_main_header = result / 'SLCP101_header_main.bin'
file_back_header = result / 'SLCP101_header_back.bin'
file_package = result / f'{hex_update.stem}.dat'
file_bin = result / f'{hex_update.stem}.bin'
data_bins = GenerateImage_SLCP101_p460(hex_boot, hex_main, hex_back)
data_package, data_bins = GeneratePackage_SLCP101_p460(hex_update)
file_image.write_bytes(data_bins[0].copy())
file_main_header.write_bytes(data_bins[1].copy())
file_back_header.write_bytes(data_bins[2].copy())
file_package.write_bytes(data_package)
file_bin.write_bytes(data_bins)
# 异常镜像-主分区md5错误
file_image1 = result / f'{file_image.stem}_b1.bin'
data_image = data_bins[0].copy()
data_image[0x054018: 0x05401A] = [0x00, 0x01]
file_image1.write_bytes(data_image)
# 异常镜像-备份分区md5错误
file_image2 = result / f'{file_image.stem}_b2.bin'
data_image = data_bins[0].copy()
data_image[0x056018: 0x05601A] = [0x00, 0x01]
file_image2.write_bytes(data_image)
# 异常镜像-双分区md5错误
file_image3 = result / f'{file_image.stem}_b3.bin'
data_image = data_bins[0].copy()
data_image[0x054018: 0x05401A] = [0x00, 0x01]
data_image[0x056018: 0x05601A] = [0x00, 0x01]
file_image3.write_bytes(data_image)
def Process2():
""" 镜像生成流程 """
root = Path(r"D:\WorkingProject\LightStackOptimizer\software\lamina_optimizer\lamina_optimizer\Debug")
# result = Path(r"test\p460_o1\result")
result = root
# 正常启动镜像
hex_boot = Path(r"test\p460_o1\bootloader.hex")
hex_main = root / r"lamina_optimizer.hex"
hex_back = root / r"lamina_optimizer.hex"
hex_update = root / r"lamina_optimizer.hex"
file_image = result / f'{hex_main.stem}_ROM.bin'
file_main_header = result / 'DLSY001_header_main.bin'
file_back_header = result / 'DLSY001_header_back.bin'
file_package = result / f'{hex_update.stem}.dat'
file_bin = result / f'{hex_update.stem}.bin'
data_bins = GenerateImage_DLSY001_p460(hex_boot, hex_main, hex_back)
data_package, data_bin = GeneratePackage_DLSY001_p460(hex_update)
file_image.write_bytes(data_bins[0].copy())
file_main_header.write_bytes(data_bins[1].copy())
file_back_header.write_bytes(data_bins[2].copy())
file_package.write_bytes(data_package)
file_bin.write_bytes(data_bin)
# 异常镜像-主分区md5错误
file_image1 = result / f'{file_image.stem}_b1.bin'
data_image = data_bins[0].copy()
data_image[0x054018: 0x05401A] = [0x00, 0x01]
file_image1.write_bytes(data_image)
# 异常镜像-备份分区md5错误
file_image2 = result / f'{file_image.stem}_b2.bin'
data_image = data_bins[0].copy()
data_image[0x056018: 0x05601A] = [0x00, 0x01]
file_image2.write_bytes(data_image)
# 异常镜像-双分区md5错误
file_image3 = result / f'{file_image.stem}_b3.bin'
data_image = data_bins[0].copy()
data_image[0x054018: 0x05401A] = [0x00, 0x01]
data_image[0x056018: 0x05601A] = [0x00, 0x01]
file_image3.write_bytes(data_image)
if __name__ == "__main__":
# path_bin = Path("F:\\Work\\FPGA\\Test\\Vivado\\test_update\\test_update.vitis\\upgrade_system\\Debug\\sd_card\\BOOT.BIN")
# GeneratePackage_Demo_Xilinx(path_bin)
Process2()
pass

View File

@@ -0,0 +1,65 @@
import re
from pathlib import Path
def parse_map_file(map_file_path):
# 定义正则表达式模式
section_pattern = re.compile(r'^\s*(\w+)\s+([0-9a-fA-F]+)\s+([0-9a-fA-F]+)\s+([0-9a-fA-F]+)\s+([0-9a-fA-F]+)\s+([0-9a-fA-F]+)\s+([0-9a-fA-F]+)\s+(.*)$')
symbol_pattern = re.compile(r'^\s*([0-9a-fA-F]+)\s+([0-9a-fA-F]+)\s+([0-9a-fA-F]+)\s+(\w+)\s+(.*)$')
sections = {}
symbols = {}
with open(map_file_path, 'r') as file:
for line in file:
# 匹配section行
match = section_pattern.match(line)
if match:
section_name = match.group(1)
start_address = int(match.group(2), 16)
end_address = int(match.group(3), 16)
size = int(match.group(4), 16)
sections[section_name] = {
'start_address': start_address,
'end_address': end_address,
'size': size
}
continue
# 匹配symbol行
match = symbol_pattern.match(line)
if match:
address = int(match.group(1), 16)
size = int(match.group(2), 16)
type_ = match.group(3)
name = match.group(4)
definition = match.group(5)
symbols[name] = {
'address': address,
'size': size,
'type': type_,
'definition': definition
}
return sections, symbols
# 使用示例
map_file_path = 'D:\WorkingProject\LightStackOptimizer\software\lamina_controller_dsp\lamina_controller_dsp\DEBUG\lamina_controller_dsp.map'
path_map = Path(map_file_path)
sections, symbols = parse_map_file(map_file_path)
print("Sections:")
for section_name, section_info in sections.items():
print(f"Section: {section_name}")
print(f" Start Address: {section_info['start_address']:X}")
print(f" End Address: {section_info['end_address']:X}")
print(f" Size: {section_info['size']:X}")
print()
print("Symbols:")
for symbol_name, symbol_info in symbols.items():
print(f"Symbol: {symbol_name}")
print(f" Address: {symbol_info['address']:X}")
print(f" Size: {symbol_info['size']:X}")
print(f" Type: {symbol_info['type']}")
print(f" Definition: {symbol_info['definition']}")
print()

View File

@@ -1,9 +1,9 @@
import time
from webui import webui
from pathlib import Path
from func_frame import check_frame_dlt645
from source.dev_LaminaAdapter import LaminaAdapter
from source.device.EnergyRouter import EnergyRouter
from function.frame import check_frame_dlt645
from device.EnergyRouter import EnergyRouter
from device.LaminaAdapter import LaminaAdapter
def my_function(e : webui.event):
@@ -15,9 +15,9 @@ def my_function(e : webui.event):
print("Data from JavaScript: " + e.window.get_str(e, 0)) # Message from JS
frame = e.window.get_str(e, 0)
block_dlt645 = e.window.get_str(e, 1)
output_text = check_frame_dlt645(frame, block=block_dlt645)
block_ouput = check_frame_dlt645(frame, block=block_dlt645)
return output_text
return block_ouput
events = []
def main_webui():

141
source/post_work.py Normal file
View File

@@ -0,0 +1,141 @@
from pathlib import Path
from device.LaminaAdapter import LaminaAdapter
from device.LaminaAdapter import GenerateImage_DLSY001_p460, GeneratePackage_DLSY001_p460
def Process2():
""" 优化器-DLSY001 镜像生成流程 """
root = Path(r"D:\WorkingProject\LightStackOptimizer\software\lamina_optimizer\lamina_optimizer\Debug")
# result = Path(r"test\p460_o1\result")
result = root
# 正常启动镜像
hex_boot = Path(r"test\p460_o1\bootloader.hex")
hex_main = root / r"lamina_optimizer.hex"
hex_back = root / r"lamina_optimizer.hex"
hex_update = root / r"lamina_optimizer.hex"
file_image = result / f'{hex_main.stem}_ROM.bin'
file_main_header = result / 'DLSY001_header_main.bin'
file_back_header = result / 'DLSY001_header_back.bin'
file_package = result / f'{hex_update.stem}.dat'
file_bin = result / f'{hex_update.stem}.bin'
data_bins = GenerateImage_DLSY001_p460(hex_boot, hex_main, hex_back)
data_package, data_bin = GeneratePackage_DLSY001_p460(hex_update)
file_image.write_bytes(data_bins[0].copy())
file_main_header.write_bytes(data_bins[1].copy())
file_back_header.write_bytes(data_bins[2].copy())
file_package.write_bytes(data_package)
file_bin.write_bytes(data_bin)
# 异常镜像-主分区md5错误
file_image1 = result / f'{file_image.stem}_b1.bin'
data_image = data_bins[0].copy()
data_image[0x054018: 0x05401A] = [0x00, 0x01]
file_image1.write_bytes(data_image)
# 异常镜像-备份分区md5错误
file_image2 = result / f'{file_image.stem}_b2.bin'
data_image = data_bins[0].copy()
data_image[0x056018: 0x05601A] = [0x00, 0x01]
file_image2.write_bytes(data_image)
# 异常镜像-双分区md5错误
file_image3 = result / f'{file_image.stem}_b3.bin'
data_image = data_bins[0].copy()
data_image[0x054018: 0x05401A] = [0x00, 0x01]
data_image[0x056018: 0x05601A] = [0x00, 0x01]
file_image3.write_bytes(data_image)
def Process(type_dev: str, path_boot: Path, path_project: Path, makePackages: bool =True, makeImage: bool = True):
""" 镜像生成流程 """
root_boot = path_boot
root_main = path_project
result = root_main
# 程序文件名适配
if type_dev[:4] == 'DLSY':
program_name = 'lamina_optimizer'
else:
program_name = 'lamina_adapter'
# 正常启动镜像
hex_boot = root_boot / "bootloader.hex"
hex_main = root_main / f"{program_name}.hex"
hex_back = root_main / f"{program_name}_back.hex"
file_image = result / f'{hex_main.stem}_ROM.bin'
file_package = result / f'{hex_main.stem}.dat'
file_package_backup = result / f'{hex_back.stem}.dat'
dev_lamina = LaminaAdapter(None, type_dev=type_dev)
if makePackages:
if hex_main.exists():
data_package = dev_lamina.make_package(hex_main)
file_package.write_bytes(data_package)
if hex_back.exists():
data_package_backup = dev_lamina.make_package(hex_back, upgrade_backup=True)
file_package_backup.write_bytes(data_package_backup)
if makeImage:
if (not hex_boot.exists()) or (not hex_main.exists()) or (not hex_back.exists()):
raise Exception("缺失必要程序文件")
data_image = dev_lamina.make_image(hex_boot, hex_main, hex_back, output_header=True, output_bin=True)
file_image.write_bytes(data_image)
# 异常镜像-主分区md5错误
file_image1 = result / f'{file_image.stem}_b1.bin'
data_image_copy = data_image
if type_dev == 'SLCP001':
data_image_copy[0x0FC018: 0x0FC01A] = [0x00, 0x01]
else:
data_image_copy[0x054018: 0x05401A] = [0x00, 0x01]
file_image1.write_bytes(data_image_copy)
# 异常镜像-备份分区md5错误
file_image2 = result / f'{file_image.stem}_b2.bin'
data_image_copy = data_image
if type_dev == 'SLCP001':
data_image_copy[0x0FE018: 0x0FE01A] = [0x00, 0x01]
else:
data_image_copy[0x056018: 0x05601A] = [0x00, 0x01]
file_image2.write_bytes(data_image_copy)
# 异常镜像-双分区md5错误
file_image3 = result / f'{file_image.stem}_b3.bin'
data_image_copy = data_image
if type_dev == 'SLCP001':
data_image_copy[0x0FE018: 0x0FE01A] = [0x00, 0x01]
data_image_copy[0x0FC018: 0x0FC01A] = [0x00, 0x01]
else:
data_image_copy[0x054018: 0x05401A] = [0x00, 0x01]
data_image_copy[0x056018: 0x05601A] = [0x00, 0x01]
file_image3.write_bytes(data_image_copy)
if __name__ == "__main__":
path_boot1 = Path(r"D:\WorkingProject\LightStackAdapter\software\umon\4A0-PROJ_STACKLIGHT_PARALLEL_ADAPTOR")
path_boot2 = Path(r"D:\WorkingProject\LightStackAdapter\software\umon\460-PROJ_STACKLIGHT_PARALLEL_ADAPTOR")
path_boot3 = Path(r"D:\WorkingProject\LightStackAdapter\software\umon\460-PROJ_STACKLIGHT_OPTIMIZER")
path_main1 = Path(r"D:\WorkingProject\LightStackAdapter\software\lamina_adapter\lamina_adapter\Debug")
path_main2 = Path(r"D:\WorkingProject\LightStackOptimizer\software\lamina_optimizer\lamina_optimizer\Debug")
path_main3 = Path(r"D:\WorkingProject\LightStackAdapter\software\lamina_adapter\release\device_V1\lamina_adapter\Debug")
path_main4 = Path(r"D:\WorkingProject\LightStackAdapter\software\lamina_adapter\release\device_V3.01\lamina_adapter\Debug")
path_main5 = Path(r"D:\WorkingProject\LightStackAdapter\software\lamina_adapter\release\device_V2.03\lamina_adapter\Debug")
mode_config = {
'SLCP001': ('SLCP001', path_boot1, path_main3),
'SLCP101': ('SLCP101', path_boot2, path_main5),
'SLCP102': ('SLCP102', path_boot2, path_main4),
'DLSY001': ('DLSY001', path_boot3, path_main2),
'SLCP102_error': ('SLCP101', path_boot2, path_main4),
}
# Process0(path_boot1, path_main) # 适配器SLCP001
# Process1(path_boot2, path_main) # 适配器SLCP101
# Process1_v2(path_boot2, path_main) # 适配器SLCP102
# Process2()
Process(*mode_config['SLCP101'])

View File

@@ -0,0 +1,234 @@
import time
from device.tools.ByteConv import display_hex
from device.LaminaController import LaminaController
from device.LaminaController import ParamMap_LaminaController
def test_communication(device: LaminaController, time_out=2):
""" 通信成功率测试 """
time_start = time.time()
param_saved = device.flag_print, device.retry, device.time_out
device.flag_print = False
device.retry = 1
try:
while True:
device.frame_read(0x0C, 0x20)
print(f"Time Stamp: {time.ctime()}")
print(f"Success Frame: {device.log['read']}")
print(f"Failed Frame: {device.log['send'] - device.log['read']}")
print(f"Max Series Failed Frame: {device.log['keep-fail']}")
time.sleep(time_out)
finally:
time_end = time.time()
print("Test Result: ")
print(f"Time Start: {time.strftime(r'%Y-%m-%d %H:%M:%S', time.localtime(time_start))}, \tTime End: {time.strftime(r'%Y-%m-%d %H:%M:%S', time.localtime(time_end))}")
print(f"Time Elapsed: {time_end - time_start}")
print(f"Success Rate: {device.log['read'] / device.log['send'] * 100}%")
device.flag_print, device.retry, device.time_out = param_saved
def test_parameters(device:LaminaController, ParamMap:dict, ParamCase:dict):
""" 参数读写测试 """
pass
def frame_write(device:LaminaController, address, info, value):
""" 整合参数写入接口 """
length = 2 if info[1] in [3, 6] else 1
length = info[2] if info[1] in [4, 5, 7] else length
if length == 1:
return device.frame_write_one(address, value)
elif length == 2:
return device.frame_write_dual(address, value)
else:
return device.frame_write_str(address, value)
def frame_read(device:LaminaController, address, info):
""" 整合参数读取接口 """
length = 2 if info[1] in [3, 6] else 1
length = info[2] if info[1] in [4, 5, 7] else length
if device.frame_read(address, length):
return device.output['Regs'][address][1]
else:
return None
for addr_param, info_param in ParamMap.items():
if addr_param not in ParamCase.keys():
continue
addr_relate = None
itemCase = ParamCase[addr_param]
list_case_normal = []
if 0 in itemCase.keys():
""" 常规读写用例测试 """
for data_write, data_read in itemCase[0]:
list_case_normal.append((data_write, data_read, True))
if 1 in itemCase.keys():
""" 写入范围用例测试 """
testzone = 0.7 if info_param[1] in [2, 3] and len(info_param) < 3 else 7 / info_param[2]
accuracy = 0.20001 if info_param[1] in [2, 3] and len(info_param) < 3 else 2.0001 / info_param[2]
val_min, val_max = itemCase[1]
if 2 in itemCase.keys():
""" 存在大小约束相关项 """
list_case_relate = [] # 对约束相关项的修改
list_case_late = [] # 存在约束相关项影响后的测试用例
mode_relate, addr_relate, deadzone = itemCase[2]
val_relate = frame_read(device, addr_relate, ParamCase[addr_relate])
if mode_relate == 1:
if (val_relate - deadzone) < val_max:
""" 约束项已限制写入范围 """
list_case_relate.append((val_max + 2 * abs(deadzone), val_max + 2 * abs(deadzone), True))
list_case_late.append((val_max + testzone, val_max + testzone, False))
list_case_late.append((val_max, val_max, True))
list_case_late.append((val_max - testzone, val_max - testzone, True))
val_max = val_relate - deadzone
else:
""" 约束项未限制写入范围 """
val_relate = val_max
list_case_relate.append((val_relate, val_relate, True))
list_case_late.append((val_relate - deadzone + testzone, val_relate - deadzone + testzone, False))
list_case_late.append((val_relate - deadzone, val_relate - deadzone, True))
list_case_late.append((val_relate - deadzone - testzone, val_relate - deadzone - testzone, True))
elif mode_relate == 2:
if (val_relate + deadzone) > val_min:
""" 约束项已限制写入范围 """
list_case_relate.append((val_min - 2 * abs(deadzone), val_min - 2 * abs(deadzone), True))
list_case_late.append((val_min - testzone, val_min - testzone, False))
list_case_late.append((val_min, val_min, True))
list_case_late.append((val_min + testzone, val_min + testzone, True))
val_min = val_relate + deadzone
else:
""" 约束项未限制写入范围 """
val_relate = val_min
list_case_relate.append((val_relate, val_relate, True))
list_case_late.append((val_relate + deadzone - testzone, val_relate + deadzone - testzone, False))
list_case_late.append((val_relate + deadzone, val_relate + deadzone, True))
list_case_late.append((val_relate + deadzone + testzone, val_relate + deadzone + testzone, True))
list_case_normal.append((val_min - testzone, val_min - testzone, False))
list_case_normal.append((val_min, val_min, True))
list_case_normal.append((val_min + testzone, val_min + testzone, True))
list_case_normal.append((val_max + testzone, val_max + testzone, False))
list_case_normal.append((val_max, val_max, True))
list_case_normal.append((val_max - testzone, val_max - testzone, True))
print(f"Param Case:\taddr={display_hex(addr_param)}")
last_value = frame_read(device, addr_param, info_param)
for case_test in list_case_normal:
print(f"\tnormal case={case_test}")
result = frame_write(device, addr_param, info_param, case_test[0])
assert result == case_test[2]
current_value = frame_read(device, addr_param, info_param)
if current_value is None:
raise Exception("Param Read Fail")
elif result:
if type(current_value) is float:
if abs(current_value - case_test[1]) > accuracy:
raise Exception("Param Check Fail")
else:
if current_value != case_test[1]:
raise Exception("Param Check Fail")
elif (not result) and current_value != last_value:
raise Exception("Param Check Fail")
last_value = current_value
if list_case_normal and list_case_normal[0][1] != last_value:
""" 为参数写入首个测试用例数据(一般为参数默认值), 避免影响后续参数测试 """
case_test = list_case_normal[0]
result = frame_write(device, addr_param, info_param, case_test[0])
assert result == case_test[2]
last_value = frame_read(device, addr_param, info_param)
if addr_relate:
""" 存在关联测试项 """
for case_relate in list_case_relate:
print(f"\trelate state: addr={display_hex(addr_relate)}, value={case_relate[0]}")
result = frame_write(device, addr_relate, info_param, case_relate[0])
if result == case_relate[2]:
for case_test in list_case_late:
print(f"\t\tstate case={case_test}")
result = frame_write(device, addr_param, info_param, case_test[0])
assert result == case_test[2]
current_value = frame_read(device, addr_param, info_param)
if current_value is None:
raise Exception("Param Read Fail")
elif result:
if type(current_value) is float:
if abs(current_value - case_test[1]) > accuracy:
raise Exception("Param Check Fail")
else:
if current_value != case_test[1]:
raise Exception("Param Check Fail")
elif (not result) and current_value != last_value:
raise Exception("Param Check Fail")
last_value = current_value
if list_case_normal and list_case_normal[0][1] != last_value:
""" 为参数写入首个测试用例数据(一般为参数默认值), 避免影响后续参数测试 """
case_test = list_case_normal[0]
result = frame_write(device, addr_param, info_param, case_test[0])
assert result == case_test[2]
def main():
mode_config = {
"Log": {'com_name': None,
# 'addr_645': [0x01, 0x00, 0x00, 0x00, 0x00, 0x40],
},
"Debug": {'com_name': 'COM3',
# 'addr_645': [0x01, 0x02, 0x03, 0x04, 0x05, 0x06],
'frame_print': None,
'time_out': 0.5, 'retry': 3},
}
TestCase = {
# 测试用例定义
# 0 - 正常写入数据序列; [(写入数据, 读取数据)]
# 1 - 范围限制测试; (最小值, 最大值)
# 2 - 相关大小限制测试; ((0-等于, 1-小于, 2-大于), 相关值地址, 死区范围)
0x60: {0: [(0, '0x0000'), (1, '0x0001'),
(2, '0x0001'), (0xFF, '0x0001'), (0xFFFF, '0x0001')]}, # 整机运行使能
0x61: {0: [(60.0, 60.0)], 1: (40, 100), 2: (1, 0x62, 0.5)}, # 最小启动允许输入电压
0x62: {0: [(440.0, 440.0)], 1: (350, 560), 2: (2, 0x61, 0.5)}, # 最大启动允许输入电压
0x63: {0: [(58, 58.0)], 1: (38, 98), 2: (1, 0x64, 0.5)}, # 最小停机输入电压
0x64: {0: [(442, 442.0)], 1: (352, 562), 2: (2, 0x63, 0.5)}, # 最大停机输入电压
0x65: {0: [(41, 41.0)], 1: (30, 60), 2: (1, 0x66, 0.5)}, # 最小启动允许输出电压
0x66: {0: [(57, 57.0)], 1: (50, 80), 2: (2, 0x65, 0.5)}, # 最大启动允许输出电压
0x67: {0: [(40, 40.0)], 1: (28, 58), 2: (1, 0x68, 0.5)}, # 最小停止允许输出电压
0x68: {0: [(58, 58.0)], 1: (52, 82), 2: (2, 0x67, 0.5)}, # 最大停止允许输出电压
0x69: {0: [(0.1, 0.1)], 1: (0, 5), 2: (1, 0x6A, 1.0)}, # 最小MPPT电流限值
0x6A: {0: [(22, 22.0)], 1: (5, 30), 2: (2, 0x69, 1.0)}, # 最大MPPT电流限值
0x6C: {0: [(6000, 6000)], 1: (3000, 7999.999),2: (0, 0x6E, 0.0)}, # 最大功率限值(由于转换误差, 无法写入8000W)
0x6E: {0: [(6000, 6000)], 1: (3000, 7999.999), }, # 最大功率限值存储值(由于转换误差, 无法写入8000W)
0x70: {0: [(500, 500)], 1: (400, 800), }, # Boost输入过压保护值
0x71: {0: [(460, 460)], 1: (300, 600), }, # Boost输出过压保护值
0x72: {0: [(60, 60)], 1: (40, 80), 2: (2, 0x73, 0.5)}, # LLC输出过压保护值
0x73: {0: [(40, 40)], 1: (20, 50), 2: (1, 0x72, 0.5)}, # LLC输出欠压保护值
0x74: {0: [(20, 20)], 1: (6, 30), }, # Boost电感过流保护值
0x75: {0: [(20, 20)], 1: (10, 30), }, # LLC输出电流均值保护值
0x76: {0: [(20, 20)], 1: (10, 30), }, # LLC输出电流峰值保护值
0x78: {0: [(6200, 6200)], 1: (4500, 9000), }, # 过载保护值
0x7A: {0: [(105, 105)], 1: (30, 150), 2: (2, 0x7B, 1.0)}, # 过温故障值
0x7B: {0: [(95, 95)], 1: (20, 150), 2: (1, 0x7A, 1.0)}, # 过温告警值
0x7C: {0: [(85, 85)], 1: (0, 120), 2: (1, 0x7A, 1.0)}, # 过温恢复值
0x7D: {0: [(10, 10)], 1: (0, 60), }, # 输出继电器故障判断差值
0x7E: {0: [(55, 55)], 1: (35, 60), }, # LLC输出电压给定值
0x7F: {0: [(420, 420)], 1: (320, 460), }, # Boost输出电压给定值
0x80: {0: [(56, 56)], 1: (10, 80), }, # 三点法中间阈值
0x81: {0: [(57, 57)], 1: (10, 80), }, # 浮充电压
0x82: {0: [(56, 56)], 1: (10, 80), }, # 恒压充电电压
0x83: {0: [(380, 380)], 1: (0, 450), }, # llc软起开始电压
0x84: {0: [(395, 395)], 1: (0, 450), 2: (1, 0x85, 0.5)}, # boost开始运行电压
0x85: {0: [(410, 410)], 1: (0, 450), 2: (2, 0x84, 0.5)}, # boost停止运行电压
0x86: {0: [(15000, 15000)], 1: (0, 30000), }, # 绝缘检测正阻抗限值
0x88: {0: [(15000, 15000)], 1: (0, 30000), }, # 绝缘检测负阻抗限值
0x8A: {0: [(123, 123), (137, 137)], 1: (50, 200), 2: (1, 0x8B, 0.2)}, # 抖动频率下限(精度误差严重, 难以正常测试)
0x8B: {0: [(137, 137), (123, 123)], 1: (50, 200), 2: (2, 0x8A, -0.2)}, # 抖动频率上限(精度误差严重, 难以正常测试)
0x170: {0: [(b'TTE0102DX20241001120001', 'TTE0102DX20241001120001\x00\x00\x00\x00\x00\x00\x00\x00\x00')]}, # 设备序列号
0x180: {0: [([0x24, 0x10, 0x01, 0x12, 0x00, 0x01], '$\x10\x01\x12\x00\x01' + 26 * '\x00')]}, # 设备MES码
0x190: {0: [(b'241001120001', '241001120001' + 20 * '\000')]}, # 出厂日期批次
}
dev_lamina = LaminaController(**mode_config['Debug'])
test_parameters(dev_lamina, ParamMap_LaminaController, TestCase)
if __name__== "__main__":
main()

364
source/test_parameter.py Normal file
View File

@@ -0,0 +1,364 @@
import time
import pytest
import logging
from typing import Tuple, Generator, List, Optional, Union
from device.DeviceSerial import DeviceSerial
from device.LaminaAdapter import LaminaAdapter
from device.tools.ByteConv import display_hex
TestCase_Controller = {
# 测试用例定义
# 0 - 正常写入数据序列; [(写入数据, 读取数据)]
# 1 - 范围限制测试; (最小值, 最大值)
# 2 - 相关大小限制测试; ((0-等于, 1-小于, 2-大于), 相关值地址, 死区范围)
0x60: {0: [(0, '0x0000'), (1, '0x0001'),
(2, '0x0001'), (0xFF, '0x0001'), (0xFFFF, '0x0001')]}, # 整机运行使能
0x61: {0: [(60.0, 60.0)], 1: (40, 100), 2: (1, 0x62, 0.5)}, # 最小启动允许输入电压
0x62: {0: [(440.0, 440.0)], 1: (350, 560), 2: (2, 0x61, 0.5)}, # 最大启动允许输入电压
0x63: {0: [(58, 58.0)], 1: (38, 98), 2: (1, 0x64, 0.5)}, # 最小停机输入电压
0x64: {0: [(442, 442.0)], 1: (352, 562), 2: (2, 0x63, 0.5)}, # 最大停机输入电压
0x65: {0: [(41, 41.0)], 1: (30, 60), 2: (1, 0x66, 0.5)}, # 最小启动允许输出电压
0x66: {0: [(57, 57.0)], 1: (50, 80), 2: (2, 0x65, 0.5)}, # 最大启动允许输出电压
0x67: {0: [(40, 40.0)], 1: (28, 58), 2: (1, 0x68, 0.5)}, # 最小停止允许输出电压
0x68: {0: [(58, 58.0)], 1: (52, 82), 2: (2, 0x67, 0.5)}, # 最大停止允许输出电压
0x69: {0: [(0.1, 0.1)], 1: (0, 5), 2: (1, 0x6A, 1.0)}, # 最小MPPT电流限值
0x6A: {0: [(22, 22.0)], 1: (5, 30), 2: (2, 0x69, 1.0)}, # 最大MPPT电流限值
0x6C: {0: [(6000, 6000)], 1: (3000, 7999.999),2: (0, 0x6E, 0.0)}, # 最大功率限值(由于转换误差, 无法写入8000W)
0x6E: {0: [(6000, 6000)], 1: (3000, 7999.999), }, # 最大功率限值存储值(由于转换误差, 无法写入8000W)
0x70: {0: [(500, 500)], 1: (400, 800), }, # Boost输入过压保护值
0x71: {0: [(460, 460)], 1: (300, 600), }, # Boost输出过压保护值
0x72: {0: [(60, 60)], 1: (40, 80), 2: (2, 0x73, 0.5)}, # LLC输出过压保护值
0x73: {0: [(40, 40)], 1: (20, 50), 2: (1, 0x72, 0.5)}, # LLC输出欠压保护值
0x74: {0: [(20, 20)], 1: (6, 30), }, # Boost电感过流保护值
0x75: {0: [(20, 20)], 1: (10, 30), }, # LLC输出电流均值保护值
0x76: {0: [(20, 20)], 1: (10, 30), }, # LLC输出电流峰值保护值
0x78: {0: [(6200, 6200)], 1: (4500, 9000), }, # 过载保护值
0x7A: {0: [(105, 105)], 1: (30, 150), 2: (2, 0x7B, 1.0)}, # 过温故障值
0x7B: {0: [(95, 95)], 1: (20, 150), 2: (1, 0x7A, 1.0)}, # 过温告警值
0x7C: {0: [(85, 85)], 1: (0, 120), 2: (1, 0x7A, 1.0)}, # 过温恢复值
0x7D: {0: [(10, 10)], 1: (0, 60), }, # 输出继电器故障判断差值
0x7E: {0: [(55, 55)], 1: (35, 60), }, # LLC输出电压给定值
0x7F: {0: [(420, 420)], 1: (320, 460), }, # Boost输出电压给定值
0x80: {0: [(56, 56)], 1: (10, 80), }, # 三点法中间阈值
0x81: {0: [(57, 57)], 1: (10, 80), }, # 浮充电压
0x82: {0: [(56, 56)], 1: (10, 80), }, # 恒压充电电压
0x83: {0: [(380, 380)], 1: (0, 450), }, # llc软起开始电压
0x84: {0: [(395, 395)], 1: (0, 450), 2: (1, 0x85, 0.5)}, # boost开始运行电压
0x85: {0: [(410, 410)], 1: (0, 450), 2: (2, 0x84, 0.5)}, # boost停止运行电压
0x86: {0: [(15000, 15000)], 1: (0, 30000), }, # 绝缘检测正阻抗限值
0x88: {0: [(15000, 15000)], 1: (0, 30000), }, # 绝缘检测负阻抗限值
0x8A: {0: [(123, 123), (137, 137)], 1: (50, 200), 2: (1, 0x8B, 0.2)}, # 抖动频率下限(精度误差严重, 难以正常测试)
0x8B: {0: [(137, 137), (123, 123)], 1: (50, 200), 2: (2, 0x8A, -0.2)}, # 抖动频率上限(精度误差严重, 难以正常测试)
0x170: {0: [(b'TTE0102DX20241001120001', 'TTE0102DX20241001120001\x00\x00\x00\x00\x00\x00\x00\x00\x00')]}, # 设备序列号
0x180: {0: [([0x24, 0x10, 0x01, 0x12, 0x00, 0x01], '$\x10\x01\x12\x00\x01' + 26 * '\x00')]}, # 设备MES码
0x190: {0: [(b'241001120001', '241001120001' + 20 * '\000')]}, # 出厂日期批次
}
TestCase_Adapter = {
# 测试用例定义
# 0 - 正常写入数据序列; [(写入数据, 读取数据)]
# 1 - 范围限制测试; (最小值, 最大值)
# 2 - 相关大小限制测试; ((0-等于, 1-小于, 2-大于), 相关值地址, 死区范围)
0x60: {0: [(0, '0x0000'), (1, '0x0001'),
(2, '0x0001'), (0xFF, '0x0001'), (0xFFFF, '0x0001')]}, # 整机运行使能
0x61: {0: [(60.0, 60.0)], 1: (40, 100), 2: (1, 0x62, 0.5)}, # 最小启动允许输入电压
0x62: {0: [(440.0, 440.0)], 1: (350, 560), 2: (2, 0x61, 0.5)}, # 最大启动允许输入电压
0x63: {0: [(58, 58.0)], 1: (38, 98), 2: (1, 0x64, 0.5)}, # 最小停机输入电压
0x64: {0: [(442, 442.0)], 1: (352, 562), 2: (2, 0x63, 0.5)}, # 最大停机输入电压
0x65: {0: [(41, 41.0)], 1: (30, 60), 2: (1, 0x66, 0.5)}, # 最小启动允许输出电压
0x66: {0: [(57, 57.0)], 1: (50, 80), 2: (2, 0x65, 0.5)}, # 最大启动允许输出电压
0x67: {0: [(40, 40.0)], 1: (28, 58), 2: (1, 0x68, 0.5)}, # 最小停止允许输出电压
0x68: {0: [(58, 58.0)], 1: (52, 82), 2: (2, 0x67, 0.5)}, # 最大停止允许输出电压
0x69: {0: [(0.1, 0.1)], 1: (0, 5), 2: (1, 0x6A, 1.0)}, # 最小MPPT电流限值
0x6A: {0: [(22, 22.0)], 1: (5, 30), 2: (2, 0x69, 1.0)}, # 最大MPPT电流限值
0x6C: {0: [(6000, 6000)], 1: (3000, 7999.999),2: (0, 0x6E, 0.0)}, # 最大功率限值(由于转换误差, 无法写入8000W)
0x6E: {0: [(6000, 6000)], 1: (3000, 7999.999), }, # 最大功率限值存储值(由于转换误差, 无法写入8000W)
0x70: {0: [(500, 500)], 1: (400, 800), }, # Boost输入过压保护值
0x71: {0: [(460, 460)], 1: (300, 600), }, # Boost输出过压保护值
0x72: {0: [(60, 60)], 1: (40, 80), 2: (2, 0x73, 0.5)}, # LLC输出过压保护值
0x73: {0: [(40, 40)], 1: (20, 50), 2: (1, 0x72, 0.5)}, # LLC输出欠压保护值
0x74: {0: [(20, 20)], 1: (6, 30), }, # Boost电感过流保护值
0x75: {0: [(20, 20)], 1: (10, 30), }, # LLC输出电流均值保护值
0x76: {0: [(20, 20)], 1: (10, 30), }, # LLC输出电流峰值保护值
0x78: {0: [(6200, 6200)], 1: (4500, 9000), }, # 过载保护值
0x7A: {0: [(105, 105)], 1: (30, 150), 2: (2, 0x7B, 1.0)}, # 过温故障值
0x7B: {0: [(95, 95)], 1: (20, 150), 2: (1, 0x7A, 1.0)}, # 过温告警值
0x7C: {0: [(85, 85)], 1: (0, 120), 2: (1, 0x7A, 1.0)}, # 过温恢复值
0x7D: {0: [(10, 10)], 1: (0, 60), }, # 输出继电器故障判断差值
0x7E: {0: [(55, 55)], 1: (35, 60), }, # LLC输出电压给定值
0x7F: {0: [(420, 420)], 1: (320, 460), }, # Boost输出电压给定值
0x80: {0: [(56, 56)], 1: (10, 80), }, # 三点法中间阈值
0x81: {0: [(57, 57)], 1: (10, 80), }, # 浮充电压
0x82: {0: [(56, 56)], 1: (10, 80), }, # 恒压充电电压
0x83: {0: [(380, 380)], 1: (0, 450), }, # llc软起开始电压
0x84: {0: [(395, 395)], 1: (0, 450), 2: (1, 0x85, 0.5)}, # boost开始运行电压
0x85: {0: [(410, 410)], 1: (0, 450), 2: (2, 0x84, 0.5)}, # boost停止运行电压
0x86: {0: [(15000, 15000)], 1: (0, 30000), }, # 绝缘检测正阻抗限值
0x88: {0: [(15000, 15000)], 1: (0, 30000), }, # 绝缘检测负阻抗限值
0x8A: {0: [(123, 123), (137, 137)], 1: (50, 200), 2: (1, 0x8B, 0.2)}, # 抖动频率下限(精度误差严重, 难以正常测试)
0x8B: {0: [(137, 137), (123, 123)], 1: (50, 200), 2: (2, 0x8A, -0.2)}, # 抖动频率上限(精度误差严重, 难以正常测试)
0x170: {0: [(b'TTE0102DX20241001120001', 'TTE0102DX20241001120001\x00\x00\x00\x00\x00\x00\x00\x00\x00')]}, # 设备序列号
0x180: {0: [([0x24, 0x10, 0x01, 0x12, 0x00, 0x01], '$\x10\x01\x12\x00\x01' + 26 * '\x00')]}, # 设备MES码
0x190: {0: [(b'241001120001', '241001120001' + 20 * '\000')]}, # 出厂日期批次
}
def frame_write(device:DeviceSerial, address, length, value):
""" 整合参数写入接口 """
return device.frame_write(address, length, value)
def frame_read(device:DeviceSerial, address, length=None):
""" 整合参数读取接口 """
if length is None:
info = device.block['data']['data_define'][address]
length = 2 if info[1] in [3, 6] else 1
length = info[2] if info[1] in [4, 5, 7] else length
return device.output['Regs'][address][1] if device.frame_read(address, length) else None
def value_case_generator(device:DeviceSerial, address:int, itemCase:dict) -> Generator[object, object, bool]:
""" 依据地址与参数配置生成测试用例 """
addr_relate = None
list_case_normal = []
match itemCase.keys():
case 0: # 常规读写用例测试
for data_write, data_read in itemCase[0]:
logging.info(f"Param Case:\taddr={display_hex(address)}, \tvalue={data_write}")
yield data_write, data_read, True
case 2: # 存在大小约束相关项
assert device.block['data']['data_define'][address][1] in [2, 3]
type_relate, addr_relate, dead_zone = itemCase[2]
val_relate = frame_read(device, addr_relate)
yield val_relate, val_relate, False
pass
case 1: # 写入范围用例测试
val_min, val_max = itemCase[1]
pass
if 1 in itemCase.keys():
""" 写入范围用例测试 """
testzone = 0.7 if info_param[1] in [2, 3] and len(info_param) < 3 else 7 / info_param[2]
accuracy = 0.20001 if info_param[1] in [2, 3] and len(info_param) < 3 else 2.0001 / info_param[2]
val_min, val_max = itemCase[1]
if 2 in itemCase.keys():
""" 存在大小约束相关项 """
list_case_relate = [] # 对约束相关项的修改
list_case_late = [] # 存在约束相关项影响后的测试用例
mode_relate, addr_relate, deadzone = itemCase[2]
val_relate = frame_read(device, addr_relate, ParamCase[addr_relate])
if mode_relate == 1:
if (val_relate - deadzone) < val_max:
""" 约束项已限制写入范围 """
list_case_relate.append((val_max + 2 * abs(deadzone), val_max + 2 * abs(deadzone), True))
list_case_late.append((val_max + testzone, val_max + testzone, False))
list_case_late.append((val_max, val_max, True))
list_case_late.append((val_max - testzone, val_max - testzone, True))
val_max = val_relate - deadzone
else:
""" 约束项未限制写入范围 """
val_relate = val_max
list_case_relate.append((val_relate, val_relate, True))
list_case_late.append((val_relate - deadzone + testzone, val_relate - deadzone + testzone, False))
list_case_late.append((val_relate - deadzone, val_relate - deadzone, True))
list_case_late.append((val_relate - deadzone - testzone, val_relate - deadzone - testzone, True))
elif mode_relate == 2:
if (val_relate + deadzone) > val_min:
""" 约束项已限制写入范围 """
list_case_relate.append((val_min - 2 * abs(deadzone), val_min - 2 * abs(deadzone), True))
list_case_late.append((val_min - testzone, val_min - testzone, False))
list_case_late.append((val_min, val_min, True))
list_case_late.append((val_min + testzone, val_min + testzone, True))
val_min = val_relate + deadzone
else:
""" 约束项未限制写入范围 """
val_relate = val_min
list_case_relate.append((val_relate, val_relate, True))
list_case_late.append((val_relate + deadzone - testzone, val_relate + deadzone - testzone, False))
list_case_late.append((val_relate + deadzone, val_relate + deadzone, True))
list_case_late.append((val_relate + deadzone + testzone, val_relate + deadzone + testzone, True))
list_case_normal.append((val_min - testzone, val_min - testzone, False))
list_case_normal.append((val_min, val_min, True))
list_case_normal.append((val_min + testzone, val_min + testzone, True))
list_case_normal.append((val_max + testzone, val_max + testzone, False))
list_case_normal.append((val_max, val_max, True))
list_case_normal.append((val_max - testzone, val_max - testzone, True))
print(f"Param Case:\taddr={display_hex(addr_param)}")
pass
def test_communication(device: DeviceSerial, time_out=2):
""" 通信成功率测试 """
time_start = time.time()
param_saved = device.flag_print, device.retry, device.time_out
device.flag_print = False
device.retry = 1
try:
while True:
device.frame_read(0x0C, 0x20)
print(f"Time Stamp: {time.ctime()}")
print(f"Success Frame: {device.log['read']}")
print(f"Failed Frame: {device.log['send'] - device.log['read']}")
print(f"Max Series Failed Frame: {device.log['keep-fail']}")
time.sleep(time_out)
finally:
time_end = time.time()
print("Test Result: ")
print(f"Time Start: {time.strftime(r'%Y-%m-%d %H:%M:%S', time.localtime(time_start))}, \tTime End: {time.strftime(r'%Y-%m-%d %H:%M:%S', time.localtime(time_end))}")
print(f"Time Elapsed: {time_end - time_start}")
print(f"Success Rate: {device.log['read'] / device.log['send'] * 100}%")
device.flag_print, device.retry, device.time_out = param_saved
@pytest.fixture
def device():
mode_config = {
"Debug": {'com_name': 'COM3',
'frame_print': None,
'time_out': 0.5, 'retry': 3},
}
device = LaminaAdapter(**mode_config['Debug'])
yield device
device.close_connection()
@pytest.mark.parametrize("address, case_value", *TestCase_Adapter.items())
def test_parameters(device:DeviceSerial, address, itemCase):
""" 参数读写测试 """
addr_relate = None
itemCase = ParamCase[addr_param]
list_case_normal = []
if 0 in itemCase.keys():
""" 常规读写用例测试 """
for data_write, data_read in itemCase[0]:
list_case_normal.append((data_write, data_read, True))
if 1 in itemCase.keys():
""" 写入范围用例测试 """
testzone = 0.7 if info_param[1] in [2, 3] and len(info_param) < 3 else 7 / info_param[2]
accuracy = 0.20001 if info_param[1] in [2, 3] and len(info_param) < 3 else 2.0001 / info_param[2]
val_min, val_max = itemCase[1]
if 2 in itemCase.keys():
""" 存在大小约束相关项 """
list_case_relate = [] # 对约束相关项的修改
list_case_late = [] # 存在约束相关项影响后的测试用例
mode_relate, addr_relate, deadzone = itemCase[2]
val_relate = frame_read(device, addr_relate, ParamCase[addr_relate])
if mode_relate == 1:
if (val_relate - deadzone) < val_max:
""" 约束项已限制写入范围 """
list_case_relate.append((val_max + 2 * abs(deadzone), val_max + 2 * abs(deadzone), True))
list_case_late.append((val_max + testzone, val_max + testzone, False))
list_case_late.append((val_max, val_max, True))
list_case_late.append((val_max - testzone, val_max - testzone, True))
val_max = val_relate - deadzone
else:
""" 约束项未限制写入范围 """
val_relate = val_max
list_case_relate.append((val_relate, val_relate, True))
list_case_late.append((val_relate - deadzone + testzone, val_relate - deadzone + testzone, False))
list_case_late.append((val_relate - deadzone, val_relate - deadzone, True))
list_case_late.append((val_relate - deadzone - testzone, val_relate - deadzone - testzone, True))
elif mode_relate == 2:
if (val_relate + deadzone) > val_min:
""" 约束项已限制写入范围 """
list_case_relate.append((val_min - 2 * abs(deadzone), val_min - 2 * abs(deadzone), True))
list_case_late.append((val_min - testzone, val_min - testzone, False))
list_case_late.append((val_min, val_min, True))
list_case_late.append((val_min + testzone, val_min + testzone, True))
val_min = val_relate + deadzone
else:
""" 约束项未限制写入范围 """
val_relate = val_min
list_case_relate.append((val_relate, val_relate, True))
list_case_late.append((val_relate + deadzone - testzone, val_relate + deadzone - testzone, False))
list_case_late.append((val_relate + deadzone, val_relate + deadzone, True))
list_case_late.append((val_relate + deadzone + testzone, val_relate + deadzone + testzone, True))
list_case_normal.append((val_min - testzone, val_min - testzone, False))
list_case_normal.append((val_min, val_min, True))
list_case_normal.append((val_min + testzone, val_min + testzone, True))
list_case_normal.append((val_max + testzone, val_max + testzone, False))
list_case_normal.append((val_max, val_max, True))
list_case_normal.append((val_max - testzone, val_max - testzone, True))
print(f"Param Case:\taddr={display_hex(addr_param)}")
last_value = frame_read(device, addr_param, info_param)
for case_test in list_case_normal:
print(f"\tnormal case={case_test}")
result = frame_write(device, addr_param, info_param, case_test[0])
assert result == case_test[2]
current_value = frame_read(device, addr_param, info_param)
if current_value is None:
raise Exception("Param Read Fail")
elif result:
if type(current_value) is float:
if abs(current_value - case_test[1]) > accuracy:
raise Exception("Param Check Fail")
else:
if current_value != case_test[1]:
raise Exception("Param Check Fail")
elif (not result) and current_value != last_value:
raise Exception("Param Check Fail")
last_value = current_value
if list_case_normal and list_case_normal[0][1] != last_value:
""" 为参数写入首个测试用例数据(一般为参数默认值), 避免影响后续参数测试 """
case_test = list_case_normal[0]
result = frame_write(device, addr_param, info_param, case_test[0])
assert result == case_test[2]
last_value = frame_read(device, addr_param, info_param)
if addr_relate:
""" 存在关联测试项 """
for case_relate in list_case_relate:
print(f"\trelate state: addr={display_hex(addr_relate)}, value={case_relate[0]}")
result = frame_write(device, addr_relate, info_param, case_relate[0])
if result == case_relate[2]:
for case_test in list_case_late:
print(f"\t\tstate case={case_test}")
result = frame_write(device, addr_param, info_param, case_test[0])
assert result == case_test[2]
current_value = frame_read(device, addr_param, info_param)
if current_value is None:
raise Exception("Param Read Fail")
elif result:
if type(current_value) is float:
if abs(current_value - case_test[1]) > accuracy:
raise Exception("Param Check Fail")
else:
if current_value != case_test[1]:
raise Exception("Param Check Fail")
elif (not result) and current_value != last_value:
raise Exception("Param Check Fail")
last_value = current_value
if list_case_normal and list_case_normal[0][1] != last_value:
""" 为参数写入首个测试用例数据(一般为参数默认值), 避免影响后续参数测试 """
case_test = list_case_normal[0]
result = frame_write(device, addr_param, info_param, case_test[0])
assert result == case_test[2]
def main():
mode_config = {
"Log": {'com_name': None,
# 'addr_645': [0x01, 0x00, 0x00, 0x00, 0x00, 0x40],
},
"Debug": {'com_name': 'COM3',
'addr_645': [0x01, 0x02, 0x03, 0x04, 0x05, 0x06],
'frame_print': True,
'time_out': 0.5, 'retry': 3},
}
dev_lamina = LaminaAdapter(**mode_config['Debug'])
test_parameters(dev_lamina, dev_lamina.block['data']['data_define'], TestCase)
if __name__== "__main__":
pytest.main()

View File

@@ -1,31 +0,0 @@
def trans_list_to_str(data: list) -> str:
""" 标准串口字符串表示 """
func_trans = lambda x: ('00' + hex(x % 256)[2:])[-2:].upper()
return " ".join(map(func_trans, data))
def trans_str_to_list(data: str) -> list:
""" 标准串口字符串转换列表 """
func_trans = lambda x: int(x, 16)
return list(map(func_trans, data.strip().split(" ")))
def conv_int_to_array(num: int, big_end=False):
""" 数值转字节数组 """
result = [0, 0, 0, 0]
if big_end:
indexlist = reversed(range(len(result)))
else:
indexlist = range(len(result))
for i in indexlist:
result[i] = int(num % 0x100)
num //= 0x100
return result
def display_hex(data, len) -> str:
""" Hex字符表示 """
data %= 2 ** (4 * len)
result = "0" * len + hex(data)[2:]
return "0x" + result[-len:].upper()