[enocean] Improved device discovery and added SMACK capability (#10157)

* Added SMACK teach in
 * Teached in devices can be teach out on a repeated teach in
 * Improved detection of RPS devices, device types can be better distinguished now
 * Bugfixes for discovery fallback to GenericThings
 * Responses to message requests are send automatically now, no need for linking SEND_COMMAND channel

Fixes #10156

Signed-off-by: Daniel Weber <uni@fruggy.de>
This commit is contained in:
Daniel Weber 2021-02-20 17:13:28 +01:00 committed by GitHub
parent a9f440dba2
commit fd1c96677e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
58 changed files with 1071 additions and 321 deletions

View File

@ -126,7 +126,21 @@ The corresponding channels are created dynamically, too.
If the actuator supports UTE teach-in, the corresponding thing can be created and paired automatically.
First you have to **start the discovery scan for a gateway**.
Then press the teach-in button of the actuator.
If the EEP of the actuator is known, the binding sends an UTE teach-in response with a new SenderId and creates a new thing with its channels.
If the EEP of the actuator is known, the binding sends an UTE teach-in response with a new SenderId and creates a new thing with its channels.
This binding supports so called smart acknowlegde (SMACK) devices too.
Before you can pair a SMACK device you have to configure your gateway bridge as a SMACK postmaster.
If this option is enabled you can pair up to 20 SMACK devices with your gateway.
Communication between your gateway and a SMACK device is handled through mailboxes.
A mailbox is created for each paired SMACK device and deleted after teach out.
You can see the paired SMACK devices and their mailbox index in the gateway properties.
SMACK devices send periodically status updates followed by a response request.
Whenever such a request is received a `requestAnswer` event is triggered for channel `statusRequestEvent`.
Afterwards you have 100ms time to recalculate your items states and update them.
A message with the updated item states is built, put into the corresponding mailbox and automatically sent upon request of the device.
Pairing and unpairing can be done through a discovery scan.
The corresponding thing of an unpaired device gets disabled, you have to delete it manually if you want to.
If the actuator does not support UTE teach-ins, you have to create, configure and choose the right EEP of the thing manually.
It is important to link the teach-in channel of this thing to a switch item.
@ -158,6 +172,8 @@ If you change the SenderId of your thing, you have to pair again the thing with
| | espVersion | ESP Version of gateway | ESP3, ESP2 |
| | rs485 | If gateway is directly connected to a RS485 bus the BaseId is set to 0x00 | true, false
| | rs485BaseId | Override BaseId 0x00 if your bus contains a telegram duplicator (FTD14 for ex) | 4 byte hex value |
| | enableSmack | Enables SMACK pairing and handling of SMACK messages | true, false |
| | sendTeachOuts | Defines if a repeated teach in request should be answered with a learned in or teach out response | true, false |
| pushButton | receivingEEPId | EEP used for receiving msg | F6_01_01, D2_03_0A |
| | enoceanId | EnOceanId of device this thing belongs to | hex value as string |
| rockerSwitch | receivingEEPId | | F6_02_01, F6_02_02 |
@ -300,6 +316,7 @@ The channels of a thing are determined automatically based on the chosen EEP.
| rssi | Number | Received Signal Strength Indication (dBm) of last received message |
| repeatCount | Number | Number of repeaters involved in the transmission of the telegram |
| lastReceived | DateTime | Date and time the last telegram was received |
| statusRequestEvent | Trigger | Emits event 'requestAnswer' |
Items linked to bi-directional actuators (actuator sends status messages back) should always disable the `autoupdate`.
This is especially true for Eltako rollershutter, as their position is calculated out of the current position and the moving time.

View File

@ -177,7 +177,7 @@ public class EnOceanBindingConstants {
public static final String CHANNEL_WAKEUPCYCLE = "wakeUpCycle";
public static final String CHANNEL_SERVICECOMMAND = "serviceCommand";
public static final String CHANNEL_STATUS_REQUEST_EVENT = "statusRequestEvent";
public static final String CHANNEL_SEND_COMMAND = "sendCommand";
public static final String VIRTUALCHANNEL_SEND_COMMAND = "sendCommand";
public static final String CHANNEL_VENTILATIONOPERATIONMODE = "ventilationOperationMode";
public static final String CHANNEL_FIREPLACESAFETYMODE = "fireplaceSafetyMode";
@ -293,7 +293,8 @@ public class EnOceanBindingConstants {
Map.entry(CHANNEL_INDOORAIRANALYSIS,
new EnOceanChannelDescription(new ChannelTypeUID(BINDING_ID, CHANNEL_INDOORAIRANALYSIS),
CoreItemFactory.STRING)),
Map.entry(CHANNEL_SETPOINT,
Map.entry(
CHANNEL_SETPOINT,
new EnOceanChannelDescription(new ChannelTypeUID(BINDING_ID, CHANNEL_SETPOINT),
CoreItemFactory.NUMBER)),
Map.entry(CHANNEL_CONTACT,
@ -444,13 +445,6 @@ public class EnOceanBindingConstants {
new EnOceanChannelDescription(new ChannelTypeUID(BINDING_ID, CHANNEL_SERVICECOMMAND),
CoreItemFactory.NUMBER)),
Map.entry(CHANNEL_STATUS_REQUEST_EVENT,
new EnOceanChannelDescription(new ChannelTypeUID(BINDING_ID, CHANNEL_STATUS_REQUEST_EVENT), null,
"", false, true)),
Map.entry(CHANNEL_SEND_COMMAND,
new EnOceanChannelDescription(new ChannelTypeUID(BINDING_ID, CHANNEL_SEND_COMMAND),
CoreItemFactory.SWITCH)),
Map.entry(CHANNEL_VENTILATIONOPERATIONMODE,
new EnOceanChannelDescription(new ChannelTypeUID(BINDING_ID, CHANNEL_VENTILATIONOPERATIONMODE),
CoreItemFactory.STRING)),
@ -527,6 +521,10 @@ public class EnOceanBindingConstants {
CoreItemFactory.NUMBER + ItemUtil.EXTENSION_SEPARATOR
+ Dimensionless.class.getSimpleName())),
Map.entry(CHANNEL_STATUS_REQUEST_EVENT,
new EnOceanChannelDescription(new ChannelTypeUID(BINDING_ID, CHANNEL_STATUS_REQUEST_EVENT), null,
"", false, true)),
Map.entry(CHANNEL_REPEATERMODE, new EnOceanChannelDescription(
new ChannelTypeUID(BINDING_ID, CHANNEL_REPEATERMODE), CoreItemFactory.STRING)));
@ -536,11 +534,8 @@ public class EnOceanBindingConstants {
public static final String REPEATERMODE_LEVEL_2 = "LEVEL2";
// Bridge config properties
public static final String SENDERID = "senderId";
public static final String PATH = "path";
public static final String HOST = "host";
public static final String RS485 = "rs485";
public static final String NEXTSENDERID = "nextSenderId";
public static final String PARAMETER_NEXT_SENDERID = "nextSenderId";
// Bridge properties
public static final String PROPERTY_BASE_ID = "Base ID";
@ -551,13 +546,12 @@ public class EnOceanBindingConstants {
public static final String PROPERTY_DESCRIPTION = "Description";
// Thing properties
public static final String PROPERTY_ENOCEAN_ID = "enoceanId";
public static final String PROPERTY_SENDINGENOCEAN_ID = "SendingEnoceanId";
// Thing config parameter
public static final String PARAMETER_SENDERIDOFFSET = "senderIdOffset";
public static final String PARAMETER_SENDINGEEPID = "sendingEEPId";
public static final String PARAMETER_RECEIVINGEEPID = "receivingEEPId";
public static final String PARAMETER_EEPID = "eepId";
public static final String PARAMETER_BROADCASTMESSAGES = "broadcastMessages";
public static final String PARAMETER_ENOCEANID = "enoceanId";

View File

@ -28,6 +28,7 @@ import org.openhab.core.config.discovery.DiscoveryService;
import org.openhab.core.io.transport.serial.SerialPortManager;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingManager;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
@ -60,6 +61,9 @@ public class EnOceanHandlerFactory extends BaseThingHandlerFactory {
@Reference
ItemChannelLinkRegistry itemChannelLinkRegistry;
@Reference
ThingManager thingManager;
@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
@ -96,7 +100,7 @@ public class EnOceanHandlerFactory extends BaseThingHandlerFactory {
}
private void registerDeviceDiscoveryService(EnOceanBridgeHandler handler) {
EnOceanDeviceDiscoveryService discoveryService = new EnOceanDeviceDiscoveryService(handler);
EnOceanDeviceDiscoveryService discoveryService = new EnOceanDeviceDiscoveryService(handler, thingManager);
discoveryService.activate();
this.discoveryServiceRegs.put(handler.getThing().getUID(),
bundleContext.registerService(DiscoveryService.class.getName(), discoveryService, new Hashtable<>()));

View File

@ -19,7 +19,7 @@ package org.openhab.binding.enocean.internal.config;
public class EnOceanActuatorConfig extends EnOceanBaseConfig {
public int channel;
public int senderIdOffset = -1;
public Integer senderIdOffset = null;
public String manufacturerId;
public String teachInType;

View File

@ -17,15 +17,23 @@ import static org.openhab.binding.enocean.internal.EnOceanBindingConstants.EMPTY
import java.util.ArrayList;
import java.util.List;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.util.HexUtils;
/**
*
* @author Daniel Weber - Initial contribution
*/
@NonNullByDefault
public class EnOceanBaseConfig {
/**
* EnOceanId of the physical device
*/
public String enoceanId;
/**
* EEP used/send by physical device
*/
public List<String> receivingEEPId = new ArrayList<>();
public boolean receivingSIGEEP = false;

View File

@ -46,10 +46,16 @@ public class EnOceanBridgeConfig {
public boolean rs485;
public String rs485BaseId;
public int nextSenderId = 0;
public Integer nextSenderId;
public boolean enableSmack;
public boolean sendTeachOuts;
public EnOceanBridgeConfig() {
espVersion = "ESP3";
sendTeachOuts = false;
enableSmack = true;
nextSenderId = null;
}
public ESPVersion getESPVersion() {

View File

@ -12,12 +12,19 @@
*/
package org.openhab.binding.enocean.internal.config;
import org.openhab.core.config.core.Configuration;
/**
*
* @author Daniel Weber - Initial contribution
*/
public class EnOceanChannelTransformationConfig {
public class EnOceanChannelTransformationConfig extends Configuration {
public String transformationType;
public String transformationFunction;
public EnOceanChannelTransformationConfig() {
put("transformationType", "");
put("transformationFunction", "");
}
}

View File

@ -25,9 +25,14 @@ import org.openhab.binding.enocean.internal.handler.EnOceanBridgeHandler;
import org.openhab.binding.enocean.internal.messages.BasePacket;
import org.openhab.binding.enocean.internal.messages.ERP1Message;
import org.openhab.binding.enocean.internal.messages.ERP1Message.RORG;
import org.openhab.binding.enocean.internal.transceiver.PacketListener;
import org.openhab.binding.enocean.internal.messages.EventMessage;
import org.openhab.binding.enocean.internal.messages.EventMessage.EventMessageType;
import org.openhab.binding.enocean.internal.messages.Responses.SMACKTeachInResponse;
import org.openhab.binding.enocean.internal.transceiver.TeachInListener;
import org.openhab.core.config.discovery.AbstractDiscoveryService;
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingManager;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.util.HexUtils;
@ -39,15 +44,16 @@ import org.slf4j.LoggerFactory;
*
* @author Daniel Weber - Initial contribution
*/
public class EnOceanDeviceDiscoveryService extends AbstractDiscoveryService implements PacketListener {
public class EnOceanDeviceDiscoveryService extends AbstractDiscoveryService implements TeachInListener {
private final Logger logger = LoggerFactory.getLogger(EnOceanDeviceDiscoveryService.class);
private EnOceanBridgeHandler bridgeHandler;
private ThingManager thingManager;
public EnOceanDeviceDiscoveryService(EnOceanBridgeHandler bridgeHandler) {
public EnOceanDeviceDiscoveryService(EnOceanBridgeHandler bridgeHandler, ThingManager thingManager) {
super(null, 60, false);
this.bridgeHandler = bridgeHandler;
this.thingManager = thingManager;
}
/**
@ -101,73 +107,140 @@ public class EnOceanDeviceDiscoveryService extends AbstractDiscoveryService impl
return;
}
String enoceanId = HexUtils.bytesToHex(eep.getSenderId());
bridgeHandler.getThing().getThings().stream()
.filter(t -> t.getConfiguration().getProperties().getOrDefault(PARAMETER_ENOCEANID, EMPTYENOCEANID)
.toString().equals(enoceanId))
.findFirst().ifPresentOrElse(t -> {
// If repeated learn is not allowed => send teach out
// otherwise do nothing
if (bridgeHandler.sendTeachOuts()) {
sendTeachOutResponse(msg, enoceanId, t);
thingManager.setEnabled(t.getUID(), false);
}
}, () -> {
Integer senderIdOffset = null;
boolean broadcastMessages = true;
// check for bidirectional communication => do not use broadcast in this case
if (msg.getRORG() == RORG.UTE && (msg.getPayload(1, 1)[0]
& UTEResponse.CommunicationType_MASK) == UTEResponse.CommunicationType_MASK) {
broadcastMessages = false;
}
if (msg.getRORG() == RORG.UTE && (msg.getPayload(1, 1)[0] & UTEResponse.ResponseNeeded_MASK) == 0) {
// if ute => send response if needed
logger.debug("Sending UTE response to {}", enoceanId);
senderIdOffset = sendTeachInResponse(msg, enoceanId);
if (senderIdOffset == null) {
return;
}
} else if ((eep instanceof _4BSMessage) && ((_4BSMessage) eep).isTeachInVariation3Supported()) {
// if 4BS teach in variation 3 => send response
logger.debug("Sending 4BS teach in variation 3 response to {}", enoceanId);
senderIdOffset = sendTeachInResponse(msg, enoceanId);
if (senderIdOffset == null) {
return;
}
}
createDiscoveryResult(eep, broadcastMessages, senderIdOffset);
});
}
@Override
public void eventReceived(EventMessage event) {
if (event.getEventMessageType() == EventMessageType.SA_CONFIRM_LEARN) {
EEP eep = EEPFactory.buildEEPFromTeachInSMACKEvent(event);
if (eep == null) {
return;
}
SMACKTeachInResponse response = EEPFactory.buildResponseFromSMACKTeachIn(event,
bridgeHandler.sendTeachOuts());
if (response != null) {
bridgeHandler.sendMessage(response, null);
if (response.isTeachIn()) {
// SenderIdOffset will be determined during Thing init
createDiscoveryResult(eep, false, -1);
} else if (response.isTeachOut()) {
// disable already teached in thing
bridgeHandler.getThing().getThings().stream()
.filter(t -> t.getConfiguration().getProperties()
.getOrDefault(PARAMETER_ENOCEANID, EMPTYENOCEANID).toString()
.equals(HexUtils.bytesToHex(eep.getSenderId())))
.findFirst().ifPresentOrElse(t -> {
thingManager.setEnabled(t.getUID(), false);
logger.info("Disable thing with id {}", t.getUID());
}, () -> {
logger.info("Thing for EnOceanId {} already deleted",
HexUtils.bytesToHex(eep.getSenderId()));
});
}
}
}
}
private Integer sendTeachInResponse(ERP1Message msg, String enoceanId) {
// get new sender Id
Integer offset = bridgeHandler.getNextSenderId(enoceanId);
if (offset != null) {
byte[] newSenderId = bridgeHandler.getBaseId();
newSenderId[3] += offset;
// send response
EEP response = EEPFactory.buildResponseEEPFromTeachInERP1(msg, newSenderId, true);
if (response != null) {
bridgeHandler.sendMessage(response.getERP1Message(), null);
logger.debug("Teach in response for {} with new senderId {} (= offset {}) sent", enoceanId,
HexUtils.bytesToHex(newSenderId), offset);
} else {
logger.warn("Teach in response for enoceanId {} not supported!", enoceanId);
}
} else {
logger.warn("Could not get new SenderIdOffset");
}
return offset;
}
private void sendTeachOutResponse(ERP1Message msg, String enoceanId, Thing thing) {
byte[] senderId = bridgeHandler.getBaseId();
senderId[3] += (byte) thing.getConfiguration().getProperties().getOrDefault(PARAMETER_SENDERIDOFFSET, 0);
// send response
EEP response = EEPFactory.buildResponseEEPFromTeachInERP1(msg, senderId, false);
if (response != null) {
bridgeHandler.sendMessage(response.getERP1Message(), null);
logger.debug("Teach out response for thing {} with EnOceanId {} sent", thing.getUID().getId(), enoceanId);
} else {
logger.warn("Teach out response for enoceanId {} not supported!", enoceanId);
}
}
protected void createDiscoveryResult(EEP eep, boolean broadcastMessages, Integer senderIdOffset) {
String enoceanId = HexUtils.bytesToHex(eep.getSenderId());
ThingTypeUID thingTypeUID = eep.getThingTypeUID();
ThingUID thingUID = new ThingUID(thingTypeUID, bridgeHandler.getThing().getUID(), enoceanId);
int senderIdOffset = 0;
boolean broadcastMessages = true;
// check for bidirectional communication => do not use broadcast in this case
if (msg.getRORG() == RORG.UTE && (msg.getPayload(1, 1)[0]
& UTEResponse.CommunicationType_MASK) == UTEResponse.CommunicationType_MASK) {
broadcastMessages = false;
}
// if ute => send response if needed
if (msg.getRORG() == RORG.UTE && (msg.getPayload(1, 1)[0] & UTEResponse.ResponseNeeded_MASK) == 0) {
logger.info("Sending UTE response to {}", enoceanId);
senderIdOffset = sendTeachInResponse(msg, enoceanId);
}
// if 4BS teach in variation 3 => send response
if ((eep instanceof _4BSMessage) && ((_4BSMessage) eep).isTeachInVariation3Supported()) {
logger.info("Sending 4BS teach in variation 3 response to {}", enoceanId);
senderIdOffset = sendTeachInResponse(msg, enoceanId);
}
DiscoveryResultBuilder discoveryResultBuilder = DiscoveryResultBuilder.create(thingUID)
.withRepresentationProperty(enoceanId).withBridge(bridgeHandler.getThing().getUID());
.withRepresentationProperty(PARAMETER_ENOCEANID).withProperty(PARAMETER_ENOCEANID, enoceanId)
.withProperty(PARAMETER_BROADCASTMESSAGES, broadcastMessages)
.withBridge(bridgeHandler.getThing().getUID());
eep.addConfigPropertiesTo(discoveryResultBuilder);
discoveryResultBuilder.withProperty(PARAMETER_BROADCASTMESSAGES, broadcastMessages);
discoveryResultBuilder.withProperty(PARAMETER_ENOCEANID, enoceanId);
if (senderIdOffset > 0) {
if (senderIdOffset != null) {
// advance config with new device id
discoveryResultBuilder.withProperty(PARAMETER_SENDERIDOFFSET, senderIdOffset);
}
thingDiscovered(discoveryResultBuilder.build());
// As we only support sensors to be teached in, we do not need to send a teach in response => 4bs
// bidirectional teach in proc is not supported yet
// this is true except for UTE teach in => we always have to send a response here
}
private int sendTeachInResponse(ERP1Message msg, String enoceanId) {
int offset;
// get new sender Id
offset = bridgeHandler.getNextSenderId(enoceanId);
if (offset > 0) {
byte[] newSenderId = bridgeHandler.getBaseId();
newSenderId[3] += offset;
// send response
EEP response = EEPFactory.buildResponseEEPFromTeachInERP1(msg, newSenderId);
if (response != null) {
bridgeHandler.sendMessage(response.getERP1Message(), null);
logger.info("Teach in response for {} with new senderId {} (= offset {}) sent", enoceanId,
HexUtils.bytesToHex(newSenderId), offset);
} else {
logger.warn("Teach in response for enoceanId {} not supported!", enoceanId);
}
}
return offset;
}
@Override
public long getSenderIdToListenTo() {
public long getEnOceanIdToListenTo() {
// we just want teach in msg, so return zero here
return 0;
}

View File

@ -20,7 +20,6 @@ import org.openhab.core.config.core.Configuration;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.unit.SIUnits;
import org.openhab.core.types.State;
import org.openhab.core.types.UnDefType;
/**
*
@ -51,9 +50,6 @@ public abstract class A5_02 extends _4BSMessage {
@Override
protected State convertToStateImpl(String channelId, String channelTypeId,
Function<String, State> getCurrentStateFunc, Configuration config) {
if (!isValid()) {
return UnDefType.UNDEF;
}
double scaledTemp = getScaledMin()
- (((getUnscaledMin() - getUnscaledTemperatureValue()) * (getScaledMin() - getScaledMax()))

View File

@ -62,9 +62,6 @@ public abstract class A5_04 extends _4BSMessage {
@Override
protected State convertToStateImpl(String channelId, String channelTypeId,
Function<String, State> getCurrentStateFunc, Configuration config) {
if (!isValid()) {
return UnDefType.UNDEF;
}
if (channelId.equals(CHANNEL_TEMPERATURE)) {
double scaledTemp = getScaledTemperatureMin()

View File

@ -53,9 +53,6 @@ public abstract class A5_07 extends _4BSMessage {
@Override
protected State convertToStateImpl(String channelId, String channelTypeId,
Function<String, State> getCurrentStateFunc, Configuration config) {
if (!isValid()) {
return UnDefType.UNDEF;
}
if (channelId.equals(CHANNEL_ILLUMINATION)) {
return getIllumination();

View File

@ -71,9 +71,6 @@ public abstract class A5_08 extends _4BSMessage {
@Override
protected State convertToStateImpl(String channelId, String channelTypeId,
Function<String, State> getCurrentStateFunc, Configuration config) {
if (!isValid()) {
return UnDefType.UNDEF;
}
if (channelId.equals(CHANNEL_TEMPERATURE)) {
double scaledTemp = getScaledTemperatureMin()

View File

@ -40,9 +40,6 @@ public abstract class A5_10 extends _4BSMessage {
@Override
protected State convertToStateImpl(String channelId, String channelTypeId,
Function<String, State> getCurrentStateFunc, Configuration config) {
if (!isValid()) {
return UnDefType.UNDEF;
}
switch (channelId) {
case CHANNEL_FANSPEEDSTAGE:

View File

@ -169,7 +169,7 @@ public class A5_20_04 extends A5_20 {
@Override
protected void convertFromCommandImpl(String channelId, String channelTypeId, Command command,
Function<String, State> getCurrentStateFunc, Configuration config) {
if (CHANNEL_SEND_COMMAND.equals(channelId) && (command.equals(OnOffType.ON))) {
if (VIRTUALCHANNEL_SEND_COMMAND.equals(channelId)) {
byte db3 = getPos(getCurrentStateFunc);
byte db2 = getTsp(getCurrentStateFunc);
byte db1 = (byte) (0x00 | getMc(getCurrentStateFunc) | getWuc(getCurrentStateFunc));

View File

@ -55,9 +55,6 @@ public class PTM200Message extends _RPSMessage {
@Override
protected State convertToStateImpl(String channelId, String channelTypeId,
Function<String, State> getCurrentStateFunc, Configuration config) {
if (!isValid()) {
return UnDefType.UNDEF;
}
switch (channelId) {
case CHANNEL_GENERAL_SWITCHING:
@ -77,4 +74,9 @@ public class PTM200Message extends _RPSMessage {
return UnDefType.UNDEF;
}
@Override
public boolean isValidForTeachIn() {
return false;
}
}

View File

@ -27,11 +27,12 @@ public class UTEResponse extends _VLDMessage {
public static final byte ResponseNeeded_MASK = 0x40;
public static final byte TeachIn_NotSpecified = 0x20;
public UTEResponse(ERP1Message packet) {
public UTEResponse(ERP1Message packet, boolean teachIn) {
int dataLength = packet.getPayload().length - ESP3_SENDERID_LENGTH - ESP3_RORG_LENGTH - ESP3_STATUS_LENGTH;
setData(packet.getPayload(ESP3_RORG_LENGTH, dataLength));
bytes[0] = (byte) 0x91; // bidirectional communication, teach in accepted, teach in response
bytes[0] = (byte) (teachIn ? 0x91 : 0xA1); // bidirectional communication, teach in accepted or teach out, teach
// in response
setStatus((byte) 0x80);
setSuppressRepeating(true);

View File

@ -23,11 +23,11 @@ import org.openhab.binding.enocean.internal.messages.ERP1Message.RORG;
*/
public class _4BSTeachInVariation3Response extends _4BSMessage {
public _4BSTeachInVariation3Response(ERP1Message packet) {
public _4BSTeachInVariation3Response(ERP1Message packet, boolean teachIn) {
byte[] payload = packet.getPayload(ESP3_RORG_LENGTH, RORG._4BS.getDataLength());
payload[3] = (byte) 0xF0; // telegram with EEP number and Manufacturer ID,
// EEP supported, Sender ID stored, Response
payload[3] = (byte) (teachIn ? 0xF0 : 0xD0); // telegram with EEP number and Manufacturer ID,
// EEP supported, Sender ID stored or deleted, Response
setData(payload);
setDestinationId(packet.getSenderId());

View File

@ -51,4 +51,6 @@ public abstract class _RPSMessage extends EEP {
return this;
}
public abstract boolean isValidForTeachIn();
}

View File

@ -98,7 +98,8 @@ public class D2_05_00 extends _VLDMessage {
@Override
public void addConfigPropertiesTo(DiscoveryResultBuilder discoveredThingResultBuilder) {
discoveredThingResultBuilder.withProperty(PARAMETER_EEPID, getEEPType().getId());
discoveredThingResultBuilder.withProperty(PARAMETER_SENDINGEEPID, getEEPType().getId())
.withProperty(PARAMETER_RECEIVINGEEPID, getEEPType().getId());
}
@Override

View File

@ -15,18 +15,24 @@ package org.openhab.binding.enocean.internal.eep;
import static org.openhab.binding.enocean.internal.messages.ESP3Packet.*;
import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
import org.openhab.binding.enocean.internal.eep.Base.UTEResponse;
import org.openhab.binding.enocean.internal.eep.Base._4BSMessage;
import org.openhab.binding.enocean.internal.eep.Base._4BSTeachInVariation3Response;
import org.openhab.binding.enocean.internal.eep.Base._RPSMessage;
import org.openhab.binding.enocean.internal.eep.D5_00.D5_00_01;
import org.openhab.binding.enocean.internal.eep.F6_01.F6_01_01;
import org.openhab.binding.enocean.internal.eep.F6_02.F6_02_01;
import org.openhab.binding.enocean.internal.eep.F6_05.F6_05_02;
import org.openhab.binding.enocean.internal.eep.F6_10.F6_10_00;
import org.openhab.binding.enocean.internal.eep.F6_10.F6_10_00_EltakoFPE;
import org.openhab.binding.enocean.internal.eep.F6_10.F6_10_01;
import org.openhab.binding.enocean.internal.messages.ERP1Message;
import org.openhab.binding.enocean.internal.messages.ERP1Message.RORG;
import org.openhab.binding.enocean.internal.messages.EventMessage;
import org.openhab.binding.enocean.internal.messages.EventMessage.EventMessageType;
import org.openhab.binding.enocean.internal.messages.Responses.SMACKTeachInResponse;
import org.openhab.core.util.HexUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -45,8 +51,9 @@ public class EEPFactory {
if (cl == null) {
throw new IllegalArgumentException("Message " + eepType + " not implemented");
}
return cl.newInstance();
} catch (IllegalAccessException | InstantiationException e) {
return cl.getDeclaredConstructor().newInstance();
} catch (IllegalAccessException | InstantiationException | IllegalArgumentException | InvocationTargetException
| NoSuchMethodException | SecurityException e) {
throw new IllegalArgumentException(e);
}
}
@ -69,6 +76,21 @@ public class EEPFactory {
}
}
private static EEPType getGenericEEPTypeFor(byte rorg) {
logger.info("Received unsupported EEP teach in, trying to fallback to generic thing");
RORG r = RORG.getRORG(rorg);
if (r == RORG._4BS) {
logger.info("Fallback to 4BS generic thing");
return EEPType.Generic4BS;
} else if (r == RORG.VLD) {
logger.info("Fallback to VLD generic thing");
return EEPType.GenericVLD;
} else {
logger.info("Fallback not possible");
return null;
}
}
public static EEP buildEEPFromTeachInERP1(ERP1Message msg) {
if (!msg.getIsTeachIn() && !(msg.getRORG() == RORG.RPS)) {
return null;
@ -77,38 +99,48 @@ public class EEPFactory {
switch (msg.getRORG()) {
case RPS:
try {
EEP result = new F6_01_01(msg);
if (result.isValid()) { // check if t21 is set, nu not set, and data == 0x10 or 0x00
_RPSMessage result = new F6_10_00(msg);
if (result.isValidForTeachIn()) {
return result;
}
} catch (Exception e) {
}
try {
EEP result = new F6_02_01(msg);
if (result.isValid()) { // check if highest bit is not set
_RPSMessage result = new F6_10_01(msg);
if (result.isValidForTeachIn()) {
return result;
}
} catch (Exception e) {
}
try {
EEP result = new F6_10_00(msg);
if (result.isValid()) {
_RPSMessage result = new F6_02_01(msg);
if (result.isValidForTeachIn()) {
return result;
}
} catch (Exception e) {
}
try {
EEP result = new F6_10_00_EltakoFPE(msg);
if (result.isValid()) { // check if data == 0x10 or 0x00
_RPSMessage result = new F6_05_02(msg);
if (result.isValidForTeachIn()) {
return result;
}
} catch (Exception e) {
}
try {
EEP result = new F6_10_01(msg);
if (result.isValid()) {
_RPSMessage result = new F6_01_01(msg);
if (result.isValidForTeachIn()) {
return result;
}
} catch (Exception e) {
}
try {
_RPSMessage result = new F6_10_00_EltakoFPE(msg);
if (result.isValidForTeachIn()) {
return result;
}
} catch (Exception e) {
@ -120,8 +152,8 @@ public class EEPFactory {
case _4BS: {
int db_0 = msg.getPayload()[4];
if ((db_0 & _4BSMessage.LRN_Type_Mask) == 0) { // Variation 1
logger.info("Received 4BS Teach In variation 1 without EEP");
return null;
logger.info("Received 4BS Teach In variation 1 without EEP, fallback to generic thing");
return buildEEP(EEPType.Generic4BS, msg);
}
byte db_3 = msg.getPayload()[1];
@ -132,19 +164,21 @@ public class EEPFactory {
int type = ((db_3 & 0b11) << 5) + ((db_2 & 0xFF) >>> 3);
int manufId = ((db_2 & 0b111) << 8) + (db_1 & 0xff);
logger.info("Received 4BS Teach In with EEP A5-{}-{} and manufacturerID {}",
logger.debug("Received 4BS Teach In with EEP A5-{}-{} and manufacturerID {}",
HexUtils.bytesToHex(new byte[] { (byte) func }),
HexUtils.bytesToHex(new byte[] { (byte) type }),
HexUtils.bytesToHex(new byte[] { (byte) manufId }));
EEPType eepType = EEPType.getType(RORG._4BS, func, type, manufId);
if (eepType == null) {
logger.debug("Received unsupported EEP teach in, fallback to generic thing");
eepType = EEPType.Generic4BS;
eepType = getGenericEEPTypeFor(RORG._4BS.getValue());
}
return buildEEP(eepType, msg);
if (eepType != null) {
return buildEEP(eepType, msg);
}
}
break;
case UTE: {
byte[] payload = msg.getPayload();
@ -161,38 +195,58 @@ public class EEPFactory {
EEPType eepType = EEPType.getType(RORG.getRORG(rorg), func, type, manufId);
if (eepType == null) {
logger.info("Received unsupported EEP teach in, fallback to generic thing");
RORG r = RORG.getRORG(rorg);
if (r == RORG._4BS) {
eepType = EEPType.Generic4BS;
} else if (r == RORG.VLD) {
eepType = EEPType.GenericVLD;
} else {
return null;
}
eepType = getGenericEEPTypeFor(rorg);
}
return buildEEP(eepType, msg);
if (eepType != null) {
return buildEEP(eepType, msg);
}
}
case Unknown:
case VLD:
case MSC:
case SIG:
break;
default:
return null;
}
return null;
}
public static EEP buildResponseEEPFromTeachInERP1(ERP1Message msg, byte[] senderId) {
public static EEP buildEEPFromTeachInSMACKEvent(EventMessage event) {
if (event.getEventMessageType() != EventMessageType.SA_CONFIRM_LEARN) {
return null;
}
byte[] payload = event.getPayload();
byte manufIdMSB = payload[2];
byte manufIdLSB = payload[3];
int manufId = ((manufIdMSB & 0b111) << 8) + (manufIdLSB & 0xff);
byte rorg = payload[4];
int func = payload[5] & 0xFF;
int type = payload[6] & 0xFF;
byte[] senderId = Arrays.copyOfRange(payload, 12, 12 + 4);
logger.debug("Received SMACK Teach In with EEP {}-{}-{} and manufacturerID {}",
HexUtils.bytesToHex(new byte[] { (byte) rorg }), HexUtils.bytesToHex(new byte[] { (byte) func }),
HexUtils.bytesToHex(new byte[] { (byte) type }), HexUtils.bytesToHex(new byte[] { (byte) manufId }));
EEPType eepType = EEPType.getType(RORG.getRORG(rorg), func, type, manufId);
if (eepType == null) {
eepType = getGenericEEPTypeFor(rorg);
}
return createEEP(eepType).setSenderId(senderId);
}
public static EEP buildResponseEEPFromTeachInERP1(ERP1Message msg, byte[] senderId, boolean teachIn) {
switch (msg.getRORG()) {
case UTE:
EEP result = new UTEResponse(msg);
EEP result = new UTEResponse(msg, teachIn);
result.setSenderId(senderId);
return result;
case _4BS:
result = new _4BSTeachInVariation3Response(msg);
result = new _4BSTeachInVariation3Response(msg, teachIn);
result.setSenderId(senderId);
return result;
@ -200,4 +254,31 @@ public class EEPFactory {
return null;
}
}
public static SMACKTeachInResponse buildResponseFromSMACKTeachIn(EventMessage event, boolean sendTeachOuts) {
SMACKTeachInResponse response = new SMACKTeachInResponse();
byte priority = event.getPayload()[1];
if ((priority & 0b1001) == 0b1001) {
logger.debug("gtw is already postmaster");
if (sendTeachOuts) {
logger.debug("Repeated learn is not allow hence send teach out");
response.setTeachOutResponse();
} else {
logger.debug("Send a repeated learn in");
response.setRepeatedTeachInResponse();
}
} else if ((priority & 0b100) == 0) {
logger.debug("no place for further mailbox");
response.setNoPlaceForFurtherMailbox();
} else if ((priority & 0b10) == 0) {
logger.debug("rssi is not good enough");
response.setBadRSSI();
} else if ((priority & 0b1) == 0b1) {
logger.debug("gtw is candidate for postmaster => teach in");
response.setTeachIn();
}
return response;
}
}

View File

@ -20,6 +20,7 @@ import java.util.Map;
import org.eclipse.jdt.annotation.NonNull;
import org.openhab.binding.enocean.internal.EnOceanChannelDescription;
import org.openhab.binding.enocean.internal.config.EnOceanChannelTransformationConfig;
import org.openhab.binding.enocean.internal.eep.A5_02.A5_02_01;
import org.openhab.binding.enocean.internal.eep.A5_02.A5_02_02;
import org.openhab.binding.enocean.internal.eep.A5_02.A5_02_03;
@ -164,15 +165,9 @@ public enum EEPType {
UTEResponse(RORG.UTE, 0, 0, false, UTEResponse.class, null),
_4BSTeachInVariation3Response(RORG._4BS, 0, 0, false, _4BSTeachInVariation3Response.class, null),
GenericRPS(RORG.RPS, 0xFF, 0xFF, false, GenericRPS.class, THING_TYPE_GENERICTHING, CHANNEL_GENERIC_SWITCH,
CHANNEL_GENERIC_ROLLERSHUTTER, CHANNEL_GENERIC_DIMMER, CHANNEL_GENERIC_NUMBER, CHANNEL_GENERIC_STRING,
CHANNEL_GENERIC_COLOR, CHANNEL_GENERIC_TEACHINCMD),
Generic4BS(RORG._4BS, 0xFF, 0xFF, false, Generic4BS.class, THING_TYPE_GENERICTHING, CHANNEL_GENERIC_SWITCH,
CHANNEL_GENERIC_ROLLERSHUTTER, CHANNEL_GENERIC_DIMMER, CHANNEL_GENERIC_NUMBER, CHANNEL_GENERIC_STRING,
CHANNEL_GENERIC_COLOR, CHANNEL_GENERIC_TEACHINCMD, CHANNEL_VIBRATION),
GenericVLD(RORG.VLD, 0xFF, 0xFF, false, GenericVLD.class, THING_TYPE_GENERICTHING, CHANNEL_GENERIC_SWITCH,
CHANNEL_GENERIC_ROLLERSHUTTER, CHANNEL_GENERIC_DIMMER, CHANNEL_GENERIC_NUMBER, CHANNEL_GENERIC_STRING,
CHANNEL_GENERIC_COLOR, CHANNEL_GENERIC_TEACHINCMD),
GenericRPS(RORG.RPS, 0xFF, 0xFF, false, GenericRPS.class, THING_TYPE_GENERICTHING),
Generic4BS(RORG._4BS, 0xFF, 0xFF, false, Generic4BS.class, THING_TYPE_GENERICTHING, CHANNEL_VIBRATION),
GenericVLD(RORG.VLD, 0xFF, 0xFF, false, GenericVLD.class, THING_TYPE_GENERICTHING),
PTM200(RORG.RPS, 0x00, 0x00, false, PTM200Message.class, null, CHANNEL_GENERAL_SWITCHING, CHANNEL_ROLLERSHUTTER,
CHANNEL_CONTACT),
@ -391,8 +386,8 @@ public enum EEPType {
// UniversalCommand(RORG._4BS, 0x3f, 0x7f, false, A5_3F_7F_Universal.class, THING_TYPE_UNIVERSALACTUATOR,
// CHANNEL_GENERIC_ROLLERSHUTTER, CHANNEL_GENERIC_LIGHT_SWITCHING, CHANNEL_GENERIC_DIMMER, CHANNEL_TEACHINCMD),
EltakoFSB(RORG._4BS, 0x3f, 0x7f, false, "EltakoFSB", 0, A5_3F_7F_EltakoFSB.class, THING_TYPE_ROLLERSHUTTER, 0,
new Hashtable<String, Configuration>() {
EltakoFSB(RORG._4BS, 0x3f, 0x7f, false, false, "EltakoFSB", 0, A5_3F_7F_EltakoFSB.class, THING_TYPE_ROLLERSHUTTER,
0, new Hashtable<String, Configuration>() {
private static final long serialVersionUID = 1L;
{
put(CHANNEL_ROLLERSHUTTER, new Configuration());
@ -404,10 +399,10 @@ public enum EEPType {
}
}),
Thermostat(RORG._4BS, 0x20, 0x04, false, A5_20_04.class, THING_TYPE_THERMOSTAT, CHANNEL_VALVE_POSITION,
Thermostat(RORG._4BS, 0x20, 0x04, false, true, A5_20_04.class, THING_TYPE_THERMOSTAT, CHANNEL_VALVE_POSITION,
CHANNEL_BUTTON_LOCK, CHANNEL_DISPLAY_ORIENTATION, CHANNEL_TEMPERATURE_SETPOINT, CHANNEL_TEMPERATURE,
CHANNEL_FEED_TEMPERATURE, CHANNEL_MEASUREMENT_CONTROL, CHANNEL_FAILURE_CODE, CHANNEL_WAKEUPCYCLE,
CHANNEL_SERVICECOMMAND, CHANNEL_STATUS_REQUEST_EVENT, CHANNEL_SEND_COMMAND),
CHANNEL_SERVICECOMMAND),
SwitchWithEnergyMeasurment_00(RORG.VLD, 0x01, 0x00, true, D2_01_00.class, THING_TYPE_MEASUREMENTSWITCH,
CHANNEL_GENERAL_SWITCHING, CHANNEL_TOTALUSAGE),
@ -512,23 +507,36 @@ public enum EEPType {
private boolean supportsRefresh;
private boolean requestsResponse;
EEPType(RORG rorg, int func, int type, boolean supportsRefresh, Class<? extends EEP> eepClass,
ThingTypeUID thingTypeUID, String... channelIds) {
this(rorg, func, type, supportsRefresh, eepClass, thingTypeUID, -1, channelIds);
}
EEPType(RORG rorg, int func, int type, boolean supportsRefresh, boolean requestsResponse,
Class<? extends EEP> eepClass, ThingTypeUID thingTypeUID, String... channelIds) {
this(rorg, func, type, supportsRefresh, requestsResponse, eepClass, thingTypeUID, -1, channelIds);
}
EEPType(RORG rorg, int func, int type, boolean supportsRefresh, String manufactorSuffix, int manufId,
Class<? extends EEP> eepClass, ThingTypeUID thingTypeUID, String... channelIds) {
this(rorg, func, type, supportsRefresh, manufactorSuffix, manufId, eepClass, thingTypeUID, 0, channelIds);
this(rorg, func, type, supportsRefresh, false, manufactorSuffix, manufId, eepClass, thingTypeUID, 0,
channelIds);
}
EEPType(RORG rorg, int func, int type, boolean supportsRefresh, Class<? extends EEP> eepClass,
ThingTypeUID thingTypeUID, int command, String... channelIds) {
this(rorg, func, type, supportsRefresh, "", 0, eepClass, thingTypeUID, command, channelIds);
this(rorg, func, type, supportsRefresh, false, "", 0, eepClass, thingTypeUID, command, channelIds);
}
EEPType(RORG rorg, int func, int type, boolean supportsRefresh, String manufactorSuffix, int manufId,
EEPType(RORG rorg, int func, int type, boolean supportsRefresh, boolean requestsResponse,
Class<? extends EEP> eepClass, ThingTypeUID thingTypeUID, int command, String... channelIds) {
this(rorg, func, type, supportsRefresh, requestsResponse, "", 0, eepClass, thingTypeUID, command, channelIds);
}
EEPType(RORG rorg, int func, int type, boolean supportsRefresh, boolean requestsResponse, String manufactorSuffix,
int manufId, Class<? extends EEP> eepClass, ThingTypeUID thingTypeUID, int command, String... channelIds) {
this.rorg = rorg;
this.func = func;
this.type = type;
@ -538,24 +546,18 @@ public enum EEPType {
this.manufactorSuffix = manufactorSuffix;
this.manufactorId = manufId;
this.supportsRefresh = supportsRefresh;
this.requestsResponse = requestsResponse;
for (String id : channelIds) {
this.channelIdsWithConfig.put(id, new Configuration());
this.supportedChannels.put(id, CHANNELID2CHANNELDESCRIPTION.get(id));
}
this.channelIdsWithConfig.put(CHANNEL_RSSI, new Configuration());
this.supportedChannels.put(CHANNEL_RSSI, CHANNELID2CHANNELDESCRIPTION.get(CHANNEL_RSSI));
this.channelIdsWithConfig.put(CHANNEL_REPEATCOUNT, new Configuration());
this.supportedChannels.put(CHANNEL_REPEATCOUNT, CHANNELID2CHANNELDESCRIPTION.get(CHANNEL_REPEATCOUNT));
this.channelIdsWithConfig.put(CHANNEL_LASTRECEIVED, new Configuration());
this.supportedChannels.put(CHANNEL_LASTRECEIVED, CHANNELID2CHANNELDESCRIPTION.get(CHANNEL_LASTRECEIVED));
addDefaultChannels();
}
EEPType(RORG rorg, int func, int type, boolean supportsRefresh, String manufactorSuffix, int manufId,
Class<? extends EEP> eepClass, ThingTypeUID thingTypeUID, int command,
EEPType(RORG rorg, int func, int type, boolean supportsRefresh, boolean requestsResponse, String manufactorSuffix,
int manufId, Class<? extends EEP> eepClass, ThingTypeUID thingTypeUID, int command,
Hashtable<String, Configuration> channelConfigs) {
this.rorg = rorg;
this.func = func;
@ -567,11 +569,46 @@ public enum EEPType {
this.manufactorSuffix = manufactorSuffix;
this.manufactorId = manufId;
this.supportsRefresh = supportsRefresh;
this.requestsResponse = requestsResponse;
for (String id : channelConfigs.keySet()) {
this.supportedChannels.put(id, CHANNELID2CHANNELDESCRIPTION.get(id));
}
addDefaultChannels();
}
private void addDefaultChannels() {
if (THING_TYPE_GENERICTHING.equals(this.thingTypeUID)) {
this.channelIdsWithConfig.put(CHANNEL_GENERIC_SWITCH, new EnOceanChannelTransformationConfig());
this.supportedChannels.put(CHANNEL_GENERIC_SWITCH,
CHANNELID2CHANNELDESCRIPTION.get(CHANNEL_GENERIC_SWITCH));
this.channelIdsWithConfig.put(CHANNEL_GENERIC_ROLLERSHUTTER, new EnOceanChannelTransformationConfig());
this.supportedChannels.put(CHANNEL_GENERIC_ROLLERSHUTTER,
CHANNELID2CHANNELDESCRIPTION.get(CHANNEL_GENERIC_ROLLERSHUTTER));
this.channelIdsWithConfig.put(CHANNEL_GENERIC_DIMMER, new EnOceanChannelTransformationConfig());
this.supportedChannels.put(CHANNEL_GENERIC_DIMMER,
CHANNELID2CHANNELDESCRIPTION.get(CHANNEL_GENERIC_DIMMER));
this.channelIdsWithConfig.put(CHANNEL_GENERIC_NUMBER, new EnOceanChannelTransformationConfig());
this.supportedChannels.put(CHANNEL_GENERIC_NUMBER,
CHANNELID2CHANNELDESCRIPTION.get(CHANNEL_GENERIC_NUMBER));
this.channelIdsWithConfig.put(CHANNEL_GENERIC_STRING, new EnOceanChannelTransformationConfig());
this.supportedChannels.put(CHANNEL_GENERIC_STRING,
CHANNELID2CHANNELDESCRIPTION.get(CHANNEL_GENERIC_STRING));
this.channelIdsWithConfig.put(CHANNEL_GENERIC_COLOR, new EnOceanChannelTransformationConfig());
this.supportedChannels.put(CHANNEL_GENERIC_COLOR, CHANNELID2CHANNELDESCRIPTION.get(CHANNEL_GENERIC_COLOR));
this.channelIdsWithConfig.put(CHANNEL_GENERIC_TEACHINCMD, new EnOceanChannelTransformationConfig());
this.supportedChannels.put(CHANNEL_GENERIC_TEACHINCMD,
CHANNELID2CHANNELDESCRIPTION.get(CHANNEL_GENERIC_TEACHINCMD));
}
this.channelIdsWithConfig.put(CHANNEL_RSSI, new Configuration());
this.supportedChannels.put(CHANNEL_RSSI, CHANNELID2CHANNELDESCRIPTION.get(CHANNEL_RSSI));
@ -580,6 +617,12 @@ public enum EEPType {
this.channelIdsWithConfig.put(CHANNEL_LASTRECEIVED, new Configuration());
this.supportedChannels.put(CHANNEL_LASTRECEIVED, CHANNELID2CHANNELDESCRIPTION.get(CHANNEL_LASTRECEIVED));
if (requestsResponse) {
this.channelIdsWithConfig.put(CHANNEL_STATUS_REQUEST_EVENT, new Configuration());
this.supportedChannels.put(CHANNEL_STATUS_REQUEST_EVENT,
CHANNELID2CHANNELDESCRIPTION.get(CHANNEL_STATUS_REQUEST_EVENT));
}
}
public Class<? extends EEP> getEEPClass() {
@ -602,6 +645,10 @@ public enum EEPType {
return supportsRefresh;
}
public boolean getRequstesResponse() {
return requestsResponse;
}
public Map<String, EnOceanChannelDescription> GetSupportedChannels() {
return Collections.unmodifiableMap(supportedChannels);
}
@ -614,7 +661,7 @@ public enum EEPType {
}
public boolean isChannelSupported(String channelId, String channelTypeId) {
return supportedChannels.containsKey(channelId)
return supportedChannels.containsKey(channelId) || VIRTUALCHANNEL_SEND_COMMAND.equals(channelId)
|| supportedChannels.values().stream().anyMatch(c -> c.channelTypeUID.getId().equals(channelTypeId));
}

View File

@ -34,9 +34,6 @@ public class F6_01_01 extends _RPSMessage {
@Override
protected String convertToEventImpl(String channelId, String channelTypeId, String lastEvent,
Configuration config) {
if (!isValid()) {
return null;
}
return getBit(bytes[0], 4) ? CommonTriggerEvents.PRESSED : CommonTriggerEvents.RELEASED;
}
@ -45,4 +42,10 @@ public class F6_01_01 extends _RPSMessage {
protected boolean validateData(byte[] bytes) {
return super.validateData(bytes) && !getBit(bytes[0], 7);
}
@Override
public boolean isValidForTeachIn() {
// just treat press as teach in, ignore release
return t21 && !nu && bytes[0] == 0x10;
}
}

View File

@ -55,9 +55,6 @@ public class F6_02_01 extends _RPSMessage {
@Override
protected String convertToEventImpl(String channelId, String channelTypeId, String lastEvent,
Configuration config) {
if (!isValid()) {
return null;
}
if (t21 && nu) {
byte dir1 = channelId.equals(CHANNEL_ROCKERSWITCH_CHANNELA) ? A0 : B0;
@ -112,11 +109,6 @@ public class F6_02_01 extends _RPSMessage {
// this method is used by the classic device listener channels to convert an rocker switch message into an
// appropriate item update
State currentState = getCurrentStateFunc.apply(channelId);
if (!isValid()) {
return UnDefType.UNDEF;
}
if (t21 && nu) {
EnOceanChannelVirtualRockerSwitchConfig c = config.as(EnOceanChannelVirtualRockerSwitchConfig.class);
byte dir1 = c.getChannel() == Channel.ChannelA ? A0 : B0;
@ -179,4 +171,22 @@ public class F6_02_01 extends _RPSMessage {
protected boolean validateData(byte[] bytes) {
return super.validateData(bytes) && !getBit(bytes[0], 7);
}
@Override
public boolean isValidForTeachIn() {
if (t21) {
// just treat press as teach in => DB0.4 has to be set
if (!getBit(bytes[0], 4)) {
return false;
}
// DB0.7 is never set for rocker switch message
if (getBit(bytes[0], 7)) {
return false;
}
} else {
return false;
}
return true;
}
}

View File

@ -52,9 +52,6 @@ public class F6_02_02 extends _RPSMessage {
@Override
protected String convertToEventImpl(String channelId, String channelTypeId, String lastEvent,
Configuration config) {
if (!isValid()) {
return null;
}
if (t21 && nu) {
byte dir1 = channelId.equals(CHANNEL_ROCKERSWITCH_CHANNELA) ? AI : BI;
@ -109,11 +106,6 @@ public class F6_02_02 extends _RPSMessage {
// this method is used by the classic device listener channels to convert an rocker switch message into an
// appropriate item update
State currentState = getCurrentStateFunc.apply(channelId);
if (!isValid()) {
return UnDefType.UNDEF;
}
if (t21 && nu) {
EnOceanChannelVirtualRockerSwitchConfig c = config.as(EnOceanChannelVirtualRockerSwitchConfig.class);
byte dir1 = c.getChannel() == Channel.ChannelA ? AI : BI;
@ -171,4 +163,9 @@ public class F6_02_02 extends _RPSMessage {
private State inverse(UpDownType currentState) {
return currentState == UpDownType.UP ? UpDownType.DOWN : UpDownType.UP;
}
@Override
public boolean isValidForTeachIn() {
return false; // Never treat a message as F6-02-02, let user decide which orientation of rocker switch is used
}
}

View File

@ -44,9 +44,6 @@ public class F6_05_02 extends _RPSMessage {
@Override
protected State convertToStateImpl(String channelId, String channelTypeId,
Function<String, State> getCurrentStateFunc, Configuration config) {
if (!isValid()) {
return UnDefType.UNDEF;
}
switch (channelId) {
case CHANNEL_SMOKEDETECTION:
@ -62,4 +59,10 @@ public class F6_05_02 extends _RPSMessage {
protected boolean validateData(byte[] bytes) {
return super.validateData(bytes) && (bytes[0] == ALARM_OFF || bytes[0] == ALARM_ON || bytes[0] == ENERGY_LOW);
}
@Override
public boolean isValidForTeachIn() {
// just treat the first message with ALARM_ON as teach in
return !t21 && !nu && bytes[0] == ALARM_ON;
}
}

View File

@ -47,9 +47,6 @@ public class F6_10_00 extends _RPSMessage {
@Override
protected State convertToStateImpl(String channelId, String channelTypeId,
Function<String, State> getCurrentStateFunc, Configuration config) {
if (!isValid()) {
return UnDefType.UNDEF;
}
byte data = (byte) (bytes[0] & 0xF0);
@ -82,4 +79,9 @@ public class F6_10_00 extends _RPSMessage {
protected boolean validateData(byte[] bytes) {
return super.validateData(bytes) && getBit(bytes[0], 7) && getBit(bytes[0], 6);
}
@Override
public boolean isValidForTeachIn() {
return t21 && !nu && getBit(bytes[0], 7) && getBit(bytes[0], 6);
}
}

View File

@ -61,4 +61,10 @@ public class F6_10_00_EltakoFPE extends _RPSMessage {
// FPE just sends 0b00010000 or 0b00000000 value, so we apply mask 0b11101111
return super.validateData(bytes) && ((bytes[0] & (byte) 0xEF) == (byte) 0x00);
}
@Override
public boolean isValidForTeachIn() {
// just treat CLOSED as teach in
return bytes[0] == CLOSED;
}
}

View File

@ -47,9 +47,6 @@ public class F6_10_01 extends _RPSMessage {
@Override
protected State convertToStateImpl(String channelId, String channelTypeId,
Function<String, State> getCurrentStateFunc, Configuration config) {
if (!isValid()) {
return UnDefType.UNDEF;
}
byte data = (byte) (bytes[0] & 0x0F);
@ -82,4 +79,10 @@ public class F6_10_01 extends _RPSMessage {
protected boolean validateData(byte[] bytes) {
return super.validateData(bytes) && getBit(bytes[0], 6) && getBit(bytes[0], 3) && getBit(bytes[0], 2);
}
@Override
public boolean isValidForTeachIn() {
return !getBit(bytes[0], 7) && getBit(bytes[0], 6) && !getBit(bytes[0], 5) && !getBit(bytes[0], 4)
&& getBit(bytes[0], 3) && getBit(bytes[0], 2);
}
}

View File

@ -12,7 +12,7 @@
*/
package org.openhab.binding.enocean.internal.eep.Generic;
import static org.openhab.binding.enocean.internal.EnOceanBindingConstants.PARAMETER_EEPID;
import static org.openhab.binding.enocean.internal.EnOceanBindingConstants.*;
import static org.openhab.binding.enocean.internal.messages.ESP3Packet.*;
import java.lang.reflect.InvocationTargetException;
@ -161,6 +161,7 @@ public class GenericEEP extends EEP {
@Override
public void addConfigPropertiesTo(DiscoveryResultBuilder discoveredThingResultBuilder) {
discoveredThingResultBuilder.withProperty(PARAMETER_EEPID, getEEPType().getId());
discoveredThingResultBuilder.withProperty(PARAMETER_SENDINGEEPID, getEEPType().getId())
.withProperty(PARAMETER_RECEIVINGEEPID, getEEPType().getId());
}
}

View File

@ -28,6 +28,7 @@ import org.openhab.binding.enocean.internal.eep.EEPFactory;
import org.openhab.binding.enocean.internal.eep.EEPType;
import org.openhab.binding.enocean.internal.messages.BasePacket;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.thing.Channel;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
@ -68,8 +69,8 @@ public class EnOceanBaseActuatorHandler extends EnOceanBaseSensorHandler {
* @param senderIdOffset to be validated
* @return true if senderIdOffset is between ]0;128[ and is not used yet
*/
private boolean validateSenderIdOffset(int senderIdOffset) {
if (senderIdOffset == -1) {
private boolean validateSenderIdOffset(Integer senderIdOffset) {
if (senderIdOffset == null) {
return true;
}
@ -157,26 +158,24 @@ public class EnOceanBaseActuatorHandler extends EnOceanBaseSensorHandler {
}
private boolean initializeIdForSending() {
// Generic things are treated as actuator things, however to support also generic sensors one can define a
// senderIdOffset of -1
// TODO: seperate generic actuators from generic sensors?
String thingTypeId = this.getThing().getThingTypeUID().getId();
String genericThingTypeId = THING_TYPE_GENERICTHING.getId();
if (getConfiguration().senderIdOffset == -1 && thingTypeId.equals(genericThingTypeId)) {
return true;
}
EnOceanBridgeHandler bridgeHandler = getBridgeHandler();
if (bridgeHandler == null) {
return false;
}
// if senderIdOffset is not set (=> defaults to -1) or set to -1, the next free senderIdOffset is determined
if (getConfiguration().senderIdOffset == -1) {
// Generic things are treated as actuator things, however to support also generic sensors one can omit
// senderIdOffset
// TODO: seperate generic actuators from generic sensors?
if ((getConfiguration().senderIdOffset == null
&& THING_TYPE_GENERICTHING.equals(this.getThing().getThingTypeUID()))) {
return true;
}
// if senderIdOffset is not set, the next free senderIdOffset is determined
if (getConfiguration().senderIdOffset == null) {
Configuration updateConfig = editConfiguration();
getConfiguration().senderIdOffset = bridgeHandler.getNextSenderId(thing);
if (getConfiguration().senderIdOffset == -1) {
if (getConfiguration().senderIdOffset == null) {
configurationErrorDescription = "Could not get a free sender Id from Bridge";
return false;
}
@ -185,12 +184,10 @@ public class EnOceanBaseActuatorHandler extends EnOceanBaseSensorHandler {
}
byte[] baseId = bridgeHandler.getBaseId();
baseId[3] = (byte) ((baseId[3] & 0xFF) + getConfiguration().senderIdOffset);
baseId[3] = (byte) ((baseId[3] + getConfiguration().senderIdOffset) & 0xFF);
this.senderId = baseId;
this.updateProperty(PROPERTY_ENOCEAN_ID, HexUtils.bytesToHex(this.senderId));
this.updateProperty(PROPERTY_SENDINGENOCEAN_ID, HexUtils.bytesToHex(this.senderId));
bridgeHandler.addSender(getConfiguration().senderIdOffset, thing);
return true;
}
@ -203,6 +200,22 @@ public class EnOceanBaseActuatorHandler extends EnOceanBaseSensorHandler {
}
}
@Override
protected void sendRequestResponse() {
sendMessage(VIRTUALCHANNEL_SEND_COMMAND, VIRTUALCHANNEL_SEND_COMMAND, OnOffType.ON, null);
}
protected void sendMessage(String channelId, String channelTypeId, Command command, Configuration channelConfig) {
EEP eep = EEPFactory.createEEP(sendingEEPType);
if (eep.convertFromCommand(channelId, channelTypeId, command, id -> getCurrentState(id), channelConfig)
.hasData()) {
BasePacket msg = eep.setSenderId(senderId).setDestinationId(destinationId)
.setSuppressRepeating(getConfiguration().suppressRepeating).getERP1Message();
getBridgeHandler().sendMessage(msg, null);
}
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
// We must have a valid sendingEEPType and sender id to send commands
@ -237,16 +250,7 @@ public class EnOceanBaseActuatorHandler extends EnOceanBaseSensorHandler {
try {
Configuration channelConfig = channel.getConfiguration();
EEP eep = EEPFactory.createEEP(sendingEEPType);
if (eep.convertFromCommand(channelId, channelTypeId, command, id -> getCurrentState(id), channelConfig)
.hasData()) {
BasePacket msg = eep.setSenderId(senderId).setDestinationId(destinationId)
.setSuppressRepeating(getConfiguration().suppressRepeating).getERP1Message();
getBridgeHandler().sendMessage(msg, null);
}
sendMessage(channelId, channelTypeId, command, channelConfig);
} catch (IllegalArgumentException e) {
logger.warn("Exception while sending telegram!", e);
}
@ -254,11 +258,16 @@ public class EnOceanBaseActuatorHandler extends EnOceanBaseSensorHandler {
@Override
public void handleRemoval() {
if (getConfiguration().senderIdOffset > 0) {
EnOceanBridgeHandler bridgeHandler = getBridgeHandler();
if (bridgeHandler != null) {
EnOceanBridgeHandler bridgeHandler = getBridgeHandler();
if (bridgeHandler != null) {
if (getConfiguration().senderIdOffset != null && getConfiguration().senderIdOffset > 0) {
bridgeHandler.removeSender(getConfiguration().senderIdOffset);
}
if (bridgeHandler.isSmackClient(this.thing)) {
logger.warn("Removing smack client (ThingId: {}) without teach out!", this.thing.getUID().getId());
}
}
super.handleRemoval();

View File

@ -19,8 +19,11 @@ import java.util.Collections;
import java.util.Comparator;
import java.util.Hashtable;
import java.util.Set;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;
import org.apache.commons.lang3.NotImplementedException;
import org.openhab.binding.enocean.internal.config.EnOceanBaseConfig;
import org.openhab.binding.enocean.internal.eep.EEP;
import org.openhab.binding.enocean.internal.eep.EEPFactory;
@ -57,6 +60,8 @@ public class EnOceanBaseSensorHandler extends EnOceanBaseThingHandler implements
protected final Hashtable<RORG, EEPType> receivingEEPTypes = new Hashtable<>();
protected ScheduledFuture<?> responseFuture = null;
public EnOceanBaseSensorHandler(Thing thing, ItemChannelLinkRegistry itemChannelLinkRegistry) {
super(thing, itemChannelLinkRegistry);
}
@ -104,7 +109,7 @@ public class EnOceanBaseSensorHandler extends EnOceanBaseThingHandler implements
}
@Override
public long getSenderIdToListenTo() {
public long getEnOceanIdToListenTo() {
return Long.parseLong(config.enoceanId, 16);
}
@ -129,6 +134,10 @@ public class EnOceanBaseSensorHandler extends EnOceanBaseThingHandler implements
};
}
protected void sendRequestResponse() {
throw new NotImplementedException("Sensor cannot send responses");
}
@Override
public void packetReceived(BasePacket packet) {
ERP1Message msg = (ERP1Message) packet;
@ -175,6 +184,15 @@ public class EnOceanBaseSensorHandler extends EnOceanBaseThingHandler implements
break;
}
});
if (receivingEEPType.getRequstesResponse()) {
// fire trigger for receive
triggerChannel(prepareAnswer, "requestAnswer");
// Send response after 100ms
if (responseFuture == null || responseFuture.isDone()) {
responseFuture = scheduler.schedule(this::sendRequestResponse, 100, TimeUnit.MILLISECONDS);
}
}
}
}
}

View File

@ -12,6 +12,8 @@
*/
package org.openhab.binding.enocean.internal.handler;
import static org.openhab.binding.enocean.internal.EnOceanBindingConstants.*;
import java.util.AbstractMap.SimpleEntry;
import java.util.Collection;
import java.util.Hashtable;
@ -63,18 +65,25 @@ public abstract class EnOceanBaseThingHandler extends ConfigStatusThingHandler {
private ItemChannelLinkRegistry itemChannelLinkRegistry;
protected @NonNull ChannelUID prepareAnswer;
public EnOceanBaseThingHandler(Thing thing, ItemChannelLinkRegistry itemChannelLinkRegistry) {
super(thing);
this.itemChannelLinkRegistry = itemChannelLinkRegistry;
prepareAnswer = new ChannelUID(thing.getUID(), CHANNEL_STATUS_REQUEST_EVENT);
}
@SuppressWarnings("null")
@Override
public void initialize() {
logger.debug("Initializing enocean base thing handler.");
this.gateway = null; // reset gateway in case we change the bridge
this.config = null;
initializeThing((getBridge() == null) ? null : getBridge().getStatus());
Bridge bridge = getBridge();
if (bridge == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "A bridge is required");
} else {
initializeThing(bridge.getStatus());
}
}
private void initializeThing(ThingStatus bridgeStatus) {
@ -143,6 +152,10 @@ public abstract class EnOceanBaseThingHandler extends ConfigStatusThingHandler {
String channelId = entry.getKey();
EnOceanChannelDescription cd = entry.getValue().GetSupportedChannels().get(channelId);
if (cd == null) {
return;
}
// if we do not need to auto create channel => skip
if (!cd.autoCreate) {
return;

View File

@ -15,30 +15,35 @@ package org.openhab.binding.enocean.internal.handler;
import static org.openhab.binding.enocean.internal.EnOceanBindingConstants.*;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedList;
import java.util.Set;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import org.openhab.binding.enocean.internal.EnOceanConfigStatusMessage;
import org.openhab.binding.enocean.internal.config.EnOceanBaseConfig;
import org.openhab.binding.enocean.internal.config.EnOceanBridgeConfig;
import org.openhab.binding.enocean.internal.config.EnOceanBridgeConfig.ESPVersion;
import org.openhab.binding.enocean.internal.messages.BasePacket;
import org.openhab.binding.enocean.internal.messages.BaseResponse;
import org.openhab.binding.enocean.internal.messages.ESP3PacketFactory;
import org.openhab.binding.enocean.internal.messages.RDBaseIdResponse;
import org.openhab.binding.enocean.internal.messages.RDRepeaterResponse;
import org.openhab.binding.enocean.internal.messages.RDVersionResponse;
import org.openhab.binding.enocean.internal.messages.Response;
import org.openhab.binding.enocean.internal.messages.Response.ResponseType;
import org.openhab.binding.enocean.internal.messages.Responses.BaseResponse;
import org.openhab.binding.enocean.internal.messages.Responses.RDBaseIdResponse;
import org.openhab.binding.enocean.internal.messages.Responses.RDLearnedClientsResponse;
import org.openhab.binding.enocean.internal.messages.Responses.RDLearnedClientsResponse.LearnedClient;
import org.openhab.binding.enocean.internal.messages.Responses.RDRepeaterResponse;
import org.openhab.binding.enocean.internal.messages.Responses.RDVersionResponse;
import org.openhab.binding.enocean.internal.transceiver.EnOceanESP2Transceiver;
import org.openhab.binding.enocean.internal.transceiver.EnOceanESP3Transceiver;
import org.openhab.binding.enocean.internal.transceiver.EnOceanTransceiver;
import org.openhab.binding.enocean.internal.transceiver.PacketListener;
import org.openhab.binding.enocean.internal.transceiver.ResponseListener;
import org.openhab.binding.enocean.internal.transceiver.ResponseListenerIgnoringTimeouts;
import org.openhab.binding.enocean.internal.transceiver.TeachInListener;
import org.openhab.binding.enocean.internal.transceiver.TransceiverErrorListener;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.config.core.status.ConfigStatusMessage;
@ -76,9 +81,12 @@ public class EnOceanBridgeHandler extends ConfigStatusBridgeHandler implements T
private byte[] baseId = null;
private Thing[] sendingThings = new Thing[128];
private int nextSenderId = 0;
private SerialPortManager serialPortManager;
private boolean smackAvailable = false;
private boolean sendTeachOuts = true;
private Set<String> smackClients = Set.of();
public EnOceanBridgeHandler(Bridge bridge, SerialPortManager serialPortManager) {
super(bridge);
this.serialPortManager = serialPortManager;
@ -157,13 +165,6 @@ public class EnOceanBridgeHandler extends ConfigStatusBridgeHandler implements T
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"SerialPortManager could not be found");
} else {
Object devId = getConfig().get(NEXTSENDERID);
if (devId != null) {
nextSenderId = ((BigDecimal) devId).intValue();
} else {
nextSenderId = 0;
}
if (connectorTask == null || connectorTask.isDone()) {
connectorTask = scheduler.scheduleWithFixedDelay(new Runnable() {
@Override
@ -187,9 +188,12 @@ public class EnOceanBridgeHandler extends ConfigStatusBridgeHandler implements T
switch (c.getESPVersion()) {
case ESP2:
transceiver = new EnOceanESP2Transceiver(c.path, this, scheduler, serialPortManager);
smackAvailable = false;
sendTeachOuts = false;
break;
case ESP3:
transceiver = new EnOceanESP3Transceiver(c.path, this, scheduler, serialPortManager);
sendTeachOuts = c.sendTeachOuts;
break;
default:
break;
@ -200,6 +204,7 @@ public class EnOceanBridgeHandler extends ConfigStatusBridgeHandler implements T
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_PENDING, "starting rx thread...");
transceiver.StartReceiving(scheduler);
logger.info("EnOceanSerialTransceiver RX thread up and running");
if (c.rs485) {
if (c.rs485BaseId != null && !c.rs485BaseId.isEmpty()) {
@ -238,6 +243,28 @@ public class EnOceanBridgeHandler extends ConfigStatusBridgeHandler implements T
}
}
});
if (c.getESPVersion() == ESPVersion.ESP3) {
logger.debug("set postmaster mailboxes");
transceiver.sendBasePacket(ESP3PacketFactory.SA_WR_POSTMASTER((byte) (c.enableSmack ? 20 : 0)),
new ResponseListenerIgnoringTimeouts<BaseResponse>() {
@Override
public void responseReceived(BaseResponse response) {
logger.debug("received response for postmaster mailboxes");
if (response.isOK()) {
updateProperty("Postmaster mailboxes:",
Integer.toString(c.enableSmack ? 20 : 0));
smackAvailable = c.enableSmack;
refreshProperties();
} else {
updateProperty("Postmaster mailboxes:", "Not supported");
smackAvailable = false;
}
}
});
}
}
logger.debug("request version info");
@ -283,7 +310,7 @@ public class EnOceanBridgeHandler extends ConfigStatusBridgeHandler implements T
Collection<ConfigStatusMessage> configStatusMessages = new LinkedList<>();
// The serial port must be provided
String path = (String) getThing().getConfiguration().get(PATH);
String path = getThing().getConfiguration().as(EnOceanBridgeConfig.class).path;
if (path == null || path.isEmpty()) {
configStatusMessages.add(ConfigStatusMessage.Builder.error(PATH)
.withMessageKeySuffix(EnOceanConfigStatusMessage.PORT_MISSING.getMessageKey()).withArguments(PATH)
@ -297,30 +324,33 @@ public class EnOceanBridgeHandler extends ConfigStatusBridgeHandler implements T
return baseId.clone();
}
public int getNextSenderId(Thing sender) {
// TODO: change id to enoceanId
public boolean isSmackClient(Thing sender) {
return smackClients.contains(sender.getConfiguration().as(EnOceanBaseConfig.class).enoceanId);
}
public Integer getNextSenderId(Thing sender) {
return getNextSenderId(sender.getConfiguration().as(EnOceanBaseConfig.class).enoceanId);
}
public int getNextSenderId(String senderId) {
if (nextSenderId != 0 && sendingThings[nextSenderId] == null) {
int result = nextSenderId;
Configuration config = getConfig();
config.put(NEXTSENDERID, null);
updateConfiguration(config);
nextSenderId = 0;
public Integer getNextSenderId(String enoceanId) {
EnOceanBridgeConfig config = getConfigAs(EnOceanBridgeConfig.class);
return result;
if (config.nextSenderId != null && sendingThings[config.nextSenderId] == null) {
Configuration c = this.editConfiguration();
c.put(PARAMETER_NEXT_SENDERID, null);
updateConfiguration(c);
return config.nextSenderId;
}
for (byte i = 1; i < sendingThings.length; i++) {
for (int i = 1; i < sendingThings.length; i++) {
if (sendingThings[i] == null || sendingThings[i].getConfiguration().as(EnOceanBaseConfig.class).enoceanId
.equalsIgnoreCase(senderId)) {
.equalsIgnoreCase(enoceanId)) {
return i;
}
}
return -1;
return null;
}
public boolean existsSender(int id, Thing sender) {
@ -345,7 +375,7 @@ public class EnOceanBridgeHandler extends ConfigStatusBridgeHandler implements T
}
public void addPacketListener(PacketListener listener) {
addPacketListener(listener, listener.getSenderIdToListenTo());
addPacketListener(listener, listener.getEnOceanIdToListenTo());
}
public void addPacketListener(PacketListener listener, long senderIdToListenTo) {
@ -355,7 +385,7 @@ public class EnOceanBridgeHandler extends ConfigStatusBridgeHandler implements T
}
public void removePacketListener(PacketListener listener) {
removePacketListener(listener, listener.getSenderIdToListenTo());
removePacketListener(listener, listener.getEnOceanIdToListenTo());
}
public void removePacketListener(PacketListener listener, long senderIdToListenTo) {
@ -364,12 +394,74 @@ public class EnOceanBridgeHandler extends ConfigStatusBridgeHandler implements T
}
}
public void startDiscovery(PacketListener teachInListener) {
public void startDiscovery(TeachInListener teachInListener) {
transceiver.startDiscovery(teachInListener);
if (smackAvailable) {
// activate smack teach in
logger.debug("activate smack teach in");
try {
transceiver.sendBasePacket(ESP3PacketFactory.SA_WR_LEARNMODE(true),
new ResponseListenerIgnoringTimeouts<BaseResponse>() {
@Override
public void responseReceived(BaseResponse response) {
if (response.isOK()) {
logger.debug("Smack teach in activated");
}
}
});
} catch (IOException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"Smack packet could not be send: " + e.getMessage());
}
}
}
public void stopDiscovery() {
transceiver.stopDiscovery();
try {
transceiver.sendBasePacket(ESP3PacketFactory.SA_WR_LEARNMODE(false), null);
refreshProperties();
} catch (IOException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"Smack packet could not be send: " + e.getMessage());
}
}
private void refreshProperties() {
if (getThing().getStatus() == ThingStatus.ONLINE && smackAvailable) {
logger.debug("request learned smack clients");
try {
transceiver.sendBasePacket(ESP3PacketFactory.SA_RD_LEARNEDCLIENTS,
new ResponseListenerIgnoringTimeouts<RDLearnedClientsResponse>() {
@Override
public void responseReceived(RDLearnedClientsResponse response) {
logger.debug("received response for learned smack clients");
if (response.isValid() && response.isOK()) {
LearnedClient[] clients = response.getLearnedClients();
updateProperty("Learned smart ack clients", Integer.toString(clients.length));
updateProperty("Smart ack clients",
Arrays.stream(clients)
.map(x -> String.format("%s (MB Idx: %d)",
HexUtils.bytesToHex(x.clientId), x.mailboxIndex))
.collect(Collectors.joining(", ")));
smackClients = Arrays.stream(clients).map(x -> HexUtils.bytesToHex(x.clientId))
.collect(Collectors.toSet());
}
}
});
} catch (IOException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"Smack packet could not be send: " + e.getMessage());
}
}
}
@Override
@ -378,4 +470,8 @@ public class EnOceanBridgeHandler extends ConfigStatusBridgeHandler implements T
transceiver = null;
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, exception.getMessage());
}
public boolean sendTeachOuts() {
return sendTeachOuts;
}
}

View File

@ -71,7 +71,7 @@ public class EnOceanClassicDeviceHandler extends EnOceanBaseActuatorHandler {
}
@Override
public long getSenderIdToListenTo() {
public long getEnOceanIdToListenTo() {
return 0;
}

View File

@ -13,8 +13,10 @@
package org.openhab.binding.enocean.internal.messages;
import org.openhab.binding.enocean.internal.EnOceanBindingConstants;
import org.openhab.binding.enocean.internal.Helper;
import org.openhab.binding.enocean.internal.messages.BasePacket.ESPPacketType;
import org.openhab.binding.enocean.internal.messages.CCMessage.CCMessageType;
import org.openhab.binding.enocean.internal.messages.SAMessage.SAMessageType;
import org.openhab.core.library.types.StringType;
/**
@ -42,6 +44,28 @@ public class ESP3PacketFactory {
}
}
public static BasePacket SA_WR_LEARNMODE(boolean activate) {
return new SAMessage(SAMessageType.SA_WR_LEARNMODE,
new byte[] { SAMessageType.SA_WR_LEARNMODE.getValue(), (byte) (activate ? 1 : 0), 0, 0, 0, 0, 0 });
}
public final static BasePacket SA_RD_LEARNEDCLIENTS = new SAMessage(SAMessageType.SA_RD_LEARNEDCLIENTS);
public static BasePacket SA_RD_MAILBOX_STATUS(byte[] clientId, byte[] controllerId) {
return new SAMessage(SAMessageType.SA_RD_MAILBOX_STATUS,
Helper.concatAll(new byte[] { SAMessageType.SA_RD_MAILBOX_STATUS.getValue() }, clientId, controllerId));
}
public static BasePacket SA_WR_POSTMASTER(byte mailboxes) {
return new SAMessage(SAMessageType.SA_WR_POSTMASTER,
new byte[] { SAMessageType.SA_WR_POSTMASTER.getValue(), mailboxes });
}
public static BasePacket SA_WR_CLIENTLEARNRQ(byte manu1, byte manu2, byte rorg, byte func, byte type) {
return new SAMessage(SAMessageType.SA_WR_CLIENTLEARNRQ,
new byte[] { SAMessageType.SA_WR_CLIENTLEARNRQ.getValue(), manu1, manu2, rorg, func, type });
}
public static BasePacket BuildPacket(int dataLength, int optionalDataLength, byte packetType, byte[] payload) {
ESPPacketType type = ESPPacketType.getPacketType(packetType);
@ -50,6 +74,8 @@ public class ESP3PacketFactory {
return new Response(dataLength, optionalDataLength, payload);
case RADIO_ERP1:
return new ERP1Message(dataLength, optionalDataLength, payload);
case EVENT:
return new EventMessage(dataLength, optionalDataLength, payload);
default:
return null;
}

View File

@ -0,0 +1,65 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.enocean.internal.messages;
import java.util.stream.Stream;
/**
*
* @author Daniel Weber - Initial contribution
*/
public class EventMessage extends BasePacket {
public enum EventMessageType {
UNKNOWN((byte) 0x00, 1),
SA_RECLAIM_NOT_SUCCESSFUL((byte) 0x01, 1),
SA_CONFIRM_LEARN((byte) 0x02, 17),
SA_LEARN_ACK((byte) 0x03, 4),
CO_READY((byte) 0x04, 2),
CO_EVENT_SECUREDEVICES((byte) 0x05, 6),
CO_DUTYCYCLE_LIMIT((byte) 0x06, 2),
CO_TRANSMIT_FAILED((byte) 0x07, 2);
private byte value;
private int dataLength;
EventMessageType(byte value, int dataLength) {
this.value = value;
this.dataLength = dataLength;
}
public byte getValue() {
return this.value;
}
public int getDataLength() {
return dataLength;
}
public static EventMessageType getEventMessageType(byte value) {
return Stream.of(EventMessageType.values()).filter(t -> t.value == value).findFirst().orElse(UNKNOWN);
}
}
private EventMessageType type;
EventMessage(int dataLength, int optionalDataLength, byte[] payload) {
super(dataLength, optionalDataLength, ESPPacketType.EVENT, payload);
type = EventMessageType.getEventMessageType(payload[0]);
}
public EventMessageType getEventMessageType() {
return type;
}
}

View File

@ -57,7 +57,7 @@ public class Response extends BasePacket {
protected ResponseType responseType;
protected boolean _isValid = false;
protected Response(int dataLength, int optionalDataLength, byte[] payload) {
public Response(int dataLength, int optionalDataLength, byte[] payload) {
super(dataLength, optionalDataLength, ESPPacketType.RESPONSE, payload);
try {

View File

@ -10,9 +10,10 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.enocean.internal.messages;
package org.openhab.binding.enocean.internal.messages.Responses;
import org.openhab.binding.enocean.internal.Helper;
import org.openhab.binding.enocean.internal.messages.Response;
/**
*

View File

@ -10,9 +10,10 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.enocean.internal.messages;
package org.openhab.binding.enocean.internal.messages.Responses;
import org.openhab.binding.enocean.internal.Helper;
import org.openhab.binding.enocean.internal.messages.Response;
/**
*

View File

@ -0,0 +1,61 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.enocean.internal.messages.Responses;
import java.util.Arrays;
import org.openhab.binding.enocean.internal.Helper;
import org.openhab.binding.enocean.internal.messages.Response;
/**
*
* @author Daniel Weber - Initial contribution
*/
public class RDLearnedClientsResponse extends Response {
public class LearnedClient {
public byte[] clientId;
public byte[] controllerId;
public int mailboxIndex;
}
LearnedClient[] learnedClients;
public RDLearnedClientsResponse(Response response) {
this(response.getPayload().length, response.getOptionalPayload().length,
Helper.concatAll(response.getPayload(), response.getOptionalPayload()));
}
RDLearnedClientsResponse(int dataLength, int optionalDataLength, byte[] payload) {
super(dataLength, optionalDataLength, payload);
if (payload == null || ((payload.length - 1) % 9) != 0) {
return;
} else {
_isValid = true;
}
learnedClients = new LearnedClient[(payload.length - 1) / 9];
for (int i = 0; i < learnedClients.length; i++) {
LearnedClient client = new LearnedClient();
client.clientId = Arrays.copyOfRange(payload, 1 + i * 9, 1 + i * 9 + 4);
client.controllerId = Arrays.copyOfRange(payload, 5 + i * 9, 5 + i * 9 + 4);
client.mailboxIndex = payload[9 + i * 9] & 0xFF;
learnedClients[i] = client;
}
}
public LearnedClient[] getLearnedClients() {
return learnedClients;
}
}

View File

@ -10,11 +10,12 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.enocean.internal.messages;
package org.openhab.binding.enocean.internal.messages.Responses;
import static org.openhab.binding.enocean.internal.EnOceanBindingConstants.*;
import org.eclipse.jdt.annotation.NonNull;
import org.openhab.binding.enocean.internal.messages.Response;
import org.openhab.core.library.types.StringType;
/**

View File

@ -10,11 +10,12 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.enocean.internal.messages;
package org.openhab.binding.enocean.internal.messages.Responses;
import java.util.Arrays;
import org.eclipse.jdt.annotation.NonNull;
import org.openhab.binding.enocean.internal.messages.Response;
import org.openhab.core.util.HexUtils;
/**

View File

@ -0,0 +1,65 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.enocean.internal.messages.Responses;
import org.openhab.binding.enocean.internal.messages.Response;
/**
*
* @author Daniel Weber - Initial contribution
*/
public class SMACKTeachInResponse extends Response {
// set response time to 250ms
static final byte RESPONSE_TIME_HVALUE = 0;
static final byte RESPONSE_TIME_LVALUE = (byte) 0xFA;
static final byte TEACH_IN = 0x00;
static final byte TEACH_OUT = 0x20;
static final byte REPEATED_TEACH_IN = 0x01;
static final byte NOPLACE_FOR_MAILBOX = 0x12;
static final byte BAD_RSSI = 0x14;
public SMACKTeachInResponse() {
super(4, 0, new byte[] { Response.ResponseType.RET_OK.getValue(), RESPONSE_TIME_HVALUE, RESPONSE_TIME_LVALUE,
TEACH_IN });
}
public void setTeachOutResponse() {
data[3] = TEACH_OUT;
}
public boolean isTeachOut() {
return data[3] == TEACH_OUT;
}
public void setRepeatedTeachInResponse() {
data[3] = REPEATED_TEACH_IN;
}
public void setNoPlaceForFurtherMailbox() {
data[3] = NOPLACE_FOR_MAILBOX;
}
public void setBadRSSI() {
data[3] = BAD_RSSI;
}
public void setTeachIn() {
data[3] = TEACH_IN;
}
public boolean isTeachIn() {
return data[3] == TEACH_IN;
}
}

View File

@ -0,0 +1,64 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.enocean.internal.messages;
/**
*
* @author Daniel Weber - Initial contribution
*/
public class SAMessage extends BasePacket {
public enum SAMessageType {
SA_WR_LEARNMODE((byte) 0x01, 7),
SA_RD_LEARNMODE((byte) 0x02, 1),
SA_WR_LEARNCONFIRM((byte) 0x03, 1),
SA_WR_CLIENTLEARNRQ((byte) 0x04, 6),
SA_WR_RESET((byte) 0x05, 1),
SA_RD_LEARNEDCLIENTS((byte) 0x06, 1),
SA_WR_RECLAIMS((byte) 0x07, 1),
SA_WR_POSTMASTER((byte) 0x08, 2),
SA_RD_MAILBOX_STATUS((byte) 0x09, 9);
private byte value;
private int dataLength;
SAMessageType(byte value, int dataLength) {
this.value = value;
this.dataLength = dataLength;
}
public byte getValue() {
return this.value;
}
public int getDataLength() {
return dataLength;
}
}
private SAMessageType type;
public SAMessage(SAMessageType type) {
this(type, new byte[] { type.getValue() });
}
public SAMessage(SAMessageType type, byte[] payload) {
super(type.getDataLength(), 0, ESPPacketType.SMART_ACK_COMMAND, payload);
this.type = type;
}
public SAMessageType getSAMessageType() {
return type;
}
}

View File

@ -18,8 +18,6 @@ import java.util.concurrent.ScheduledExecutorService;
import org.openhab.binding.enocean.internal.EnOceanException;
import org.openhab.binding.enocean.internal.messages.BasePacket;
import org.openhab.binding.enocean.internal.messages.ERP1Message;
import org.openhab.binding.enocean.internal.messages.ERP1Message.RORG;
import org.openhab.binding.enocean.internal.messages.ESP3Packet;
import org.openhab.binding.enocean.internal.messages.ESP3PacketFactory;
import org.openhab.binding.enocean.internal.messages.Response;
@ -138,21 +136,8 @@ public class EnOceanESP3Transceiver extends EnOceanTransceiver {
HexUtils.bytesToHex(packet.getPayload()));
break;
case EVENT:
logger.debug("Event occured: {}", HexUtils.bytesToHex(packet.getPayload()));
break;
case RADIO_ERP1: {
ERP1Message msg = (ERP1Message) packet;
logger.debug("{} with RORG {} for {} payload {} received",
packet.getPacketType().name(), msg.getRORG().name(),
HexUtils.bytesToHex(msg.getSenderId()), HexUtils.bytesToHex(
Arrays.copyOf(dataBuffer, dataLength + optionalLength)));
if (msg.getRORG() != RORG.Unknown) {
informListeners(msg);
} else {
logger.debug("Received unknown RORG");
}
}
case RADIO_ERP1:
informListeners(packet);
break;
case RADIO_ERP2:
break;

View File

@ -27,9 +27,13 @@ import java.util.concurrent.TimeUnit;
import org.openhab.binding.enocean.internal.EnOceanBindingConstants;
import org.openhab.binding.enocean.internal.EnOceanException;
import org.openhab.binding.enocean.internal.Helper;
import org.openhab.binding.enocean.internal.messages.BasePacket;
import org.openhab.binding.enocean.internal.messages.BasePacket.ESPPacketType;
import org.openhab.binding.enocean.internal.messages.ERP1Message;
import org.openhab.binding.enocean.internal.messages.ERP1Message.RORG;
import org.openhab.binding.enocean.internal.messages.EventMessage;
import org.openhab.binding.enocean.internal.messages.EventMessage.EventMessageType;
import org.openhab.binding.enocean.internal.messages.Response;
import org.openhab.core.io.transport.serial.PortInUseException;
import org.openhab.core.io.transport.serial.SerialPort;
@ -138,7 +142,8 @@ public abstract class EnOceanTransceiver implements SerialPortEventListener {
Request currentRequest = null;
protected Map<Long, HashSet<PacketListener>> listeners;
protected PacketListener teachInListener;
protected HashSet<EventListener> eventListeners;
protected TeachInListener teachInListener;
protected InputStream inputStream;
protected OutputStream outputStream;
@ -151,6 +156,7 @@ public abstract class EnOceanTransceiver implements SerialPortEventListener {
requestQueue = new RequestQueue(scheduler);
listeners = new HashMap<>();
eventListeners = new HashSet<>();
teachInListener = null;
this.errorListener = errorListener;
@ -192,6 +198,7 @@ public abstract class EnOceanTransceiver implements SerialPortEventListener {
}
});
}
logger.info("EnOceanSerialTransceiver RX thread started");
}
public void ShutDown() {
@ -266,36 +273,65 @@ public abstract class EnOceanTransceiver implements SerialPortEventListener {
}
}
protected void informListeners(ERP1Message msg) {
protected void informListeners(BasePacket packet) {
try {
byte[] senderId = msg.getSenderId();
if (packet.getPacketType() == ESPPacketType.RADIO_ERP1) {
ERP1Message msg = (ERP1Message) packet;
byte[] senderId = msg.getSenderId();
byte[] d = Helper.concatAll(msg.getPayload(), msg.getOptionalPayload());
if (senderId != null) {
if (filteredDeviceId != null && senderId[0] == filteredDeviceId[0] && senderId[1] == filteredDeviceId[1]
&& senderId[2] == filteredDeviceId[2]) {
// filter away own messages which are received through a repeater
return;
}
logger.debug("{} with RORG {} for {} payload {} received", packet.getPacketType().name(),
msg.getRORG().name(), HexUtils.bytesToHex(msg.getSenderId()), HexUtils.bytesToHex(d));
if (teachInListener != null) {
if (msg.getIsTeachIn() || (msg.getRORG() == RORG.RPS)) {
logger.info("Received teach in message from {}", HexUtils.bytesToHex(msg.getSenderId()));
teachInListener.packetReceived(msg);
return;
if (msg.getRORG() != RORG.Unknown) {
if (senderId != null) {
if (filteredDeviceId != null && senderId[0] == filteredDeviceId[0]
&& senderId[1] == filteredDeviceId[1] && senderId[2] == filteredDeviceId[2]) {
// filter away own messages which are received through a repeater
return;
}
if (teachInListener != null && (msg.getIsTeachIn() || msg.getRORG() == RORG.RPS)) {
logger.info("Received teach in message from {}", HexUtils.bytesToHex(msg.getSenderId()));
teachInListener.packetReceived(msg);
return;
} else if (teachInListener == null && msg.getIsTeachIn()) {
logger.info("Discard message because this is a teach-in telegram from {}!",
HexUtils.bytesToHex(msg.getSenderId()));
return;
}
long s = Long.parseLong(HexUtils.bytesToHex(senderId), 16);
HashSet<PacketListener> pl = listeners.get(s);
if (pl != null) {
pl.forEach(l -> l.packetReceived(msg));
}
}
} else {
if (msg.getIsTeachIn()) {
logger.info("Discard message because this is a teach-in telegram from {}!",
HexUtils.bytesToHex(msg.getSenderId()));
logger.debug("Received unknown RORG");
}
} else if (packet.getPacketType() == ESPPacketType.EVENT) {
EventMessage event = (EventMessage) packet;
byte[] d = Helper.concatAll(packet.getPayload(), packet.getOptionalPayload());
logger.debug("{} with type {} payload {} received", ESPPacketType.EVENT.name(),
event.getEventMessageType().name(), HexUtils.bytesToHex(d));
if (event.getEventMessageType() == EventMessageType.SA_CONFIRM_LEARN) {
byte[] senderId = event.getPayload(EventMessageType.SA_CONFIRM_LEARN.getDataLength() - 5, 4);
if (teachInListener != null) {
logger.info("Received smart teach in from {}", HexUtils.bytesToHex(senderId));
teachInListener.eventReceived(event);
return;
} else {
logger.info("Discard message because this is a smart teach-in telegram from {}!",
HexUtils.bytesToHex(senderId));
return;
}
}
long s = Long.parseLong(HexUtils.bytesToHex(senderId), 16);
HashSet<PacketListener> pl = listeners.get(s);
if (pl != null) {
pl.forEach(l -> l.packetReceived(msg));
}
eventListeners.forEach(l -> l.eventReceived(event));
}
} catch (Exception e) {
logger.error("Exception in informListeners", e);
@ -354,7 +390,15 @@ public abstract class EnOceanTransceiver implements SerialPortEventListener {
}
}
public void startDiscovery(PacketListener teachInListener) {
public void addEventMessageListener(EventListener listener) {
eventListeners.add(listener);
}
public void removeEventMessageListener(EventListener listener) {
eventListeners.remove(listener);
}
public void startDiscovery(TeachInListener teachInListener) {
this.teachInListener = teachInListener;
}

View File

@ -0,0 +1,23 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.enocean.internal.transceiver;
import org.openhab.binding.enocean.internal.messages.EventMessage;
/**
*
* @author Daniel Weber - Initial contribution
*/
public interface EventListener {
public void eventReceived(EventMessage event);
}

View File

@ -22,5 +22,5 @@ public interface PacketListener {
public void packetReceived(BasePacket packet);
public long getSenderIdToListenTo();
public long getEnOceanIdToListenTo();
}

View File

@ -0,0 +1,21 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.enocean.internal.transceiver;
/**
*
* @author Daniel Weber - Initial contribution
*/
public interface TeachInListener extends PacketListener, EventListener {
}

View File

@ -18,7 +18,7 @@
<description>EnOceanId of device this thing belongs to</description>
<required>true</required>
</parameter>
<parameter name="senderIdOffset" type="integer">
<parameter name="senderIdOffset" type="integer" required="false" min="1" max="127">
<label>Sender Id</label>
<description>Id is used to generate the EnOcean Id (Int between [1-127]). If not specified the next free Id will be
determined by bridge</description>

View File

@ -20,7 +20,7 @@
</channels>
<config-description>
<parameter name="senderIdOffset" type="integer">
<parameter name="senderIdOffset" type="integer" required="false" min="1" max="127">
<label>Sender Id</label>
<description>Id is used to generate the EnOcean Id (Int between [1-127]). If not specified the next free Id will be
determined by bridge</description>

View File

@ -18,7 +18,7 @@
<description>EnOceanId of device this thing belongs to</description>
<required>true</required>
</parameter>
<parameter name="senderIdOffset" type="integer">
<parameter name="senderIdOffset" type="integer" required="false" min="1" max="127">
<label>Sender Id</label>
<description>Id is used to generate the EnOcean Id (Int between [1-127]). If not specified the next free Id will be
determined by bridge</description>

View File

@ -18,7 +18,7 @@
<description>EnOceanId of device this thing belongs to</description>
<required>true</required>
</parameter>
<parameter name="senderIdOffset" type="integer">
<parameter name="senderIdOffset" type="integer" required="false" min="1" max="127">
<label>Sender Id</label>
<description>Id is used to generate the EnOcean Id (Int between [1-127]). If not specified the next free Id will be
determined by bridge</description>

View File

@ -18,7 +18,7 @@
<description>EnOceanId of device this thing belongs to</description>
<required>true</required>
</parameter>
<parameter name="senderIdOffset" type="integer">
<parameter name="senderIdOffset" type="integer" required="false" min="1" max="127">
<label>Sender Id</label>
<description>Id is used to generate the EnOcean Id (Int between [1-127]). If not specified the next free Id will be
determined by bridge</description>

View File

@ -18,7 +18,7 @@
<description>EnOceanId of device this thing belongs to</description>
<required>true</required>
</parameter>
<parameter name="senderIdOffset" type="integer">
<parameter name="senderIdOffset" type="integer" required="false" min="1" max="127">
<label>Sender Id</label>
<description>Id is used to generate the EnOcean Id (Int between [1-127]). If not specified the next free Id will be
determined by bridge</description>

View File

@ -18,7 +18,7 @@
<description>EnOceanId of device this thing belongs to</description>
<required>true</required>
</parameter>
<parameter name="senderIdOffset" type="integer">
<parameter name="senderIdOffset" type="integer" required="false" min="1" max="127">
<label>Sender Id</label>
<description>Id is used to generate the EnOcean Id (Int between [1-127]). If not specified the next free Id will be
determined by bridge</description>

View File

@ -31,6 +31,11 @@
<required>true</required>
<default>ESP3</default>
</parameter>
<parameter name="enableSmack" type="boolean">
<label>Handle SMACK Messages</label>
<description>Declare Gateway as a SMACK Postmaster and handle SMACK messages</description>
<default>true</default>
</parameter>
<parameter name="rs485" type="boolean">
<advanced>true</advanced>
<label>Gateway Connected Directly to RS485 BUS</label>
@ -41,9 +46,15 @@
<label>Use following BaseId when connected directly to RS485 BUS</label>
<default>00000000</default>
</parameter>
<parameter name="nextSenderId" type="integer">
<parameter name="sendTeachOuts" type="boolean">
<advanced>true</advanced>
<label>Next Device Id</label>
<label>Send TeachOuts</label>
<description>Should a learned in or teach out response been send on a repeated smack teach in request</description>
<default>false</default>
</parameter>
<parameter name="nextSenderId" type="integer" min="1" max="127">
<advanced>true</advanced>
<label>Next Sender Id Offset</label>
<description>Defines the next device Id, if empty, the next device id is automatically determined</description>
</parameter>
</config-description>

View File

@ -488,13 +488,6 @@
<event/>
</channel-type>
<channel-type id="sendCommand">
<item-type>Switch</item-type>
<label>Send Command</label>
<description>You can send telegrams to the device by switching this channel on</description>
<category>thermostat</category>
</channel-type>
<channel-type id="smokeDetection">
<item-type>Switch</item-type>
<label>Smoke Detected</label>