mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2025-12-06 07:49:52 +03:00
optimized scripts
This commit is contained in:
@@ -1,98 +1,111 @@
|
|||||||
import fileinput
|
import fileinput
|
||||||
import csv
|
import csv
|
||||||
|
import sys
|
||||||
from itertools import groupby
|
from itertools import groupby
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
#
|
#
|
||||||
# This is used to build the contents of the `Modbus-Entity-Registers.md` file used in the emsesp.org documentation.
|
# This is used to build the contents of the `Modbus-Entity-Registers.md` file used in the emsesp.org documentation.
|
||||||
#
|
#
|
||||||
|
|
||||||
# static data
|
|
||||||
|
|
||||||
tag_to_tagtype = {
|
def get_tag_type(modbus_block):
|
||||||
-1: "TAG_TYPE_NONE",
|
"""Convert modbus block number to tag type using lookup."""
|
||||||
0: "DEVICE_DATA",
|
block = int(modbus_block)
|
||||||
1: "HC",
|
|
||||||
2: "HC",
|
|
||||||
3: "HC",
|
|
||||||
4: "HC",
|
|
||||||
5: "HC",
|
|
||||||
6: "HC",
|
|
||||||
7: "HC",
|
|
||||||
8: "HC",
|
|
||||||
9: "DHW",
|
|
||||||
10: "DHW",
|
|
||||||
11: "DHW",
|
|
||||||
12: "DHW",
|
|
||||||
13: "DHW",
|
|
||||||
14: "DHW",
|
|
||||||
15: "DHW",
|
|
||||||
16: "DHW",
|
|
||||||
17: "DHW",
|
|
||||||
18: "DHW",
|
|
||||||
19: "AHS",
|
|
||||||
20: "HS",
|
|
||||||
21: "HS",
|
|
||||||
22: "HS",
|
|
||||||
23: "HS",
|
|
||||||
24: "HS",
|
|
||||||
25: "HS",
|
|
||||||
26: "HS",
|
|
||||||
27: "HS",
|
|
||||||
28: "HS",
|
|
||||||
29: "HS",
|
|
||||||
30: "HS",
|
|
||||||
31: "HS",
|
|
||||||
32: "HS",
|
|
||||||
33: "HS",
|
|
||||||
34: "HS",
|
|
||||||
35: "HS"
|
|
||||||
}
|
|
||||||
|
|
||||||
# read entities csv from stdin
|
# Handle special cases first
|
||||||
|
if block == -1:
|
||||||
|
return "TAG_TYPE_NONE"
|
||||||
|
if block == 0:
|
||||||
|
return "DEVICE_DATA"
|
||||||
|
if block == 19:
|
||||||
|
return "AHS"
|
||||||
|
|
||||||
|
# Use ranges for efficiency
|
||||||
|
if 1 <= block <= 8:
|
||||||
|
return "HC"
|
||||||
|
if 9 <= block <= 18:
|
||||||
|
return "DHW"
|
||||||
|
if 20 <= block <= 35:
|
||||||
|
return "HS"
|
||||||
|
if 36 <= block <= 50 or block == 52:
|
||||||
|
return "SRC"
|
||||||
|
|
||||||
|
# Default fallback
|
||||||
|
return "UNKNOWN"
|
||||||
|
|
||||||
|
|
||||||
|
def read_entities():
|
||||||
|
"""Read and parse CSV entities from stdin with error handling."""
|
||||||
entities = []
|
entities = []
|
||||||
|
|
||||||
|
try:
|
||||||
with fileinput.input() as f_input:
|
with fileinput.input() as f_input:
|
||||||
entities_reader = csv.reader(f_input, delimiter=',', quotechar='"')
|
entities_reader = csv.reader(f_input, delimiter=',', quotechar='"')
|
||||||
headers = next(entities_reader)
|
headers = next(entities_reader)
|
||||||
|
|
||||||
for row in entities_reader:
|
# Validate required headers
|
||||||
entity = {}
|
required_headers = {'device name', 'device type', 'shortname', 'fullname',
|
||||||
for i, val in enumerate(row):
|
'type [options...] \\| (min/max)', 'uom', 'writeable',
|
||||||
entity[headers[i]] = val
|
'modbus block', 'modbus offset', 'modbus count', 'modbus scale factor'}
|
||||||
|
missing_headers = required_headers - set(headers)
|
||||||
|
if missing_headers:
|
||||||
|
raise ValueError(
|
||||||
|
f"Missing required headers: {missing_headers}")
|
||||||
|
|
||||||
|
for row_num, row in enumerate(entities_reader, start=2):
|
||||||
|
if len(row) != len(headers):
|
||||||
|
print(
|
||||||
|
f"Warning: Row {row_num} has {len(row)} columns, expected {len(headers)}", file=sys.stderr)
|
||||||
|
continue
|
||||||
|
|
||||||
|
entity = dict(zip(headers, row))
|
||||||
entities.append(entity)
|
entities.append(entity)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error reading CSV data: {e}", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
def device_name_key(e): return e["device name"]
|
return entities
|
||||||
|
|
||||||
|
|
||||||
def device_type_key(e): return e["device type"]
|
def group_entities_by_device_type(entities):
|
||||||
|
"""Group entities by device type efficiently using defaultdict."""
|
||||||
|
grouped = defaultdict(list)
|
||||||
|
for entity in entities:
|
||||||
|
grouped[entity["device type"]].append(entity)
|
||||||
|
return grouped
|
||||||
|
|
||||||
|
|
||||||
def grouped_by(list, key): return groupby(sorted(list, key=key), key)
|
def print_device_entities(device_name, device_entities):
|
||||||
|
"""Print device entities table using f-strings for better performance."""
|
||||||
|
print(f"### {device_name}")
|
||||||
# entities_by_device_type = grouped_by(entities, device_type_key)
|
|
||||||
|
|
||||||
|
|
||||||
def printDeviceEntities(device_name, device_entities):
|
|
||||||
print("### " + device_name)
|
|
||||||
print("| shortname | fullname | type | uom | writeable | tag type | register offset | register count | scale factor |")
|
print("| shortname | fullname | type | uom | writeable | tag type | register offset | register count | scale factor |")
|
||||||
print("|-|-|-|-|-|-|-|-|-|")
|
print("|-|-|-|-|-|-|-|-|-|")
|
||||||
for de in device_entities:
|
|
||||||
print("| " + de["shortname"] + " | " + de["fullname"] + " | " + de["type [options...] \\| (min/max)"] + " | " + de["uom"] + " | " + de["writeable"] +
|
for entity in device_entities:
|
||||||
" | " + tag_to_tagtype[int(de["modbus block"])] + " | " + de["modbus offset"] + " | " + de["modbus count"] + " | " + de["modbus scale factor"] + " | ")
|
tag_type = get_tag_type(entity["modbus block"])
|
||||||
|
print(f"| {entity['shortname']} | {entity['fullname']} | {entity['type [options...] \\| (min/max)']} | "
|
||||||
|
f"{entity['uom']} | {entity['writeable']} | {tag_type} | {entity['modbus offset']} | "
|
||||||
|
f"{entity['modbus count']} | {entity['modbus scale factor']} |")
|
||||||
print()
|
print()
|
||||||
|
|
||||||
|
|
||||||
def printDeviceTypeDevices(device_type, devices):
|
def print_device_type_devices(device_type, devices):
|
||||||
print("## Devices of type *" + device_type + "*")
|
"""Print all devices of a specific type."""
|
||||||
for device_name, device_entities in grouped_by(devices, device_name_key):
|
print(f"## Devices of type *{device_type}*")
|
||||||
printDeviceEntities(device_name, device_entities)
|
|
||||||
|
# Group devices by name
|
||||||
|
device_groups = defaultdict(list)
|
||||||
|
for device in devices:
|
||||||
|
device_groups[device["device name"]].append(device)
|
||||||
|
|
||||||
|
for device_name, device_entities in device_groups.items():
|
||||||
|
print_device_entities(device_name, device_entities)
|
||||||
|
|
||||||
|
|
||||||
# write header
|
def print_header():
|
||||||
|
"""Print the markdown document header."""
|
||||||
print("<!-- Use full browser width for this page, the tables are wide -->")
|
print("<!-- Use full browser width for this page, the tables are wide -->")
|
||||||
print("<style>")
|
print("<style>")
|
||||||
print(".md-grid {")
|
print(".md-grid {")
|
||||||
@@ -107,16 +120,26 @@ print()
|
|||||||
print(" This file has been auto-generated. Do not modify.")
|
print(" This file has been auto-generated. Do not modify.")
|
||||||
print()
|
print()
|
||||||
|
|
||||||
for device_type, devices in grouped_by(entities, device_type_key):
|
|
||||||
printDeviceTypeDevices(device_type, devices)
|
|
||||||
|
|
||||||
# def printGroupedData(groupedData):
|
def main():
|
||||||
# for k, v in groupedData:
|
"""Main function to process entities and generate documentation."""
|
||||||
# # print("Group {} {}".format(k, list(v)))
|
# Read entities from stdin
|
||||||
# print(k)
|
entities = read_entities()
|
||||||
|
|
||||||
|
if not entities:
|
||||||
|
print("No entities found in input data.", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Print header
|
||||||
|
print_header()
|
||||||
|
|
||||||
|
# Group entities by device type and process
|
||||||
|
grouped_entities = group_entities_by_device_type(entities)
|
||||||
|
|
||||||
|
# Print documentation for each device type
|
||||||
|
for device_type, devices in grouped_entities.items():
|
||||||
|
print_device_type_devices(device_type, devices)
|
||||||
|
|
||||||
|
|
||||||
# printGroupedData(grouped_entities)
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
# for e in entities:
|
|
||||||
# print(e)
|
|
||||||
|
|||||||
@@ -158,15 +158,15 @@ cpp_entry_template = Template(
|
|||||||
# read translations
|
# read translations
|
||||||
listNames = {}
|
listNames = {}
|
||||||
transre = re.compile(r'^MAKE_TRANSLATION\(([^,\s]+)\s*,\s*\"([^\"]+)\"')
|
transre = re.compile(r'^MAKE_TRANSLATION\(([^,\s]+)\s*,\s*\"([^\"]+)\"')
|
||||||
transf = open('./src/core/locale_translations.h', 'r')
|
try:
|
||||||
while True:
|
with open('./src/core/locale_translations.h', 'r') as transf:
|
||||||
line = transf.readline()
|
for line in transf:
|
||||||
if not line:
|
|
||||||
break
|
|
||||||
m = transre.match(line)
|
m = transre.match(line)
|
||||||
if m is not None:
|
if m is not None:
|
||||||
listNames[m.group(2)] = m.group(1)
|
listNames[m.group(2)] = m.group(1)
|
||||||
transf.close()
|
except FileNotFoundError:
|
||||||
|
# Handle case where file doesn't exist
|
||||||
|
pass
|
||||||
|
|
||||||
entities = []
|
entities = []
|
||||||
|
|
||||||
@@ -175,10 +175,8 @@ with fileinput.input() as f_input:
|
|||||||
headers = next(entities_reader)
|
headers = next(entities_reader)
|
||||||
|
|
||||||
for row in entities_reader:
|
for row in entities_reader:
|
||||||
entity = {}
|
# Use dict comprehension for better performance
|
||||||
for i, val in enumerate(row):
|
entity = {headers[i]: val for i, val in enumerate(row)}
|
||||||
# print(headers[i] + ": " + val)
|
|
||||||
entity[headers[i]] = val
|
|
||||||
entities.append(entity)
|
entities.append(entity)
|
||||||
|
|
||||||
# print(json.dumps(entities, indent=" "))
|
# print(json.dumps(entities, indent=" "))
|
||||||
@@ -234,30 +232,28 @@ for entity in entities:
|
|||||||
|
|
||||||
for device_type_name, device_type in device_types.items():
|
for device_type_name, device_type in device_types.items():
|
||||||
for tag, entities in device_type.items():
|
for tag, entities in device_type.items():
|
||||||
total_registers = 0
|
# Pre-calculate all register info to avoid repeated int() conversions
|
||||||
|
register_info = []
|
||||||
next_free_offset = 0
|
next_free_offset = 0
|
||||||
|
|
||||||
for entity_name, modbus_info in entities.items():
|
for entity_name, modbus_info in entities.items():
|
||||||
register_offset = int(modbus_info['modbus offset'])
|
register_offset = int(modbus_info['modbus offset'])
|
||||||
register_count = int(modbus_info['modbus count'])
|
register_count = int(modbus_info['modbus count'])
|
||||||
total_registers += register_count
|
register_info.append(
|
||||||
|
(entity_name, modbus_info, register_offset, register_count))
|
||||||
|
|
||||||
if register_offset >= 0 and register_offset + register_count > next_free_offset:
|
if register_offset >= 0 and register_offset + register_count > next_free_offset:
|
||||||
next_free_offset = register_offset + register_count
|
next_free_offset = register_offset + register_count
|
||||||
|
|
||||||
# print(device_type_name + "/" + tag + ": total_registers=" + str(total_registers) + "; next_free_offset=" + str(
|
# Assign registers for unassigned entities
|
||||||
# next_free_offset))
|
for entity_name, modbus_info, register_offset, register_count in register_info:
|
||||||
|
|
||||||
for entity_name, modbus_info in entities.items():
|
|
||||||
register_offset = int(modbus_info['modbus offset'])
|
|
||||||
register_count = int(modbus_info['modbus count'])
|
|
||||||
if register_offset < 0 and register_count > 0:
|
if register_offset < 0 and register_count > 0:
|
||||||
# assign register
|
|
||||||
# print("assign " + entity_name + " -> " + str(next_free_offset))
|
|
||||||
modbus_info['modbus offset'] = str(next_free_offset)
|
modbus_info['modbus offset'] = str(next_free_offset)
|
||||||
next_free_offset += register_count
|
next_free_offset += register_count
|
||||||
|
|
||||||
# OUTPUT
|
# OUTPUT
|
||||||
|
|
||||||
cpp_entries = ""
|
cpp_entries = []
|
||||||
|
|
||||||
# traverse all elements in correct order so they are correctly sorted
|
# traverse all elements in correct order so they are correctly sorted
|
||||||
for device_type_name in device_type_names:
|
for device_type_name in device_type_names:
|
||||||
@@ -267,18 +263,22 @@ for device_type_name in device_type_names:
|
|||||||
tag = str(ntag)
|
tag = str(ntag)
|
||||||
if tag in device_type:
|
if tag in device_type:
|
||||||
entities = device_type[tag]
|
entities = device_type[tag]
|
||||||
for entity_name, modbus_info in sorted(entities.items(), key=lambda x: int(x[1]["modbus offset"])):
|
# Sort once and reuse
|
||||||
|
sorted_entities = sorted(
|
||||||
|
entities.items(), key=lambda x: int(x[1]["modbus offset"]))
|
||||||
|
for entity_name, modbus_info in sorted_entities:
|
||||||
params = {
|
params = {
|
||||||
'devtype': "dt::" + device_type_name,
|
'devtype': "dt::" + device_type_name,
|
||||||
# re.sub(r"[0-9]+", "*", tag),
|
|
||||||
"tagtype": tag_to_tagtype[int(tag)],
|
"tagtype": tag_to_tagtype[int(tag)],
|
||||||
"shortname": 'FL_(' + listNames[entity_name] + ")",
|
"shortname": 'FL_(' + listNames[entity_name] + ")",
|
||||||
"entity_name": entity_name,
|
"entity_name": entity_name,
|
||||||
'registeroffset': modbus_info["modbus offset"],
|
'registeroffset': modbus_info["modbus offset"],
|
||||||
'registercount': modbus_info["modbus count"]
|
'registercount': modbus_info["modbus count"]
|
||||||
}
|
}
|
||||||
# print(entitypath + ": " + str(modbus_info))
|
cpp_entries.append(cpp_entry_template.substitute(params))
|
||||||
cpp_entries += cpp_entry_template.substitute(params)
|
|
||||||
|
|
||||||
cpp_src = cpp_file_template.substitute({'entries': cpp_entries})
|
# Join all entries at once
|
||||||
|
cpp_entries_str = "".join(cpp_entries)
|
||||||
|
|
||||||
|
cpp_src = cpp_file_template.substitute({'entries': cpp_entries_str})
|
||||||
print(cpp_src)
|
print(cpp_src)
|
||||||
|
|||||||
Reference in New Issue
Block a user