Tests / test (push) Successful in 1m1s
Approved by Daniel Haefliger for develop and beta. Author dh_Agent, authenticated account dh. Preserve published battery, charging and overall Energy Pie changes. No deployment or plant control authorization.
89 lines
4.1 KiB
Python
89 lines
4.1 KiB
Python
"""Check literal Symcon registrations and reviewed dynamic families against READMEs.
|
|
|
|
This is a documentation-presence check, not a PHP evaluator or hardware test.
|
|
Only repository sources are read; no credentials, runtime state or device APIs.
|
|
"""
|
|
import pathlib
|
|
import re
|
|
import sys
|
|
|
|
ROOT = pathlib.Path(__file__).resolve().parents[1]
|
|
MODULES = {
|
|
'Manager': 'Manager', 'Batterie': 'Batterie',
|
|
'Warmwassererwaermer': 'Wassererwaermer', 'Pufferspeicher': 'Pufferspeicher',
|
|
'VerbraucherEinStufig': 'Verbraucher-1-Stufig', 'Waermepumpe': 'Waermepumpe',
|
|
'LadestationStandAlone': 'Ladestation-Stand-Alone',
|
|
'LadestationGateway': 'Ladestation-Gateway', 'EaseeGateway': 'Easee-Gateway',
|
|
'Energiediagramm': 'Energiediagramm', 'CC100Hardware': 'CC100-Hardware',
|
|
'ShellyModul': 'Shelly-Modul', 'Verbrauchskostenreport': 'Verbrauchskostenreport',
|
|
'VirtuelleBatterie': 'Virtuelle-Batterie', 'VGTSchnittstelle': 'VGT-Schnittstelle',
|
|
}
|
|
DYNAMIC = {
|
|
'Energiediagramm': ['FarbeEigenverbrauchRGB', 'FarbeAutarkieRGB',
|
|
'FarbeHintergrundRGB', 'FarbeHintergrundAkzent1RGB', 'FarbeHintergrundAkzent2RGB'],
|
|
'Manager': ['PVLeistungArchiv', 'HausverbrauchLeistungArchiv', 'NetzleistungArchiv',
|
|
'BatterieleistungArchiv', 'SDLLeistungArchiv', 'PVEnergie', 'Hausenergie',
|
|
'NetzbezugEnergie', 'EinspeisungEnergie', 'BatterieLadenEnergie',
|
|
'BatterieEntladenEnergie', 'SDLLadenEnergie', 'SDLEntladenEnergie',
|
|
'EnergieflussPV', 'EnergieflussNetz', 'EnergieflussHaus', 'EnergieflussBatterie',
|
|
'EnergieflussSDL', 'EnergieflussIst_', 'EnergieflussSoll_', 'DiagrammHausLeistung',
|
|
'DiagrammHausEnergie', 'DiagrammPVLeistung_', 'DiagrammPVEnergie_',
|
|
'DiagrammBatterieLeistung_', 'DiagrammBatterieLaden_', 'DiagrammBatterieEntladen_',
|
|
'DiagrammVerbraucherLeistung_', 'DiagrammVerbraucherEnergie_'],
|
|
'CC100Hardware': ['DI1', 'DI8', 'DO1', 'DO4', 'AI1', 'AI2', 'AO1', 'AO2', 'PT1', 'PT2'],
|
|
'ShellyModul': ['Typabbildung', 'Hash', 'switch:0.aenergy.total', 'Online', 'Switch.Set'],
|
|
}
|
|
|
|
|
|
def sources(path, seen=None):
|
|
seen = set() if seen is None else seen
|
|
path = path.resolve()
|
|
if path in seen:
|
|
return
|
|
seen.add(path)
|
|
text = path.read_text(encoding='utf-8')
|
|
yield text
|
|
for match in re.finditer(r"require_once\s+__DIR__\s*\.\s*['\"]([^'\"]+)['\"]", text):
|
|
target = (path.parent / match[1].lstrip('/')).resolve()
|
|
if ROOT in target.parents and target.is_file() and 'vendor' not in target.parts:
|
|
yield from sources(target, seen)
|
|
|
|
|
|
def main():
|
|
modules = sorted(ROOT.glob('*/module.php'))
|
|
errors, checked, dynamic = [], 0, 0
|
|
for source in modules:
|
|
name = source.parent.name
|
|
if name not in MODULES:
|
|
errors.append(name + ': missing documentation mapping')
|
|
continue
|
|
document = ROOT / 'docs/module' / MODULES[name] / 'README.md'
|
|
if not document.is_file() or not (source.parent / 'README.md').is_file():
|
|
errors.append(name + ': README missing')
|
|
continue
|
|
text = document.read_text(encoding='utf-8')
|
|
shared = ROOT / 'docs/Schnittstelle.md'
|
|
if name not in ['Manager', 'EaseeGateway'] and shared.exists():
|
|
text += shared.read_text(encoding='utf-8')
|
|
fields = set()
|
|
for code in sources(source):
|
|
fields.update(re.findall(r"->Register(Property|Variable)(?:Boolean|Integer|Float|String)\s*\(\s*['\"]([^'\"]+)['\"]", code))
|
|
for kind, ident in sorted(fields):
|
|
checked += 1
|
|
if ident not in text:
|
|
errors.append(name + ': undocumented ' + kind + ' ' + ident)
|
|
for ident in DYNAMIC.get(name, []):
|
|
dynamic += 1
|
|
if ident not in text:
|
|
errors.append(name + ': undocumented dynamic family ' + ident)
|
|
if not modules:
|
|
errors.append('No installable module sources found')
|
|
for error in errors:
|
|
print(error)
|
|
print(f'{len(modules)} modules; {checked} literal registrations; {dynamic} dynamic markers; {len(errors)} gaps')
|
|
return bool(errors)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
sys.exit(main())
|