knx
This commit is contained in:
parent
3e487c10b5
commit
db9db83eca
@ -1,4 +1,6 @@
|
||||
logging.level.de.ph87.home.tvheadend.TvheadendService=DEBUG
|
||||
#logging.level.de.ph87.home.knx=DEBUG
|
||||
#logging.level.de.ph87.home.property=DEBUG
|
||||
#logging.level.de.ph87.home.tvheadend=DEBUG
|
||||
#-
|
||||
spring.datasource.url=jdbc:h2:./database;AUTO_SERVER=TRUE
|
||||
spring.datasource.driverClassName=org.h2.Driver
|
||||
|
||||
237
data/G
Normal file
237
data/G
Normal file
@ -0,0 +1,237 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<GAs>
|
||||
<GRs>
|
||||
<GR Id="P-02E5-0_GR-1" RangeStart="1" RangeEnd="2047" Name="Haus" Puid="18">
|
||||
<GR Id="P-02E5-0_GR-2" RangeStart="1" RangeEnd="255" Name="Neue Mittelgruppe" Puid="19">
|
||||
<GA Id="P-02E5-0_GA-12" Address="12" Name="Haus / Datum, Uhrzeit" Description="" DatapointType="DPST-19-1" Puid="47" />
|
||||
<GA Id="P-02E5-0_GA-41" Address="21" Name="Haus / Szene" Comment="{\rtf1\ansi\ansicpg1252\uc1\htmautsp\deff2{\fonttbl{\f0\fcharset0 Times New Roman;}{\f2\fcharset0 Segoe UI;}}{\colortbl\red0\green0\blue0;\red255\green255\blue255;}\loch\hich\dbch\pard\plain\ltrpar\itap0{\lang1031\fs16\f2\cf0 \cf0\ql{\f2 {\ltrch 01: Alles AUS}\li0\ri0\sa0\sb0\fi0\ql\par}
{\f2 \li0\ri0\sa0\sb0\fi0\ql\par}
{\f2 {\ltrch 20: Sonnenschutz Front}\li0\ri0\sa0\sb0\fi0\ql\par}
{\f2 \li0\ri0\sa0\sb0\fi0\ql\par}
{\f2 {\ltrch 30: Dekoration AUS}\li0\ri0\sa0\sb0\fi0\ql\par}
{\f2 {\ltrch 30: Dekoration AN}\li0\ri0\sa0\sb0\fi0\ql\par}
}
}" Description="" DatapointType="DPST-17-1" Puid="148" />
|
||||
<GA Id="P-02E5-0_GA-43" Address="1" Name="Erdgeschoss / Szene" Description="" DatapointType="DPST-17-1" Puid="156" />
|
||||
<GA Id="P-02E5-0_GA-48" Address="22" Name="Haus / Tag, Nacht" Description="" DatapointType="DPST-1-2" Puid="178" />
|
||||
<GA Id="P-02E5-0_GA-86" Address="28" Name="Wohnzimmer / Licht / Vorne / Status / Lesen" Description="" DatapointType="DPST-1-1" Puid="284" />
|
||||
<GA Id="P-02E5-0_GA-108" Address="4" Name="Wohnzimmer / Fernseher / Status / Setzen" Description="" DatapointType="DPST-1-1" Puid="347" />
|
||||
<GA Id="P-02E5-0_GA-109" Address="20" Name="Wohnzimmer / Fernseher / Status / Lesen" Description="" DatapointType="DPST-1-1" Puid="349" />
|
||||
</GR>
|
||||
<GR Id="P-02E5-0_GR-5" RangeStart="768" RangeEnd="1023" Name="Neue Mittelgruppe" Puid="331">
|
||||
<GA Id="P-02E5-0_GA-107" Address="770" Name="Obergeschoss / Szene" Description="" DatapointType="DPST-17-1" Puid="336" />
|
||||
<GA Id="P-02E5-0_GA-111" Address="772" Name="Wohnzimmer / Szene" Description="" DatapointType="DPST-17-1" Puid="354" />
|
||||
<GA Id="P-02E5-0_GA-116" Address="773" Name="Bad / Licht / Dusche / Status / Setzen" Description="" DatapointType="DPST-1-1" Puid="378" />
|
||||
<GA Id="P-02E5-0_GA-117" Address="774" Name="Bad / Licht / Dusche / Status / Lesen" Description="" DatapointType="DPST-1-1" Puid="380" />
|
||||
<GA Id="P-02E5-0_GA-124" Address="781" Name="Bad / Licht / Badewanne / Status / Setzen" Description="" DatapointType="DPST-1-1" Puid="395" />
|
||||
<GA Id="P-02E5-0_GA-125" Address="782" Name="Bad / Licht / Badewanne / Status / Lesen" Description="" DatapointType="DPST-1-1" Puid="397" />
|
||||
<GA Id="P-02E5-0_GA-132" Address="789" Name="Bad / Licht / Waschbecken / Status / Setzen" Description="" DatapointType="DPST-1-1" Puid="412" />
|
||||
<GA Id="P-02E5-0_GA-133" Address="790" Name="Bad / Licht / Waschbecken / Status / Lesen" Description="" DatapointType="DPST-1-1" Puid="414" />
|
||||
<GA Id="P-02E5-0_GA-140" Address="797" Name="Bad / Licht / Mitte / Status / Setzen" Description="" DatapointType="DPST-1-1" Puid="429" />
|
||||
<GA Id="P-02E5-0_GA-141" Address="798" Name="Bad / Licht / Mitte / Status / Lesen" Description="" DatapointType="DPST-1-1" Puid="431" />
|
||||
<GA Id="P-02E5-0_GA-156" Address="813" Name="Flur OG / Licht / Status / Setzen" Description="" DatapointType="DPST-1-1" Puid="463" />
|
||||
<GA Id="P-02E5-0_GA-157" Address="814" Name="Flur OG / Licht / Status / Lesen" Description="" DatapointType="DPST-1-1" Puid="465" />
|
||||
<GA Id="P-02E5-0_GA-158" Address="815" Name="Flur OG / Licht / Helligkeit / Schritt" Description="" DatapointType="DPST-3-7" Puid="467" />
|
||||
<GA Id="P-02E5-0_GA-172" Address="829" Name="Schlafzimmer / Licht / Status / Setzen" Description="" DatapointType="DPST-1-1" Puid="497" />
|
||||
<GA Id="P-02E5-0_GA-174" Address="831" Name="Schlafzimmer / Licht / Helligkeit / Schritt" Description="" DatapointType="DPST-3-7" Puid="501" />
|
||||
<GA Id="P-02E5-0_GA-175" Address="832" Name="Schlafzimmer / Licht / Farbtemperatur / Schritt" Description="" DatapointType="DPST-3-7" Puid="503" />
|
||||
<GA Id="P-02E5-0_GA-181" Address="822" Name="Schlafzimmer / Rollläden / Fahren" Description="" DatapointType="DPST-1-8" Puid="539" />
|
||||
<GA Id="P-02E5-0_GA-182" Address="823" Name="Schlafzimmer / Rollläden / Schritt/Stop" Description="" DatapointType="DPST-1-7" Puid="541" />
|
||||
<GA Id="P-02E5-0_GA-185" Address="826" Name="Wohnzimmer / Rollläden / Fahren" Description="" DatapointType="DPST-1-8" Puid="548" />
|
||||
<GA Id="P-02E5-0_GA-186" Address="827" Name="Wohnzimmer / Rollläden / Schritt/Stop" Description="" DatapointType="DPST-1-7" Puid="550" />
|
||||
<GA Id="P-02E5-0_GA-192" Address="839" Name="Bad / Licht / Helligkeit / Schritt" Description="" DatapointType="DPST-3-7" Puid="569" />
|
||||
<GA Id="P-02E5-0_GA-193" Address="840" Name="Bad / Licht / Farbtemperatur / Schritt" Description="" DatapointType="DPST-3-7" Puid="571" />
|
||||
<GA Id="P-02E5-0_GA-194" Address="841" Name="Bad / Licht / Status / Setzen" Description="" DatapointType="DPST-1-1" Puid="581" />
|
||||
<GA Id="P-02E5-0_GA-196" Address="843" Name="Schlafzimmer / Löwe / Status / Setzen" Description="" DatapointType="DPST-1-1" Puid="586" />
|
||||
<GA Id="P-02E5-0_GA-197" Address="844" Name="Schlafzimmer / Löwe / Status / Lesen" Description="" DatapointType="DPST-1-1" Puid="588" />
|
||||
<GA Id="P-02E5-0_GA-198" Address="845" Name="Schlafzimmer / Sternenhimmel / Status / Setzen" Description="" DatapointType="DPST-1-1" Puid="590" />
|
||||
<GA Id="P-02E5-0_GA-199" Address="846" Name="Schlafzimmer / Sternenhimmel / Status / Lesen" Description="" DatapointType="DPST-1-1" Puid="592" />
|
||||
<GA Id="P-02E5-0_GA-201" Address="824" Name="Wohnzimmer / Verstärker / Status / Setzen" Description="" DatapointType="DPST-1-1" Puid="638" />
|
||||
<GA Id="P-02E5-0_GA-202" Address="825" Name="Wohnzimmer / Verstärker / Status / Lesen" Description="" DatapointType="DPST-1-1" Puid="640" />
|
||||
<GA Id="P-02E5-0_GA-203" Address="828" Name="Wohnzimmer / Licht / Status / Setzen" Description="" DatapointType="DPST-1-1" Puid="642" />
|
||||
<GA Id="P-02E5-0_GA-204" Address="837" Name="Wohnzimmer / Licht / Helligkeit / Schritt" Description="" DatapointType="DPST-3-7" Puid="644" />
|
||||
<GA Id="P-02E5-0_GA-205" Address="847" Name="Wohnzimmer / Licht / Farbtemperatur / Schritt" Description="" DatapointType="DPST-3-7" Puid="646" />
|
||||
<GA Id="P-02E5-0_GA-206" Address="848" Name="Erdgeschoss / Ambiente / Status / Setzen" Description="" DatapointType="DPST-1-1" Puid="648" />
|
||||
<GA Id="P-02E5-0_GA-207" Address="849" Name="Erdgeschoss / Ambiente / Status / Lesen" Description="" DatapointType="DPST-1-11" Puid="650" />
|
||||
<GA Id="P-02E5-0_GA-233" Address="768" Name="Schlafzimmer / Gesamt / Status / Lesen" Description="" DatapointType="DPST-1-1" Puid="719" />
|
||||
<GA Id="P-02E5-0_GA-234" Address="769" Name="Bad / Gesamt / Status / Lesen" Description="" DatapointType="DPST-1-1" Puid="722" />
|
||||
<GA Id="P-02E5-0_GA-259" Address="776" Name="Schlafzimmer / Szene" Description="" DatapointType="DPST-17-1" Puid="791" />
|
||||
</GR>
|
||||
<GR Id="P-02E5-0_GR-6" RangeStart="1024" RangeEnd="1279" Name="Neue Mittelgruppe" Puid="561">
|
||||
<GA Id="P-02E5-0_GA-190" Address="1024" Name="Terrasse / Licht / Status / Setzen" Description="" DatapointType="DPST-1-1" Puid="562" />
|
||||
<GA Id="P-02E5-0_GA-191" Address="1025" Name="Terrasse / Licht / Status / Lesen" Description="" DatapointType="DPST-1-1" Puid="564" />
|
||||
<GA Id="P-02E5-0_GA-215" Address="1032" Name="Flur EG / Licht / Status / Lesen" Description="" DatapointType="DPST-1-1" Puid="672" />
|
||||
<GA Id="P-02E5-0_GA-221" Address="1038" Name="Erdgeschoss / Gesamt / Status / Lesen" Description="" DatapointType="DPST-1-1" Puid="684" />
|
||||
<GA Id="P-02E5-0_GA-232" Address="1047" Name="Flur OG / Gesamt / Status / Lesen" Description="" DatapointType="DPST-1-1" Puid="716" />
|
||||
<GA Id="P-02E5-0_GA-248" Address="1048" Name="Wohnzimmer / Rollladen / Links / Position" Description="" DatapointType="DPST-5-1" Puid="759" />
|
||||
<GA Id="P-02E5-0_GA-272" Address="1049" Name="Wohnzimmer / Licht / Status / Lesen" Description="" DatapointType="DPST-1-2" Puid="851" />
|
||||
<GA Id="P-02E5-0_GA-282" Address="1035" Name="Vorgarten / Steckdosen / Status / Setzen" Description="" DatapointType="DPST-1-1" Puid="960" />
|
||||
<GA Id="P-02E5-0_GA-283" Address="1036" Name="Vorgarten / Steckdosen / Status / Lesen" Description="" DatapointType="DPST-1-11" Puid="962" />
|
||||
</GR>
|
||||
<GR Id="P-02E5-0_GR-7" RangeStart="1280" RangeEnd="1535" Name="Neue Mittelgruppe" Puid="704">
|
||||
<GA Id="P-02E5-0_GA-228" Address="1280" Name="Waschküche / Licht / Neon / Status / Setzen" Description="" DatapointType="DPST-1-1" Puid="705" />
|
||||
<GA Id="P-02E5-0_GA-229" Address="1281" Name="Waschküche / Licht / Neon / Status / Lesen" Description="" DatapointType="DPST-1-1" Puid="707" />
|
||||
<GA Id="P-02E5-0_GA-235" Address="1282" Name="Haus / Überwachung / Überstrom" Description="" DatapointType="DPST-1-5" Puid="724" />
|
||||
<GA Id="P-02E5-0_GA-236" Address="1283" Name="Haus / Überwachung / Übertemperatur" Description="" DatapointType="DPST-1-5" Puid="726" />
|
||||
<GA Id="P-02E5-0_GA-237" Address="1284" Name="Haus / Überwachung / In Betrieb" Description="" DatapointType="DPST-1-11" Puid="728" />
|
||||
<GA Id="P-02E5-0_GA-238" Address="1285" Name="Haus / Überwachung / Spannungsversorgung" Description="" DatapointType="DPST-1-11" Puid="734" />
|
||||
<GA Id="P-02E5-0_GA-239" Address="1286" Name="Haus / Überwachung / Helligkeit" Description="" DatapointType="DPST-9-4" Puid="738" />
|
||||
<GA Id="P-02E5-0_GA-242" Address="1289" Name="Bad / Szene" Description="" DatapointType="DPST-17-1" Puid="746" />
|
||||
<GA Id="P-02E5-0_GA-243" Address="1290" Name="Flur OG / Rollladen / Vorne / Fahren" Description="" DatapointType="DPST-1-8" Puid="749" />
|
||||
<GA Id="P-02E5-0_GA-244" Address="1291" Name="Flur OG / Rollladen / Vorne / Stopp" Description="" DatapointType="DPST-1-7" Puid="751" />
|
||||
<GA Id="P-02E5-0_GA-246" Address="1293" Name="Flur OG / Rollladen / Vorne / Position anfahren" Description="" DatapointType="DPST-5-1" Puid="755" />
|
||||
<GA Id="P-02E5-0_GA-254" Address="1294" Name="Flur EG / Licht / Status / Setzen" Description="" DatapointType="DPST-1-1" Puid="778" />
|
||||
<GA Id="P-02E5-0_GA-255" Address="1295" Name="Flur EG / Licht / Helligkeit / Schritt" Description="" DatapointType="DPST-3-7" Puid="780" />
|
||||
<GA Id="P-02E5-0_GA-256" Address="1296" Name="Flur EG / Licht / Farbtemperatur / Schritt" Description="" DatapointType="DPST-3-7" Puid="782" />
|
||||
<GA Id="P-02E5-0_GA-258" Address="1297" Name="Küche / Szene" Description="" DatapointType="DPST-17-1" Puid="788" />
|
||||
<GA Id="P-02E5-0_GA-260" Address="1298" Name="Flur EG / Szene" Description="" DatapointType="DPST-17-1" Puid="794" />
|
||||
<GA Id="P-02E5-0_GA-261" Address="1299" Name="Bad / Licht / Status / Lesen" Description="" DatapointType="DPST-1-2" Puid="805" />
|
||||
<GA Id="P-02E5-0_GA-286" Address="1287" Name="Schlafzimmer / Rollladen / Sperren" Description="" DatapointType="DPST-1-1" Puid="980" />
|
||||
<GA Id="P-02E5-0_GA-322" Address="1292" Name="Wohnzimmer / Licht / Szene" Description="" DatapointType="DPST-17-1" Puid="1130" />
|
||||
<GA Id="P-02E5-0_GA-328" Address="1300" Name="Bad / Langzeitpräsenz" Description="" DatapointType="DPST-1-2" Puid="1167" />
|
||||
<GA Id="P-02E5-0_GA-329" Address="1301" Name="Schlafzimmer / Langzeitpräsenz" Description="" DatapointType="DPST-1-2" Puid="1169" />
|
||||
<GA Id="P-02E5-0_GA-330" Address="1302" Name="Flur OG / Langzeitpräsenz" Description="" DatapointType="DPST-1-2" Puid="1171" />
|
||||
<GA Id="P-02E5-0_GA-331" Address="1305" Name="Emil / Langzeitpräsenz" Description="" DatapointType="DPST-1-2" Puid="1173" />
|
||||
<GA Id="P-02E5-0_GA-332" Address="1306" Name="Arbeitszimmer / Langzeitpräsenz" Description="" DatapointType="DPST-1-2" Puid="1175" />
|
||||
<GA Id="P-02E5-0_GA-349" Address="1309" Name="Schlafzimmer / Licht / Status / Lesen" Description="" DatapointType="DPST-1-1" Puid="1254" />
|
||||
<GA Id="P-02E5-0_GA-377" Address="1288" Name="Bad / Rollladen / Fahren" Description="" DatapointType="DPST-1-8" Puid="1365" />
|
||||
<GA Id="P-02E5-0_GA-378" Address="1307" Name="Bad / Rollladen / Position Anfahren" Description="" DatapointType="DPST-5-1" Puid="1367" />
|
||||
<GA Id="P-02E5-0_GA-379" Address="1308" Name="Bad / Rollladen / Position Status" Description="" DatapointType="DPST-5-1" Puid="1369" />
|
||||
<GA Id="P-02E5-0_GA-380" Address="1310" Name="Bad / Rollladen / Richtung Status" Description="" DatapointType="DPST-1-8" Puid="1371" />
|
||||
<GA Id="P-02E5-0_GA-381" Address="1311" Name="Bad / Rollladen / Stopp" Description="" DatapointType="DPST-1-1" Puid="1373" />
|
||||
<GA Id="P-02E5-0_GA-382" Address="1312" Name="Bad / Licht / Spiegel / Status / Setzen" Description="" DatapointType="DPST-1-1" Puid="1386" />
|
||||
<GA Id="P-02E5-0_GA-383" Address="1313" Name="Bad / Licht / Spiegel / Status / Lesen" Description="" DatapointType="DPST-1-2" Puid="1388" />
|
||||
<GA Id="P-02E5-0_GA-384" Address="1314" Name="Bad / Licht / Spiegel / Helligkeit / Schritt" Description="" DatapointType="DPST-3-7" Puid="1390" />
|
||||
<GA Id="P-02E5-0_GA-423" Address="1303" Name="Schlafzimmer / Rollladen / Links / Position Anfahren" Description="" DatapointType="DPST-5-1" Puid="1495" />
|
||||
<GA Id="P-02E5-0_GA-424" Address="1304" Name="Schlafzimmer / Rollladen / Rechts / Position Anfahren" Description="" DatapointType="DPST-5-1" Puid="1497" />
|
||||
<GA Id="P-02E5-0_GA-435" Address="1315" Name="Schlafzimmer / Licht / Farbtemperatur / Setzen" DatapointType="DPST-5-1" Puid="1561" />
|
||||
<GA Id="P-02E5-0_GA-436" Address="1316" Name="Schlafzimmer / Licht / Farbtemperatur / Lesen" DatapointType="DPST-5-1" Puid="1563" />
|
||||
<GA Id="P-02E5-0_GA-441" Address="1317" Name="Bad / Licht / Farbtemperatur / Setzen" DatapointType="DPST-5-1" Puid="1573" />
|
||||
<GA Id="P-02E5-0_GA-442" Address="1318" Name="Bad / Licht / Farbtemperatur / Lesen" DatapointType="DPST-5-1" Puid="1575" />
|
||||
<GA Id="P-02E5-0_GA-443" Address="1319" Name="Bad / Licht / Helligkeit / Setzen" DatapointType="DPST-5-1" Puid="1577" />
|
||||
<GA Id="P-02E5-0_GA-444" Address="1320" Name="Bad / Licht / Helligkeit / Lesen" DatapointType="DPST-5-1" Puid="1579" />
|
||||
<GA Id="P-02E5-0_GA-449" Address="1321" Name="Schlafzimmer / Licht / Helligkeit / Setzen" DatapointType="DPST-5-1" Puid="1593" />
|
||||
<GA Id="P-02E5-0_GA-450" Address="1322" Name="Schlafzimmer / Licht / Helligkeit / Lesen" DatapointType="DPST-5-1" Puid="1595" />
|
||||
</GR>
|
||||
<GR Id="P-02E5-0_GR-8" RangeStart="1536" RangeEnd="1791" Name="Neue Mittelgruppe" Puid="769">
|
||||
<GA Id="P-02E5-0_GA-252" Address="1538" Name="Obergeschoss / Ambiente / Status / Lesen" Description="" DatapointType="DPST-1-11" Puid="774" />
|
||||
<GA Id="P-02E5-0_GA-253" Address="1539" Name="Obergeschoss / Ambiente / Status / Setzen" Description="" DatapointType="DPST-1-1" Puid="776" />
|
||||
<GA Id="P-02E5-0_GA-257" Address="1540" Name="Flur OG / Szene" Description="" DatapointType="DPST-17-1" Puid="785" />
|
||||
<GA Id="P-02E5-0_GA-266" Address="1545" Name="Flur OG / Licht / Farbtemperatur / Schritt" Description="" DatapointType="DPST-3-7" Puid="820" />
|
||||
<GA Id="P-02E5-0_GA-278" Address="1542" Name="Obergeschoss / Gesamt / Status / Lesen" Description="" DatapointType="DPST-1-1" Puid="883" />
|
||||
</GR>
|
||||
<GR Id="P-02E5-0_GR-10" RangeStart="1792" RangeEnd="2047" Name="Neue Mittelgruppe" Puid="885">
|
||||
<GA Id="P-02E5-0_GA-279" Address="1792" Name="Emil / Gesamt / Status / Lesen" Description="" DatapointType="DPST-1-1" Puid="886" />
|
||||
<GA Id="P-02E5-0_GA-288" Address="1795" Name="Emil / Licht / Status / Setzen" Description="" DatapointType="DPST-1-1" Puid="984" />
|
||||
<GA Id="P-02E5-0_GA-289" Address="1796" Name="Emil / Licht / Status / Lesen" Description="" DatapointType="DPST-1-11" Puid="986" />
|
||||
<GA Id="P-02E5-0_GA-290" Address="1797" Name="Emil / Licht / Helligkeit / Schritt" Description="" DatapointType="DPST-3-7" Puid="988" />
|
||||
<GA Id="P-02E5-0_GA-291" Address="1798" Name="Emil / Szene" Description="" DatapointType="DPST-17-1" Puid="995" />
|
||||
<GA Id="P-02E5-0_GA-293" Address="1800" Name="Emil / Licht / Farbtemperatur / Schritt" Description="" DatapointType="DPST-3-7" Puid="1000" />
|
||||
<GA Id="P-02E5-0_GA-294" Address="1801" Name="Emil / Steckdose Fenster Rechts / Status / Setzen" Description="" DatapointType="DPST-1-1" Puid="1006" />
|
||||
<GA Id="P-02E5-0_GA-295" Address="1802" Name="Emil / Steckdose Fenster Rechts / Status / Lesen" Description="" DatapointType="DPST-1-1" Puid="1008" />
|
||||
<GA Id="P-02E5-0_GA-296" Address="1803" Name="Emil / Steckdose Fenster Links / Status / Setzen" Description="" DatapointType="DPST-1-1" Puid="1010" />
|
||||
<GA Id="P-02E5-0_GA-297" Address="1804" Name="Emil / Steckdose Fenster Links / Status / Lesen" Description="" DatapointType="DPST-1-1" Puid="1012" />
|
||||
<GA Id="P-02E5-0_GA-298" Address="1805" Name="Emil / Rollladen / Fahren" Description="" DatapointType="DPST-1-8" Puid="1014" />
|
||||
<GA Id="P-02E5-0_GA-299" Address="1806" Name="Emil / Rollladen / Stopp" Description="" DatapointType="DPST-1-1" Puid="1016" />
|
||||
<GA Id="P-02E5-0_GA-300" Address="1807" Name="Emil / Rollladen / Position Anfahren" Description="" DatapointType="DPST-5-1" Puid="1018" />
|
||||
<GA Id="P-02E5-0_GA-320" Address="1808" Name="Flur EG / Licht / Szene" Description="" DatapointType="DPST-17-1" Puid="1093" />
|
||||
<GA Id="P-02E5-0_GA-323" Address="1809" Name="Flur EG / Langzeitpräsenz" Description="" DatapointType="DPST-1-2" Puid="1153" />
|
||||
<GA Id="P-02E5-0_GA-324" Address="1810" Name="Wohnzimmer / Langzeitpräsenz" Description="" DatapointType="DPST-1-2" Puid="1155" />
|
||||
<GA Id="P-02E5-0_GA-326" Address="1812" Name="Küche / Langzeitpräsenz" Description="" DatapointType="DPST-1-2" Puid="1159" />
|
||||
<GA Id="P-02E5-0_GA-327" Address="1813" Name="Waschküche / Langzeitpräsenz" Description="" DatapointType="DPST-1-2" Puid="1161" />
|
||||
<GA Id="P-02E5-0_GA-333" Address="1814" Name="Flur EG / Gesamt / Status / Lesen" Description="" DatapointType="DPST-1-2" Puid="1181" />
|
||||
<GA Id="P-02E5-0_GA-334" Address="1815" Name="Wohnzimmer / Gesamt / Status" Description="" DatapointType="DPST-1-2" Puid="1183" />
|
||||
<GA Id="P-02E5-0_GA-335" Address="1816" Name="Waschküche / Gesamt / Status" Description="" DatapointType="DPST-1-2" Puid="1185" />
|
||||
<GA Id="P-02E5-0_GA-336" Address="1817" Name="Küche / Gesamt / Status / Lesen" Description="" DatapointType="DPST-1-2" Puid="1187" />
|
||||
<GA Id="P-02E5-0_GA-360" Address="1793" Name="Wohnzimmer / Licht / Präsenz Sperren" Description="" DatapointType="DPST-1-3" Puid="1297" />
|
||||
<GA Id="P-02E5-0_GA-375" Address="1794" Name="Wohnzimmer / Licht Mitte / Status / Lesen" Description="" DatapointType="DPST-1-1" Puid="1360" />
|
||||
<GA Id="P-02E5-0_GA-376" Address="1799" Name="Wohnzimmer / Licht Mitte / Status / Setzen" Description="" DatapointType="DPST-1-1" Puid="1362" />
|
||||
<GA Id="P-02E5-0_GA-402" Address="1818" Name="Waschküche / Rollladen / Fahren" Description="" DatapointType="DPST-1-8" Puid="1437" />
|
||||
<GA Id="P-02E5-0_GA-403" Address="1819" Name="Waschküche / Rollladen / Stopp" Description="" DatapointType="DPST-1-7" Puid="1439" />
|
||||
<GA Id="P-02E5-0_GA-404" Address="1820" Name="Waschküche / Rollladen / Position" Description="" DatapointType="DPST-5-1" Puid="1441" />
|
||||
<GA Id="P-02E5-0_GA-405" Address="1821" Name="Waschküche / Szene" Description="" DatapointType="DPST-17-1" Puid="1443" />
|
||||
<GA Id="P-02E5-0_GA-425" Address="1811" Name="Wohnzimmer / Rollladen / Rechts / Position" Description="" DatapointType="DPST-5-1" Puid="1523" />
|
||||
<GA Id="P-02E5-0_GA-426" Address="1822" Name="Wohnzimmer / Weihnachtsbeleuchtung / Status / Setzen" DatapointType="DPST-1-1" Puid="1531" />
|
||||
<GA Id="P-02E5-0_GA-427" Address="1823" Name="Wohnzimmer / Weihnachtsbeleuchtung / Status / Lesen" DatapointType="DPST-1-11" Puid="1533" />
|
||||
<GA Id="P-02E5-0_GA-431" Address="1824" Name="Wohnzimmer / Licht / Farbtemperatur / Setzen" DatapointType="DPST-5-1" Puid="1553" />
|
||||
<GA Id="P-02E5-0_GA-432" Address="1825" Name="Wohnzimmer / Licht / Farbtemperatur / Lesen" DatapointType="DPST-5-1" Puid="1555" />
|
||||
<GA Id="P-02E5-0_GA-451" Address="1826" Name="Waschküche / Licht / Spots / Setzen" DatapointType="DPST-1-1" Puid="1597" />
|
||||
<GA Id="P-02E5-0_GA-452" Address="1827" Name="Waschküche / Licht / Spots / Lesen" DatapointType="DPST-1-11" Puid="1599" />
|
||||
<GA Id="P-02E5-0_GA-453" Address="1828" Name="Waschküche / Licht / Spots / Helligkeit / Setzen" DatapointType="DPST-5-1" Puid="1601" />
|
||||
<GA Id="P-02E5-0_GA-454" Address="1829" Name="Waschküche / Licht / Spots / Helligkeit / Lesen" DatapointType="DPST-5-1" Puid="1603" />
|
||||
<GA Id="P-02E5-0_GA-455" Address="1830" Name="Waschküche / Licht / Spots / Farbtemperatur / Setzen" DatapointType="DPST-5-1" Puid="1605" />
|
||||
<GA Id="P-02E5-0_GA-456" Address="1831" Name="Waschküche / Licht / Spots / Farbtemperatur / Lesen" DatapointType="DPST-5-1" Puid="1607" />
|
||||
<GA Id="P-02E5-0_GA-457" Address="1832" Name="Waschküche / Licht / Spots / Helligkeit / Schritt" DatapointType="DPST-3-7" Puid="1609" />
|
||||
<GA Id="P-02E5-0_GA-458" Address="1833" Name="Waschküche / Licht / Spots / Farbtemperatur / Schritt" DatapointType="DPST-3-7" Puid="1611" />
|
||||
</GR>
|
||||
</GR>
|
||||
<GR Id="P-02E5-0_GR-11" RangeStart="2048" RangeEnd="4095" Name="Neue Hauptgruppe" Puid="1032">
|
||||
<GR Id="P-02E5-0_GR-12" RangeStart="2048" RangeEnd="2303" Name="Neue Mittelgruppe" Puid="1033">
|
||||
<GA Id="P-02E5-0_GA-301" Address="2048" Name="Arbeitszimmer / Schreibtisch / Status / Setzen" Description="" DatapointType="DPST-1-1" Puid="1034" />
|
||||
<GA Id="P-02E5-0_GA-303" Address="2050" Name="Arbeitszimmer / Schreibtisch / Status / Lesen" Description="" DatapointType="DPST-1-11" Puid="1038" />
|
||||
<GA Id="P-02E5-0_GA-304" Address="2051" Name="Arbeitszimmer / PC / Status / Setzen" Description="" DatapointType="DPST-1-1" Puid="1040" />
|
||||
<GA Id="P-02E5-0_GA-305" Address="2052" Name="Arbeitszimmer / PC / Status / Lesen" Description="" DatapointType="DPST-1-1" Puid="1043" />
|
||||
<GA Id="P-02E5-0_GA-306" Address="2053" Name="Arbeitszimmer / Szene" Description="" DatapointType="DPST-17-1" Puid="1045" />
|
||||
<GA Id="P-02E5-0_GA-308" Address="2055" Name="Emil / Licht / Szene" Description="" DatapointType="DPST-17-1" Puid="1054" />
|
||||
<GA Id="P-02E5-0_GA-309" Address="2056" Name="Arbeitszimmer / Licht / Szene" Description="" DatapointType="DPST-17-1" Puid="1063" />
|
||||
<GA Id="P-02E5-0_GA-310" Address="2057" Name="Arbeitszimmer / Licht / Status / Setzen" Description="" DatapointType="DPST-1-1" Puid="1065" />
|
||||
<GA Id="P-02E5-0_GA-311" Address="2058" Name="Arbeitszimmer / Licht / Status / Lesen" Description="" DatapointType="DPST-1-2" Puid="1067" />
|
||||
<GA Id="P-02E5-0_GA-312" Address="2059" Name="Arbeitszimmer / Licht / Farbtemperatur / Schritt" Description="" DatapointType="DPST-3-7" Puid="1069" />
|
||||
<GA Id="P-02E5-0_GA-313" Address="2060" Name="Arbeitszimmer / Licht / Helligkeit / Schritt" Description="" DatapointType="DPST-3-7" Puid="1071" />
|
||||
<GA Id="P-02E5-0_GA-314" Address="2061" Name="Arbeitszimmer / Gesamt / Status / Lesen" Description="" DatapointType="DPST-1-2" Puid="1073" />
|
||||
<GA Id="P-02E5-0_GA-315" Address="2062" Name="Arbeitszimmer / Rollladen / Fahren" Description="" DatapointType="DPST-1-8" Puid="1076" />
|
||||
<GA Id="P-02E5-0_GA-316" Address="2063" Name="Arbeitszimmer / Rollladen / Stopp" Description="" DatapointType="DPST-1-1" Puid="1078" />
|
||||
<GA Id="P-02E5-0_GA-317" Address="2064" Name="Arbeitszimmer / Rollladen / Position Setzen" Description="" DatapointType="DPST-5-1" Puid="1080" />
|
||||
<GA Id="P-02E5-0_GA-318" Address="2065" Name="Arbeitszimmer / Drucker / Status / Setzen" Description="" DatapointType="DPST-1-1" Puid="1082" />
|
||||
<GA Id="P-02E5-0_GA-319" Address="2066" Name="Arbeitszimmer / Drucker / Status / Lesen" Description="" DatapointType="DPST-1-1" Puid="1084" />
|
||||
<GA Id="P-02E5-0_GA-354" Address="2071" Name="Emil / Steckdose Wand Scheune / Status / Setzen" Description="" DatapointType="DPST-1-1" Puid="1280" />
|
||||
<GA Id="P-02E5-0_GA-355" Address="2072" Name="Emil / Steckdose Wand Scheune / Status / Lesen" Description="" DatapointType="DPST-1-2" Puid="1282" />
|
||||
<GA Id="P-02E5-0_GA-361" Address="2049" Name="Arbeitszimmer / Licht / Farbtemperatur / Lesen" Description="" DatapointType="DPST-5-1" Puid="1303" />
|
||||
<GA Id="P-02E5-0_GA-362" Address="2054" Name="Arbeitszimmer / Licht / Farbtemperatur / Setzen" Description="" DatapointType="DPST-5-1" Puid="1305" />
|
||||
<GA Id="P-02E5-0_GA-363" Address="2067" Name="Arbeitszimmer / Licht / Helligkeit / Lesen" Description="" DatapointType="DPST-5-1" Puid="1307" />
|
||||
<GA Id="P-02E5-0_GA-364" Address="2069" Name="Arbeitszimmer / Licht / Helligkeit / Setzen" Description="" DatapointType="DPST-5-1" Puid="1309" />
|
||||
<GA Id="P-02E5-0_GA-371" Address="2075" Name="Emil / Rollladen / Sperren" Description="" DatapointType="DPST-1-1" Puid="1340" />
|
||||
<GA Id="P-02E5-0_GA-385" Address="2076" Name="Flur OG / Rollladen / Hinten / Fahren" Description="" DatapointType="DPST-1-8" Puid="1392" />
|
||||
<GA Id="P-02E5-0_GA-386" Address="2077" Name="Flur OG / Rollladen / Hinten / Stopp" Description="" DatapointType="DPST-1-17" Puid="1394" />
|
||||
<GA Id="P-02E5-0_GA-387" Address="2078" Name="Flur OG / Rollladen / Hinten / Richtung Status" Description="" DatapointType="DPST-1-8" Puid="1396" />
|
||||
<GA Id="P-02E5-0_GA-388" Address="2079" Name="Flur OG / Rollladen / Hinten / Position Anfahren" Description="" DatapointType="DPST-5-1" Puid="1398" />
|
||||
<GA Id="P-02E5-0_GA-389" Address="2080" Name="Flur OG / Rollladen / Hinten / Position Status" Description="" DatapointType="DPST-5-1" Puid="1400" />
|
||||
<GA Id="P-02E5-0_GA-418" Address="2081" Name="Arbeitszimmer / Licht / Bewegung Sperren" Description="" DatapointType="DPST-1-1" Puid="1476" />
|
||||
<GA Id="P-02E5-0_GA-437" Address="2073" Name="Flur OG / Licht / Farbtemperatur / Setzen" DatapointType="DPST-5-1" Puid="1565" />
|
||||
<GA Id="P-02E5-0_GA-438" Address="2074" Name="Flur OG / Licht / Farbtemperatur / Lesen" DatapointType="DPST-5-1" Puid="1567" />
|
||||
<GA Id="P-02E5-0_GA-439" Address="2082" Name="Emil / Licht / Farbtemperatur / Setzen" DatapointType="DPST-5-1" Puid="1569" />
|
||||
<GA Id="P-02E5-0_GA-440" Address="2083" Name="Emil / Licht / Farbtemperatur / Lesen" DatapointType="DPST-5-1" Puid="1571" />
|
||||
<GA Id="P-02E5-0_GA-445" Address="2084" Name="Emil / Licht / Helligkeit / Setzen" DatapointType="DPST-5-1" Puid="1581" />
|
||||
<GA Id="P-02E5-0_GA-446" Address="2085" Name="Emil / Licht / Helligkeit / Lesen" DatapointType="DPST-5-1" Puid="1583" />
|
||||
<GA Id="P-02E5-0_GA-447" Address="2070" Name="Flur OG / Licht / Helligkeit / Setzen" DatapointType="DPST-5-1" Puid="1585" />
|
||||
<GA Id="P-02E5-0_GA-448" Address="2086" Name="Flur OG / Licht / Helligkeit / Lesen" DatapointType="DPST-5-1" Puid="1587" />
|
||||
</GR>
|
||||
<GR Id="P-02E5-0_GR-13" RangeStart="2304" RangeEnd="2559" Name="Neue Mittelgruppe" Puid="1228">
|
||||
<GA Id="P-02E5-0_GA-356" Address="2306" Name="Außen / Gesamt / Status / Lesen" Description="" DatapointType="DPST-1-1" Puid="1285" />
|
||||
<GA Id="P-02E5-0_GA-357" Address="2307" Name="Außen / Szene" Description="" DatapointType="DPST-17-1" Puid="1287" />
|
||||
<GA Id="P-02E5-0_GA-390" Address="2314" Name="Küche / Rollladen / Seite / Fahren" Description="" DatapointType="DPST-1-8" Puid="1408" />
|
||||
<GA Id="P-02E5-0_GA-391" Address="2315" Name="Küche / Rollladen / Seite / Stopp" Description="" DatapointType="DPST-1-7" Puid="1410" />
|
||||
<GA Id="P-02E5-0_GA-392" Address="2316" Name="Küche / Rollladen / Seite / Position Setzen" Description="" DatapointType="DPST-5-1" Puid="1412" />
|
||||
<GA Id="P-02E5-0_GA-394" Address="2318" Name="Küche / Rollladen / Theke / Fahren" Description="" DatapointType="DPST-1-8" Puid="1416" />
|
||||
<GA Id="P-02E5-0_GA-395" Address="2319" Name="Küche / Rollladen / Theke / Stopp" Description="" DatapointType="DPST-1-7" Puid="1418" />
|
||||
<GA Id="P-02E5-0_GA-396" Address="2320" Name="Küche / Rollladen / Theke / Position Setzen" Description="" DatapointType="DPST-5-1" Puid="1420" />
|
||||
<GA Id="P-02E5-0_GA-398" Address="2322" Name="Küche / Rollladen / Tür / Fahren" Description="" DatapointType="DPST-1-8" Puid="1424" />
|
||||
<GA Id="P-02E5-0_GA-399" Address="2323" Name="Küche / Rollladen / Tür / Stopp" Description="" DatapointType="DPST-1-7" Puid="1426" />
|
||||
<GA Id="P-02E5-0_GA-400" Address="2324" Name="Küche / Rollladen / Tür / Position Setzen" Description="" DatapointType="DPST-5-1" Puid="1428" />
|
||||
<GA Id="P-02E5-0_GA-406" Address="2326" Name="Küche / Rollladen / Tür / Zwang AUF" Description="" DatapointType="DPT-1" Puid="1445" />
|
||||
<GA Id="P-02E5-0_GA-408" Address="2304" Name="Küche / Licht / Status / Setzen" Description="" DatapointType="DPST-1-1" Puid="1453" />
|
||||
<GA Id="P-02E5-0_GA-409" Address="2305" Name="Küche / Licht / Helligkeit / Schritt" Description="" DatapointType="DPST-3-7" Puid="1455" />
|
||||
<GA Id="P-02E5-0_GA-410" Address="2308" Name="Küche / Licht / Farbtemperatur / Schritt" Description="" DatapointType="DPST-3-7" Puid="1457" />
|
||||
<GA Id="P-02E5-0_GA-411" Address="2309" Name="Küche / Präsenzmelder / Slave" Description="" DatapointType="DPST-1-1" Puid="1459" />
|
||||
<GA Id="P-02E5-0_GA-412" Address="2310" Name="Küche / Spots / Status / Lesen" Description="" DatapointType="DPST-1-11" Puid="1461" />
|
||||
<GA Id="P-02E5-0_GA-413" Address="2311" Name="Küche / Licht / Status / Lesen" Description="" DatapointType="DPST-1-1" Puid="1464" />
|
||||
<GA Id="P-02E5-0_GA-414" Address="2312" Name="Küche / Hängelampe / Status / Setzen" Description="" DatapointType="DPST-1-1" Puid="1466" />
|
||||
<GA Id="P-02E5-0_GA-415" Address="2313" Name="Küche / Hängelampe / Status / Lesen" Description="" DatapointType="DPST-1-1" Puid="1468" />
|
||||
<GA Id="P-02E5-0_GA-416" Address="2328" Name="Küche / Licht / Szene" Description="" DatapointType="DPST-17-1" Puid="1470" />
|
||||
<GA Id="P-02E5-0_GA-417" Address="2329" Name="Treppe / Präsenzmelder / Langzeitpräsenz" Description="" DatapointType="DPST-1-1" Puid="1473" />
|
||||
<GA Id="P-02E5-0_GA-429" Address="2317" Name="Küche / Licht / Farbtemperatur / Setzen" DatapointType="DPST-5-1" Puid="1549" />
|
||||
<GA Id="P-02E5-0_GA-430" Address="2321" Name="Küche / Licht / Farbtemperatur / Lesen" DatapointType="DPST-5-1" Puid="1551" />
|
||||
<GA Id="P-02E5-0_GA-433" Address="2325" Name="Flur EG / Licht / Farbtemperatur / Setzen" DatapointType="DPST-5-1" Puid="1557" />
|
||||
<GA Id="P-02E5-0_GA-434" Address="2327" Name="Flur EG / Licht / Farbtemperatur / Lesen" DatapointType="DPST-5-1" Puid="1559" />
|
||||
<GA Id="P-02E5-0_GA-459" Address="2330" Name="Haus / Überwachung / Bewegung" DatapointType="DPST-1-1" Puid="1613" />
|
||||
</GR>
|
||||
<GR Id="P-02E5-0_GR-14" RangeStart="2560" RangeEnd="2815" Name="Neue Mittelgruppe" Puid="1616">
|
||||
<GA Id="P-02E5-0_GA-460" Address="2560" Name="Keller / Sat. Receiver / Status / Setzen" DatapointType="DPST-1-1" Puid="1617" />
|
||||
<GA Id="P-02E5-0_GA-461" Address="2561" Name="Keller / Sat. Receiver / Status / Lesen" DatapointType="DPST-1-11" Puid="1619" />
|
||||
</GR>
|
||||
</GR>
|
||||
</GRs>
|
||||
</GAs>
|
||||
10
pom.xml
10
pom.xml
@ -44,6 +44,16 @@
|
||||
<groupId>org.apache.httpcomponents.client5</groupId>
|
||||
<artifactId>httpclient5</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.calimero</groupId>
|
||||
<artifactId>calimero-core</artifactId>
|
||||
<version>2.5.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jsoup</groupId>
|
||||
<artifactId>jsoup</artifactId>
|
||||
<version>1.18.1</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.h2database</groupId>
|
||||
|
||||
42
src/main/java/de/ph87/home/demo/DemoService.java
Normal file
42
src/main/java/de/ph87/home/demo/DemoService.java
Normal file
@ -0,0 +1,42 @@
|
||||
package de.ph87.home.demo;
|
||||
|
||||
import de.ph87.home.device.DeviceService;
|
||||
import de.ph87.home.knx.property.KnxPropertyService;
|
||||
import de.ph87.home.knx.property.KnxPropertyType;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.context.event.ApplicationStartedEvent;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import tuwien.auto.calimero.GroupAddress;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@Transactional
|
||||
@RequiredArgsConstructor
|
||||
public class DemoService {
|
||||
|
||||
private final KnxPropertyService knxPropertyService;
|
||||
|
||||
private final DeviceService deviceService;
|
||||
|
||||
@EventListener(ApplicationStartedEvent.class)
|
||||
public void startup() {
|
||||
knxPropertyService.create("fernseher", KnxPropertyType.BOOLEAN, adr(20), adr(4));
|
||||
knxPropertyService.create("verstaerker", KnxPropertyType.BOOLEAN, adr(825), adr(824));
|
||||
knxPropertyService.create("receiver", KnxPropertyType.BOOLEAN, adr(2561), adr(2560));
|
||||
|
||||
deviceService.create("EG Ambiente", "eg_ambiente", "eg_ambiente");
|
||||
deviceService.create("Wohnzimmer Fernseher", "fernseher", "fernseher");
|
||||
deviceService.create("Wohnzimmer Verstärker", "verstaerker", "verstaerker");
|
||||
deviceService.create("Wohnzimmer Fensterdeko", "fensterdeko", "fensterdeko");
|
||||
deviceService.create("Wohnzimmer Hängelampe", "haengelampe", "haengelampe");
|
||||
deviceService.create("Receiver", "receiver", "receiver");
|
||||
}
|
||||
|
||||
private static GroupAddress adr(final int rawGroupAddress) {
|
||||
return GroupAddress.freeStyle(rawGroupAddress);
|
||||
}
|
||||
|
||||
}
|
||||
@ -22,7 +22,7 @@ public class DeviceController {
|
||||
|
||||
@NonNull
|
||||
@RequestMapping(value = "list", method = {RequestMethod.GET, RequestMethod.POST})
|
||||
private List<DeviceDto> list(@RequestBody(required = false) @Nullable final DeviceFilter filter, @NonNull final HttpServletRequest request) {
|
||||
private List<DeviceDto> list(@RequestBody(required = false) @Nullable final DeviceFilter filter, @NonNull final HttpServletRequest request) throws PropertyTypeMismatch {
|
||||
log.debug("list: path={} filter={}", request.getServletPath(), filter);
|
||||
return deviceService.list(filter);
|
||||
}
|
||||
@ -36,7 +36,7 @@ public class DeviceController {
|
||||
|
||||
@Nullable
|
||||
@GetMapping("getState/{uuidOrSlug}")
|
||||
private Boolean getState(@PathVariable @NonNull final String uuidOrSlug, @NonNull final HttpServletRequest request) throws DeviceNotFound {
|
||||
private Boolean getState(@PathVariable @NonNull final String uuidOrSlug, @NonNull final HttpServletRequest request) throws DeviceNotFound, PropertyTypeMismatch {
|
||||
log.debug("getState: path={}", request.getServletPath());
|
||||
return deviceService.toDto(uuidOrSlug).getStateValue();
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package de.ph87.home.device;
|
||||
|
||||
import de.ph87.home.property.State;
|
||||
import de.ph87.home.property.PropertyDto;
|
||||
import de.ph87.home.property.PropertyTypeMismatch;
|
||||
import jakarta.annotation.Nullable;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
@ -20,25 +21,36 @@ public class DeviceDto {
|
||||
private final String slug;
|
||||
|
||||
@NonNull
|
||||
private final String stateProperty;
|
||||
private final String stateConfig;
|
||||
|
||||
@Nullable
|
||||
private final State<Boolean> state;
|
||||
@ToString.Exclude
|
||||
private final PropertyDto<Boolean> state;
|
||||
|
||||
public DeviceDto(@NonNull final Device device, @Nullable final State<Boolean> state) {
|
||||
public DeviceDto(@NonNull final Device device, @Nullable final PropertyDto<Boolean> state) {
|
||||
this.uuid = device.getUuid();
|
||||
this.name = device.getName();
|
||||
this.slug = device.getSlug();
|
||||
this.stateProperty = device.getStateProperty();
|
||||
this.stateConfig = device.getStateProperty();
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Boolean getStateValue() {
|
||||
@ToString.Include
|
||||
public String state() {
|
||||
try {
|
||||
return "" + getStateValue();
|
||||
} catch (PropertyTypeMismatch e) {
|
||||
return "[PropertyTypeMismatch]";
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Boolean getStateValue() throws PropertyTypeMismatch {
|
||||
if (state == null) {
|
||||
return null;
|
||||
}
|
||||
return state.getValue();
|
||||
return state.getStateValueAs(Boolean.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -1,21 +0,0 @@
|
||||
package de.ph87.home.device;
|
||||
|
||||
import de.ph87.home.property.PropertyDto;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.ToString;
|
||||
|
||||
@Getter
|
||||
@ToString
|
||||
@RequiredArgsConstructor
|
||||
public class DeviceEvent {
|
||||
|
||||
private final DeviceDto deviceDto;
|
||||
|
||||
private final PropertyDto<?> propertyDto;
|
||||
|
||||
public boolean isValueDifferent() {
|
||||
return propertyDto.isValueChanged();
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,6 +1,7 @@
|
||||
package de.ph87.home.device;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import de.ph87.home.property.PropertyTypeMismatch;
|
||||
import jakarta.annotation.Nullable;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
@ -23,14 +24,15 @@ public class DeviceFilter {
|
||||
private Boolean stateFalse;
|
||||
|
||||
@SuppressWarnings("RedundantIfStatement")
|
||||
public boolean filter(@NonNull final DeviceDto dto) {
|
||||
public boolean filter(@NonNull final DeviceDto dto) throws PropertyTypeMismatch {
|
||||
if (stateNull != null && stateNull != (dto.getState() == null)) {
|
||||
return false;
|
||||
}
|
||||
if (stateTrue != null && (dto.getState() == null || stateTrue != dto.getState().getValue())) {
|
||||
final Boolean value = dto.getStateValue();
|
||||
if (stateTrue != null && (dto.getState() == null || stateTrue != value)) {
|
||||
return false;
|
||||
}
|
||||
if (stateFalse != null && (dto.getState() == null || stateFalse == dto.getState().getValue())) {
|
||||
if (stateFalse != null && (dto.getState() == null || stateFalse == value)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
||||
@ -2,7 +2,6 @@ package de.ph87.home.device;
|
||||
|
||||
import de.ph87.home.property.*;
|
||||
import jakarta.annotation.Nullable;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@ -11,6 +10,7 @@ import org.springframework.context.event.EventListener;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Slf4j
|
||||
@ -25,54 +25,57 @@ public class DeviceService {
|
||||
|
||||
private final ApplicationEventPublisher applicationEventPublisher;
|
||||
|
||||
@PostConstruct
|
||||
public void postConstruct() {
|
||||
deviceRepository.save(new Device("EG Ambiente", "eg_ambiente", "eg_ambiente"));
|
||||
deviceRepository.save(new Device("Wohnzimmer Fernseher", "wohnzimmer_fernseher", "wohnzimmer_fernseher"));
|
||||
deviceRepository.save(new Device("Wohnzimmer Verstärker", "wohnzimmer_verstaerker", "wohnzimmer_verstaerker"));
|
||||
deviceRepository.save(new Device("Wohnzimmer Fensterdeko", "wohnzimmer_fensterdeko", "wohnzimmer_fensterdeko"));
|
||||
deviceRepository.save(new Device("Wohnzimmer Hängelampe", "wohnzimmer_haengelampe", "wohnzimmer_haengelampe"));
|
||||
@NonNull
|
||||
public DeviceDto create(@NonNull final String name, @NonNull final String slug, @NonNull final String stateProperty) {
|
||||
return toDto(deviceRepository.save(new Device(name, slug, stateProperty)));
|
||||
}
|
||||
|
||||
public void setState(@NonNull final String uuidOrSlug, final boolean state) throws DeviceNotFound, PropertyNotFound, PropertyNotWritable, PropertyTypeMismatch {
|
||||
log.debug("setState: uuidOrSlug={}, state={}", uuidOrSlug, state);
|
||||
final Device device = byUuidOrSlug(uuidOrSlug);
|
||||
log.debug("setState: device={}", device);
|
||||
final Device device = getByUuidOrSlug(uuidOrSlug);
|
||||
propertyService.write(device.getStateProperty(), state, Boolean.class);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public DeviceDto toDto(final @NonNull String uuidOrSlug) throws DeviceNotFound {
|
||||
return toDto(byUuidOrSlug(uuidOrSlug));
|
||||
return toDto(getByUuidOrSlug(uuidOrSlug));
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public DeviceDto toDto(@NonNull final Device device) {
|
||||
final State<Boolean> state = propertyService.readSafe(device.getStateProperty(), Boolean.class);
|
||||
final PropertyDto<Boolean> state = propertyService.dtoByIdAndTypeOrNull(device.getStateProperty(), Boolean.class);
|
||||
return new DeviceDto(device, state);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private Device byUuidOrSlug(@NonNull final String uuidOrSlug) throws DeviceNotFound {
|
||||
private Device getByUuidOrSlug(@NonNull final String uuidOrSlug) throws DeviceNotFound {
|
||||
return deviceRepository.findByUuidOrSlug(uuidOrSlug, uuidOrSlug).orElseThrow(() -> new DeviceNotFound("uuidOrSlug", uuidOrSlug));
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public List<DeviceDto> list(@Nullable final DeviceFilter filter) {
|
||||
return deviceRepository.findAll().stream().map(this::toDto).filter(device -> filter == null || filter.filter(device)).toList();
|
||||
public List<DeviceDto> list(@Nullable final DeviceFilter filter) throws PropertyTypeMismatch {
|
||||
final List<DeviceDto> all = deviceRepository.findAll().stream().map(this::toDto).toList();
|
||||
if (filter == null) {
|
||||
return all;
|
||||
}
|
||||
final List<DeviceDto> results = new ArrayList<>();
|
||||
for (final DeviceDto dto : all) {
|
||||
if (filter.filter(dto)) {
|
||||
results.add(dto);
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
@EventListener(PropertyDto.class)
|
||||
public void onPropertyChange(@NonNull final PropertyDto<?> dto) {
|
||||
deviceRepository.findAllByStateProperty(dto.getId())
|
||||
.forEach(device -> {
|
||||
final DeviceEvent deviceEvent = new DeviceEvent(toDto(device), dto);
|
||||
log.debug("Device updated: {}", deviceEvent.getDeviceDto());
|
||||
if (deviceEvent.isValueDifferent()) {
|
||||
log.info("Device changed: {}", deviceEvent.getDeviceDto());
|
||||
}
|
||||
applicationEventPublisher.publishEvent(deviceEvent);
|
||||
});
|
||||
deviceRepository.findAllByStateProperty(dto.getId()).forEach(this::publish);
|
||||
}
|
||||
|
||||
private void publish(@NonNull final Device device) {
|
||||
final DeviceDto deviceDto = toDto(device);
|
||||
log.info("Device updated: {}", deviceDto);
|
||||
applicationEventPublisher.publishEvent(deviceDto);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -1,31 +0,0 @@
|
||||
package de.ph87.home.dummy;
|
||||
|
||||
import de.ph87.home.property.PropertyService;
|
||||
import de.ph87.home.property.State;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class DummyService {
|
||||
|
||||
private final PropertyService propertyService;
|
||||
|
||||
@PostConstruct
|
||||
public void postConstruct() {
|
||||
register("eg_ambiente");
|
||||
register("wohnzimmer_fernseher");
|
||||
register("wohnzimmer_verstaerker");
|
||||
register("wohnzimmer_fensterdeko");
|
||||
register("wohnzimmer_haengelampe");
|
||||
register("receiver");
|
||||
}
|
||||
|
||||
private void register(final String id) {
|
||||
propertyService.register(id, Boolean.class, (property, value) -> property.setState(new State<>(value)));
|
||||
}
|
||||
|
||||
}
|
||||
45
src/main/java/de/ph87/home/knx/DPT.java
Normal file
45
src/main/java/de/ph87/home/knx/DPT.java
Normal file
@ -0,0 +1,45 @@
|
||||
package de.ph87.home.knx;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import tuwien.auto.calimero.KNXException;
|
||||
import tuwien.auto.calimero.dptxlator.DPTXlator;
|
||||
import tuwien.auto.calimero.dptxlator.TranslatorTypes;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@Getter
|
||||
public class DPT {
|
||||
|
||||
public static final Pattern DPT_REGEX = Pattern.compile("^DPS?T-(?<main>\\d+)(?:-(?<sub>\\d+))?$");
|
||||
|
||||
public final int main;
|
||||
|
||||
public final int sub;
|
||||
|
||||
public DPT(@NonNull final String dpt) throws DPTException {
|
||||
final Matcher matcher = DPT_REGEX.matcher(dpt);
|
||||
if (!matcher.find()) {
|
||||
throw new DPTException(dpt);
|
||||
}
|
||||
try {
|
||||
main = Integer.parseInt(matcher.group("main"));
|
||||
final String subStr = matcher.group("sub");
|
||||
sub = Integer.parseInt(subStr == null ? "1" : subStr);
|
||||
} catch (NumberFormatException e) {
|
||||
throw new DPTException(dpt, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "%d.%03d".formatted(main, sub);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public DPTXlator createTranslator() throws KNXException {
|
||||
return TranslatorTypes.createTranslator(main, toString());
|
||||
}
|
||||
|
||||
}
|
||||
15
src/main/java/de/ph87/home/knx/DPTException.java
Normal file
15
src/main/java/de/ph87/home/knx/DPTException.java
Normal file
@ -0,0 +1,15 @@
|
||||
package de.ph87.home.knx;
|
||||
|
||||
import lombok.NonNull;
|
||||
|
||||
public class DPTException extends Exception {
|
||||
|
||||
public DPTException(@NonNull final String dpt) {
|
||||
super("Failed to parse DPT: " + dpt);
|
||||
}
|
||||
|
||||
public DPTException(@NonNull final String dpt, @NonNull final Exception inner) {
|
||||
super("Failed to parse DPT: " + dpt, inner);
|
||||
}
|
||||
|
||||
}
|
||||
67
src/main/java/de/ph87/home/knx/Group.java
Normal file
67
src/main/java/de/ph87/home/knx/Group.java
Normal file
@ -0,0 +1,67 @@
|
||||
package de.ph87.home.knx;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import lombok.ToString;
|
||||
import tuwien.auto.calimero.GroupAddress;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
@Getter
|
||||
@ToString
|
||||
public class Group {
|
||||
|
||||
@NonNull
|
||||
@ToString.Exclude
|
||||
private String id;
|
||||
|
||||
@NonNull
|
||||
private final GroupAddress address;
|
||||
|
||||
@NonNull
|
||||
private String name;
|
||||
|
||||
@NonNull
|
||||
@ToString.Exclude
|
||||
private String description;
|
||||
|
||||
@NonNull
|
||||
private DPT dpt;
|
||||
|
||||
@ToString.Exclude
|
||||
private long puid;
|
||||
|
||||
public Group(@NonNull final String id, @NonNull final GroupAddress address, @NonNull final String name, @NonNull final String description, @NonNull final DPT dpt, final long puid) {
|
||||
this.id = id;
|
||||
this.address = address;
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
this.dpt = dpt;
|
||||
this.puid = puid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (!(o instanceof final Group group)) {
|
||||
return false;
|
||||
}
|
||||
return Objects.equals(address, group.address);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hashCode(address);
|
||||
}
|
||||
|
||||
public void merge(@NonNull final Group group) {
|
||||
this.id = group.id;
|
||||
this.name = group.name;
|
||||
this.description = group.description;
|
||||
this.dpt = group.dpt;
|
||||
this.puid = group.puid;
|
||||
}
|
||||
|
||||
}
|
||||
31
src/main/java/de/ph87/home/knx/GroupAddressJpaConverter.java
Normal file
31
src/main/java/de/ph87/home/knx/GroupAddressJpaConverter.java
Normal file
@ -0,0 +1,31 @@
|
||||
package de.ph87.home.knx;
|
||||
|
||||
import io.micrometer.common.lang.Nullable;
|
||||
import jakarta.persistence.AttributeConverter;
|
||||
import jakarta.persistence.Converter;
|
||||
import lombok.SneakyThrows;
|
||||
import tuwien.auto.calimero.GroupAddress;
|
||||
|
||||
@Converter(autoApply = true)
|
||||
public class GroupAddressJpaConverter implements AttributeConverter<GroupAddress, String> {
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String convertToDatabaseColumn(@Nullable final GroupAddress groupAddress) {
|
||||
if (groupAddress == null) {
|
||||
return null;
|
||||
}
|
||||
return groupAddress.toString();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public GroupAddress convertToEntityAttribute(@Nullable final String string) {
|
||||
if (string == null) {
|
||||
return null;
|
||||
}
|
||||
return GroupAddress.from(string);
|
||||
}
|
||||
|
||||
}
|
||||
16
src/main/java/de/ph87/home/knx/GroupLoaded.java
Normal file
16
src/main/java/de/ph87/home/knx/GroupLoaded.java
Normal file
@ -0,0 +1,16 @@
|
||||
package de.ph87.home.knx;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.ToString;
|
||||
|
||||
@Getter
|
||||
@ToString
|
||||
public class GroupLoaded {
|
||||
|
||||
private final Group group;
|
||||
|
||||
public GroupLoaded(final Group group) {
|
||||
this.group = group;
|
||||
}
|
||||
|
||||
}
|
||||
80
src/main/java/de/ph87/home/knx/GroupService.java
Normal file
80
src/main/java/de/ph87/home/knx/GroupService.java
Normal file
@ -0,0 +1,80 @@
|
||||
package de.ph87.home.knx;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jsoup.Jsoup;
|
||||
import org.jsoup.nodes.Document;
|
||||
import org.jsoup.nodes.Element;
|
||||
import org.jsoup.select.Elements;
|
||||
import org.springframework.boot.context.event.ApplicationStartedEvent;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.stereotype.Service;
|
||||
import tuwien.auto.calimero.GroupAddress;
|
||||
import tuwien.auto.calimero.KNXFormatException;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class GroupService {
|
||||
|
||||
private final List<Group> groupList = new ArrayList<>();
|
||||
|
||||
private final ApplicationEventPublisher applicationEventPublisher;
|
||||
|
||||
@EventListener(ApplicationStartedEvent.class)
|
||||
public void load() {
|
||||
try {
|
||||
final Document document = Jsoup.parse(new File("./data/G"));
|
||||
final Elements gaList = document.select("GA");
|
||||
gaList.stream().map(this::load).filter(Optional::isPresent).map(Optional::get).forEach(this::merge);
|
||||
} catch (IOException e) {
|
||||
log.error("Failed to load groups: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private Optional<Group> load(@NonNull final Element element) {
|
||||
try {
|
||||
final String id = element.attr("Id");
|
||||
final GroupAddress address = GroupAddress.from(element.attr("Address"));
|
||||
final String name = element.attr("Name");
|
||||
final String description = element.attr("Description");
|
||||
final DPT dpt = new DPT(element.attr("DatapointType"));
|
||||
final long puid = Long.parseLong(element.attr("Puid"));
|
||||
final Group group = new Group(id, address, name, description, dpt, puid);
|
||||
log.info("Group loaded: {}", group);
|
||||
return Optional.of(group);
|
||||
} catch (KNXFormatException | DPTException e) {
|
||||
log.error(e.getMessage());
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
private void merge(@NonNull Group group) {
|
||||
synchronized (groupList) {
|
||||
groupList.stream()
|
||||
.filter(g -> g.equals(group))
|
||||
.findFirst().ifPresentOrElse(
|
||||
existing -> existing.merge(group),
|
||||
() -> groupList.add(group)
|
||||
);
|
||||
}
|
||||
applicationEventPublisher.publishEvent(new GroupLoaded(group));
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Optional<Group> findByAddress(@NonNull final GroupAddress address) {
|
||||
synchronized (groupList) {
|
||||
return groupList.stream().filter(g -> g.getAddress().equals(address)).findFirst();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
18
src/main/java/de/ph87/home/knx/KnxConfig.java
Normal file
18
src/main/java/de/ph87/home/knx/KnxConfig.java
Normal file
@ -0,0 +1,18 @@
|
||||
package de.ph87.home.knx;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Data
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "de.ph87.home.knx")
|
||||
public class KnxConfig {
|
||||
|
||||
private boolean enabled = true;
|
||||
|
||||
private String remoteAddress = "10.0.0.102";
|
||||
|
||||
private int remotePort = 3671;
|
||||
|
||||
}
|
||||
184
src/main/java/de/ph87/home/knx/link/KnxLinkService.java
Normal file
184
src/main/java/de/ph87/home/knx/link/KnxLinkService.java
Normal file
@ -0,0 +1,184 @@
|
||||
package de.ph87.home.knx.link;
|
||||
|
||||
import de.ph87.home.knx.Group;
|
||||
import de.ph87.home.knx.KnxConfig;
|
||||
import de.ph87.home.knx.link.request.Request;
|
||||
import de.ph87.home.knx.link.request.RequestRead;
|
||||
import de.ph87.home.knx.link.request.RequestWrite;
|
||||
import de.ph87.home.knx.link.router.Router;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.NotImplementedException;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.stereotype.Service;
|
||||
import tuwien.auto.calimero.DetachEvent;
|
||||
import tuwien.auto.calimero.KNXException;
|
||||
import tuwien.auto.calimero.datapoint.StateDP;
|
||||
import tuwien.auto.calimero.dptxlator.DPTXlator;
|
||||
import tuwien.auto.calimero.link.KNXNetworkLinkIP;
|
||||
import tuwien.auto.calimero.link.medium.TPSettings;
|
||||
import tuwien.auto.calimero.process.ProcessCommunicatorImpl;
|
||||
import tuwien.auto.calimero.process.ProcessEvent;
|
||||
import tuwien.auto.calimero.process.ProcessListener;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.Inet4Address;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class KnxLinkService {
|
||||
|
||||
private static final Duration RESPONSE_TIMEOUT = Duration.ofMillis(500);
|
||||
|
||||
private final KnxConfig knxConfig;
|
||||
|
||||
private final ApplicationEventPublisher applicationEventPublisher;
|
||||
|
||||
private KNXNetworkLinkIP link = null;
|
||||
|
||||
private ProcessCommunicatorImpl processCommunicator = null;
|
||||
|
||||
private final List<Request> requests = new ArrayList<>();
|
||||
|
||||
@PostConstruct
|
||||
public void postConstruct() {
|
||||
if (!knxConfig.isEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final Thread thread = new Thread(this::run, getClass().getSimpleName());
|
||||
thread.setDaemon(true);
|
||||
thread.start();
|
||||
}
|
||||
|
||||
private void run() {
|
||||
try {
|
||||
while (true) {
|
||||
execute(waitForRequest());
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
log.warn("Interrupted");
|
||||
}
|
||||
}
|
||||
|
||||
public Request waitForRequest() throws InterruptedException {
|
||||
synchronized (requests) {
|
||||
while (requests.isEmpty() || processCommunicator == null) {
|
||||
if (processCommunicator == null) {
|
||||
connect();
|
||||
continue;
|
||||
}
|
||||
requests.wait();
|
||||
}
|
||||
return requests.removeFirst();
|
||||
}
|
||||
}
|
||||
|
||||
private void execute(@NonNull final Request request) {
|
||||
try {
|
||||
log.debug("Executing request: {}", request);
|
||||
if (request instanceof final RequestRead read) {
|
||||
processCommunicator.read(new StateDP(read.getGroup().getAddress(), ""));
|
||||
} else if (request instanceof final RequestWrite write) {
|
||||
processCommunicator.write(write.getGroup().getAddress(), write.getDptXlator());
|
||||
} else {
|
||||
throw new NotImplementedException(Request.class.getSimpleName());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("KnxRequest failed: request={}, error={}", request, e.getMessage());
|
||||
processCommunicator = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void queueRead(@NonNull final Group group) {
|
||||
if (!knxConfig.isEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
synchronized (requests) {
|
||||
if (requests.stream().filter(r -> r instanceof RequestRead read && read.group.equals(group)).findFirst().isEmpty()) {
|
||||
add(new RequestRead(group));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void queueWrite(@NonNull final Group group, @NonNull final DPTXlator dptXlator) {
|
||||
if (!knxConfig.isEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
synchronized (requests) {
|
||||
requests.stream().filter(r -> r instanceof RequestWrite write && write.getGroup().equals(group)).map(RequestWrite.class::cast).findFirst().ifPresentOrElse(
|
||||
request -> request.update(dptXlator),
|
||||
() -> add(new RequestWrite(group, dptXlator))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private void add(@NonNull final Request request) {
|
||||
synchronized (requests) {
|
||||
requests.add(request);
|
||||
requests.notify();
|
||||
}
|
||||
}
|
||||
|
||||
public void connect() throws InterruptedException {
|
||||
try {
|
||||
final Inet4Address remoteAddress = (Inet4Address) Inet4Address.getByName(knxConfig.getRemoteAddress());
|
||||
final Inet4Address localAddress = Router.getLocalInet4AddressForRemoteAddress(remoteAddress);
|
||||
log.info("Connecting KNX link: {} -> {}", localAddress, remoteAddress);
|
||||
link = KNXNetworkLinkIP.newTunnelingLink(new InetSocketAddress(localAddress, 0), new InetSocketAddress(remoteAddress, knxConfig.getRemotePort()), false, new TPSettings());
|
||||
processCommunicator = new ProcessCommunicatorImpl(link);
|
||||
processCommunicator.addProcessListener(new MyProcessListener());
|
||||
processCommunicator.responseTimeout(RESPONSE_TIMEOUT);
|
||||
log.info("KNX link established.");
|
||||
synchronized (requests) {
|
||||
requests.notify();
|
||||
}
|
||||
} catch (IOException | KNXException | InterruptedException e) {
|
||||
log.error("Failed to connect KNX: {}", e.toString());
|
||||
synchronized (requests) {
|
||||
requests.wait(1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class MyProcessListener implements ProcessListener {
|
||||
|
||||
@Override
|
||||
public void groupReadRequest(@NonNull final ProcessEvent processEvent) {
|
||||
log.debug("groupReadRequest: {}", processEvent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void groupReadResponse(@NonNull final ProcessEvent processEvent) {
|
||||
log.debug("groupReadResponse: {}", processEvent);
|
||||
applicationEventPublisher.publishEvent(processEvent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void groupWrite(@NonNull final ProcessEvent processEvent) {
|
||||
log.debug("groupWrite: {}", processEvent);
|
||||
applicationEventPublisher.publishEvent(processEvent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void detached(@NonNull final DetachEvent detachEvent) {
|
||||
log.info("KNX link disconnected.");
|
||||
synchronized (requests) {
|
||||
processCommunicator = null;
|
||||
link = null;
|
||||
requests.notify();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
17
src/main/java/de/ph87/home/knx/link/request/Request.java
Normal file
17
src/main/java/de/ph87/home/knx/link/request/Request.java
Normal file
@ -0,0 +1,17 @@
|
||||
package de.ph87.home.knx.link.request;
|
||||
|
||||
import de.ph87.home.knx.Group;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
|
||||
@Getter
|
||||
public abstract class Request {
|
||||
|
||||
@NonNull
|
||||
public final Group group;
|
||||
|
||||
public Request(@NonNull final Group group) {
|
||||
this.group = group;
|
||||
}
|
||||
|
||||
}
|
||||
17
src/main/java/de/ph87/home/knx/link/request/RequestRead.java
Normal file
17
src/main/java/de/ph87/home/knx/link/request/RequestRead.java
Normal file
@ -0,0 +1,17 @@
|
||||
package de.ph87.home.knx.link.request;
|
||||
|
||||
import de.ph87.home.knx.Group;
|
||||
import lombok.NonNull;
|
||||
|
||||
public class RequestRead extends Request {
|
||||
|
||||
public RequestRead(@NonNull final Group group) {
|
||||
super(group);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Read(%s, %s, %s)".formatted(group.getAddress(), group.getDpt(), group.getName());
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,28 @@
|
||||
package de.ph87.home.knx.link.request;
|
||||
|
||||
import de.ph87.home.knx.Group;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import tuwien.auto.calimero.dptxlator.DPTXlator;
|
||||
|
||||
@Getter
|
||||
public class RequestWrite extends Request {
|
||||
|
||||
@NonNull
|
||||
private DPTXlator dptXlator;
|
||||
|
||||
public RequestWrite(@NonNull final Group group, @NonNull final DPTXlator dptXlator) {
|
||||
super(group);
|
||||
this.dptXlator = dptXlator;
|
||||
}
|
||||
|
||||
public void update(@NonNull final DPTXlator dptXlator) {
|
||||
this.dptXlator = dptXlator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Write(%s, %s, %s, %s)".formatted(group.getAddress(), group.getDpt(), group.getName(), dptXlator.getValue());
|
||||
}
|
||||
|
||||
}
|
||||
93
src/main/java/de/ph87/home/knx/link/router/Route.java
Normal file
93
src/main/java/de/ph87/home/knx/link/router/Route.java
Normal file
@ -0,0 +1,93 @@
|
||||
package de.ph87.home.knx.link.router;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.net.Inet4Address;
|
||||
import java.net.UnknownHostException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@Slf4j
|
||||
public class Route {
|
||||
|
||||
private static final Pattern regex = Pattern.compile("^([\\d.]+)\\s+([\\d.]+)\\s+([\\d.]+)\\s+(\\S+)\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)\\s+(\\S+)$");
|
||||
|
||||
public final Inet4Address network;
|
||||
|
||||
public final Inet4Address router;
|
||||
|
||||
public final Inet4Address netmask;
|
||||
|
||||
public final int metric;
|
||||
|
||||
public final String iface;
|
||||
|
||||
public final int networkInt;
|
||||
|
||||
public final int routerInt;
|
||||
|
||||
public final int netmaskInt;
|
||||
|
||||
public final Inet4Address addressFirst;
|
||||
|
||||
public final int addressFirstInt;
|
||||
|
||||
public final Inet4Address addressLast;
|
||||
|
||||
public final int addressLastInt;
|
||||
|
||||
public Route(final Inet4Address network, final Inet4Address router, final Inet4Address netmask, final int metric, final String iface) throws UnknownHostException {
|
||||
this.network = network;
|
||||
this.router = router;
|
||||
this.netmask = netmask;
|
||||
this.metric = metric;
|
||||
this.iface = iface;
|
||||
|
||||
this.networkInt = toInt(network);
|
||||
this.routerInt = toInt(router);
|
||||
this.netmaskInt = toInt(netmask);
|
||||
this.addressFirstInt = networkInt & netmaskInt;
|
||||
this.addressLastInt = networkInt | ~netmaskInt;
|
||||
this.addressFirst = fromInt(addressFirstInt);
|
||||
this.addressLast = fromInt(addressLastInt);
|
||||
}
|
||||
|
||||
public boolean matches(final Inet4Address address) {
|
||||
final int addressInt = toInt(address);
|
||||
return addressFirstInt <= addressInt && addressInt <= addressLastInt;
|
||||
}
|
||||
|
||||
private static int toInt(final Inet4Address address) {
|
||||
return ByteBuffer.wrap(address.getAddress()).getInt();
|
||||
}
|
||||
|
||||
private static Inet4Address fromInt(final int value) throws UnknownHostException {
|
||||
return (Inet4Address) Inet4Address.getByAddress(ByteBuffer.allocate(4).putInt(value).array());
|
||||
}
|
||||
|
||||
public static Optional<Route> parse(final String line) {
|
||||
final Matcher matcher = regex.matcher(line);
|
||||
try {
|
||||
if (matcher.matches()) {
|
||||
final String networkString = matcher.group(1);
|
||||
final Inet4Address network = (Inet4Address) Inet4Address.getByName(Objects.equals(networkString, "default") ? "0.0.0.0" : networkString);
|
||||
final Inet4Address router = (Inet4Address) Inet4Address.getByName(matcher.group(2));
|
||||
final Inet4Address netmask = (Inet4Address) Inet4Address.getByName(matcher.group(3));
|
||||
final int metric = Integer.parseInt(matcher.group(5));
|
||||
final String iface = matcher.group(8);
|
||||
return Optional.of(new Route(network, router, netmask, metric, iface));
|
||||
}
|
||||
} catch (UnknownHostException e) {
|
||||
log.error(e.getMessage());
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
public boolean isDefault() {
|
||||
return network.isAnyLocalAddress() && netmask.isAnyLocalAddress();
|
||||
}
|
||||
|
||||
}
|
||||
40
src/main/java/de/ph87/home/knx/link/router/Router.java
Normal file
40
src/main/java/de/ph87/home/knx/link/router/Router.java
Normal file
@ -0,0 +1,40 @@
|
||||
package de.ph87.home.knx.link.router;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.Inet4Address;
|
||||
import java.net.NetworkInterface;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class Router {
|
||||
|
||||
public static Inet4Address getLocalInet4AddressForRemoteAddress(final Inet4Address remoteAddress) throws IOException {
|
||||
final Route route = getRoute(remoteAddress);
|
||||
final NetworkInterface networkInterface = NetworkInterface.getByName(route.iface);
|
||||
return (Inet4Address) networkInterface.inetAddresses().filter(address -> address instanceof Inet4Address).findFirst().orElse(null);
|
||||
}
|
||||
|
||||
public static Route getRoute(final Inet4Address remoteAddress) throws IOException {
|
||||
final List<Route> routes = execute(Route::parse, "/sbin/route", "-n");
|
||||
final Optional<Route> routeOptional = routes.stream()
|
||||
.filter(r -> !r.isDefault())
|
||||
.filter(r -> r.matches(remoteAddress))
|
||||
.reduce((a, b) -> a.metric < b.metric ? a : b)
|
||||
.or(() -> routes.stream()
|
||||
.filter(Route::isDefault)
|
||||
.reduce((a, b) -> a.metric < b.metric ? a : b)
|
||||
);
|
||||
if (routeOptional.isEmpty()) {
|
||||
throw new IOException("No route found for remoteAddress: " + remoteAddress);
|
||||
}
|
||||
return routeOptional.get();
|
||||
}
|
||||
|
||||
private static <T> List<T> execute(final Function<String, Optional<T>> parser, final String... commands) throws IOException {
|
||||
final String output = new String(new ProcessBuilder(commands).start().getInputStream().readAllBytes());
|
||||
return Arrays.stream(output.split("\\n")).map(parser).filter(Optional::isPresent).map(Optional::get).toList();
|
||||
}
|
||||
|
||||
}
|
||||
44
src/main/java/de/ph87/home/knx/property/KnxProperty.java
Normal file
44
src/main/java/de/ph87/home/knx/property/KnxProperty.java
Normal file
@ -0,0 +1,44 @@
|
||||
package de.ph87.home.knx.property;
|
||||
|
||||
import de.ph87.home.knx.GroupAddressJpaConverter;
|
||||
import jakarta.annotation.Nullable;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Convert;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Id;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.NonNull;
|
||||
import lombok.ToString;
|
||||
import tuwien.auto.calimero.GroupAddress;
|
||||
|
||||
@Entity
|
||||
@Getter
|
||||
@ToString
|
||||
@NoArgsConstructor
|
||||
public class KnxProperty {
|
||||
|
||||
@Id
|
||||
@NonNull
|
||||
private String id;
|
||||
|
||||
@Nullable
|
||||
@Convert(converter = GroupAddressJpaConverter.class)
|
||||
private GroupAddress read;
|
||||
|
||||
@Nullable
|
||||
@Convert(converter = GroupAddressJpaConverter.class)
|
||||
private GroupAddress write;
|
||||
|
||||
@NonNull
|
||||
@Column(nullable = false)
|
||||
private KnxPropertyType type;
|
||||
|
||||
public KnxProperty(@NonNull final String id, @NonNull final KnxPropertyType type, @Nullable final GroupAddress read, @Nullable final GroupAddress write) {
|
||||
this.id = id;
|
||||
this.type = type;
|
||||
this.read = read;
|
||||
this.write = write;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,13 @@
|
||||
package de.ph87.home.knx.property;
|
||||
|
||||
import lombok.NonNull;
|
||||
import org.springframework.data.repository.ListCrudRepository;
|
||||
import tuwien.auto.calimero.GroupAddress;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface KnxPropertyRepository extends ListCrudRepository<KnxProperty, String> {
|
||||
|
||||
List<KnxProperty> findDistinctByReadOrWrite(@NonNull GroupAddress read, @NonNull GroupAddress write);
|
||||
|
||||
}
|
||||
136
src/main/java/de/ph87/home/knx/property/KnxPropertyService.java
Normal file
136
src/main/java/de/ph87/home/knx/property/KnxPropertyService.java
Normal file
@ -0,0 +1,136 @@
|
||||
package de.ph87.home.knx.property;
|
||||
|
||||
import de.ph87.home.knx.Group;
|
||||
import de.ph87.home.knx.GroupLoaded;
|
||||
import de.ph87.home.knx.GroupService;
|
||||
import de.ph87.home.knx.link.KnxLinkService;
|
||||
import de.ph87.home.property.PropertyNotFound;
|
||||
import de.ph87.home.property.PropertyNotOwned;
|
||||
import de.ph87.home.property.PropertyService;
|
||||
import de.ph87.home.property.PropertyTypeMismatch;
|
||||
import jakarta.annotation.Nullable;
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import tuwien.auto.calimero.GroupAddress;
|
||||
import tuwien.auto.calimero.KNXException;
|
||||
import tuwien.auto.calimero.dptxlator.DPTXlator;
|
||||
import tuwien.auto.calimero.dptxlator.DPTXlatorBoolean;
|
||||
import tuwien.auto.calimero.process.ProcessEvent;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@Transactional
|
||||
@EnableScheduling
|
||||
@RequiredArgsConstructor
|
||||
public class KnxPropertyService {
|
||||
|
||||
private final KnxPropertyRepository knxPropertyRepository;
|
||||
|
||||
private final PropertyService propertyService;
|
||||
|
||||
private final GroupService groupService;
|
||||
|
||||
private final KnxLinkService knxLinkService;
|
||||
|
||||
@Scheduled(initialDelay = 1, fixedDelay = 1, timeUnit = TimeUnit.HOURS)
|
||||
public void readAll() {
|
||||
knxPropertyRepository.findAll().forEach(this::read);
|
||||
}
|
||||
|
||||
@EventListener(GroupLoaded.class)
|
||||
public void onGroupLoad(@NonNull final GroupLoaded groupLoaded) {
|
||||
findAllByAddress(groupLoaded.getGroup().getAddress()).forEach(this::read);
|
||||
}
|
||||
|
||||
public void create(@NonNull final String id, @NonNull final KnxPropertyType type, @Nullable final GroupAddress read, @Nullable final GroupAddress write) {
|
||||
final KnxProperty knxProperty = knxPropertyRepository.save(new KnxProperty(id, type, read, write));
|
||||
updateOrCreateProperty(knxProperty);
|
||||
}
|
||||
|
||||
private void updateOrCreateProperty(@NonNull final KnxProperty knxProperty) {
|
||||
try {
|
||||
propertyService.createOrUpdate(this, knxProperty.getId(), knxProperty.getType().clazz, (p, value) -> write(knxProperty, value));
|
||||
read(knxProperty);
|
||||
} catch (PropertyNotOwned e) {
|
||||
log.error("Failed to register KnxProperty: knxProperty={}, error={}", knxProperty, e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@EventListener(ProcessEvent.class)
|
||||
public void onProcessEvent(@NonNull final ProcessEvent event) {
|
||||
findAllByAddress(event.getDestination()).forEach(knxProperty -> onProcessEvent(knxProperty, event));
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private List<KnxProperty> findAllByAddress(@NonNull final GroupAddress address) {
|
||||
return knxPropertyRepository.findDistinctByReadOrWrite(address, address);
|
||||
}
|
||||
|
||||
private void onProcessEvent(@NonNull final KnxProperty knxProperty, @NonNull final ProcessEvent event) {
|
||||
log.debug("onProcessEvent: knxProperty={}, event={}", knxProperty, event);
|
||||
groupService.findByAddress(event.getDestination()).ifPresent(group -> onProcessEvent(knxProperty, event, group));
|
||||
}
|
||||
|
||||
private void onProcessEvent(@NonNull final KnxProperty knxProperty, @NonNull final ProcessEvent event, @NonNull final Group group) {
|
||||
log.debug("onProcessEvent: knxProperty={}, group={}, event={}", knxProperty, group, event);
|
||||
try {
|
||||
final DPTXlator translator = group.getDpt().createTranslator();
|
||||
translator.setData(event.getASDU());
|
||||
log.debug("translator: {}", translator);
|
||||
switch (knxProperty.getType()) {
|
||||
case BOOLEAN -> {
|
||||
if (!(translator instanceof final DPTXlatorBoolean booleanTranslator)) {
|
||||
throw new RuntimeException("DPTXlator type should be DPTXlatorBoolean for property.type = BOOLEAN but is: " + translator.getClass().getSimpleName());
|
||||
}
|
||||
propertyService.update(this, knxProperty.getId(), Boolean.class, booleanTranslator.getValueBoolean(), booleanTranslator.getValue());
|
||||
}
|
||||
case DOUBLE -> propertyService.update(this, knxProperty.getId(), Double.class, translator.getNumericValue(), translator.getValue());
|
||||
}
|
||||
} catch (KNXException | PropertyNotFound | PropertyTypeMismatch | PropertyNotOwned e) {
|
||||
log.error("Failed to handle ProcessEvent: knxProperty={}, error={}", knxProperty, e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private void read(@NonNull final KnxProperty knxProperty) {
|
||||
final Optional<KnxProperty> property = knxPropertyRepository.findById(knxProperty.getId());
|
||||
final Optional<GroupAddress> address = property.map(KnxProperty::getRead);
|
||||
final Optional<Group> group = address.map(groupService::findByAddress).filter(Optional::isPresent).map(Optional::get);
|
||||
group.ifPresent(knxLinkService::queueRead);
|
||||
}
|
||||
|
||||
private void write(@NonNull final KnxProperty knxProperty, @NonNull final Object value) {
|
||||
knxPropertyRepository.findById(knxProperty.getId()).map(KnxProperty::getWrite).map(groupService::findByAddress).filter(Optional::isPresent).map(Optional::get).ifPresent(group -> write(knxProperty, group, value));
|
||||
}
|
||||
|
||||
private void write(@NonNull final KnxProperty knxProperty, @NonNull final Group group, @NonNull final Object value) {
|
||||
try {
|
||||
if (!knxProperty.getType().clazz.isInstance(value)) {
|
||||
throw new RuntimeException("Cannot write invalid value type: type=%s, value=%s, knxProperty=%s".formatted(value.getClass(), value, knxProperty));
|
||||
}
|
||||
final DPTXlator translator = group.getDpt().createTranslator();
|
||||
switch (knxProperty.getType()) {
|
||||
case BOOLEAN -> {
|
||||
if (!(translator instanceof final DPTXlatorBoolean booleanTranslator)) {
|
||||
throw new RuntimeException("DPTXlator type should be DPTXlatorBoolean for property.type = BOOLEAN but is: " + translator.getClass().getSimpleName());
|
||||
}
|
||||
booleanTranslator.setValue((boolean) value);
|
||||
}
|
||||
case DOUBLE -> translator.setValue((double) value);
|
||||
}
|
||||
knxLinkService.queueWrite(group, translator);
|
||||
} catch (KNXException e) {
|
||||
log.error("Failed to write KnxProperty: knxProperty={}, error={}", knxProperty, e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
17
src/main/java/de/ph87/home/knx/property/KnxPropertyType.java
Normal file
17
src/main/java/de/ph87/home/knx/property/KnxPropertyType.java
Normal file
@ -0,0 +1,17 @@
|
||||
package de.ph87.home.knx.property;
|
||||
|
||||
import lombok.NonNull;
|
||||
|
||||
public enum KnxPropertyType {
|
||||
BOOLEAN(Boolean.class),
|
||||
DOUBLE(Double.class),
|
||||
;
|
||||
|
||||
@NonNull
|
||||
public final Class<?> clazz;
|
||||
|
||||
KnxPropertyType(@NonNull final Class<?> clazz) {
|
||||
this.clazz = clazz;
|
||||
}
|
||||
|
||||
}
|
||||
42
src/main/java/de/ph87/home/property/IProperty.java
Normal file
42
src/main/java/de/ph87/home/property/IProperty.java
Normal file
@ -0,0 +1,42 @@
|
||||
package de.ph87.home.property;
|
||||
|
||||
import jakarta.annotation.Nullable;
|
||||
import lombok.NonNull;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public interface IProperty<T> {
|
||||
|
||||
@NonNull
|
||||
String getId();
|
||||
|
||||
@Nullable
|
||||
State<T> getState();
|
||||
|
||||
@NonNull
|
||||
Class<T> getType();
|
||||
|
||||
@Nullable
|
||||
default T getStateValue() {
|
||||
if (getState() == null) {
|
||||
return null;
|
||||
}
|
||||
return getState().getValue();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
default <R extends T> R getStateValueAs(@NonNull final Class<R> type) throws PropertyTypeMismatch {
|
||||
if (this.getType() != type) {
|
||||
throw new PropertyTypeMismatch(this, type);
|
||||
}
|
||||
//noinspection unchecked
|
||||
return (R) getStateValue();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
default <R extends T> R getStateValueAs(@NonNull final Class<R> type, @NonNull final R fallbackIfNull) throws PropertyTypeMismatch {
|
||||
final R value = getStateValueAs(type);
|
||||
return Objects.requireNonNullElse(value, fallbackIfNull);
|
||||
}
|
||||
|
||||
}
|
||||
@ -13,21 +13,39 @@ import java.util.function.Consumer;
|
||||
@Getter
|
||||
@ToString
|
||||
@RequiredArgsConstructor
|
||||
public class Property<T> {
|
||||
public class Property<T> implements IProperty<T> {
|
||||
|
||||
@NonNull
|
||||
@ToString.Exclude
|
||||
private final Object owner;
|
||||
|
||||
@ToString.Include
|
||||
public String owner() {
|
||||
return owner.getClass().getSimpleName();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private final String id;
|
||||
|
||||
@NonNull
|
||||
@ToString.Exclude
|
||||
private final Class<T> type;
|
||||
|
||||
@ToString.Include
|
||||
public String type() {
|
||||
return type.getSimpleName();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@ToString.Exclude
|
||||
private final BiConsumer<Property<T>, T> write;
|
||||
|
||||
@NonNull
|
||||
@ToString.Exclude
|
||||
private final Consumer<Property<T>> onStateSet;
|
||||
|
||||
@Nullable
|
||||
@ToString.Exclude
|
||||
private State<T> lastState = null;
|
||||
|
||||
@Nullable
|
||||
@ -35,36 +53,13 @@ public class Property<T> {
|
||||
|
||||
private boolean valueChanged = false;
|
||||
|
||||
public void setState(@Nullable final State<T> state) {
|
||||
public void update(@Nullable final State<T> state) {
|
||||
this.lastState = this.state;
|
||||
this.state = state;
|
||||
this.valueChanged = (lastState == null) == (state == null) && (lastState == null || Objects.equals(lastState.getValue(), state.getValue()));
|
||||
this.onStateSet.accept(this);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public T getStateValue() {
|
||||
if (state == null) {
|
||||
return null;
|
||||
}
|
||||
return state.getValue();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public <R extends T> R getStateValueAs(@NonNull final Class<R> type) throws PropertyTypeMismatch {
|
||||
if (this.type != type) {
|
||||
throw new PropertyTypeMismatch(this, type);
|
||||
}
|
||||
//noinspection unchecked
|
||||
return (R) getStateValue();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public <R extends T> R getStateValueAs(@NonNull final Class<R> type, @NonNull final R fallbackIfNull) throws PropertyTypeMismatch {
|
||||
final R value = getStateValueAs(type);
|
||||
return Objects.requireNonNullElse(value, fallbackIfNull);
|
||||
}
|
||||
|
||||
public void write(@NonNull final T value) throws PropertyNotWritable {
|
||||
if (write == null) {
|
||||
throw new PropertyNotWritable(this);
|
||||
|
||||
@ -7,15 +7,22 @@ import lombok.ToString;
|
||||
|
||||
@Getter
|
||||
@ToString
|
||||
public class PropertyDto<T> {
|
||||
public class PropertyDto<T> implements IProperty<T> {
|
||||
|
||||
@NonNull
|
||||
private final String id;
|
||||
|
||||
@NonNull
|
||||
@ToString.Exclude
|
||||
private final Class<T> type;
|
||||
|
||||
@ToString.Include
|
||||
public String type() {
|
||||
return type.getSimpleName();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@ToString.Exclude
|
||||
private final State<T> lastState;
|
||||
|
||||
@Nullable
|
||||
|
||||
11
src/main/java/de/ph87/home/property/PropertyNotOwned.java
Normal file
11
src/main/java/de/ph87/home/property/PropertyNotOwned.java
Normal file
@ -0,0 +1,11 @@
|
||||
package de.ph87.home.property;
|
||||
|
||||
import lombok.NonNull;
|
||||
|
||||
public class PropertyNotOwned extends Exception {
|
||||
|
||||
public PropertyNotOwned(@NonNull final Property<?> property, @NonNull final Object tryingOwner) {
|
||||
super("Property not owned: property=%s, tryingOwner=%s".formatted(property, tryingOwner));
|
||||
}
|
||||
|
||||
}
|
||||
@ -21,56 +21,76 @@ public class PropertyService {
|
||||
|
||||
private final List<Property<?>> propertyList = new ArrayList<>();
|
||||
|
||||
@Nullable
|
||||
public <TYPE> State<TYPE> readSafe(final @NonNull String id, @NonNull final Class<TYPE> type) {
|
||||
try {
|
||||
return this.read(id, type);
|
||||
} catch (PropertyTypeMismatch | PropertyNotFound e) {
|
||||
log.error(e.getMessage());
|
||||
return null;
|
||||
@SuppressWarnings("UnusedReturnValue")
|
||||
public <T> Property<T> createOrUpdate(@NonNull final Object owner, @NonNull final String id, final Class<T> type, final BiConsumer<Property<T>, T> write) throws PropertyNotOwned {
|
||||
if (id.isEmpty()) {
|
||||
throw new IllegalArgumentException("id cannot be empty");
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public <TYPE> State<TYPE> read(@NonNull final String id, @NonNull final Class<TYPE> type) throws PropertyNotFound, PropertyTypeMismatch {
|
||||
log.debug("read: id={}", id);
|
||||
return byIdAndType(id, type).getState();
|
||||
}
|
||||
|
||||
public <TYPE> void write(@NonNull final String id, @NonNull final TYPE value, final Class<TYPE> type) throws PropertyNotFound, PropertyTypeMismatch, PropertyNotWritable {
|
||||
log.debug("write: id={}, type={}, value={}", id, value.getClass().getSimpleName(), value);
|
||||
byIdAndType(id, type).write(value);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public <TYPE> Property<TYPE> byIdAndType(final String id, final Class<TYPE> type) throws PropertyNotFound, PropertyTypeMismatch {
|
||||
final Property<?> property = findById(id).orElseThrow(() -> new PropertyNotFound(id));
|
||||
if (type != property.getType()) {
|
||||
throw new PropertyTypeMismatch(property, type);
|
||||
}
|
||||
//noinspection unchecked
|
||||
return (Property<TYPE>) property;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private Optional<Property<?>> findById(final @NonNull String id) {
|
||||
final Optional<Property<?>> optional;
|
||||
synchronized (propertyList) {
|
||||
optional = propertyList.stream().filter(p -> p.getId().equals(id)).findFirst();
|
||||
final boolean removed = findByIdAndOwner(id, owner).map(propertyList::remove).orElse(false);
|
||||
if (propertyList.stream().anyMatch(property -> property.getId().equals(id))) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
final Property<T> property = new Property<>(owner, id, type, write, this::onStateSet);
|
||||
propertyList.add(property);
|
||||
if (removed) {
|
||||
log.info("Property property: {}", property);
|
||||
} else {
|
||||
log.info("Property modified: {}", property);
|
||||
}
|
||||
return property;
|
||||
}
|
||||
}
|
||||
|
||||
public <T> void write(@NonNull final String id, @NonNull final T value, final Class<T> type) throws PropertyNotFound, PropertyTypeMismatch, PropertyNotWritable {
|
||||
log.debug("write: id={}, type={}, value={}", id, value.getClass().getSimpleName(), value);
|
||||
getByIdAndType(id, type).write(value);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public <T> Property<T> getByIdAndTypeAndOwner(@NonNull final String id, @NonNull final Class<T> type, @NonNull final Object owner) throws PropertyNotFound, PropertyTypeMismatch, PropertyNotOwned {
|
||||
final Property<T> property = getByIdAndType(id, type);
|
||||
if (property.getOwner() != owner) {
|
||||
throw new PropertyNotOwned(property, owner);
|
||||
}
|
||||
return property;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Optional<Property<?>> findByIdAndOwner(@NonNull final String id, @NonNull final Object owner) throws PropertyNotOwned {
|
||||
final Optional<Property<?>> optional = findById(id);
|
||||
if (optional.isEmpty()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
if (optional.get().getOwner() != owner) {
|
||||
throw new PropertyNotOwned(optional.get(), owner);
|
||||
}
|
||||
return optional;
|
||||
}
|
||||
|
||||
@SuppressWarnings("UnusedReturnValue")
|
||||
public <TYPE> Property<TYPE> register(@NonNull final String id, final Class<TYPE> type, final BiConsumer<Property<TYPE>, TYPE> write) {
|
||||
if (id.isEmpty()) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
final Property<TYPE> property = new Property<>(id, type, write, this::onStateSet);
|
||||
@NonNull
|
||||
public <T> Property<T> getByIdAndType(@NonNull final String id, @NonNull final Class<T> type) throws PropertyNotFound, PropertyTypeMismatch {
|
||||
synchronized (propertyList) {
|
||||
propertyList.add(property);
|
||||
return findByIdAndType(id, type).orElseThrow(() -> new PropertyNotFound(id));
|
||||
}
|
||||
return property;
|
||||
}
|
||||
|
||||
private <T> Optional<Property<T>> findByIdAndType(final String id, final Class<T> type) throws PropertyNotFound, PropertyTypeMismatch {
|
||||
final @NonNull Optional<Property<?>> optional = findById(id);
|
||||
if (optional.isEmpty()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
final Property<?> property = optional.get();
|
||||
if (type != property.getType()) {
|
||||
throw new PropertyTypeMismatch(property, type);
|
||||
}
|
||||
//noinspection unchecked
|
||||
return Optional.of((Property<T>) property);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private Optional<Property<?>> findById(final String id) {
|
||||
return propertyList.stream().filter(p -> p.getId().equals(id)).findFirst();
|
||||
}
|
||||
|
||||
private void onStateSet(@NonNull final Property<?> property) {
|
||||
@ -87,4 +107,19 @@ public class PropertyService {
|
||||
return new PropertyDto<>(property);
|
||||
}
|
||||
|
||||
public <T> void update(@NonNull final Object owner, @NonNull final String id, @NonNull final Class<T> type, @Nullable final T value, @NonNull final String string) throws PropertyNotFound, PropertyNotOwned, PropertyTypeMismatch {
|
||||
final Property<T> property = getByIdAndTypeAndOwner(id, type, owner);
|
||||
property.update(new State<>(property.getType().cast(value), string));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public <T> PropertyDto<T> dtoByIdAndTypeOrNull(final @NonNull String id, final Class<T> type) {
|
||||
try {
|
||||
return findByIdAndType(id, type).map(this::toDto).orElse(null);
|
||||
} catch (PropertyNotFound | PropertyTypeMismatch e) {
|
||||
log.error(e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -7,7 +7,7 @@ import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
public class PropertyTypeMismatch extends Exception {
|
||||
|
||||
public PropertyTypeMismatch(@NonNull final Property<?> property, @NonNull final Class<?> type) {
|
||||
public PropertyTypeMismatch(@NonNull final IProperty<?> property, @NonNull final Class<?> type) {
|
||||
super("Property type mismatch: id=%s, expected=%s, given=%s".formatted(property.getId(), property.getType().getSimpleName(), type.getSimpleName()));
|
||||
}
|
||||
|
||||
|
||||
@ -17,7 +17,11 @@ public class State<T> {
|
||||
@Nullable
|
||||
private final T value;
|
||||
|
||||
public State(@Nullable final T value) {
|
||||
@Nullable
|
||||
private final String string;
|
||||
|
||||
public State(@Nullable final T value, @Nullable final String string) {
|
||||
this.string = string;
|
||||
this.timestamp = ZonedDateTime.now();
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@ -47,7 +47,7 @@ public class TvheadendService {
|
||||
|
||||
final Property<Boolean> receiver;
|
||||
try {
|
||||
receiver = propertyService.byIdAndType(tvheadendConfig.getReceiver(), Boolean.class);
|
||||
receiver = propertyService.getByIdAndType(tvheadendConfig.getReceiver(), Boolean.class);
|
||||
} catch (PropertyNotFound | PropertyTypeMismatch e) {
|
||||
log.warn("Failed to retrieve receiver Property: id={}, error={}", tvheadendConfig.getReceiver(), e.getMessage());
|
||||
return;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user