mirror of
https://github.com/NickoScope/nickol-knx-mcp.git
synced 2026-09-29 19:31:12 +02:00
The CI ran tests only. Added a lint job with two narrow gates: ruff with select = E9,F — syntax errors and pyflakes (undefined names, unused imports, broken f-strings). Style rules are deliberately off: the full default set flagged 202 things, almost all cosmetic, and a gate nobody reads is not a gate. Fixed the 9 findings it did have (8 unused imports, 1 empty f-string). mypy, clean at 13 errors fixed. None of the 13 was a live bug, so every fix is an annotation or a local rename: the two accumulators in advanced, the block map in appprog_parser, a reused loop name in project and in repair, a TypedDict lost through dict(), a mixed-type summary dict in handover. Two are worth naming. _CH_TOKEN_RE now captures the letter prefix directly instead of re-matching its own capture, which removes an unguarded .group() on a possibly-None match; the regex accepts exactly the same strings. repair's status-DPT lookup now spells out the None case, which is what dict.get already did since None is not a key there. Verified the fixes moved nothing: the full suite passes, and the corpus guard reports no drift across all six real projects, the 3646-GA one included. Matrix extended to 3.13 and 3.14; both were smoke-tested locally first. Dependabot config for pip and github-actions, weekly and grouped so a quiet week is one PR. Dependabot security updates and CodeQL default setup enabled in the repository settings.
60 lines
2.5 KiB
Python
60 lines
2.5 KiB
Python
"""C1 — KNX IoT semantic export (Turtle / RDF).
|
|
|
|
KNX IoT is the IP-native future: an installation is described by a *semantic* model
|
|
(functions, datapoints, DPTs) rather than only classic group addresses. This module
|
|
emits a Turtle/RDF view of the project's functional group addresses from the same graph
|
|
we already build for the GA XML — one source of truth, two exports. Read-only file
|
|
generation; no bus.
|
|
|
|
The output is a pragmatic semantic skeleton (datapoint per functional GA with its DPT,
|
|
name, role and 3-level address). It is not the full certified KNX IoT ontology, and says
|
|
so — a starting point for an IoT-native description, reviewed before use.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from .project import LoadedProject
|
|
from .intent import INTENT_FUNCTIONAL
|
|
|
|
|
|
def _esc(s: str) -> str:
|
|
return (s or "").replace("\\", "\\\\").replace('"', '\\"').replace("\n", " ")
|
|
|
|
|
|
def generate_knx_iot_turtle(project: LoadedProject) -> str:
|
|
"""Emit a Turtle/RDF semantic view of the project's functional datapoints."""
|
|
lines = [
|
|
"@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .",
|
|
"@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .",
|
|
"@prefix knx: <https://ns.knx.org/> .",
|
|
"@prefix dpt: <https://ns.knx.org/dpt/> .",
|
|
"@prefix proj: <urn:nickol-knx:project#> .",
|
|
"",
|
|
"# KNX IoT semantic view — generated read-only from the .knxproj.",
|
|
"# Pragmatic skeleton (datapoint per functional group address), NOT the full",
|
|
"# certified KNX IoT ontology. Review before use.",
|
|
"",
|
|
'proj:project a knx:Installation ;',
|
|
f' rdfs:label "{_esc(project.info.get("name","KNX project"))}" ;',
|
|
f' knx:groupAddressStyle "{_esc(project.info.get("group_address_style","ThreeLevel"))}" .',
|
|
"",
|
|
]
|
|
n = 0
|
|
for ga in project.gas.values():
|
|
if ga.intent != INTENT_FUNCTIONAL or ga.dpt_main is None:
|
|
continue
|
|
n += 1
|
|
node = "proj:ga_" + ga.address.replace("/", "_")
|
|
lines += [
|
|
f"{node} a knx:Datapoint ;",
|
|
f' knx:groupAddress "{ga.address}" ;',
|
|
f' rdfs:label "{_esc(ga.name)}" ;',
|
|
f" knx:datapointType dpt:{(ga.dpt or '').replace('.', '-') or 'unknown'} ;",
|
|
f' knx:role "{ga.kind}" ;',
|
|
f' knx:function "{_esc(ga.category)}" ;',
|
|
f" knx:secure {'true' if ga.data_secure else 'false'} .",
|
|
"",
|
|
]
|
|
lines.insert(len(lines), f"# {n} functional datapoints exported.")
|
|
return "\n".join(lines)
|