Skip to content

Commit 93381e0

Browse files
committed
[enocean] Consolidate datagram injector motion/FTK updates
Signed-off-by: Sven Schad <svnsssd@gmail.com>
1 parent d94c628 commit 93381e0

23 files changed

+700
-87
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
FTK (EEP:D5-00-01)
2+
ORG = 0x06
3+
Data_byte3= Kontakt geschlossen -> 0x09
4+
Kontakt offen -> 0x08
5+
Data_byte2= -
6+
Data_byte1= -
7+
Data_byte0= -
8+
Lerntelegramm DB3..DB0: 0x00, 0x00, 0x00, 0x00

bundles/org.openhab.binding.enocean/README.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ Hence if your device supports one of the following EEPs the chances are good tha
9999
| multiFunctionSmokeDetector | D2-14/F6-05 | 0x30/02 | smokeDetection, batteryLow | Insafe+, Afriso ASD | Discovery |
100100
| heatRecoveryVentilation | D2-50 | 0x00,01,10,11 | a lot of different state channels | Dimplex DL WE2 | Discovery |
101101
| classicDevice | F6-02 | 0x01-02 | virtualRockerswitchA, virtualRockerswitchB | - | Teach-in |
102+
| datagramInjector | D5-00 | 0x01 (FTK profile) | switch | FTK-like contact use cases | Manually |
102103

103104
¹ Not all channels are supported by all devices, it depends which specific EEP type is used by the device, all thing types additionally support `rssi`, `repeatCount` and `lastReceived` channels
104105

@@ -261,10 +262,14 @@ rejected during validation.
261262
| | broadcastMessages | | true, false |
262263
| | suppressRepeating | | true, false |
263264
| classicDevice | senderIdOffset | | 1-127 |
265+
| | senderAddress | Full sender address (takes precedence over senderIdOffset, RS485 only) | 8 hex chars |
264266
| | sendingEEPId | | F6_02_01, F6_02_02 |
265267
| | broadcastMessages | | true, false |
266268
| | receivingEEPId | | F6_02_01, F6_02_02 |
267269
| | suppressRepeating | | true, false |
270+
| datagramInjector | senderAddress | Virtual sender address used by injector telegrams (RS485 only) | 8 hex chars |
271+
| | sendingProfileId | Profile used for command encoding | FTK_D5_00_01, MOTION_A5_07_01 |
272+
| | suppressRepeating | Suppress repeating of msg | true, false |
268273

