Files
SolarManager/sunspec2/models/utils/add_sunspec_standards.py
T
adminandClaude Opus 5 79843aa2ae SolarManager unter Versionsverwaltung
Erster Stand der Hintergrundprozesse, die auf der Synology unter
/volume1/homes/wagner/SolarManager laufen: der Manager selbst, die Sammler
je Geraet, die MQTT-Bruecke, der Wecker und - neu hinzugezogen - der
AutoAction-Runner, der als Hintergrundprozess hierher gehoert und nicht ins
Web-Verzeichnis.

Zugangsdaten stehen nicht mehr im Quelltext, sondern in config.ini, die
nicht mit eingecheckt wird. Vorlage ist config.ini.example, gelesen wird sie
von konfig.py. Betroffen waren solarManager.py (Datenbank und Wattpilot),
zeit.py, gatherWaterData.py, wecker.py und skoda_testdaten.py, das sich das
Passwort bisher aus dem Quelltext eines anderen Moduls herausgesucht hat.

Die Kia-Anbindung ist mit dem Fahrzeug entfallen: kiaTest.py,
gatherCarData.py und hyundai_kia_connect_api sind nicht mehr dabei, ebenso
gatherInverterData.py, auf das nur noch eine auskommentierte Zeile zeigte.

Die mitgelieferten Bibliotheken bleiben im Repository - die NAS hat kein
pip, sie muessen neben den Skripten liegen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-02 20:46:59 +02:00

141 lines
5.7 KiB
Python

