Compare commits

...

10 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
19 changed files with 49985 additions and 674 deletions

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

View File

@@ -1,73 +1,637 @@
import time
import logging
import datetime
import requests import requests
import numpy as np
import pandas as pd import pandas as pd
from pathlib import Path from pathlib import Path
from bs4 import BeautifulSoup 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_URL = "https://energy-iot.chinatowercom.cn/api/device/device/historyPerformance"
headers = { API_HEADER = {
"accept": "application/json, text/plain, */*",
"Accept-Encoding": "gzip, deflate, br, zstd", "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", "Connection": "keep-alive",
"Content-Length": "211", "Content-Length": "170",
"Cookie": "HWWAFSESTIME=1732173820506; HWWAFSESID=1739685743c73769ff; dc04ed2361044be8a9355f6efb378cf2=WyIzNTI0NjE3OTgzIl0", "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", "Host": "energy-iot.chinatowercom.cn",
"Origin": "https://energy-iot.chinatowercom.cn", "Origin": "https://energy-iot.chinatowercom.cn",
"Sec-Fetch-Dest": "empty", "Sec-Fetch-Dest": "empty",
"Sec-Fetch-Mode": "cors", "Sec-Fetch-Mode": "cors",
"Sec-Fetch-Site": "same-origin", "Sec-Fetch-Site": "same-origin",
"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",
"accept": "application/json, text/plain, */*",
"accept-language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6",
"authorization": "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiIl0sInVzZXJfbmFtZSI6IndlYl9tYW5hZ2V8d2FuZ2xlaTQiLCJzY29wZSI6WyJhbGwiXSwiZXhwIjoxNzMyMjc4NDA5LCJ1c2VySWQiOjI0Mjg1LCJqdGkiOiJkODE0YTZhYy05YmJmLTQ0ZjQtYWRhYi0wMzAzNjUzNmNhNWIiLCJjbGllbnRfaWQiOiJ3ZWJfbWFuYWdlIn0.VhJaDKwzjekwOCsw_jOF_jvg7sX45okFcxkLyWtbfFVGWVWANhKNhVqj5Dn0Qb3wUXH3e-w74sDN1RI9QADngMOGP_H7aTwI_nukj6VmjpFA7kEtOBwa6ouvPZQMa1qa3UWl21Ac6GoLu14T4TIf4kQAMTdaYAMFrwDAXAkqvIDmKKjZbnDFUjUIcj-J_Y-LfHCEBjtcz7Rp_wMO-PMA5wII6kbcNoSFiYb0djcFQyeBcIUSUTRPixPcTYBkS-IhNrsOePIWlpNYMHbPxZdrZkV4M65BmBn4A9MUjWYHm7iIut8WVMdCXR4Sxp9m0mJHXR_IPWES4O7aBcuMkOmjyw",
"content-type": "application/json;charset=UTF-8",
"sec-ch-ua": "\"Microsoft Edge\";v=\"131\", \"Chromium\";v=\"131\", \"Not_A Brand\";v=\"24\"", "sec-ch-ua": "\"Microsoft Edge\";v=\"131\", \"Chromium\";v=\"131\", \"Not_A Brand\";v=\"24\"",
"sec-ch-ua-mobile": "?0", "sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": "Windows", "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",
} }
body = { SemaMap_adapter = {
"startTimestamp": 1732032000000, 'apt_facturer': ('0305113001', 'adapter', False, "厂家"),
"endTimestamp": 1732291199000, 'apt_version': ('0305114001', 'adapter', False, "软件版本"),
"deviceCode": "TTE0102DX2406240497", 'apt_model': ('0305115001', 'adapter', False, "型号"),
"mid": "0305120001", '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", "businessType": "7",
"pageNum": 2, "startTimestamp": int(time_start * 1000),
"pageSize": 5, "endTimestamp": int(time_end * 1000),
"deviceCode": f"{device_id}",
"mid": f"{data_type[0]}",
"pageNum": 1,
"pageSize": 10,
"total": 0 "total": 0
} }
# 1. 读取本地HTML文件 req = requests.post(API_URL, json=body, headers=header)
file_path = Path(r'D:\WorkingProject\LightStackAdapter\Log\设备测试数据记录-铁塔主站\南和县牧村\Untitled-1.html') json_data = req.json()
html_content = file_path.read_text() 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}")
# 2. 解析HTML文件 def save_station_by_file2(data_lamina: Lamina_Data, file_path):
soup = BeautifulSoup(html_content, 'html.parser') """ 依据文件爬取所需站点数据 """
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
# 3. 找到表格元素 dataset = []
table = soup.find_all('table') # 假设页面中只有一个表格,如果有多个表格,可能需要进一步筛选 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']
# 4. 提取表格数据 logging.info(f"All Station Done.")
data = []
headers = []
# 提取表头 # 使用 ExcelWriter 将多个 DataFrame 保存到不同的工作表中
header_row = table.find('thead').find('tr') df_station = pd.DataFrame([], columns=['station', '点位名称'])
for header in header_row.find_all('th'): df_station.station = [data['station'] for data in dataset]
headers.append(header.text.strip()) 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}")
for row in table.find('tbody').find_all('tr'): return result
row_data = []
for cell in row.find_all(['td', 'th']):
row_data.append(cell.text.strip())
data.append(row_data)
# 5. 将数据保存为DataFrame def analysis_info(df_station: pd.DataFrame):
df = pd.DataFrame(data, columns=headers) """ 站点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()
# 6. 将DataFrame保存为CSV文件 def analysis_info1(data_station: dict):
output_file = 'extracted_table.csv' """ 站点spider返回数据分析 """
df.to_csv(output_file, index=False, encoding='utf-8') 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
print(f'表格数据已成功提取并保存到 {output_file}') 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,8 +1,7 @@
import time import time
import warnings
from pathlib import Path from pathlib import Path
from device.LaminaAdapter import LaminaAdapter from device.LaminaAdapter import LaminaAdapter
from device.LaminaAdapter import GeneratePackage_SLCP001_p4a0, GeneratePackage_SLCP101_p460, GeneratePackage_DLSY001_p460
from device.LaminaAdapter import GeneratePackage_SLCP102_p460
from device.tools.ByteConv import trans_list_to_str, trans_str_to_list from device.tools.ByteConv import trans_list_to_str, trans_str_to_list
def test_communication(time_out=2): def test_communication(time_out=2):
@@ -27,22 +26,6 @@ def test_communication(time_out=2):
print(f"Success Rate: {dev_lamina.log['read'] / dev_lamina.log['send'] * 100}%") 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 dev_lamina.flag_print, dev_lamina.retry, dev_lamina.time_out = param_saved
def make_Pakeage(fp: Path, func):
""" 生成升级包 """
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 = func(hex_update)
file_package.write_bytes(data_package)
file_update_bin.write_bytes(data_update_bin)
return file_package
def test(): def test():
if 0: if 0:
dev_lamina.frame_read(0xA9, 1) # 读ADC参考电压 dev_lamina.frame_read(0xA9, 1) # 读ADC参考电压
@@ -130,6 +113,103 @@ def test():
time.sleep(2) time.sleep(2)
dev_lamina.frame_read(0x0170, 0x30) dev_lamina.frame_read(0x0170, 0x30)
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__': if __name__=='__main__':
mode_config = { mode_config = {
@@ -139,26 +219,43 @@ if __name__=='__main__':
"Debug": {'com_name': 'COM3', 'baudrate': 115200, 'parity': 'N', 'bytesize': 8, 'stopbits': 1, "Debug": {'com_name': 'COM3', 'baudrate': 115200, 'parity': 'N', 'bytesize': 8, 'stopbits': 1,
# 'addr_645': [0x01, 0x02, 0x03, 0x04, 0x05, 0x06], # 'addr_645': [0x01, 0x02, 0x03, 0x04, 0x05, 0x06],
'frame_print': True, 'frame_print': True,
'time_out': 0.1, 'retry': 1, 'retry_sub': 10}, 'time_out': 0.5, 'retry': 1, 'retry_sub': 10},
"HPLC": {'com_name': 'COM9', 'baudrate': 9600, 'parity': 'E', 'bytesize': 8, 'stopbits': 1, "HPLC": {'com_name': 'COM8', 'baudrate': 9600, 'parity': 'E', 'bytesize': 8, 'stopbits': 1,
# 'addr_645': [0x11, 0x01, 0x18, 0x06, 0x24, 0x02], 'addr_645': trans_str_to_list("02 01 00 00 24 20"),
'frame_print': True, 'frame_print': True,
'time_out': 3, 'time_gap': 0.1, 'retry': 3, 'retry_sub': 10}, 'time_out': 3, 'time_gap': 0.1, 'retry': 3, 'retry_sub': 10},
} }
dev_lamina = LaminaAdapter(**mode_config['Debug']) dev_lamina = LaminaAdapter(type_dev="SLCP101", **mode_config['Debug'])
dev_lamina.frame_read(0x0100, 0x20) 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():
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)
if not hasattr(__builtins__,"__IPYTHON__") and 0:
""" 测试备份程序升级 """
dev_lamina.frame_update(file_dat_back)
time.sleep(3)
ret = dev_lamina.frame_read(0x0100, 0x20)
if not hasattr(__builtins__,"__IPYTHON__"): if not hasattr(__builtins__,"__IPYTHON__"):
# 工程-即时转换
file_hex = Path(r"D:\WorkingProject\LightStackAdapter\software\lamina_adapter\lamina_adapter\Debug\lamina_adapter.hex")
# file_hex = Path(r"D:\WorkingProject\LightStackOptimizer\software\lamina_optimizer\lamina_optimizer\Debug\lamina_optimizer.hex")
if not file_hex.exists():
raise Exception("工程编译目标文件不存在.")
file_package = make_Pakeage(file_hex, GeneratePackage_SLCP102_p460)
version = "SLCP101_241030_2000_V2.03" version = "SLCP101_241030_2000_V2.03"
addr = [0x24, 0x09, 0x12, 0x00, 0x00, 0x00] addr = [0x24, 0x09, 0x12, 0x00, 0x00, 0x00]
@@ -170,7 +267,7 @@ if __name__=='__main__':
ret = dev_lamina.frame_read(0x0100, 0x20) ret = dev_lamina.frame_read(0x0100, 0x20)
time.sleep(1) time.sleep(1)
dev_lamina.frame_update(file_package) dev_lamina.frame_update(file_hex, makefile=True)
time.sleep(3) time.sleep(3)

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,7 @@ import time
from pathlib import Path from pathlib import Path
from datetime import datetime from datetime import datetime
from tenacity import retry, stop_after_attempt, wait_fixed from tenacity import retry, stop_after_attempt, wait_fixed
from device.LaminaController import LaminaController from device.LaminaController import LaminaController, LaminaController_new
from device.LaminaController import GeneratePackage_DLSP001_p280039 from device.LaminaController import GeneratePackage_DLSP001_p280039
from device.LaminaController import GenerateImage_DLSP001_p280039 from device.LaminaController import GenerateImage_DLSP001_p280039
from device.tools.ByteConv import trans_list_to_str from device.tools.ByteConv import trans_list_to_str
@@ -233,17 +233,17 @@ if __name__=='__main__':
"Debug": {'com_name': 'COM8', '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], # 'addr_645': [0x01, 0x02, 0x03, 0x04, 0x05, 0x06],
'frame_print': True, 'frame_print': True,
'time_out': 0.5, 'retry': 3}, '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(0x0100, 0x20)
# dev_lamina.frame_read(0x0A, 0x20) # dev_lamina.frame_read(0x0A, 0x20)
# dev_lamina.frame_read(0x60, 0x60) # 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_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") path_data = Path(r"D:\WorkSpace\UserTool\SelfTool\FrameParser\test\p280039\result\record4.dat")
@@ -262,7 +262,7 @@ if __name__=='__main__':
dev_lamina.frame_write_one(0x60, 0) dev_lamina.frame_write_one(0x60, 0)
time.sleep(1) time.sleep(1)
if dev_lamina.frame_update(file_hex, makefile=True): if dev_lamina.frame_update(file_hex, makefile=True):
time.sleep(6) time.sleep(2)
dev_lamina.frame_read(0x0100, 0x20) dev_lamina.frame_read(0x0100, 0x20)
# dev_lamina.frame_write_one(0x52, 0x01) # dev_lamina.frame_write_one(0x52, 0x01)

1280
source/dev_inverter.ipynb Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -28,6 +28,46 @@ ParamMap_LaminaCombiner = {
0x000: ["系统型号", 4, 16], 0x000: ["系统型号", 4, 16],
0x010: ["程序版本", 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], 0x800: ["设备地址", 5, 3],
0x803: ["时间", 5, 3], 0x803: ["时间", 5, 3],
0x806: ["文件记录日志级别", 1], 0x806: ["文件记录日志级别", 1],
@@ -128,6 +168,10 @@ class LaminaStation(DeviceMQTT):
'data_define': ParamMap_LaminaCombiner, '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: def frame_read(self, daddr=0x60, dlen=0x30) -> bool:
self.block['type'] = 'read' self.block['type'] = 'read'
self.block['data_addr'] = daddr self.block['data_addr'] = daddr
@@ -137,15 +181,15 @@ class LaminaStation(DeviceMQTT):
def frame_write_one(self, daddr=0x85, dval=-900) -> bool: def frame_write_one(self, daddr=0x85, dval=-900) -> bool:
self.block['type'] = 'write_one' self.block['type'] = 'write_one'
self.block['data_addr'] = daddr self.block['data_addr'] = daddr
item_coff = self.block['data_define'][daddr][2] if len(self.block['data_define'][daddr]) > 2 else 1 # 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) self.block['data_val'] = int(dval) # * item_coff
return self._transfer_data() return self._transfer_data()
def frame_write_dual(self, daddr=0x91, dval=600) -> bool: def frame_write_dual(self, daddr=0x91, dval=600) -> bool:
self.block['type'] = 'write_dual' self.block['type'] = 'write_dual'
self.block['data_addr'] = daddr self.block['data_addr'] = daddr
item_coff = self.block['data_define'][daddr][2] if len(self.block['data_define'][daddr]) > 2 else 1 # 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) self.block['data_val'] = int(dval) # * item_coff
return self._transfer_data() return self._transfer_data()
def frame_write_str(self, daddr=0x82, dval=[0x06, 0x05, 0x04, 0x03, 0x02, 0x01]) -> bool: def frame_write_str(self, daddr=0x82, dval=[0x06, 0x05, 0x04, 0x03, 0x02, 0x01]) -> bool:
@@ -163,17 +207,100 @@ if __name__ == '__main__':
"dev2": {'device_id': 'TTE0101DX2409230113', # 常来东-光伏 "dev2": {'device_id': 'TTE0101DX2409230113', # 常来东-光伏
'frame_print': True, 'frame_print': True,
'time_out': 4, 'retry': 1}, 'time_out': 4, 'retry': 1},
"dev3": {'device_id': 'TTE0101DX2406270041', # 大丰市镇区补点139 "dev3": {'device_id': 'TTE0101DX2406270041', # 大丰市镇区补点139
'frame_print': True, 'frame_print': True,
'time_out': 4, 'retry': 1}, 'time_out': 4, 'retry': 1},
"dev4": {'device_id': 'TTE0101DX2407020114', # 大丰大龙南 "dev4": {'device_id': 'TTE0101DX2407020114', # 大丰大龙南
'frame_print': True, 'frame_print': True,
'time_out': 4, 'retry': 1}, '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["dev4"]) dev_lamina = LaminaStation(**mode_config["dev29"])
dev_lamina.frame_read(0x0000, 0x20) dev_lamina.frame_read(0x0000, 0x20)
dev_lamina.frame_read(0x4100, 0x20) time.sleep(2)
dev_lamina.frame_read(0x857, 0x40)
dev_lamina.frame_read_adapter(0, 0x0E, 0x20)
if not hasattr(__builtins__,"__IPYTHON__"): if not hasattr(__builtins__,"__IPYTHON__"):
pass pass
dev_lamina.frame_read(0x400E + 0x200 * (6-1), 0x20)
dev_lamina.frame_read(0x4072 + 0x200 * (6-1), 0x03)

View File

@@ -52,7 +52,7 @@ class DeviceMQTT:
}, },
} }
def open_connection(self, broker, port, account=None, **kwargs) ->bool: def open_connection(self, broker, port, account=None, **kwargs):
""" 创建链接 """ """ 创建链接 """
def on_connect(client, userdata, flags, rc, properties): def on_connect(client, userdata, flags, rc, properties):
""" 回调函数-创建链接 """ """ 回调函数-创建链接 """
@@ -87,15 +87,18 @@ class DeviceMQTT:
if account is not None: if account is not None:
client.username_pw_set(account[0], account[1]) 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_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_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.on_message = on_message if 'func_on_message' not in kwargs.keys() else kwargs['func_on_message']
client.connect(broker, port) client.connect(broker, port)
client.loop_start()
return client return client
def close_connection(self) ->bool: def close_connection(self) ->bool:
""" 关闭连接 """ """ 关闭连接 """
self.client.loop_close()
self.client.disconnect() self.client.disconnect()
return True
def _subscribe(self, device_id): def _subscribe(self, device_id):
""" 订阅主题 """ """ 订阅主题 """

View File

@@ -1,6 +1,8 @@
import time import time
from pathlib import Path
from serial import Serial from serial import Serial
from serial.tools import list_ports from serial.tools import list_ports
from abc import ABC, abstractmethod
from .tools import ByteConv from .tools import ByteConv
from .function import protocols from .function import protocols
@@ -119,6 +121,7 @@ class DeviceSerial:
'bytesize': 8, 'bytesize': 8,
'stopbits': 1, 'stopbits': 1,
} }
kwargs = kwargs if kwargs else None
serial_close = lambda com: com.close() if com.isOpen() 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()) serial_port_check = lambda port: port.upper() in (com.name for com in list_ports.comports())
@@ -126,9 +129,13 @@ class DeviceSerial:
match (self.__com, port, kwargs): match (self.__com, port, kwargs):
case (None, str() as port, None): case (None, str() as port, None):
""" 使用默认参数打开串口 """ """ 使用默认参数打开串口 """
if not serial_port_check(port):
raise ValueError("无效串口端口: %s" % port)
self.__com = Serial(port, timeout=0, **com_config) self.__com = Serial(port, timeout=0, **com_config)
case (None, str() as port, dict() as kwargs): 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['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['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['bytesize'] = kwargs['bytesize'] if 'bytesize' in kwargs.keys() else com_config['bytesize']
@@ -136,19 +143,19 @@ class DeviceSerial:
self.__com = Serial(port, timeout=0, **com_config) self.__com = Serial(port, timeout=0, **com_config)
case (Serial() as com, None, None): case (Serial() as com, None, None):
""" 无参数重开串口 """ """ 无参数重开串口 """
serial_close(self.__com) serial_close(com)
com.open() com.open()
case (Serial() as com, port, None): case (Serial() as com, port, None):
""" 重新指定端口号并打开串口 """ """ 重新指定端口号并打开串口 """
serial_close(self.__com) serial_close(com)
if serial_port_check(port): if not serial_port_check(port):
raise ValueError("无效串口端口: %s" % port) raise ValueError("无效串口端口: %s" % port)
com.port = port com.port = port
com.open() com.open()
case (None, str() as port, dict() as kwargs): case (Serial() as com, str() as port, dict() as kwargs):
""" 重新指定端口号与配置并打开串口 """ """ 重新指定端口号与配置并打开串口 """
serial_close(self.__com) serial_close(com)
if serial_port_check(port): if not serial_port_check(port):
raise ValueError("无效串口端口: %s" % port) raise ValueError("无效串口端口: %s" % port)
com.port = port com.port = port
com.baudrate = kwargs['baudrate'] if 'baudrate' in kwargs.keys() else com_config['baudrate'] com.baudrate = kwargs['baudrate'] if 'baudrate' in kwargs.keys() else com_config['baudrate']
@@ -162,6 +169,18 @@ class DeviceSerial:
return self.__com.is_open 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,4 +1,5 @@
import time import time
import warnings
import hashlib import hashlib
from math import ceil from math import ceil
from tqdm import tqdm from tqdm import tqdm
@@ -16,6 +17,7 @@ ParamMap_LaminaAdapter = {
# 4 - str # 4 - str
# 5 - addr # 5 - addr
# 6 - float # 6 - float
# 8 - func_callback
0x00: ["硬件版本识别电压", 2, 1000], 0x00: ["硬件版本识别电压", 2, 1000],
0x01: ["输出电容电电压", 2, 10], 0x01: ["输出电容电电压", 2, 10],
0x02: ["参考电压", 2, 10], 0x02: ["参考电压", 2, 10],
@@ -46,6 +48,8 @@ ParamMap_LaminaAdapter = {
0x54: ["主动故障命令" , 2], 0x54: ["主动故障命令" , 2],
0x55: ["短时停机命令" , 2], 0x55: ["短时停机命令" , 2],
0x5E: ["抖动频率上限", 2, 100],
0x5F: ["抖动频率下限", 2, 100],
0x60: ["光伏通道使能", 1], 0x60: ["光伏通道使能", 1],
0x61: ["最小启动输入电压", 2, 10], 0x61: ["最小启动输入电压", 2, 10],
0x62: ["最大启动输入电压", 2, 10], 0x62: ["最大启动输入电压", 2, 10],
@@ -110,7 +114,7 @@ ParamMap_LaminaAdapter = {
0xA7: ["电池电压判断限值", 2, 10], 0xA7: ["电池电压判断限值", 2, 10],
0xA8: ["MPPT追踪模式", 1], 0xA8: ["MPPT追踪模式", 1],
0xA9: ["ADC参考电压", 2, 1000], 0xA9: ["ADC参考电压", 2, 1000],
0xAA: ["保留", 1], 0xAA: ["硬件版本", 1],
0xAB: ["保留", 1], 0xAB: ["保留", 1],
0xAC: ["保留", 1], 0xAC: ["保留", 1],
0xAD: ["保留", 1], 0xAD: ["保留", 1],
@@ -128,6 +132,13 @@ ParamMap_LaminaAdapter = {
0x170: ["SN", 4, 16], 0x170: ["SN", 4, 16],
0x180: ["MES", 4, 16], 0x180: ["MES", 4, 16],
0x190: ["Datetime", 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 = { MemoryMap_SLCP001 = {
@@ -194,25 +205,44 @@ class LaminaAdapter(DeviceSerial):
} }
def frame_read(self, daddr=0x60, dlen=0x50) -> bool: def frame_read(self, daddr=0x60, dlen=0x50) -> bool:
""" 通用参数读取 """
self.block['data']['type'] = 'read' self.block['data']['type'] = 'read'
self.block['data']['data_addr'] = daddr self.block['data']['data_addr'] = daddr
self.block['data']['data_len'] = dlen self.block['data']['data_len'] = dlen
return self._transfer_data() 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: def frame_write_one(self, daddr=0x85, dval=-900) -> bool:
self.block['data']['type'] = 'write_one' self.block['data']['type'] = 'write_one'
self.block['data']['data_addr'] = daddr self.block['data']['data_addr'] = daddr
self.block['data']['data_val'] = dval
item_coff = self.block['data']['data_define'][daddr][2] if len(self.block['data']['data_define'][daddr]) > 2 else 1 item_coff = self.block['data']['data_define'][daddr][2] if len(self.block['data']['data_define'][daddr]) > 2 else 1
self.block['data_val'] = int(dval * item_coff) self.block['data']['data_val'] = int(dval * item_coff)
return self._transfer_data() return self._transfer_data()
def frame_write_dual(self, daddr=0x91, dval=600) -> bool: def frame_write_dual(self, daddr=0x91, dval=600) -> bool:
self.block['data']['type'] = 'write_dual' self.block['data']['type'] = 'write_dual'
self.block['data']['data_addr'] = daddr self.block['data']['data_addr'] = daddr
self.block['data']['data_val'] = dval
item_coff = self.block['data']['data_define'][daddr][2] if len(self.block['data']['data_define'][daddr]) > 2 else 1 item_coff = self.block['data']['data_define'][daddr][2] if len(self.block['data']['data_define'][daddr]) > 2 else 1
self.block['data_val'] = int(dval * item_coff) self.block['data']['data_val'] = int(dval * item_coff)
return self._transfer_data() return self._transfer_data()
def frame_write_str(self, daddr=0x82, dval=[0x06, 0x05, 0x04, 0x03, 0x02, 0x01]) -> bool: def frame_write_str(self, daddr=0x82, dval=[0x06, 0x05, 0x04, 0x03, 0x02, 0x01]) -> bool:
@@ -221,7 +251,12 @@ class LaminaAdapter(DeviceSerial):
self.block['data']['data_val'] = dval self.block['data']['data_val'] = dval
return self._transfer_data() return self._transfer_data()
def frame_update(self, path_file: Path, makefile: bool = False) -> bool: 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:
""" 程序升级 """ 程序升级
注意: 在使用单板升级测试时, 需要关闭低电压检测功能, 否则无法启动升级流程; 注意: 在使用单板升级测试时, 需要关闭低电压检测功能, 否则无法启动升级流程;
@@ -233,7 +268,9 @@ class LaminaAdapter(DeviceSerial):
if not path_file.exists(): if not path_file.exists():
raise Exception("工程编译目标文件不存在.") raise Exception("工程编译目标文件不存在.")
if makefile and self.make_package is not None: if makefile and self.make_package is not None:
self.block['data']['file'] = self.make_package(path_file) 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: else:
self.block['data']['file'] = path_file.read_bytes() self.block['data']['file'] = path_file.read_bytes()
@@ -274,11 +311,15 @@ class LaminaAdapter(DeviceSerial):
return False return False
self.flag_print, self.retry, self.time_out = param_saved self.flag_print, self.retry, self.time_out = param_saved
time.sleep(6)
self.frame_read(0x100, 0x20)
return True return True
def GeneratePackage(type_dev: str, path_hex: Path, **kwargs) -> bytearray: def GeneratePackage(type_dev: str, path_hex: Path, **kwargs) -> bytearray:
""" 生成升级包 """ """ 生成升级包 """
upgrade_backup = kwargs['upgrade_backup'] if 'upgrade_backup' in kwargs.keys() else None
config = { config = {
'prod_type': [0x45, 0x00], # 产品类型 'prod_type': [0x45, 0x00], # 产品类型
'method_compress': False, # 文件压缩 'method_compress': False, # 文件压缩
@@ -307,6 +348,8 @@ def GeneratePackage(type_dev: str, path_hex: Path, **kwargs) -> bytearray:
config['file_length'] = ByteConv.conv_int_to_array(len(bin_main)) config['file_length'] = ByteConv.conv_int_to_array(len(bin_main))
config['hex_name'] = list(path_hex.name.encode())[:64] config['hex_name'] = list(path_hex.name.encode())[:64]
config['hex_name'] += [0] * (64 - len(config['hex_name'])) 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: if (main_header:=file_upgrade.build_header_new(config)) is None:
raise Exception("Header tag oversize. ") raise Exception("Header tag oversize. ")
@@ -326,6 +369,9 @@ def GeneratePackage(type_dev: str, path_hex: Path, **kwargs) -> bytearray:
if 'output_bin' in kwargs.keys() and kwargs['output_bin']: if 'output_bin' in kwargs.keys() and kwargs['output_bin']:
(path_hex / (path_hex.stem + '.bin')).write_bytes(bin_main) (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) return bytearray(Image)
def GenerateImage(type_dev: str, path_boot: Path, path_main: Path, path_back: Path, **kwargs) ->bytearray: def GenerateImage(type_dev: str, path_boot: Path, path_main: Path, path_back: Path, **kwargs) ->bytearray:
@@ -368,6 +414,7 @@ def GenerateImage(type_dev: str, path_boot: Path, path_main: Path, path_back: Pa
config['file_length'] = ByteConv.conv_int_to_array(len(bin_back)) config['file_length'] = ByteConv.conv_int_to_array(len(bin_back))
config['hex_name'] = list(path_back.name.encode())[:64] config['hex_name'] = list(path_back.name.encode())[:64]
config['hex_name'] += [0] * (64 - len(config['hex_name'])) 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: if (back_header:=file_upgrade.build_header_new(config)) is None:
raise Exception("Header tag oversize. ") raise Exception("Header tag oversize. ")
@@ -401,277 +448,6 @@ def GenerateImage(type_dev: str, path_boot: Path, path_main: Path, path_back: Pa
return bytearray(Image) return bytearray(Image)
def GeneratePackage_SLCP001_p4a0(path_hex: Path):
""" 叠光适配器-460平台版本 生成升级包 """
config = {
'prod_type': [0x45, 0x00], # 产品类型
'method_compress': False, # 文件压缩
'prog_id': list(b"SLCP001"), # 程序识别号
'prog_type': 'app', # 程序类型
'area_code': [0x00, 0x00], # 地区
}
bin_main = IntelHex.file_IntelHex_to_Bin(path_hex.read_text(), len_max=0x040000)
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 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 = 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 GeneratePackage_SLCP102_p460(path_hex: Path):
""" 叠光适配器-460平台版本 生成升级包 """
config = {
'prod_type': [0x45, 0x00], # 产品类型
'method_compress': False, # 文件压缩
'prog_id': list(b"SLCP102"), # 程序识别号
'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_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 = IntelHex.file_IntelHex_to_Bin(path_boot.read_text(), len_max=0x010000)
bin_main = IntelHex.file_IntelHex_to_Bin(path_main.read_text(), len_max=0x0CC000)
bin_back = IntelHex.file_IntelHex_to_Bin(path_back.read_text(), len_max=0x040000)
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] * 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 = 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
def GenerateImage_SLCP102_p460(path_boot: Path, path_main: Path, path_back: Path):
""" 叠光适配器-460平台版本 镜像生成 """
config = {
'prod_type': [0x45, 0x00], # 产品类型
'method_compress': False, # 文件压缩
'prog_id': list(b"SLCP102"), # 程序识别号
'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
def GeneratePackage_DLSY001_p460(path_hex: Path): def GeneratePackage_DLSY001_p460(path_hex: Path):
""" 叠光优化器-460平台版本 生成升级包 """ """ 叠光优化器-460平台版本 生成升级包 """

View File

@@ -21,6 +21,10 @@ ParamMap_LaminaController = {
# 7 - numberList # 7 - numberList
0x00: ["绝缘检测正电阻", 6], 0x00: ["绝缘检测正电阻", 6],
0x02: ["绝缘检测负电阻", 6], 0x02: ["绝缘检测负电阻", 6],
0x04: ["负对地绝缘检测电压(动作)" , 2, 100],
0x05: ["负对地绝缘检测电压(未动作)" , 2, 100],
0x06: ["正对地绝缘检测电压(动作)" , 2, 100],
0x07: ["正对地绝缘检测电压(未动作)" , 2, 100],
0x0B: ["事件标志", 1], 0x0B: ["事件标志", 1],
0x0C: ["告警字1", 1], 0x0C: ["告警字1", 1],
0x0D: ["告警字2", 1], 0x0D: ["告警字2", 1],
@@ -117,16 +121,17 @@ ParamMap_LaminaController = {
MemoryMap_280039 = { MemoryMap_280039 = {
'image_size': 2*0x030000, # 镜像文件大小 'image_size': 2*0x030000, # 镜像文件大小
'app_size': 2*0x004000, # 应用程序大小 'boot_size': 2*0x004000, # Boot程序大小
'boot_size': 2*0x014000, # Boot程序大小 'app_size': 2*0x014000, # 应用程序大小
'boot_addr': 0x000000, # Boot程序地址 'boot_addr': 0x000000, # Boot程序地址
'main_header': 0x006000, # main信息地址 'main_header': 0x006000, # main信息地址
'back_header': 0x007000, # back信息地址 'back_header': 0x007000, # back信息地址
'main_info': 0x088000, # main版本地址
'back_info': 0x007000, # back版本地址
'main_addr': 0x008000, # main程序地址 'main_addr': 0x008000, # main程序地址
'back_addr': 0x01C000, # back程序地址 'back_addr': 0x01C000, # back程序地址
'main_info': 0x088000, # main版本地址
'back_info': 0x09C000, # back版本地址
} }
class LaminaController: class LaminaController:
@@ -553,44 +558,49 @@ class LaminaController_new(DeviceSerial):
self.flag_print = False self.flag_print = False
self.retry = 3 self.retry = 3
self.time_out = 1.5 self.time_out = 1.5
try:
status = 'init' # 初始化
self.block['file_block_size'] = 240 self.block['file_block_size'] = 240
# 读取config
status = 'read_config' # 读取config
self.block['type'] = 'record_cfg' self.block['type'] = 'record_cfg'
self.block['step'] = 'start' self.block['step'] = 'start'
frame_master = function.protocols.make_frame_modbus(self.block)
ret, pbar = True, None ret, pbar = True, None
frame_data_cfg = [] frame_data_cfg = []
while ret: while ret:
if ret := self.__transfer_data(frame_master): if ret := self._transfer_data():
frame_data_cfg.append(self.output['record']) frame_data_cfg.append(self.output['record'])
if self.output['record']['seq'] == 0: if self.output['record']['seq'] == 0:
pbar = tqdm(total=self.output['record']['total'] + 1, desc="Record Config Reading") pbar = tqdm(total=self.output['record']['total'] + 1, desc="Record Config Reading")
self.block['step'] = 'next' self.block['step'] = 'next'
frame_master = function.protocols.make_frame_modbus(self.block)
elif (self.output['record']['seq']) >= self.output['record']['total']: elif (self.output['record']['seq']) >= self.output['record']['total']:
ret = False ret = False
pbar and pbar.update() pbar and pbar.update()
pbar and pbar.close() pbar and pbar.close()
# 读取data status = 'read_data' # 读取data
self.block['type'] = 'record_data' self.block['type'] = 'record_data'
self.block['step'] = 'start' self.block['step'] = 'start'
frame_master = function.protocols.make_frame_modbus(self.block)
ret, pbar = True, None ret, pbar = True, None
frame_data_record = [] frame_data_record = []
while ret: while ret:
if ret := self.__transfer_data(frame_master): if ret := self._transfer_data():
frame_data_record.append(self.output['record']) frame_data_record.append(self.output['record'])
if self.output['record']['seq'] == 0: if self.output['record']['seq'] == 0:
pbar = tqdm(total=self.output['record']['total'] + 1, desc="Record Data Reading") pbar = tqdm(total=self.output['record']['total'] + 1, desc="Record Data Reading")
self.block['step'] = 'next' self.block['step'] = 'next'
frame_master = function.protocols.make_frame_modbus(self.block)
elif (self.output['record']['seq']) >= self.output['record']['total']: elif (self.output['record']['seq']) >= self.output['record']['total']:
ret = False ret = False
pbar and pbar.update() pbar and pbar.update()
pbar and pbar.close() pbar and pbar.close()
except Exception as ex:
""" 通用异常处理 """
self.flag_print, self.retry, self.time_out = param_saved
report = f'Record Fail: {status}'
report += f', Frame in {self.block["record"]["seq"]}' if status == 'read_config' or status == 'read_data' 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 self.flag_print, self.retry, self.time_out = param_saved
if (len(frame_data_record) != 32) or (len(frame_data_cfg) != 3): if (len(frame_data_record) != 32) or (len(frame_data_cfg) != 3):
@@ -602,7 +612,7 @@ class LaminaController_new(DeviceSerial):
config_record = {'LinePos': []} config_record = {'LinePos': []}
pos = 0 pos = 0
if data_cfg_bare[pos+1] != 0xF1 or data_cfg_bare[pos] != 0xF1: if data_cfg_bare[pos+1] != 0xF1 or data_cfg_bare[pos] != 0xF1:
Warning("config 配置文件格式异常") raise Warning("config 配置文件格式异常")
pos += 2 pos += 2
config_record['LinePos'].append(pos) config_record['LinePos'].append(pos)
len_faultword = (data_cfg_bare[3] * 0x100 + data_cfg_bare[2]) len_faultword = (data_cfg_bare[3] * 0x100 + data_cfg_bare[2])
@@ -621,7 +631,7 @@ class LaminaController_new(DeviceSerial):
config_record['Standard'] = data_cfg_bare[pos+1] * 0x100 + data_cfg_bare[pos] config_record['Standard'] = data_cfg_bare[pos+1] * 0x100 + data_cfg_bare[pos]
pos += 2 pos += 2
if data_cfg_bare[pos+1] != 0xF2 or data_cfg_bare[pos] != 0xF2: if data_cfg_bare[pos+1] != 0xF2 or data_cfg_bare[pos] != 0xF2:
Warning("config 配置文件格式异常") raise Warning("config 配置文件格式异常")
pos += 2 pos += 2
config_record['LinePos'].append(pos) config_record['LinePos'].append(pos)
config_record['ChannelNum'] = data_cfg_bare[pos+1] * 0x100 + data_cfg_bare[pos] config_record['ChannelNum'] = data_cfg_bare[pos+1] * 0x100 + data_cfg_bare[pos]
@@ -631,7 +641,7 @@ class LaminaController_new(DeviceSerial):
config_record['ChNum_Dgt'] = data_cfg_bare[pos+1] * 0x100 + data_cfg_bare[pos] config_record['ChNum_Dgt'] = data_cfg_bare[pos+1] * 0x100 + data_cfg_bare[pos]
pos += 2 pos += 2
if data_cfg_bare[pos+1] != 0xF3 or data_cfg_bare[pos] != 0xF3: if data_cfg_bare[pos+1] != 0xF3 or data_cfg_bare[pos] != 0xF3:
Warning("config 配置文件格式异常") raise Warning("config 配置文件格式异常")
pos += 2 pos += 2
config_record['LinePos'].append(pos) config_record['LinePos'].append(pos)
config_record['ChannelDescription'] = [] config_record['ChannelDescription'] = []
@@ -648,7 +658,7 @@ class LaminaController_new(DeviceSerial):
config_record['ChannelDescription'].append(bytearray(ch_desc).decode()) config_record['ChannelDescription'].append(bytearray(ch_desc).decode())
config_record['ChannelCoefficient'].append(ch_coe) config_record['ChannelCoefficient'].append(ch_coe)
if data_cfg_bare[pos+1] != 0xF4 or data_cfg_bare[pos] != 0xF4: if data_cfg_bare[pos+1] != 0xF4 or data_cfg_bare[pos] != 0xF4:
Warning("config 配置文件格式异常") raise Warning("config 配置文件格式异常")
pos += 2 pos += 2
config_record['LinePos'].append(pos) config_record['LinePos'].append(pos)
config_record['SysFreq'] = data_cfg_bare[pos+1] * 0x100 + data_cfg_bare[pos] config_record['SysFreq'] = data_cfg_bare[pos+1] * 0x100 + data_cfg_bare[pos]
@@ -664,7 +674,7 @@ class LaminaController_new(DeviceSerial):
config_record['TimeFactor'] = data_cfg_bare[pos+1] * 0x100 + data_cfg_bare[pos] config_record['TimeFactor'] = data_cfg_bare[pos+1] * 0x100 + data_cfg_bare[pos]
pos += 2 pos += 2
if data_cfg_bare[pos+1] != 0xF5 or data_cfg_bare[pos] != 0xF5: if data_cfg_bare[pos+1] != 0xF5 or data_cfg_bare[pos] != 0xF5:
Warning("config 配置文件格式异常") raise Warning("config 配置文件格式异常")
pos += 2 pos += 2
config_record['LinePos'].append(pos) config_record['LinePos'].append(pos)
time_stamp = { time_stamp = {
@@ -726,56 +736,57 @@ class LaminaController_new(DeviceSerial):
return True return True
def frame_update(self, path_bin: Path, makefile: bool = False) -> bool: def frame_update(self, path_file: Path, makefile: bool = False) -> bool:
""" 程序升级 """ 程序升级
注意: 在使用单板升级测试时, 需要关闭低电压检测功能, 否则无法启动升级流程; 注意: 在使用单板升级测试时, 需要关闭低电压检测功能, 否则无法启动升级流程;
""" """
if makefile:
self.block['file'], file_bin = GeneratePackage_DLSP001_p280039(path_bin)
else:
self.block['file'] = path_bin.read_bytes()
self.block['header_offset'] = 184
self.block['type'] = 'update'
param_saved = self.flag_print, self.retry, self.time_out param_saved = self.flag_print, self.retry, self.time_out
self.retry = 5 self.retry = 3
self.time_out = 0.5 self.time_out = 0.5
self.flag_print = False 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['file'] = self.make_package(path_file)
else:
self.block['file'] = path_file.read_bytes()
# 启动帧 self.block['type'] = 'update'
self.block['header_offset'] = 184
status = 'start' # 启动帧
self.block['step'] = 'start' self.block['step'] = 'start'
self.block['index'] = 0 self.block['index'] = 0
frame_master = function.protocols.make_frame_modbus(self.block) assert self._transfer_data()
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["data"]['file_block_size'] = self.output['upgrade']['length']
self.block['file_block_size'] = self.output['upgrade']['length']
# 避免接收到延迟返回报文 # 避免接收到延迟返回报文
time.sleep(self.time_out) time.sleep(self.time_out)
# 文件传输 status = 'trans' # 文件传输
self.retry = 3 self.retry = 3
self.time_out = 1.5 self.time_out = 1.5
self.block["data"]['step'] = 'trans' self.block['step'] = 'trans'
self.block['index'] = 0 self.block['index'] = 0
frame_total = ceil((len(self.block["data"]['file']) - self.block['header_offset']) / self.block["data"]['file_block_size']) frame_total = ceil((len(self.block['file']) - self.block['header_offset']) / self.block['file_block_size'])
for idx in tqdm(range(frame_total), desc="File Transmitting"): for idx in tqdm(range(frame_total), desc="File Transmitting"):
self.block["data"]['index'] = idx self.block['index'] = idx
frame_master = function.protocols.make_frame_modbus(self.block) assert self._transfer_data()
if not self.__transfer_data(frame_master):
self.flag_print, self.retry, self.time_out = param_saved
print(f'Upgrade Fail: trans data in {idx}')
return False
# 结束升级 status = 'end' # 结束升级
self.time_out = 1 self.time_out = 1
self.block["data"]['step'] = 'end' self.block['step'] = 'end'
self.block["data"]['index'] += 1 self.block['index'] += 1
frame_master = function.protocols.make_frame_modbus(self.block) assert self._transfer_data()
if not self.__transfer_data(frame_master): except Exception as ex:
""" 通用异常处理 """
self.flag_print, self.retry, self.time_out = param_saved self.flag_print, self.retry, self.time_out = param_saved
print(f'Upgrade Fail: end') report = f'Upgrade Fail: {status}'
report += f', Frame in {self.block["index"]}' if status == 'trans' else ''
report += f'\n Error by {ex}' if not isinstance(ex, AssertionError) else ''
print(report)
return False return False
self.flag_print, self.retry, self.time_out = param_saved self.flag_print, self.retry, self.time_out = param_saved
@@ -814,7 +825,7 @@ def GeneratePackage(type_dev: str, path_hex: Path, **kwargs) -> bytearray:
print("Package generated successfully.") print("Package generated successfully.")
print(f"File name: {path_hex.name}") print(f"File name: {path_hex.name}")
print(f"File Info:") print(f"File Info:")
print(f"\t header_length={len(main_header)}, bin_length={len(bin_main)}[{hex(len(bin_main))}]") print(f"\t header_length={len(main_header)}, bin_length={len(bin_main)}[{hex(len(bin_main) // 2)}]")
# 组装镜像 # 组装镜像
Image = [0xFF] * (len(main_header) + len(encrypt_main)) Image = [0xFF] * (len(main_header) + len(encrypt_main))
@@ -883,9 +894,9 @@ def GenerateImage(type_dev: str, path_boot: Path, path_main: Path, path_back: Pa
# Log打印 # Log打印
print("Merge Image generated successfully.") print("Merge Image generated successfully.")
print(f"Main File:") print(f"Main File:")
print(f"\t header_length={len(main_header)}, bin_length={len(bin_main)}[{hex(len(bin_main))}]") print(f"\t header_length={len(main_header)}, bin_length={len(bin_main)}[{hex(len(bin_main) // 2)}]")
print(f"Back File:") print(f"Back File:")
print(f"\t header_length={len(back_header)}, bin_length={len(bin_back)}[{hex(len(bin_back))}]") print(f"\t header_length={len(back_header)}, bin_length={len(bin_back)}[{hex(len(bin_back) // 2)}]")
# 额外文件 # 额外文件
if 'output_header' in kwargs.keys() and kwargs['output_header']: if 'output_header' in kwargs.keys() and kwargs['output_header']:

View File

@@ -14,9 +14,11 @@ modbus_map = {
0x01: ["Hex示例", 1], 0x01: ["Hex示例", 1],
0x02: ["Int示例", 2], 0x02: ["Int示例", 2],
0x03: ["Int32示例", 3], 0x03: ["Int32示例", 3],
0x04: ["str示例", 4, 16], 0x05: ["str示例", 4, 16],
0x10: ["addr示例", 5, 6], 0x15: ["addr示例", 5, 6],
0x20: ["Float示例", 6], 0x1B: ["Float示例", 6],
0x1D: ["numberList示例", 3],
0x20: ["callback示例", 16],
} }
frame_modbus = { frame_modbus = {
@@ -29,6 +31,7 @@ frame_modbus = {
0x04: [5, 1, 2], 0x04: [5, 1, 2],
0x06: [8, 0], 0x06: [8, 0],
0x07: [8, 3, 2, {0x01: 2, 0x02: 0, 0x03: 0}], 0x07: [8, 3, 2, {0x01: 2, 0x02: 0, 0x03: 0}],
0x08: [5, 3, 2, {0x01: 1, 0x02: 0x22, 0x03: 1}],
0x10: [8, 0], 0x10: [8, 0],
0x11: [11, 2, 7] 0x11: [11, 2, 7]
} }
@@ -94,6 +97,19 @@ def make_frame_modbus(block:dict) -> bytearray:
frame.append(block['file_block_size'] // 256) frame.append(block['file_block_size'] // 256)
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: else:
""" 数据读取系列报文 """ """ 数据读取系列报文 """
data_addr = block['data_addr'] data_addr = block['data_addr']
@@ -131,7 +147,7 @@ def make_frame_modbus(block:dict) -> bytearray:
elif block['type'] == "write_str": elif block['type'] == "write_str":
frame.append(0x10) frame.append(0x10)
data_len = len(block['data_val']) data_len = len(block['data_val'])
item_len = 2 * block['data_define'][data_addr][2] 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'] data_val = block['data_val']
if data_len > item_len: if data_len > item_len:
raise Exception("Modbus data len oversize.") raise Exception("Modbus data len oversize.")
@@ -217,6 +233,12 @@ def check_frame_modbus(frame:bytes, block:dict) -> dict:
else: else:
raise Exception("Frame read data length error.") raise Exception("Frame read data length error.")
output['result'] = True 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: elif output['code_func'] == 0x06:
""" 单个数据写入帧 """ """ 单个数据写入帧 """
output['result'] = True output['result'] = True
@@ -374,6 +396,32 @@ def display_data(address: int, data: bytes, modbus_map: dict=modbus_map) -> dict
""" 正序数值表示 """ """ 正序数值表示 """
data_len = current_map[2] data_len = current_map[2]
item = list(map(lambda x: x, data[:2 * data_len])) 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 output_data[idx] = data_label, item
idx += data_len idx += data_len
@@ -383,14 +431,20 @@ def display_data(address: int, data: bytes, modbus_map: dict=modbus_map) -> dict
def print_display(output_data: dict): def print_display(output_data: dict):
""" 格式化表示输出数据 """ """ 格式化表示输出数据 """
bank_chars = ' \t\000'
print("Parse Result:") print("Parse Result:")
label_len_max = max(map(lambda x: len(x[0]), output_data.values())) 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())) data_len_max = max(map(lambda x: len(str(x[1])), output_data.values()))
for key, value in output_data.items(): for key, value in output_data.items():
label = value[0] match value:
data = "-".join(map(str, value[1])) if type(value) == list else value[1] 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}}") 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

@@ -1,7 +1,7 @@
def trans_list_to_str(data: list) -> str: def trans_list_to_str(data: list, word_len=2, prefix='') -> str:
""" 标准串口字符串表示 """ """ 标准串口字符串表示 """
func_trans = lambda x: ('00' + hex(x % 256)[2:])[-2:].upper() func_trans_word = lambda x: prefix + ('0' * word_len + hex(x % (0x10 ** word_len))[2:])[-word_len:].upper()
return " ".join(map(func_trans, data)) return " ".join(map(func_trans_word, data))
def trans_str_to_list(data: str) -> list: def trans_str_to_list(data: str) -> list:
@@ -27,3 +27,13 @@ def conv_int_to_array(num: int, big_end=False):
def display_hex(data:int, length:int=2) -> str: def display_hex(data:int, length:int=2) -> str:
""" Hex字符固定最小长度表示 """ """ Hex字符固定最小长度表示 """
return f"0x{data:0{length}X}" 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,158 +1,8 @@
from pathlib import Path from pathlib import Path
from device.LaminaAdapter import LaminaAdapter from device.LaminaAdapter import LaminaAdapter
from device.LaminaAdapter import GenerateImage_SLCP001_p4a0, GeneratePackage_SLCP001_p4a0
from device.LaminaAdapter import GenerateImage_SLCP101_p460, GeneratePackage_SLCP101_p460
from device.LaminaAdapter import GenerateImage_SLCP102_p460, GeneratePackage_SLCP102_p460
from device.LaminaAdapter import GenerateImage_DLSY001_p460, GeneratePackage_DLSY001_p460 from device.LaminaAdapter import GenerateImage_DLSY001_p460, GeneratePackage_DLSY001_p460
def Process0(path_boot: Path, path_project: Path):
""" 适配器-SLCP001 镜像生成流程 """
root_boot = path_boot
root_main = path_project
result = root_main
# 正常启动镜像
hex_boot = root_boot / "bootloader.hex"
hex_main = root_main / "lamina_adapter.hex"
hex_back = root_main / "lamina_adapter_back.hex"
hex_update = hex_main
if (not hex_boot.exists()) or (not hex_main.exists()) or (not hex_back.exists()):
raise Exception("缺失必要程序文件")
file_image = result / f'{hex_main.stem}_ROM.bin'
file_main_header = result / 'SLCP001_main.header'
file_back_header = result / 'SLCP001_back.header'
file_package = result / f'{hex_update.stem}.dat'
file_bin = result / f'{hex_update.stem}.bin'
data_bins = GenerateImage_SLCP001_p4a0(hex_boot, hex_main, hex_back)
data_package, data_bin = GeneratePackage_SLCP001_p4a0(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[0x0FC018: 0x0FC01A] = [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[0x0FE018: 0x0FE01A] = [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[0x0FC018: 0x0FC01A] = [0x00, 0x01]
data_image[0x0FE018: 0x0FE01A] = [0x00, 0x01]
file_image3.write_bytes(data_image)
def Process1(path_boot: Path, path_project: Path):
""" 适配器-SLCP101 镜像生成流程 """
root_boot = path_boot
root_main = path_project
result = root_main
# 正常启动镜像
hex_boot = root_boot / "bootloader.hex"
hex_main = root_main / "lamina_adapter.hex"
hex_back = root_main / "lamina_adapter_back.hex"
hex_update = hex_main
if (not hex_boot.exists()) or (not hex_main.exists()) or (not hex_back.exists()):
raise Exception("缺失必要程序文件")
file_image = result / f'{hex_main.stem}_ROM.bin'
file_main_header = result / 'SLCP101_main.header'
file_back_header = result / 'SLCP101_back.header'
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_bin = 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_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 Process1_v2(path_boot: Path, path_project: Path):
""" 适配器-SLCP102 镜像生成流程 """
root_boot = path_boot
root_main = path_project
result = root_main
# 正常启动镜像
hex_boot = root_boot / "bootloader.hex"
hex_main = root_main / "lamina_adapter.hex"
hex_back = root_main / "lamina_adapter_back.hex"
hex_update = hex_main
if (not hex_boot.exists()) or (not hex_main.exists()) or (not hex_back.exists()):
raise Exception("缺失必要程序文件")
file_image = result / f'{hex_main.stem}_ROM.bin'
file_main_header = result / 'SLCP102_main.header'
file_back_header = result / 'SLCP102_back.header'
file_package = result / f'{hex_update.stem}.dat'
file_bin = result / f'{hex_update.stem}.bin'
data_bins = GenerateImage_SLCP102_p460(hex_boot, hex_main, hex_back)
data_package, data_bin = GeneratePackage_SLCP102_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 Process2(): def Process2():
""" 优化器-DLSY001 镜像生成流程 """ """ 优化器-DLSY001 镜像生成流程 """
root = Path(r"D:\WorkingProject\LightStackOptimizer\software\lamina_optimizer\lamina_optimizer\Debug") root = Path(r"D:\WorkingProject\LightStackOptimizer\software\lamina_optimizer\lamina_optimizer\Debug")
@@ -199,46 +49,67 @@ def Process2():
file_image3.write_bytes(data_image) file_image3.write_bytes(data_image)
def Process(type_dev: str, path_boot: Path, path_project: Path): def Process(type_dev: str, path_boot: Path, path_project: Path, makePackages: bool =True, makeImage: bool = True):
""" 镜像生成流程 """ """ 镜像生成流程 """
root_boot = path_boot root_boot = path_boot
root_main = path_project root_main = path_project
result = root_main result = root_main
# 程序文件名适配
if type_dev[:4] == 'DLSY':
program_name = 'lamina_optimizer'
else:
program_name = 'lamina_adapter'
# 正常启动镜像 # 正常启动镜像
hex_boot = root_boot / "bootloader.hex" hex_boot = root_boot / "bootloader.hex"
hex_main = root_main / "lamina_adapter.hex" hex_main = root_main / f"{program_name}.hex"
hex_back = root_main / "lamina_adapter_back.hex" hex_back = root_main / f"{program_name}_back.hex"
hex_update = hex_main
if (not hex_boot.exists()) or (not hex_main.exists()) or (not hex_back.exists()):
raise Exception("缺失必要程序文件")
file_image = result / f'{hex_main.stem}_ROM.bin' file_image = result / f'{hex_main.stem}_ROM.bin'
file_package = result / f'{hex_update.stem}.dat' 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) dev_lamina = LaminaAdapter(None, type_dev=type_dev)
data_package = dev_lamina.make_package(hex_update) if makePackages:
data_image = dev_lamina.make_image(hex_boot, hex_main, hex_back, output_header=True, output_bin=True) if hex_main.exists():
data_package = dev_lamina.make_package(hex_main)
file_image.write_bytes(data_image)
file_package.write_bytes(data_package) 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错误 # 异常镜像-主分区md5错误
file_image1 = result / f'{file_image.stem}_b1.bin' file_image1 = result / f'{file_image.stem}_b1.bin'
data_image_copy = data_image data_image_copy = data_image
if type_dev == 'SLCP001':
data_image_copy[0x0FC018: 0x0FC01A] = [0x00, 0x01]
else:
data_image_copy[0x054018: 0x05401A] = [0x00, 0x01] data_image_copy[0x054018: 0x05401A] = [0x00, 0x01]
file_image1.write_bytes(data_image_copy) file_image1.write_bytes(data_image_copy)
# 异常镜像-备份分区md5错误 # 异常镜像-备份分区md5错误
file_image2 = result / f'{file_image.stem}_b2.bin' file_image2 = result / f'{file_image.stem}_b2.bin'
data_image_copy = data_image data_image_copy = data_image
if type_dev == 'SLCP001':
data_image_copy[0x0FE018: 0x0FE01A] = [0x00, 0x01]
else:
data_image_copy[0x056018: 0x05601A] = [0x00, 0x01] data_image_copy[0x056018: 0x05601A] = [0x00, 0x01]
file_image2.write_bytes(data_image_copy) file_image2.write_bytes(data_image_copy)
# 异常镜像-双分区md5错误 # 异常镜像-双分区md5错误
file_image3 = result / f'{file_image.stem}_b3.bin' file_image3 = result / f'{file_image.stem}_b3.bin'
data_image_copy = data_image 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[0x054018: 0x05401A] = [0x00, 0x01]
data_image_copy[0x056018: 0x05601A] = [0x00, 0x01] data_image_copy[0x056018: 0x05601A] = [0x00, 0x01]
file_image3.write_bytes(data_image_copy) file_image3.write_bytes(data_image_copy)
@@ -247,14 +118,24 @@ def Process(type_dev: str, path_boot: Path, path_project: Path):
if __name__ == "__main__": if __name__ == "__main__":
path_boot1 = Path(r"D:\WorkingProject\LightStackAdapter\software\umon\4A0-PROJ_STACKLIGHT_PARALLEL_ADAPTOR") 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_boot2 = Path(r"D:\WorkingProject\LightStackAdapter\software\umon\460-PROJ_STACKLIGHT_PARALLEL_ADAPTOR")
path_main = Path(r"D:\WorkingProject\LightStackAdapter\software\lamina_adapter\lamina_adapter\Debug") 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 # Process0(path_boot1, path_main) # 适配器SLCP001
Process1(path_boot2, path_main) # 适配器SLCP101 # Process1(path_boot2, path_main) # 适配器SLCP101
# Process1_v2(path_boot2, path_main) # 适配器SLCP102 # Process1_v2(path_boot2, path_main) # 适配器SLCP102
# Process2() # Process2()
path_boot1 = Path(r"resource/460-PROJ_STACKLIGHT_OPTIMIZER") Process(*mode_config['SLCP101'])
path_boot2 = Path(r"resource/460-PROJ_STACKLIGHT_PARALLEL_ADAPTOR")
path_project = Path(r"resource\SLCP101")
Process('SLCP101', path_boot2, path_project)

View File

@@ -1,5 +1,5 @@
import time import time
from function.tools.ByteConv import display_hex from device.tools.ByteConv import display_hex
from device.LaminaController import LaminaController from device.LaminaController import LaminaController
from device.LaminaController import ParamMap_LaminaController from device.LaminaController import ParamMap_LaminaController

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()