269274
¹ multiple values possible, EEPs have to be of different EEP families.
270275
If you want to receive messages of your EnOcean devices you have to set **the enoceanId to the EnOceanId of your device**.
@@ -450,6 +455,40 @@ Switch Garage_Light "Switch" {
450455
}
451456
```
452457

458+
## Datagram Injector
459+
460+
The `datagramInjector` thing is a send-only profile based injector for EnOcean telegrams.
461+
It can only be used when RS485 mode is enabled on the bridge.
462+
It supports the following profiles: `FTK_D5_00_01`, `MOTION_A5_07_01`.
463+
464+
For profile `FTK_D5_00_01` the channel command is converted as follows:
465+
466+
- `CLOSED` or `ON` -> telegram payload `0x09`
467+
- `OPEN` or `OFF` -> telegram payload `0x08`
468+
469+
For profile `MOTION_A5_07_01` the channel command is converted as follows:
470+
471+
- `ON` -> motion detected
472+
- `OFF` -> no motion detected
473+
474+
Example Thing DSL definition:
475+
476+
```xtend
477+
Bridge enocean:bridge:gtwy "EnOcean Gateway" [ path="/dev/ttyAMA0" ] {
478+
Thing datagramInjector ftkTx "FTK TX" [
479+
sendingProfileId="FTK_D5_00_01",
480+
senderAddress="00AABBCC",
481+
suppressRepeating=false
482+
]
483+
}
484+
```
485+
486+
Example Item linking:
487+
488+
```xtend
489+
Switch Window_Contact_TX "Window Contact TX" { channel="enocean:datagramInjector:gtwy:ftkTx:switch" }
490+
```
491+
453492
## Generic Things
454493

455494
If an EnOcean device uses an unsupported EEP or _A5-3F-7F_, you have to create a `genericThing`.
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
## Plan: Symmetrischer Datagram Injector (FTK Start, ohne Transform-Service)
2+
3+
Ziel ist ein send-only `datagramInjector`-Pfad, der symmetrisch zur bestehenden EEP-Typisierung aufgebaut ist: statt externer MAP/Transformationen wird eine interne Profil-/Codec-Struktur genutzt. Einstieg erfolgt mit einem konkreten Profil für FTK (`D5-00-01`) auf Basis der Spezifikation in `EEP_FTK.txt`.
4+
5+
Zusätzlich muss die bereits als PR vorliegende Alternative zu `senderIdOffset` umgesetzt werden: `senderAddress` (vollständige 32-bit Senderadresse in Hex), mit Vorrang vor `senderIdOffset` und RS485-Validierung.
6+
7+
**Architekturentscheidungen**
8+
- Eigener Handler (`EnOceanDatagramInjectorHandler`) statt Vererbung vom Sensor-Handler.
9+
- Interne Mapping-/Codec-Registry (profilbasiert), kein openHAB Transform-Service.
10+
- Start mit genau einem Profil: `FTK_D5_00_01`.
11+
- Erweiterbar auf weitere Geräteprofile analog zu `EEPType`.
12+
- Senderidentität dual:
13+
- Standard: `senderIdOffset` (bestehendes Verhalten)
14+
- Alternative: `senderAddress` (PR-Lösung), hat Vorrang.
15+
16+
**Schritt A: PR-Lösung für Senderadresse (Pflichtteil)**
17+
1. Parameter `senderAddress` ergänzen (wie in PR):
18+
- `EnOceanBindingConstants`: `PARAMETER_SENDERADDRESS`
19+
- `EnOceanActuatorConfig`: `@Nullable String senderAddress`
20+
2. Validierungs-/Initialisierungslogik im TX-Handler übernehmen:
21+
- Wenn `senderAddress` gesetzt: hex-dekodieren, Länge = 4 Byte prüfen, als `senderId` verwenden.
22+
- `senderAddress` hat Vorrang vor `senderIdOffset`.
23+
- Bei `senderAddress` keine Offset-Reservierung am Bridge-Allocator.
24+
3. RS485-Guard übernehmen:
25+
- `EnOceanBridgeHandler#isRS485Enabled()` bereitstellen.
26+
- `senderAddress` nur zulassen, wenn Bridge im RS485-Modus läuft.
27+
- Sonst `CONFIGURATION_ERROR` mit klarer Fehlermeldung.
28+
4. XML/i18n für relevante sendende Thing-Typen ergänzen:
29+
- Label/Description für `senderAddress`
30+
- Beschreibung: „32-bit hex, 8 Stellen, Vorrang vor senderIdOffset“.
31+
32+
**Schritt B: DatagramInjector V1 (FTK) auf dieser Basis**
33+
1. Neues Thing-Modell `datagramInjector` in `OH-INF/thing/DatagramInjector.xml` definieren.
34+
- Konfiguration: `sendingProfileId`, `senderIdOffset`, `senderAddress`, `broadcastMessages`, `suppressRepeating`, optional `enoceanId`.
35+
- Identitätsauflösung wie oben: `senderAddress` vor `senderIdOffset`.
36+
2. Neue Thing-Type-Konstante `THING_TYPE_DATAGRAMINJECTOR` in `EnOceanBindingConstants` ergänzen und in `SUPPORTED_DEVICE_THING_TYPES_UIDS` aufnehmen.
37+
3. Neues Profil-Enum/Registry einführen (z. B. `DatagramProfileType`), initial mit `FTK_D5_00_01`.
38+
4. Codec-Schnittstelle für Outbound definieren (z. B. `DatagramCommandEncoder`).
39+
- Methode: `encode(channelId, channelTypeId, command, channelConfig, currentStateProvider)` → ERP1 payload bytes.
40+
5. FTK-Encoder implementieren (`FtkD5_00_01Encoder`).
41+
- Mapping gemäß `EEP_FTK.txt`: `CLOSED -> 0x09`, `OPEN -> 0x08` (DB3), Teach-In optional `0x00`.
42+
6. `EnOceanDatagramInjectorHandler` implementieren auf Basis von `EnOceanBaseThingHandler`.
43+
- `validateConfig`: Profil + Senderidentität (`senderAddress`/`senderIdOffset`) + Zieladresse.
44+
- `handleCommand`: Command an Encoder, ERP1 bauen, via Bridge senden.
45+
- Keine PacketListener-Registrierung, keine RX-EEP-Pflicht.
46+
7. Handler-Factory (`EnOceanHandlerFactory`) erweitern, damit `datagramInjector` den neuen Handler erhält.
47+
8. i18n ergänzen (`enocean.properties`) für Thing/Config/Channel inkl. Profiloption `FTK_D5_00_01` und `senderAddress`.
48+
9. README erweitern:
49+
- Neues Kapitel „Datagram Injector (Profile-based, send-only)“.
50+
- FTK-Beispiel inkl. `senderAddress`-Variante und `senderIdOffset`-Variante.
51+
52+
**FTK V1 Mapping**
53+
- Profil: `FTK_D5_00_01`
54+
- EEP: `D5-00-01` (1BS, ORG/RORG `0x06`)
55+
- Nutzdaten:
56+
- Kontakt geschlossen → `0x09`
57+
- Kontakt offen → `0x08`
58+
- Teach-In Telegramm DB3..DB0 → `0x00,0x00,0x00,0x00` (optional)
59+
60+
**Verification**
61+
- Build: `mvn clean install -DskipTests` im Binding-Verzeichnis.
62+
- Formatierung: `mvn spotless:apply` im Binding-Verzeichnis.
63+
- Funktionstest Senderidentität:
64+
- Fall 1: `senderIdOffset` gesetzt, `senderAddress` leer → bestehendes Verhalten.
65+
- Fall 2: `senderAddress` gesetzt, RS485 aktiv → direkte 32-bit Senderadresse wird verwendet.
66+
- Fall 3: `senderAddress` gesetzt, RS485 inaktiv → erwarteter `CONFIGURATION_ERROR`.
67+
- Funktionstest FTK:
68+
- Injector-Thing mit Profil `FTK_D5_00_01` anlegen.
69+
- `OPEN`/`CLOSED` Command senden, Bus auf `0x08/0x09` prüfen.
70+
- Negativtests:
71+
- Ungültiges Profil/Command oder ungültiges `senderAddress` (nicht-hex / !=8 Zeichen) → keine Sendung + klare Fehlermeldung.
72+
73+
**Abgrenzung V1**
74+
- Kein RX-Pfad für den Injector.
75+
- Kein generischer Hex-Injector.
76+
- Keine zusätzlichen Geräteprofile außer FTK im ersten Schnitt.

bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/EnOceanBindingConstants.java

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ public class EnOceanBindingConstants {
7272
public static final ThingTypeUID THING_TYPE_ENVIRONMENTALSENSOR = new ThingTypeUID(BINDING_ID,
7373
"environmentalSensor");
7474
public static final ThingTypeUID THING_TYPE_GENERICTHING = new ThingTypeUID(BINDING_ID, "genericThing");
75+
public static final ThingTypeUID THING_TYPE_DATAGRAMINJECTOR = new ThingTypeUID(BINDING_ID, "datagramInjector");
7576
public static final ThingTypeUID THING_TYPE_ROLLERSHUTTER = new ThingTypeUID(BINDING_ID, "rollershutter");
7677
public static final ThingTypeUID THING_TYPE_MULTFUNCTIONSMOKEDETECTOR = new ThingTypeUID(BINDING_ID,
7778
"multiFunctionSmokeDetector");
@@ -85,10 +86,10 @@ public class EnOceanBindingConstants {
8586
THING_TYPE_ROCKERSWITCH, THING_TYPE_CLASSICDEVICE, THING_TYPE_CENTRALCOMMAND, THING_TYPE_ROOMOPERATINGPANEL,
8687
THING_TYPE_MECHANICALHANDLE, THING_TYPE_CONTACT, THING_TYPE_MEASUREMENTSWITCH, THING_TYPE_TEMPERATURESENSOR,
8788
THING_TYPE_TEMPERATUREHUMIDITYSENSOR, THING_TYPE_GASSENSOR, THING_TYPE_GENERICTHING,
88-
THING_TYPE_ROLLERSHUTTER, THING_TYPE_OCCUPANCYSENSOR, THING_TYPE_LIGHTTEMPERATUREOCCUPANCYSENSOR,
89-
THING_TYPE_LIGHTSENSOR, THING_TYPE_ENVIRONMENTALSENSOR, THING_TYPE_AUTOMATEDMETERSENSOR,
90-
THING_TYPE_THERMOSTAT, THING_TYPE_MULTFUNCTIONSMOKEDETECTOR, THING_TYPE_HEATRECOVERYVENTILATION,
91-
THING_TYPE_WINDOWSASHHANDLESENSOR);
89+
THING_TYPE_DATAGRAMINJECTOR, THING_TYPE_ROLLERSHUTTER, THING_TYPE_OCCUPANCYSENSOR,
90+
THING_TYPE_LIGHTTEMPERATUREOCCUPANCYSENSOR, THING_TYPE_LIGHTSENSOR, THING_TYPE_ENVIRONMENTALSENSOR,
91+
THING_TYPE_AUTOMATEDMETERSENSOR, THING_TYPE_THERMOSTAT, THING_TYPE_MULTFUNCTIONSMOKEDETECTOR,
92+
THING_TYPE_HEATRECOVERYVENTILATION, THING_TYPE_WINDOWSASHHANDLESENSOR);
9293