"""
This script updates the SunSpec Models with new standards
"""
import json
import os
STD_NAME = 'IEEE 1547-2018'
MANDATORY_1547_POINTS = { # ID and L are added by default
1: ['Mn', 'Md', 'SN', 'Vr'],
701: ['W', 'Var', 'Hz', 'St', 'ConnSt', 'Alrm', 'LLV', 'LNV', 'VL1L2', 'VL1', 'VL2L3', 'VL2', 'VL3L1', 'VL3'],
702: ['WMaxRtg', 'WOvrExtRtg', 'WOvrExtRtgPF', 'WUndExtRtg', 'WUndExtRtgPF', 'VAMaxRtg', 'NorOpCatRtg',
'AbnOpCatRtg', 'VarMaxInjRtg', 'VarMaxAbsRtg', 'WChaRteMaxRtg', 'VAChaRteMaxRtg', 'VNomRtg', 'VMaxRtg',
'VMinRtg', 'CtrlModes', 'ReactSusceptRtg'],
703: ['ES', 'ESVHi', 'ESVLo', 'ESHzHi', 'ESHzLo', 'ESDlyTms', 'ESRmpRem', 'V_SF', 'Hz_SF'],
704: ['PFWInjEna', 'PF_SF', 'PFWInj.PF', 'PFWInj.Ext', 'VarSetEna', 'VarSetMod', 'VarSetPri', 'VarSetPct',
'VarSetPct_SF', 'WMaxLimPctEna', 'WMaxLimPct', 'WMaxLimPct_SF'],
705: ['Ena', 'AdptCrvReq', 'AdptCrvRslt', 'NPt', 'NCrv', 'V_SF', 'DeptRef_SF', 'RspTms_SF', 'Crv.ActPt',
'Crv.DeptRef', 'Crv.Pri', 'Crv.VRef', 'Crv.VRefAutoEna', 'Crv.VRefAutoTms', 'Crv.RspTms', 'Crv.ReadOnly',
'Crv.Pt.V', 'Crv.Pt.Var'],
706: ['Ena', 'RspTms', 'AdptCrvReq', 'AdptCrvRslt', 'NPt', 'NCrv', 'V_SF', 'DeptRef_SF', 'RspTms_SF',
'Crv.ActPt', 'Crv.DeptRef', 'Crv.RspTms', 'Crv.ReadOnly', 'Crv.Pt.V', 'Crv.Pt.W'],
707: ['Ena', 'AdptCrvReq', 'AdptCrvRslt', 'NPt', 'NCrvSet', 'V_SF', 'Tms_SF', 'Crv.ReadOnly', 'Crv.MustTrip.ActPt',
'Crv.MustTrip.Pt.V', 'Crv.MustTrip.Pt.Tms'],
708: ['Ena', 'AdptCrvReq', 'AdptCrvRslt', 'NPt', 'NCrvSet', 'V_SF', 'Tms_SF', 'Crv.ReadOnly', 'Crv.MustTrip.ActPt',
'Crv.MustTrip.Pt.V', 'Crv.MustTrip.Pt.Tms'],
709: ['Ena', 'AdptCrvReq', 'AdptCrvRslt', 'NPt', 'NCrvSet', 'Hz_SF', 'Tms_SF', 'Crv.ReadOnly', 'Crv.MustTrip.ActPt',
'Crv.MustTrip.Pt.Hz', 'Crv.MustTrip.Pt.Tms'],
710: ['Ena', 'AdptCrvReq', 'AdptCrvRslt', 'NPt', 'NCrvSet', 'Hz_SF', 'Tms_SF', 'Crv.ReadOnly', 'Crv.MustTrip.ActPt',
'Crv.MustTrip.Pt.Hz', 'Crv.MustTrip.Pt.Tms'],
711: ['Ena', 'AdptCtlReq', 'AdptCtlRslt', 'NCtl', 'Db_SF', 'K_SF', 'RspTms_SF', 'Ctl.ReadOnly',
'Ctl.DbOf', 'Ctl.DbUf', 'Ctl.KOf', 'Ctl.KUf', 'Ctl.RspTms'],
712: ['Ena', 'AdptCrvReq', 'AdptCrvRslt', 'NPt', 'NCrv', 'W_SF', 'DeptRef_SF', 'Crv.ActPt', 'Crv.DeptRef',
'Crv.Pri', 'Crv.ReadOnly', 'Crv.Pt.W', 'Crv.Pt.Var'],
713: ['SoC'],
}
def group_parser(group, name, tabs=0, print_groups=False):
"""
Parse the group and return a dictionary of the fields
:param group: the group to parse
:param name: the name of the group
:param tabs: the number of tabs to print
:param print_groups: whether to print the groups
:return: a dictionary of the fields
"""
if 'name' in group and print_groups:
if name != '':
print('\t' * tabs + '%s.%s (Group):' % (name, group['name']))
else:
print('\t' * tabs + '%s (Group):' % (group['name']))
if 'points' in group:
if name != '':
pt_name = '%s.%s' % (name, group['name'])
point_parser(group['points'], pt_name, tabs + 1)
else: # add ID and model name to the point name
pt_name = '%s.%s' % (group['points'][0]['value'], group['name'])
point_parser(group['points'], pt_name, tabs + 1)
if 'groups' in group:
for g in group['groups']: # list of groups
if name != '':
gp_name = '%s.%s' % (name, group['name'])
group_parser(g, gp_name, tabs + 1)
else: # add ID and model name to the group name
gp_name = '%s.%s' % (group['points'][0]['value'], group['name'])
group_parser(g, gp_name, tabs + 1)
return group
def point_parser(points, name, tabs=0):
"""
Parse the points and return a dictionary of the fields
:param points: the points to parse
:param name: the name string
:param tabs: the number of tabs to print
:return: a dictionary of the fields
"""
for point in points:
mandatory = False
model = name.split('.')[0] # get model number
for pt in MANDATORY_1547_POINTS.get(int(model), []):
if pt == '.'.join(name.split('.')[2:] + [point['name']]):
mandatory = True
break
if point['name'] == 'ID' or point['name'] == 'L' or mandatory:
if point.get('standards') is None:
point['standards'] = [STD_NAME]
elif STD_NAME not in point['standards']:
point['standards'].append(STD_NAME)
else:
# check for duplicates and remove them
point['standards'] = list(set(point['standards']))
print('\t' * tabs + '%s.%s (+)' % (name, point['name']))
else:
if point.get('standards') is None:
point['standards'] = []
elif STD_NAME in point['standards']:
point['standards'].remove(STD_NAME)
print('\t' * tabs + '%s.%s (-)' % (name, point['name']))
return points
def main():
"""
Scan json files and add new fields to the dataset
:return: None
"""
json_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), '..', 'json')
for model, pts in MANDATORY_1547_POINTS.items():
file = 'model_%s.json' % model
with open(os.path.join(json_path, file), 'r') as f:
data = json.load(f)
print('Parsing ID %s' % data['id'])
if 'points' in data:
point_parser(data['points'], '')
if 'group' in data:
group_parser(data['group'], '')
# write the new data back to the file
with open(os.path.join(json_path, file), 'w') as f:
json.dump(data, f, indent=4)
if __name__ == '__main__':
main()