9394
// List of all Channel Type Ids, these type ids are also used as channel ids during dynamic creation of channels
9495
// this makes it a lot easier as we do not have to manage a type id and an id, drawback long channel names
@@ -613,7 +614,7 @@ public class EnOceanBindingConstants {
613614

614615
// Thing config parameter
615616
public static final String PARAMETER_SENDERIDOFFSET = "senderIdOffset";
616-
public static final String PARAMETER_SENDERID = "senderId";
617+
public static final String PARAMETER_SENDERADDRESS = "senderAddress";
617618
public static final String PARAMETER_SENDINGEEPID = "sendingEEPId";
618619
public static final String PARAMETER_RECEIVINGEEPID = "receivingEEPId";
619620

bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/EnOceanHandlerFactory.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import org.openhab.binding.enocean.internal.handler.EnOceanBaseSensorHandler;
2727
import org.openhab.binding.enocean.internal.handler.EnOceanBridgeHandler;
2828
import org.openhab.binding.enocean.internal.handler.EnOceanClassicDeviceHandler;
29+
import org.openhab.binding.enocean.internal.handler.EnOceanDatagramInjectorHandler;
2930
import org.openhab.core.config.discovery.DiscoveryService;
3031
import org.openhab.core.io.transport.serial.SerialPortManager;
3132
import org.openhab.core.thing.Bridge;
@@ -91,6 +92,8 @@ public boolean supportsThingType(ThingTypeUID thingTypeUID) {
9192
return new EnOceanBaseSensorHandler(thing, itemChannelLinkRegistry);
9293
} else if (EnOceanClassicDeviceHandler.SUPPORTED_THING_TYPES.contains(thingTypeUID)) {
9394
return new EnOceanClassicDeviceHandler(thing, itemChannelLinkRegistry);
95+
} else if (EnOceanDatagramInjectorHandler.SUPPORTED_THING_TYPES.contains(thingTypeUID)) {
96+
return new EnOceanDatagramInjectorHandler(thing, itemChannelLinkRegistry);
9497
}
9598

9699
return null;

bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/config/EnOceanActuatorConfig.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ public class EnOceanActuatorConfig extends EnOceanBaseConfig {
2424

2525
public int channel;
2626
public @Nullable Integer senderIdOffset = null;
27-
public @Nullable String senderId = null;
27+
public @Nullable String senderAddress = null;
2828
public String manufacturerId = "";
2929
public String teachInType = "";
3030

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
* Copyright (c) 2010-2026 Contributors to the openHAB project
3+
*
4+
* See the NOTICE file(s) distributed with this work for additional
5+
* information.
6+
*
7+
* This program and the accompanying materials are made available under the
8+
* terms of the Eclipse Public License 2.0 which is available at
9+
* http://www.eclipse.org/legal/epl-2.0
10+
*
11+
* SPDX-License-Identifier: EPL-2.0
12+
*/
13+
package org.openhab.binding.enocean.internal.config;
14+
15+
import org.eclipse.jdt.annotation.NonNullByDefault;
16+
import org.eclipse.jdt.annotation.Nullable;
17+
18+
/**
19+
* Configuration for send-only datagram injector thing.
20+
*
21+
* @author openHAB Contributors
22+
*/
23+
@NonNullByDefault
24+
public class EnOceanDatagramInjectorConfig extends EnOceanBaseConfig {
25+
26+
public @Nullable String senderAddress = null;
27+
28+
public String sendingProfileId = "";
29+
30+
public boolean suppressRepeating = false;
31+
}

bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/A5_07/A5_07.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@
3333
@NonNullByDefault
3434
public abstract class A5_07 extends _4BSMessage {
3535

36+
public A5_07() {
37+
}
38+
3639
public A5_07(ERP1Message packet) {
3740
super(packet);
3841
}

bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/A5_07/A5_07_01.java

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,20 @@
1212
*/
1313
package org.openhab.binding.enocean.internal.eep.A5_07;
1414

15+
import static org.openhab.binding.enocean.internal.EnOceanBindingConstants.CHANNEL_GENERAL_SWITCHING;
16+
17+
import java.util.function.Function;
18+
1519
import org.eclipse.jdt.annotation.NonNullByDefault;
20+
import org.eclipse.jdt.annotation.Nullable;
1621
import org.openhab.binding.enocean.internal.messages.ERP1Message;
22+
import org.openhab.core.config.core.Configuration;
1723
import org.openhab.core.library.types.OnOffType;
24+
import org.openhab.core.types.Command;
1825
import org.openhab.core.types.State;
1926
import org.openhab.core.types.UnDefType;
27+
import org.slf4j.Logger;
28+
import org.slf4j.LoggerFactory;
2029

2130
/**
2231
*
@@ -25,12 +34,47 @@
2534
@NonNullByDefault
2635
public class A5_07_01 extends A5_07 {
2736

37+
@SuppressWarnings("null")
38+
private final Logger logger = LoggerFactory.getLogger(A5_07_01.class);
39+
40+
private static final String CHANNEL_SWITCH = "switch";
41+
42+
private static final byte OUTBOUND_PIR_ON = (byte) 0xFF;
2843
private static final int PIR_OFF = 0x7f;
44+
private static final byte DB3_DEFAULT = 0x00;
45+
private static final byte DB2_DEFAULT = 0x00;
46+
private static final byte DB0_DEFAULT = 0x09;
47+
48+
public A5_07_01() {
49+
super();
50+
}
2951

3052
public A5_07_01(ERP1Message packet) {
3153
super(packet);
3254
}
3355

56+
@Override
57+
protected void convertFromCommandImpl(String channelId, String channelTypeId, Command command,
58+
Function<String, State> getCurrentStateFunc, @Nullable Configuration config) {
59+
if (!CHANNEL_SWITCH.equals(channelId) && !CHANNEL_GENERAL_SWITCHING.equals(channelTypeId)) {
60+
throw new IllegalArgumentException("Unsupported channel for A5_07_01 outbound command: " + channelId);
61+
}
62+
63+
if (command instanceof OnOffType motionCommand) {
64+
if (motionCommand == OnOffType.ON) {
65+
setData(DB3_DEFAULT, DB2_DEFAULT, OUTBOUND_PIR_ON, DB0_DEFAULT);
66+
logger.debug("A5_07_01 outbound motion switch command: {}, data DB3..DB0={} {} {} {}", motionCommand,
67+
String.format("0x%02X", DB3_DEFAULT), String.format("0x%02X", DB2_DEFAULT),
68+
String.format("0x%02X", OUTBOUND_PIR_ON), String.format("0x%02X", DB0_DEFAULT));
69+
} else {
70+
logger.debug("A5_07_01 outbound motion switch command OFF: no telegram sent");
71+
}
72+
return;
73+
}
74+
75+
throw new IllegalArgumentException("Unsupported command for A5_07_01 outbound motion detection: " + command);
76+
}
77+
3478
@Override
3579
protected State getIllumination() {
3680
return UnDefType.UNDEF;

bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/D5_00/D5_00_01.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,13 @@
2222
import org.openhab.binding.enocean.internal.eep.Base._1BSMessage;
2323
import org.openhab.binding.enocean.internal.messages.ERP1Message;
2424
import org.openhab.core.config.core.Configuration;
25+
import org.openhab.core.library.types.OnOffType;
2526
import org.openhab.core.library.types.OpenClosedType;
27+
import org.openhab.core.types.Command;
2628
import org.openhab.core.types.State;
2729
import org.openhab.core.types.UnDefType;
30+
import org.slf4j.Logger;
31+
import org.slf4j.LoggerFactory;
2832

2933
/**
3034
*
@@ -33,6 +37,11 @@
3337
@NonNullByDefault
3438
public class D5_00_01 extends _1BSMessage {
3539

40+
@SuppressWarnings("null")
41+
private final Logger logger = LoggerFactory.getLogger(D5_00_01.class);
42+
43+
private static final String CHANNEL_SWITCH = "switch";
44+
3645
protected static final byte OPEN = 0 | TEACHIN_BIT;
3746
protected static final byte CLOSED = 1 | TEACHIN_BIT;
3847

@@ -43,6 +52,22 @@ public D5_00_01(ERP1Message packet) {
4352
super(packet);
4453
}
4554

55+
@Override
56+
protected void convertFromCommandImpl(String channelId, String channelTypeId, Command command,
57+
Function<String, State> getCurrentStateFunc, @Nullable Configuration config) {
58+
if (!channelId.equals(CHANNEL_SWITCH)) {
59+
throw new IllegalArgumentException("Unsupported channel for D5_00_01 outbound command: " + channelId);
60+
}
61+
62+
if (command instanceof OnOffType switchCommand) {
63+
logger.debug("D5_00_01 outbound switch command: {}", switchCommand);
64+
setData(switchCommand == OnOffType.ON ? CLOSED : OPEN);
65+
return;
66+
}
67+
68+
throw new IllegalArgumentException("Unsupported command for D5_00_01 outbound switch: " + command);
69+
}
70+
4671
@Override
4772
protected State convertToStateImpl(String channelId, String channelTypeId,
4873
Function<String, @Nullable State> getCurrentStateFunc, Configuration config) {

0 commit comments

Comments
 (0)