[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

@ -128,6 +128,20 @@ First you have to **start the discovery scan for a gateway**.
Then press the teach-in button of the actuator. 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. 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. It is important to link the teach-in channel of this thing to a switch item.
Afterwards you have to **activate the pairing mode of the actuator**. Afterwards you have to **activate the pairing mode of the actuator**.
@ -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 | | | 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 | | 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 | | | 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 | | 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 | | | enoceanId | EnOceanId of device this thing belongs to | hex value as string |
| rockerSwitch | receivingEEPId | | F6_02_01, F6_02_02 | | 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 | | rssi | Number | Received Signal Strength Indication (dBm) of last received message |
| repeatCount | Number | Number of repeaters involved in the transmission of the telegram | | repeatCount | Number | Number of repeaters involved in the transmission of the telegram |
| lastReceived | DateTime | Date and time the last telegram was received | | 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`. 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. 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_WAKEUPCYCLE = "wakeUpCycle";
public static final String CHANNEL_SERVICECOMMAND = "serviceCommand"; public static final String CHANNEL_SERVICECOMMAND = "serviceCommand";
public static final String CHANNEL_STATUS_REQUEST_EVENT = "statusRequestEvent"; 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_VENTILATIONOPERATIONMODE = "ventilationOperationMode";
public static final String CHANNEL_FIREPLACESAFETYMODE = "fireplaceSafetyMode"; public static final String CHANNEL_FIREPLACESAFETYMODE = "fireplaceSafetyMode";
@ -293,7 +293,8 @@ public class EnOceanBindingConstants {
Map.entry(CHANNEL_INDOORAIRANALYSIS, Map.entry(CHANNEL_INDOORAIRANALYSIS,
new EnOceanChannelDescription(new ChannelTypeUID(BINDING_ID, CHANNEL_INDOORAIRANALYSIS), new EnOceanChannelDescription(new ChannelTypeUID(BINDING_ID, CHANNEL_INDOORAIRANALYSIS),
CoreItemFactory.STRING)), CoreItemFactory.STRING)),
Map.entry(CHANNEL_SETPOINT, Map.entry(
CHANNEL_SETPOINT,
new EnOceanChannelDescription(new ChannelTypeUID(BINDING_ID, CHANNEL_SETPOINT), new EnOceanChannelDescription(new ChannelTypeUID(BINDING_ID, CHANNEL_SETPOINT),
CoreItemFactory.NUMBER)), CoreItemFactory.NUMBER)),
Map.entry(CHANNEL_CONTACT, Map.entry(CHANNEL_CONTACT,
@ -444,13 +445,6 @@ public class EnOceanBindingConstants {
new EnOceanChannelDescription(new ChannelTypeUID(BINDING_ID, CHANNEL_SERVICECOMMAND), new EnOceanChannelDescription(new ChannelTypeUID(BINDING_ID, CHANNEL_SERVICECOMMAND),
CoreItemFactory.NUMBER)), 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, Map.entry(CHANNEL_VENTILATIONOPERATIONMODE,
new EnOceanChannelDescription(new ChannelTypeUID(BINDING_ID, CHANNEL_VENTILATIONOPERATIONMODE), new EnOceanChannelDescription(new ChannelTypeUID(BINDING_ID, CHANNEL_VENTILATIONOPERATIONMODE),
CoreItemFactory.STRING)), CoreItemFactory.STRING)),
@ -527,6 +521,10 @@ public class EnOceanBindingConstants {
CoreItemFactory.NUMBER + ItemUtil.EXTENSION_SEPARATOR CoreItemFactory.NUMBER + ItemUtil.EXTENSION_SEPARATOR
+ Dimensionless.class.getSimpleName())), + 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( Map.entry(CHANNEL_REPEATERMODE, new EnOceanChannelDescription(
new ChannelTypeUID(BINDING_ID, CHANNEL_REPEATERMODE), CoreItemFactory.STRING))); new ChannelTypeUID(BINDING_ID, CHANNEL_REPEATERMODE), CoreItemFactory.STRING)));
@ -536,11 +534,8 @@ public class EnOceanBindingConstants {
public static final String REPEATERMODE_LEVEL_2 = "LEVEL2"; public static final String REPEATERMODE_LEVEL_2 = "LEVEL2";
// Bridge config properties // Bridge config properties
public static final String SENDERID = "senderId";
public static final String PATH = "path"; public static final String PATH = "path";
public static final String HOST = "host"; public static final String PARAMETER_NEXT_SENDERID = "nextSenderId";
public static final String RS485 = "rs485";
public static final String NEXTSENDERID = "nextSenderId";
// Bridge properties // Bridge properties
public static final String PROPERTY_BASE_ID = "Base ID"; public static final String PROPERTY_BASE_ID = "Base ID";
@ -551,13 +546,12 @@ public class EnOceanBindingConstants {
public static final String PROPERTY_DESCRIPTION = "Description"; public static final String PROPERTY_DESCRIPTION = "Description";
// Thing properties // Thing properties
public static final String PROPERTY_ENOCEAN_ID = "enoceanId"; public static final String PROPERTY_SENDINGENOCEAN_ID = "SendingEnoceanId";
// Thing config parameter // Thing config parameter
public static final String PARAMETER_SENDERIDOFFSET = "senderIdOffset"; public static final String PARAMETER_SENDERIDOFFSET = "senderIdOffset";
public static final String PARAMETER_SENDINGEEPID = "sendingEEPId"; public static final String PARAMETER_SENDINGEEPID = "sendingEEPId";
public static final String PARAMETER_RECEIVINGEEPID = "receivingEEPId"; 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_BROADCASTMESSAGES = "broadcastMessages";
public static final String PARAMETER_ENOCEANID = "enoceanId"; 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.io.transport.serial.SerialPortManager;
import org.openhab.core.thing.Bridge; import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Thing; import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingManager;
import org.openhab.core.thing.ThingTypeUID; import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.ThingUID; import org.openhab.core.thing.ThingUID;
import org.openhab.core.thing.binding.BaseThingHandlerFactory; import org.openhab.core.thing.binding.BaseThingHandlerFactory;
@ -60,6 +61,9 @@ public class EnOceanHandlerFactory extends BaseThingHandlerFactory {
@Reference @Reference
ItemChannelLinkRegistry itemChannelLinkRegistry; ItemChannelLinkRegistry itemChannelLinkRegistry;
@Reference
ThingManager thingManager;
@Override @Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) { public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID); return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
@ -96,7 +100,7 @@ public class EnOceanHandlerFactory extends BaseThingHandlerFactory {
} }
private void registerDeviceDiscoveryService(EnOceanBridgeHandler handler) { private void registerDeviceDiscoveryService(EnOceanBridgeHandler handler) {
EnOceanDeviceDiscoveryService discoveryService = new EnOceanDeviceDiscoveryService(handler); EnOceanDeviceDiscoveryService discoveryService = new EnOceanDeviceDiscoveryService(handler, thingManager);
discoveryService.activate(); discoveryService.activate();
this.discoveryServiceRegs.put(handler.getThing().getUID(), this.discoveryServiceRegs.put(handler.getThing().getUID(),
bundleContext.registerService(DiscoveryService.class.getName(), discoveryService, new Hashtable<>())); 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 class EnOceanActuatorConfig extends EnOceanBaseConfig {
public int channel; public int channel;
public int senderIdOffset = -1; public Integer senderIdOffset = null;
public String manufacturerId; public String manufacturerId;
public String teachInType; 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.ArrayList;
import java.util.List; import java.util.List;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.util.HexUtils; import org.openhab.core.util.HexUtils;
/** /**
* *
* @author Daniel Weber - Initial contribution * @author Daniel Weber - Initial contribution
*/ */
@NonNullByDefault
public class EnOceanBaseConfig { public class EnOceanBaseConfig {
/**
* EnOceanId of the physical device
*/
public String enoceanId; public String enoceanId;
/**
* EEP used/send by physical device
*/
public List<String> receivingEEPId = new ArrayList<>(); public List<String> receivingEEPId = new ArrayList<>();
public boolean receivingSIGEEP = false; public boolean receivingSIGEEP = false;

View File

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

View File

@ -12,12 +12,19 @@
*/ */
package org.openhab.binding.enocean.internal.config; package org.openhab.binding.enocean.internal.config;
import org.openhab.core.config.core.Configuration;
/** /**
* *
* @author Daniel Weber - Initial contribution * @author Daniel Weber - Initial contribution
*/ */
public class EnOceanChannelTransformationConfig { public class EnOceanChannelTransformationConfig extends Configuration {
public String transformationType; public String transformationType;
public String transformationFunction; 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.BasePacket;
import org.openhab.binding.enocean.internal.messages.ERP1Message; import org.openhab.binding.enocean.internal.messages.ERP1Message;
import org.openhab.binding.enocean.internal.messages.ERP1Message.RORG; 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.AbstractDiscoveryService;
import org.openhab.core.config.discovery.DiscoveryResultBuilder; 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.ThingTypeUID;
import org.openhab.core.thing.ThingUID; import org.openhab.core.thing.ThingUID;
import org.openhab.core.util.HexUtils; import org.openhab.core.util.HexUtils;
@ -39,15 +44,16 @@ import org.slf4j.LoggerFactory;
* *
* @author Daniel Weber - Initial contribution * @author Daniel Weber - Initial contribution
*/ */
public class EnOceanDeviceDiscoveryService extends AbstractDiscoveryService implements TeachInListener {
public class EnOceanDeviceDiscoveryService extends AbstractDiscoveryService implements PacketListener {
private final Logger logger = LoggerFactory.getLogger(EnOceanDeviceDiscoveryService.class); private final Logger logger = LoggerFactory.getLogger(EnOceanDeviceDiscoveryService.class);
private EnOceanBridgeHandler bridgeHandler; private EnOceanBridgeHandler bridgeHandler;
private ThingManager thingManager;
public EnOceanDeviceDiscoveryService(EnOceanBridgeHandler bridgeHandler) { public EnOceanDeviceDiscoveryService(EnOceanBridgeHandler bridgeHandler, ThingManager thingManager) {
super(null, 60, false); super(null, 60, false);
this.bridgeHandler = bridgeHandler; this.bridgeHandler = bridgeHandler;
this.thingManager = thingManager;
} }
/** /**
@ -102,10 +108,19 @@ public class EnOceanDeviceDiscoveryService extends AbstractDiscoveryService impl
} }
String enoceanId = HexUtils.bytesToHex(eep.getSenderId()); String enoceanId = HexUtils.bytesToHex(eep.getSenderId());
ThingTypeUID thingTypeUID = eep.getThingTypeUID();
ThingUID thingUID = new ThingUID(thingTypeUID, bridgeHandler.getThing().getUID(), enoceanId);
int senderIdOffset = 0; 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; boolean broadcastMessages = true;
// check for bidirectional communication => do not use broadcast in this case // check for bidirectional communication => do not use broadcast in this case
@ -114,60 +129,118 @@ public class EnOceanDeviceDiscoveryService extends AbstractDiscoveryService impl
broadcastMessages = false; broadcastMessages = false;
} }
// if ute => send response if needed
if (msg.getRORG() == RORG.UTE && (msg.getPayload(1, 1)[0] & UTEResponse.ResponseNeeded_MASK) == 0) { if (msg.getRORG() == RORG.UTE && (msg.getPayload(1, 1)[0] & UTEResponse.ResponseNeeded_MASK) == 0) {
logger.info("Sending UTE response to {}", enoceanId); // if ute => send response if needed
logger.debug("Sending UTE response to {}", enoceanId);
senderIdOffset = sendTeachInResponse(msg, 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;
}
} }
// if 4BS teach in variation 3 => send response createDiscoveryResult(eep, broadcastMessages, senderIdOffset);
if ((eep instanceof _4BSMessage) && ((_4BSMessage) eep).isTeachInVariation3Supported()) { });
logger.info("Sending 4BS teach in variation 3 response to {}", enoceanId);
senderIdOffset = sendTeachInResponse(msg, enoceanId);
} }
@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);
DiscoveryResultBuilder discoveryResultBuilder = DiscoveryResultBuilder.create(thingUID) 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); 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 // advance config with new device id
discoveryResultBuilder.withProperty(PARAMETER_SENDERIDOFFSET, senderIdOffset); discoveryResultBuilder.withProperty(PARAMETER_SENDERIDOFFSET, senderIdOffset);
} }
thingDiscovered(discoveryResultBuilder.build()); 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 @Override
public long getSenderIdToListenTo() { public long getEnOceanIdToListenTo() {
// we just want teach in msg, so return zero here // we just want teach in msg, so return zero here
return 0; 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.types.QuantityType;
import org.openhab.core.library.unit.SIUnits; import org.openhab.core.library.unit.SIUnits;
import org.openhab.core.types.State; import org.openhab.core.types.State;
import org.openhab.core.types.UnDefType;
/** /**
* *
@ -51,9 +50,6 @@ public abstract class A5_02 extends _4BSMessage {
@Override @Override
protected State convertToStateImpl(String channelId, String channelTypeId, protected State convertToStateImpl(String channelId, String channelTypeId,
Function<String, State> getCurrentStateFunc, Configuration config) { Function<String, State> getCurrentStateFunc, Configuration config) {
if (!isValid()) {
return UnDefType.UNDEF;
}
double scaledTemp = getScaledMin() double scaledTemp = getScaledMin()
- (((getUnscaledMin() - getUnscaledTemperatureValue()) * (getScaledMin() - getScaledMax())) - (((getUnscaledMin() - getUnscaledTemperatureValue()) * (getScaledMin() - getScaledMax()))

View File

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

View File

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

View File

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

View File

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

View File

@ -169,7 +169,7 @@ public class A5_20_04 extends A5_20 {
@Override @Override
protected void convertFromCommandImpl(String channelId, String channelTypeId, Command command, protected void convertFromCommandImpl(String channelId, String channelTypeId, Command command,
Function<String, State> getCurrentStateFunc, Configuration config) { 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 db3 = getPos(getCurrentStateFunc);
byte db2 = getTsp(getCurrentStateFunc); byte db2 = getTsp(getCurrentStateFunc);
byte db1 = (byte) (0x00 | getMc(getCurrentStateFunc) | getWuc(getCurrentStateFunc)); byte db1 = (byte) (0x00 | getMc(getCurrentStateFunc) | getWuc(getCurrentStateFunc));

View File

@ -55,9 +55,6 @@ public class PTM200Message extends _RPSMessage {
@Override @Override
protected State convertToStateImpl(String channelId, String channelTypeId, protected State convertToStateImpl(String channelId, String channelTypeId,
Function<String, State> getCurrentStateFunc, Configuration config) { Function<String, State> getCurrentStateFunc, Configuration config) {
if (!isValid()) {
return UnDefType.UNDEF;
}
switch (channelId) { switch (channelId) {
case CHANNEL_GENERAL_SWITCHING: case CHANNEL_GENERAL_SWITCHING:
@ -77,4 +74,9 @@ public class PTM200Message extends _RPSMessage {
return UnDefType.UNDEF; 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 ResponseNeeded_MASK = 0x40;
public static final byte TeachIn_NotSpecified = 0x20; 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; int dataLength = packet.getPayload().length - ESP3_SENDERID_LENGTH - ESP3_RORG_LENGTH - ESP3_STATUS_LENGTH;
setData(packet.getPayload(ESP3_RORG_LENGTH, dataLength)); 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); setStatus((byte) 0x80);
setSuppressRepeating(true); setSuppressRepeating(true);

View File

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

View File

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

View File

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

View File

@ -15,18 +15,24 @@ package org.openhab.binding.enocean.internal.eep;
import static org.openhab.binding.enocean.internal.messages.ESP3Packet.*; import static org.openhab.binding.enocean.internal.messages.ESP3Packet.*;
import java.lang.reflect.InvocationTargetException; 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.UTEResponse;
import org.openhab.binding.enocean.internal.eep.Base._4BSMessage; 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._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.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_01.F6_01_01;
import org.openhab.binding.enocean.internal.eep.F6_02.F6_02_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;
import org.openhab.binding.enocean.internal.eep.F6_10.F6_10_00_EltakoFPE; 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.eep.F6_10.F6_10_01;
import org.openhab.binding.enocean.internal.messages.ERP1Message; import org.openhab.binding.enocean.internal.messages.ERP1Message;
import org.openhab.binding.enocean.internal.messages.ERP1Message.RORG; 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.openhab.core.util.HexUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -45,8 +51,9 @@ public class EEPFactory {
if (cl == null) { if (cl == null) {
throw new IllegalArgumentException("Message " + eepType + " not implemented"); throw new IllegalArgumentException("Message " + eepType + " not implemented");
} }
return cl.newInstance(); return cl.getDeclaredConstructor().newInstance();
} catch (IllegalAccessException | InstantiationException e) { } catch (IllegalAccessException | InstantiationException | IllegalArgumentException | InvocationTargetException
| NoSuchMethodException | SecurityException e) {
throw new IllegalArgumentException(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) { public static EEP buildEEPFromTeachInERP1(ERP1Message msg) {
if (!msg.getIsTeachIn() && !(msg.getRORG() == RORG.RPS)) { if (!msg.getIsTeachIn() && !(msg.getRORG() == RORG.RPS)) {
return null; return null;
@ -77,38 +99,48 @@ public class EEPFactory {
switch (msg.getRORG()) { switch (msg.getRORG()) {
case RPS: case RPS:
try { try {
EEP result = new F6_01_01(msg); _RPSMessage result = new F6_10_00(msg);
if (result.isValid()) { // check if t21 is set, nu not set, and data == 0x10 or 0x00 if (result.isValidForTeachIn()) {
return result; return result;
} }
} catch (Exception e) { } catch (Exception e) {
} }
try { try {
EEP result = new F6_02_01(msg); _RPSMessage result = new F6_10_01(msg);
if (result.isValid()) { // check if highest bit is not set if (result.isValidForTeachIn()) {
return result; return result;
} }
} catch (Exception e) { } catch (Exception e) {
} }
try { try {
EEP result = new F6_10_00(msg); _RPSMessage result = new F6_02_01(msg);
if (result.isValid()) { if (result.isValidForTeachIn()) {
return result; return result;
} }
} catch (Exception e) { } catch (Exception e) {
} }
try { try {
EEP result = new F6_10_00_EltakoFPE(msg); _RPSMessage result = new F6_05_02(msg);
if (result.isValid()) { // check if data == 0x10 or 0x00 if (result.isValidForTeachIn()) {
return result; return result;
} }
} catch (Exception e) { } catch (Exception e) {
} }
try { try {
EEP result = new F6_10_01(msg); _RPSMessage result = new F6_01_01(msg);
if (result.isValid()) { if (result.isValidForTeachIn()) {
return result;
}
} catch (Exception e) {
}
try {
_RPSMessage result = new F6_10_00_EltakoFPE(msg);
if (result.isValidForTeachIn()) {
return result; return result;
} }
} catch (Exception e) { } catch (Exception e) {
@ -120,8 +152,8 @@ public class EEPFactory {
case _4BS: { case _4BS: {
int db_0 = msg.getPayload()[4]; int db_0 = msg.getPayload()[4];
if ((db_0 & _4BSMessage.LRN_Type_Mask) == 0) { // Variation 1 if ((db_0 & _4BSMessage.LRN_Type_Mask) == 0) { // Variation 1
logger.info("Received 4BS Teach In variation 1 without EEP"); logger.info("Received 4BS Teach In variation 1 without EEP, fallback to generic thing");
return null; return buildEEP(EEPType.Generic4BS, msg);
} }
byte db_3 = msg.getPayload()[1]; byte db_3 = msg.getPayload()[1];
@ -132,19 +164,21 @@ public class EEPFactory {
int type = ((db_3 & 0b11) << 5) + ((db_2 & 0xFF) >>> 3); int type = ((db_3 & 0b11) << 5) + ((db_2 & 0xFF) >>> 3);
int manufId = ((db_2 & 0b111) << 8) + (db_1 & 0xff); 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) func }),
HexUtils.bytesToHex(new byte[] { (byte) type }), HexUtils.bytesToHex(new byte[] { (byte) type }),
HexUtils.bytesToHex(new byte[] { (byte) manufId })); HexUtils.bytesToHex(new byte[] { (byte) manufId }));
EEPType eepType = EEPType.getType(RORG._4BS, func, type, manufId); EEPType eepType = EEPType.getType(RORG._4BS, func, type, manufId);
if (eepType == null) { if (eepType == null) {
logger.debug("Received unsupported EEP teach in, fallback to generic thing"); eepType = getGenericEEPTypeFor(RORG._4BS.getValue());
eepType = EEPType.Generic4BS;
} }
if (eepType != null) {
return buildEEP(eepType, msg); return buildEEP(eepType, msg);
} }
}
break;
case UTE: { case UTE: {
byte[] payload = msg.getPayload(); byte[] payload = msg.getPayload();
@ -161,38 +195,58 @@ public class EEPFactory {
EEPType eepType = EEPType.getType(RORG.getRORG(rorg), func, type, manufId); EEPType eepType = EEPType.getType(RORG.getRORG(rorg), func, type, manufId);
if (eepType == null) { if (eepType == null) {
logger.info("Received unsupported EEP teach in, fallback to generic thing"); eepType = getGenericEEPTypeFor(rorg);
RORG r = RORG.getRORG(rorg);
if (r == RORG._4BS) {
eepType = EEPType.Generic4BS;
} else if (r == RORG.VLD) {
eepType = EEPType.GenericVLD;
} else {
return null;
}
} }
if (eepType != null) {
return buildEEP(eepType, msg); return buildEEP(eepType, msg);
} }
case Unknown: }
case VLD: break;
case MSC: default:
case SIG:
return null; return null;
} }
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()) { switch (msg.getRORG()) {
case UTE: case UTE:
EEP result = new UTEResponse(msg); EEP result = new UTEResponse(msg, teachIn);
result.setSenderId(senderId); result.setSenderId(senderId);
return result; return result;
case _4BS: case _4BS:
result = new _4BSTeachInVariation3Response(msg); result = new _4BSTeachInVariation3Response(msg, teachIn);
result.setSenderId(senderId); result.setSenderId(senderId);
return result; return result;
@ -200,4 +254,31 @@ public class EEPFactory {
return null; 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.eclipse.jdt.annotation.NonNull;
import org.openhab.binding.enocean.internal.EnOceanChannelDescription; 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_01;
import org.openhab.binding.enocean.internal.eep.A5_02.A5_02_02; import org.openhab.binding.enocean.internal.eep.A5_02.A5_02_02;
import org.openhab.binding.enocean.internal.eep.A5_02.A5_02_03; 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), UTEResponse(RORG.UTE, 0, 0, false, UTEResponse.class, null),
_4BSTeachInVariation3Response(RORG._4BS, 0, 0, false, _4BSTeachInVariation3Response.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, GenericRPS(RORG.RPS, 0xFF, 0xFF, false, GenericRPS.class, THING_TYPE_GENERICTHING),
CHANNEL_GENERIC_ROLLERSHUTTER, CHANNEL_GENERIC_DIMMER, CHANNEL_GENERIC_NUMBER, CHANNEL_GENERIC_STRING, Generic4BS(RORG._4BS, 0xFF, 0xFF, false, Generic4BS.class, THING_TYPE_GENERICTHING, CHANNEL_VIBRATION),
CHANNEL_GENERIC_COLOR, CHANNEL_GENERIC_TEACHINCMD), GenericVLD(RORG.VLD, 0xFF, 0xFF, false, GenericVLD.class, THING_TYPE_GENERICTHING),
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),
PTM200(RORG.RPS, 0x00, 0x00, false, PTM200Message.class, null, CHANNEL_GENERAL_SWITCHING, CHANNEL_ROLLERSHUTTER, PTM200(RORG.RPS, 0x00, 0x00, false, PTM200Message.class, null, CHANNEL_GENERAL_SWITCHING, CHANNEL_ROLLERSHUTTER,
CHANNEL_CONTACT), CHANNEL_CONTACT),
@ -391,8 +386,8 @@ public enum EEPType {
// UniversalCommand(RORG._4BS, 0x3f, 0x7f, false, A5_3F_7F_Universal.class, THING_TYPE_UNIVERSALACTUATOR, // 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), // 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, EltakoFSB(RORG._4BS, 0x3f, 0x7f, false, false, "EltakoFSB", 0, A5_3F_7F_EltakoFSB.class, THING_TYPE_ROLLERSHUTTER,
new Hashtable<String, Configuration>() { 0, new Hashtable<String, Configuration>() {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
{ {
put(CHANNEL_ROLLERSHUTTER, new Configuration()); 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_BUTTON_LOCK, CHANNEL_DISPLAY_ORIENTATION, CHANNEL_TEMPERATURE_SETPOINT, CHANNEL_TEMPERATURE,
CHANNEL_FEED_TEMPERATURE, CHANNEL_MEASUREMENT_CONTROL, CHANNEL_FAILURE_CODE, CHANNEL_WAKEUPCYCLE, 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, SwitchWithEnergyMeasurment_00(RORG.VLD, 0x01, 0x00, true, D2_01_00.class, THING_TYPE_MEASUREMENTSWITCH,
CHANNEL_GENERAL_SWITCHING, CHANNEL_TOTALUSAGE), CHANNEL_GENERAL_SWITCHING, CHANNEL_TOTALUSAGE),
@ -512,23 +507,36 @@ public enum EEPType {
private boolean supportsRefresh; private boolean supportsRefresh;
private boolean requestsResponse;
EEPType(RORG rorg, int func, int type, boolean supportsRefresh, Class<? extends EEP> eepClass, EEPType(RORG rorg, int func, int type, boolean supportsRefresh, Class<? extends EEP> eepClass,
ThingTypeUID thingTypeUID, String... channelIds) { ThingTypeUID thingTypeUID, String... channelIds) {
this(rorg, func, type, supportsRefresh, eepClass, thingTypeUID, -1, 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, EEPType(RORG rorg, int func, int type, boolean supportsRefresh, String manufactorSuffix, int manufId,
Class<? extends EEP> eepClass, ThingTypeUID thingTypeUID, String... channelIds) { 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, EEPType(RORG rorg, int func, int type, boolean supportsRefresh, Class<? extends EEP> eepClass,
ThingTypeUID thingTypeUID, int command, String... channelIds) { 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) { 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.rorg = rorg;
this.func = func; this.func = func;
this.type = type; this.type = type;
@ -538,24 +546,18 @@ public enum EEPType {
this.manufactorSuffix = manufactorSuffix; this.manufactorSuffix = manufactorSuffix;
this.manufactorId = manufId; this.manufactorId = manufId;
this.supportsRefresh = supportsRefresh; this.supportsRefresh = supportsRefresh;
this.requestsResponse = requestsResponse;
for (String id : channelIds) { for (String id : channelIds) {
this.channelIdsWithConfig.put(id, new Configuration()); this.channelIdsWithConfig.put(id, new Configuration());
this.supportedChannels.put(id, CHANNELID2CHANNELDESCRIPTION.get(id)); this.supportedChannels.put(id, CHANNELID2CHANNELDESCRIPTION.get(id));
} }
this.channelIdsWithConfig.put(CHANNEL_RSSI, new Configuration()); addDefaultChannels();
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));
} }
EEPType(RORG rorg, int func, int type, boolean supportsRefresh, String manufactorSuffix, int manufId, EEPType(RORG rorg, int func, int type, boolean supportsRefresh, boolean requestsResponse, String manufactorSuffix,
Class<? extends EEP> eepClass, ThingTypeUID thingTypeUID, int command, int manufId, Class<? extends EEP> eepClass, ThingTypeUID thingTypeUID, int command,
Hashtable<String, Configuration> channelConfigs) { Hashtable<String, Configuration> channelConfigs) {
this.rorg = rorg; this.rorg = rorg;
this.func = func; this.func = func;
@ -567,11 +569,46 @@ public enum EEPType {
this.manufactorSuffix = manufactorSuffix; this.manufactorSuffix = manufactorSuffix;
this.manufactorId = manufId; this.manufactorId = manufId;
this.supportsRefresh = supportsRefresh; this.supportsRefresh = supportsRefresh;
this.requestsResponse = requestsResponse;
for (String id : channelConfigs.keySet()) { for (String id : channelConfigs.keySet()) {
this.supportedChannels.put(id, CHANNELID2CHANNELDESCRIPTION.get(id)); 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.channelIdsWithConfig.put(CHANNEL_RSSI, new Configuration());
this.supportedChannels.put(CHANNEL_RSSI, CHANNELID2CHANNELDESCRIPTION.get(CHANNEL_RSSI)); this.supportedChannels.put(CHANNEL_RSSI, CHANNELID2CHANNELDESCRIPTION.get(CHANNEL_RSSI));
@ -580,6 +617,12 @@ public enum EEPType {
this.channelIdsWithConfig.put(CHANNEL_LASTRECEIVED, new Configuration()); this.channelIdsWithConfig.put(CHANNEL_LASTRECEIVED, new Configuration());
this.supportedChannels.put(CHANNEL_LASTRECEIVED, CHANNELID2CHANNELDESCRIPTION.get(CHANNEL_LASTRECEIVED)); 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() { public Class<? extends EEP> getEEPClass() {
@ -602,6 +645,10 @@ public enum EEPType {
return supportsRefresh; return supportsRefresh;
} }
public boolean getRequstesResponse() {
return requestsResponse;
}
public Map<String, EnOceanChannelDescription> GetSupportedChannels() { public Map<String, EnOceanChannelDescription> GetSupportedChannels() {
return Collections.unmodifiableMap(supportedChannels); return Collections.unmodifiableMap(supportedChannels);
} }
@ -614,7 +661,7 @@ public enum EEPType {
} }
public boolean isChannelSupported(String channelId, String channelTypeId) { 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)); || supportedChannels.values().stream().anyMatch(c -> c.channelTypeUID.getId().equals(channelTypeId));
} }

View File

@ -34,9 +34,6 @@ public class F6_01_01 extends _RPSMessage {
@Override @Override
protected String convertToEventImpl(String channelId, String channelTypeId, String lastEvent, protected String convertToEventImpl(String channelId, String channelTypeId, String lastEvent,
Configuration config) { Configuration config) {
if (!isValid()) {
return null;
}
return getBit(bytes[0], 4) ? CommonTriggerEvents.PRESSED : CommonTriggerEvents.RELEASED; 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) { protected boolean validateData(byte[] bytes) {
return super.validateData(bytes) && !getBit(bytes[0], 7); 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 @Override
protected String convertToEventImpl(String channelId, String channelTypeId, String lastEvent, protected String convertToEventImpl(String channelId, String channelTypeId, String lastEvent,
Configuration config) { Configuration config) {
if (!isValid()) {
return null;
}
if (t21 && nu) { if (t21 && nu) {
byte dir1 = channelId.equals(CHANNEL_ROCKERSWITCH_CHANNELA) ? A0 : B0; 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 // this method is used by the classic device listener channels to convert an rocker switch message into an
// appropriate item update // appropriate item update
State currentState = getCurrentStateFunc.apply(channelId); State currentState = getCurrentStateFunc.apply(channelId);
if (!isValid()) {
return UnDefType.UNDEF;
}
if (t21 && nu) { if (t21 && nu) {
EnOceanChannelVirtualRockerSwitchConfig c = config.as(EnOceanChannelVirtualRockerSwitchConfig.class); EnOceanChannelVirtualRockerSwitchConfig c = config.as(EnOceanChannelVirtualRockerSwitchConfig.class);
byte dir1 = c.getChannel() == Channel.ChannelA ? A0 : B0; byte dir1 = c.getChannel() == Channel.ChannelA ? A0 : B0;
@ -179,4 +171,22 @@ public class F6_02_01 extends _RPSMessage {
protected boolean validateData(byte[] bytes) { protected boolean validateData(byte[] bytes) {
return super.validateData(bytes) && !getBit(bytes[0], 7); 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 @Override
protected String convertToEventImpl(String channelId, String channelTypeId, String lastEvent, protected String convertToEventImpl(String channelId, String channelTypeId, String lastEvent,
Configuration config) { Configuration config) {
if (!isValid()) {
return null;
}
if (t21 && nu) { if (t21 && nu) {
byte dir1 = channelId.equals(CHANNEL_ROCKERSWITCH_CHANNELA) ? AI : BI; 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 // this method is used by the classic device listener channels to convert an rocker switch message into an
// appropriate item update // appropriate item update
State currentState = getCurrentStateFunc.apply(channelId); State currentState = getCurrentStateFunc.apply(channelId);
if (!isValid()) {
return UnDefType.UNDEF;
}
if (t21 && nu) { if (t21 && nu) {
EnOceanChannelVirtualRockerSwitchConfig c = config.as(EnOceanChannelVirtualRockerSwitchConfig.class); EnOceanChannelVirtualRockerSwitchConfig c = config.as(EnOceanChannelVirtualRockerSwitchConfig.class);
byte dir1 = c.getChannel() == Channel.ChannelA ? AI : BI; byte dir1 = c.getChannel() == Channel.ChannelA ? AI : BI;
@ -171,4 +163,9 @@ public class F6_02_02 extends _RPSMessage {
private State inverse(UpDownType currentState) { private State inverse(UpDownType currentState) {
return currentState == UpDownType.UP ? UpDownType.DOWN : UpDownType.UP; 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 @Override
protected State convertToStateImpl(String channelId, String channelTypeId, protected State convertToStateImpl(String channelId, String channelTypeId,
Function<String, State> getCurrentStateFunc, Configuration config) { Function<String, State> getCurrentStateFunc, Configuration config) {
if (!isValid()) {
return UnDefType.UNDEF;
}
switch (channelId) { switch (channelId) {
case CHANNEL_SMOKEDETECTION: case CHANNEL_SMOKEDETECTION:
@ -62,4 +59,10 @@ public class F6_05_02 extends _RPSMessage {
protected boolean validateData(byte[] bytes) { protected boolean validateData(byte[] bytes) {
return super.validateData(bytes) && (bytes[0] == ALARM_OFF || bytes[0] == ALARM_ON || bytes[0] == ENERGY_LOW); 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 @Override
protected State convertToStateImpl(String channelId, String channelTypeId, protected State convertToStateImpl(String channelId, String channelTypeId,
Function<String, State> getCurrentStateFunc, Configuration config) { Function<String, State> getCurrentStateFunc, Configuration config) {
if (!isValid()) {
return UnDefType.UNDEF;
}
byte data = (byte) (bytes[0] & 0xF0); byte data = (byte) (bytes[0] & 0xF0);
@ -82,4 +79,9 @@ public class F6_10_00 extends _RPSMessage {
protected boolean validateData(byte[] bytes) { protected boolean validateData(byte[] bytes) {
return super.validateData(bytes) && getBit(bytes[0], 7) && getBit(bytes[0], 6); 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 // FPE just sends 0b00010000 or 0b00000000 value, so we apply mask 0b11101111
return super.validateData(bytes) && ((bytes[0] & (byte) 0xEF) == (byte) 0x00); 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 @Override
protected State convertToStateImpl(String channelId, String channelTypeId, protected State convertToStateImpl(String channelId, String channelTypeId,
Function<String, State> getCurrentStateFunc, Configuration config) { Function<String, State> getCurrentStateFunc, Configuration config) {
if (!isValid()) {
return UnDefType.UNDEF;
}
byte data = (byte) (bytes[0] & 0x0F); byte data = (byte) (bytes[0] & 0x0F);
@ -82,4 +79,10 @@ public class F6_10_01 extends _RPSMessage {
protected boolean validateData(byte[] bytes) { protected boolean validateData(byte[] bytes) {
return super.validateData(bytes) && getBit(bytes[0], 6) && getBit(bytes[0], 3) && getBit(bytes[0], 2); 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; 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 static org.openhab.binding.enocean.internal.messages.ESP3Packet.*;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
@ -161,6 +161,7 @@ public class GenericEEP extends EEP {
@Override @Override
public void addConfigPropertiesTo(DiscoveryResultBuilder discoveredThingResultBuilder) { 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.eep.EEPType;
import org.openhab.binding.enocean.internal.messages.BasePacket; import org.openhab.binding.enocean.internal.messages.BasePacket;
import org.openhab.core.config.core.Configuration; 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.Channel;
import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing; import org.openhab.core.thing.Thing;
@ -68,8 +69,8 @@ public class EnOceanBaseActuatorHandler extends EnOceanBaseSensorHandler {
* @param senderIdOffset to be validated * @param senderIdOffset to be validated
* @return true if senderIdOffset is between ]0;128[ and is not used yet * @return true if senderIdOffset is between ]0;128[ and is not used yet
*/ */
private boolean validateSenderIdOffset(int senderIdOffset) { private boolean validateSenderIdOffset(Integer senderIdOffset) {
if (senderIdOffset == -1) { if (senderIdOffset == null) {
return true; return true;
} }
@ -157,26 +158,24 @@ public class EnOceanBaseActuatorHandler extends EnOceanBaseSensorHandler {
} }
private boolean initializeIdForSending() { 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(); EnOceanBridgeHandler bridgeHandler = getBridgeHandler();
if (bridgeHandler == null) { if (bridgeHandler == null) {
return false; return false;
} }
// if senderIdOffset is not set (=> defaults to -1) or set to -1, the next free senderIdOffset is determined // Generic things are treated as actuator things, however to support also generic sensors one can omit
if (getConfiguration().senderIdOffset == -1) { // 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(); Configuration updateConfig = editConfiguration();
getConfiguration().senderIdOffset = bridgeHandler.getNextSenderId(thing); getConfiguration().senderIdOffset = bridgeHandler.getNextSenderId(thing);
if (getConfiguration().senderIdOffset == -1) { if (getConfiguration().senderIdOffset == null) {
configurationErrorDescription = "Could not get a free sender Id from Bridge"; configurationErrorDescription = "Could not get a free sender Id from Bridge";
return false; return false;
} }
@ -185,12 +184,10 @@ public class EnOceanBaseActuatorHandler extends EnOceanBaseSensorHandler {
} }
byte[] baseId = bridgeHandler.getBaseId(); byte[] baseId = bridgeHandler.getBaseId();
baseId[3] = (byte) ((baseId[3] & 0xFF) + getConfiguration().senderIdOffset); baseId[3] = (byte) ((baseId[3] + getConfiguration().senderIdOffset) & 0xFF);
this.senderId = baseId; this.senderId = baseId;
this.updateProperty(PROPERTY_SENDINGENOCEAN_ID, HexUtils.bytesToHex(this.senderId));
this.updateProperty(PROPERTY_ENOCEAN_ID, HexUtils.bytesToHex(this.senderId));
bridgeHandler.addSender(getConfiguration().senderIdOffset, thing); bridgeHandler.addSender(getConfiguration().senderIdOffset, thing);
return true; 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 @Override
public void handleCommand(ChannelUID channelUID, Command command) { public void handleCommand(ChannelUID channelUID, Command command) {
// We must have a valid sendingEEPType and sender id to send commands // We must have a valid sendingEEPType and sender id to send commands
@ -237,16 +250,7 @@ public class EnOceanBaseActuatorHandler extends EnOceanBaseSensorHandler {
try { try {
Configuration channelConfig = channel.getConfiguration(); Configuration channelConfig = channel.getConfiguration();
sendMessage(channelId, channelTypeId, command, 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);
}
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
logger.warn("Exception while sending telegram!", e); logger.warn("Exception while sending telegram!", e);
} }
@ -254,11 +258,16 @@ public class EnOceanBaseActuatorHandler extends EnOceanBaseSensorHandler {
@Override @Override
public void handleRemoval() { public void handleRemoval() {
if (getConfiguration().senderIdOffset > 0) {
EnOceanBridgeHandler bridgeHandler = getBridgeHandler(); EnOceanBridgeHandler bridgeHandler = getBridgeHandler();
if (bridgeHandler != null) { if (bridgeHandler != null) {
if (getConfiguration().senderIdOffset != null && getConfiguration().senderIdOffset > 0) {
bridgeHandler.removeSender(getConfiguration().senderIdOffset); bridgeHandler.removeSender(getConfiguration().senderIdOffset);
} }
if (bridgeHandler.isSmackClient(this.thing)) {
logger.warn("Removing smack client (ThingId: {}) without teach out!", this.thing.getUID().getId());
}
} }
super.handleRemoval(); super.handleRemoval();

View File

@ -19,8 +19,11 @@ import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.Hashtable; import java.util.Hashtable;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.function.Predicate; 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.config.EnOceanBaseConfig;
import org.openhab.binding.enocean.internal.eep.EEP; import org.openhab.binding.enocean.internal.eep.EEP;
import org.openhab.binding.enocean.internal.eep.EEPFactory; 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 final Hashtable<RORG, EEPType> receivingEEPTypes = new Hashtable<>();
protected ScheduledFuture<?> responseFuture = null;
public EnOceanBaseSensorHandler(Thing thing, ItemChannelLinkRegistry itemChannelLinkRegistry) { public EnOceanBaseSensorHandler(Thing thing, ItemChannelLinkRegistry itemChannelLinkRegistry) {
super(thing, itemChannelLinkRegistry); super(thing, itemChannelLinkRegistry);
} }
@ -104,7 +109,7 @@ public class EnOceanBaseSensorHandler extends EnOceanBaseThingHandler implements
} }
@Override @Override
public long getSenderIdToListenTo() { public long getEnOceanIdToListenTo() {
return Long.parseLong(config.enoceanId, 16); 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 @Override
public void packetReceived(BasePacket packet) { public void packetReceived(BasePacket packet) {
ERP1Message msg = (ERP1Message) packet; ERP1Message msg = (ERP1Message) packet;
@ -175,6 +184,15 @@ public class EnOceanBaseSensorHandler extends EnOceanBaseThingHandler implements
break; 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; package org.openhab.binding.enocean.internal.handler;
import static org.openhab.binding.enocean.internal.EnOceanBindingConstants.*;
import java.util.AbstractMap.SimpleEntry; import java.util.AbstractMap.SimpleEntry;
import java.util.Collection; import java.util.Collection;
import java.util.Hashtable; import java.util.Hashtable;
@ -63,18 +65,25 @@ public abstract class EnOceanBaseThingHandler extends ConfigStatusThingHandler {
private ItemChannelLinkRegistry itemChannelLinkRegistry; private ItemChannelLinkRegistry itemChannelLinkRegistry;
protected @NonNull ChannelUID prepareAnswer;
public EnOceanBaseThingHandler(Thing thing, ItemChannelLinkRegistry itemChannelLinkRegistry) { public EnOceanBaseThingHandler(Thing thing, ItemChannelLinkRegistry itemChannelLinkRegistry) {
super(thing); super(thing);
this.itemChannelLinkRegistry = itemChannelLinkRegistry; this.itemChannelLinkRegistry = itemChannelLinkRegistry;
prepareAnswer = new ChannelUID(thing.getUID(), CHANNEL_STATUS_REQUEST_EVENT);
} }
@SuppressWarnings("null")
@Override @Override
public void initialize() { public void initialize() {
logger.debug("Initializing enocean base thing handler."); logger.debug("Initializing enocean base thing handler.");
this.gateway = null; // reset gateway in case we change the bridge this.gateway = null; // reset gateway in case we change the bridge
this.config = null; 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) { private void initializeThing(ThingStatus bridgeStatus) {
@ -143,6 +152,10 @@ public abstract class EnOceanBaseThingHandler extends ConfigStatusThingHandler {
String channelId = entry.getKey(); String channelId = entry.getKey();
EnOceanChannelDescription cd = entry.getValue().GetSupportedChannels().get(channelId); EnOceanChannelDescription cd = entry.getValue().GetSupportedChannels().get(channelId);
if (cd == null) {
return;
}
// if we do not need to auto create channel => skip // if we do not need to auto create channel => skip
if (!cd.autoCreate) { if (!cd.autoCreate) {
return; return;

View File

@ -15,30 +15,35 @@ package org.openhab.binding.enocean.internal.handler;
import static org.openhab.binding.enocean.internal.EnOceanBindingConstants.*; import static org.openhab.binding.enocean.internal.EnOceanBindingConstants.*;
import java.io.IOException; import java.io.IOException;
import java.math.BigDecimal; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import org.openhab.binding.enocean.internal.EnOceanConfigStatusMessage; import org.openhab.binding.enocean.internal.EnOceanConfigStatusMessage;
import org.openhab.binding.enocean.internal.config.EnOceanBaseConfig; import org.openhab.binding.enocean.internal.config.EnOceanBaseConfig;
import org.openhab.binding.enocean.internal.config.EnOceanBridgeConfig; 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.BasePacket;
import org.openhab.binding.enocean.internal.messages.BaseResponse;
import org.openhab.binding.enocean.internal.messages.ESP3PacketFactory; 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;
import org.openhab.binding.enocean.internal.messages.Response.ResponseType; 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.EnOceanESP2Transceiver;
import org.openhab.binding.enocean.internal.transceiver.EnOceanESP3Transceiver; import org.openhab.binding.enocean.internal.transceiver.EnOceanESP3Transceiver;
import org.openhab.binding.enocean.internal.transceiver.EnOceanTransceiver; import org.openhab.binding.enocean.internal.transceiver.EnOceanTransceiver;
import org.openhab.binding.enocean.internal.transceiver.PacketListener; import org.openhab.binding.enocean.internal.transceiver.PacketListener;
import org.openhab.binding.enocean.internal.transceiver.ResponseListener; import org.openhab.binding.enocean.internal.transceiver.ResponseListener;
import org.openhab.binding.enocean.internal.transceiver.ResponseListenerIgnoringTimeouts; 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.binding.enocean.internal.transceiver.TransceiverErrorListener;
import org.openhab.core.config.core.Configuration; import org.openhab.core.config.core.Configuration;
import org.openhab.core.config.core.status.ConfigStatusMessage; import org.openhab.core.config.core.status.ConfigStatusMessage;
@ -76,9 +81,12 @@ public class EnOceanBridgeHandler extends ConfigStatusBridgeHandler implements T
private byte[] baseId = null; private byte[] baseId = null;
private Thing[] sendingThings = new Thing[128]; private Thing[] sendingThings = new Thing[128];
private int nextSenderId = 0;
private SerialPortManager serialPortManager; private SerialPortManager serialPortManager;
private boolean smackAvailable = false;
private boolean sendTeachOuts = true;
private Set<String> smackClients = Set.of();
public EnOceanBridgeHandler(Bridge bridge, SerialPortManager serialPortManager) { public EnOceanBridgeHandler(Bridge bridge, SerialPortManager serialPortManager) {
super(bridge); super(bridge);
this.serialPortManager = serialPortManager; this.serialPortManager = serialPortManager;
@ -157,13 +165,6 @@ public class EnOceanBridgeHandler extends ConfigStatusBridgeHandler implements T
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"SerialPortManager could not be found"); "SerialPortManager could not be found");
} else { } else {
Object devId = getConfig().get(NEXTSENDERID);
if (devId != null) {
nextSenderId = ((BigDecimal) devId).intValue();
} else {
nextSenderId = 0;
}
if (connectorTask == null || connectorTask.isDone()) { if (connectorTask == null || connectorTask.isDone()) {
connectorTask = scheduler.scheduleWithFixedDelay(new Runnable() { connectorTask = scheduler.scheduleWithFixedDelay(new Runnable() {
@Override @Override
@ -187,9 +188,12 @@ public class EnOceanBridgeHandler extends ConfigStatusBridgeHandler implements T
switch (c.getESPVersion()) { switch (c.getESPVersion()) {
case ESP2: case ESP2:
transceiver = new EnOceanESP2Transceiver(c.path, this, scheduler, serialPortManager); transceiver = new EnOceanESP2Transceiver(c.path, this, scheduler, serialPortManager);
smackAvailable = false;
sendTeachOuts = false;
break; break;
case ESP3: case ESP3:
transceiver = new EnOceanESP3Transceiver(c.path, this, scheduler, serialPortManager); transceiver = new EnOceanESP3Transceiver(c.path, this, scheduler, serialPortManager);
sendTeachOuts = c.sendTeachOuts;
break; break;
default: default:
break; break;
@ -200,6 +204,7 @@ public class EnOceanBridgeHandler extends ConfigStatusBridgeHandler implements T
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_PENDING, "starting rx thread..."); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_PENDING, "starting rx thread...");
transceiver.StartReceiving(scheduler); transceiver.StartReceiving(scheduler);
logger.info("EnOceanSerialTransceiver RX thread up and running");
if (c.rs485) { if (c.rs485) {
if (c.rs485BaseId != null && !c.rs485BaseId.isEmpty()) { 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"); logger.debug("request version info");
@ -283,7 +310,7 @@ public class EnOceanBridgeHandler extends ConfigStatusBridgeHandler implements T
Collection<ConfigStatusMessage> configStatusMessages = new LinkedList<>(); Collection<ConfigStatusMessage> configStatusMessages = new LinkedList<>();
// The serial port must be provided // 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()) { if (path == null || path.isEmpty()) {
configStatusMessages.add(ConfigStatusMessage.Builder.error(PATH) configStatusMessages.add(ConfigStatusMessage.Builder.error(PATH)
.withMessageKeySuffix(EnOceanConfigStatusMessage.PORT_MISSING.getMessageKey()).withArguments(PATH) .withMessageKeySuffix(EnOceanConfigStatusMessage.PORT_MISSING.getMessageKey()).withArguments(PATH)
@ -297,30 +324,33 @@ public class EnOceanBridgeHandler extends ConfigStatusBridgeHandler implements T
return baseId.clone(); return baseId.clone();
} }
public int getNextSenderId(Thing sender) { public boolean isSmackClient(Thing sender) {
// TODO: change id to enoceanId return smackClients.contains(sender.getConfiguration().as(EnOceanBaseConfig.class).enoceanId);
}
public Integer getNextSenderId(Thing sender) {
return getNextSenderId(sender.getConfiguration().as(EnOceanBaseConfig.class).enoceanId); return getNextSenderId(sender.getConfiguration().as(EnOceanBaseConfig.class).enoceanId);
} }
public int getNextSenderId(String senderId) { public Integer getNextSenderId(String enoceanId) {
if (nextSenderId != 0 && sendingThings[nextSenderId] == null) { EnOceanBridgeConfig config = getConfigAs(EnOceanBridgeConfig.class);
int result = nextSenderId;
Configuration config = getConfig();
config.put(NEXTSENDERID, null);
updateConfiguration(config);
nextSenderId = 0;
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 if (sendingThings[i] == null || sendingThings[i].getConfiguration().as(EnOceanBaseConfig.class).enoceanId
.equalsIgnoreCase(senderId)) { .equalsIgnoreCase(enoceanId)) {
return i; return i;
} }
} }
return -1; return null;
} }
public boolean existsSender(int id, Thing sender) { public boolean existsSender(int id, Thing sender) {
@ -345,7 +375,7 @@ public class EnOceanBridgeHandler extends ConfigStatusBridgeHandler implements T
} }
public void addPacketListener(PacketListener listener) { public void addPacketListener(PacketListener listener) {
addPacketListener(listener, listener.getSenderIdToListenTo()); addPacketListener(listener, listener.getEnOceanIdToListenTo());
} }
public void addPacketListener(PacketListener listener, long senderIdToListenTo) { public void addPacketListener(PacketListener listener, long senderIdToListenTo) {
@ -355,7 +385,7 @@ public class EnOceanBridgeHandler extends ConfigStatusBridgeHandler implements T
} }
public void removePacketListener(PacketListener listener) { public void removePacketListener(PacketListener listener) {
removePacketListener(listener, listener.getSenderIdToListenTo()); removePacketListener(listener, listener.getEnOceanIdToListenTo());
} }
public void removePacketListener(PacketListener listener, long senderIdToListenTo) { 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); 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() { public void stopDiscovery() {
transceiver.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 @Override
@ -378,4 +470,8 @@ public class EnOceanBridgeHandler extends ConfigStatusBridgeHandler implements T
transceiver = null; transceiver = null;
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, exception.getMessage()); 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 @Override
public long getSenderIdToListenTo() { public long getEnOceanIdToListenTo() {
return 0; return 0;
} }

View File

@ -13,8 +13,10 @@
package org.openhab.binding.enocean.internal.messages; package org.openhab.binding.enocean.internal.messages;
import org.openhab.binding.enocean.internal.EnOceanBindingConstants; 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.BasePacket.ESPPacketType;
import org.openhab.binding.enocean.internal.messages.CCMessage.CCMessageType; 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; 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) { public static BasePacket BuildPacket(int dataLength, int optionalDataLength, byte packetType, byte[] payload) {
ESPPacketType type = ESPPacketType.getPacketType(packetType); ESPPacketType type = ESPPacketType.getPacketType(packetType);
@ -50,6 +74,8 @@ public class ESP3PacketFactory {
return new Response(dataLength, optionalDataLength, payload); return new Response(dataLength, optionalDataLength, payload);
case RADIO_ERP1: case RADIO_ERP1:
return new ERP1Message(dataLength, optionalDataLength, payload); return new ERP1Message(dataLength, optionalDataLength, payload);
case EVENT:
return new EventMessage(dataLength, optionalDataLength, payload);
default: default:
return null; 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 ResponseType responseType;
protected boolean _isValid = false; 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); super(dataLength, optionalDataLength, ESPPacketType.RESPONSE, payload);
try { try {

View File

@ -10,9 +10,10 @@
* *
* SPDX-License-Identifier: EPL-2.0 * 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.Helper;
import org.openhab.binding.enocean.internal.messages.Response;
/** /**
* *

View File

@ -10,9 +10,10 @@
* *
* SPDX-License-Identifier: EPL-2.0 * 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.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 * 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 static org.openhab.binding.enocean.internal.EnOceanBindingConstants.*;
import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNull;
import org.openhab.binding.enocean.internal.messages.Response;
import org.openhab.core.library.types.StringType; import org.openhab.core.library.types.StringType;
/** /**

View File

@ -10,11 +10,12 @@
* *
* SPDX-License-Identifier: EPL-2.0 * 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 java.util.Arrays;
import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNull;
import org.openhab.binding.enocean.internal.messages.Response;
import org.openhab.core.util.HexUtils; 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.EnOceanException;
import org.openhab.binding.enocean.internal.messages.BasePacket; 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.ESP3Packet;
import org.openhab.binding.enocean.internal.messages.ESP3PacketFactory; import org.openhab.binding.enocean.internal.messages.ESP3PacketFactory;
import org.openhab.binding.enocean.internal.messages.Response; import org.openhab.binding.enocean.internal.messages.Response;
@ -138,21 +136,8 @@ public class EnOceanESP3Transceiver extends EnOceanTransceiver {
HexUtils.bytesToHex(packet.getPayload())); HexUtils.bytesToHex(packet.getPayload()));
break; break;
case EVENT: case EVENT:
logger.debug("Event occured: {}", HexUtils.bytesToHex(packet.getPayload())); case RADIO_ERP1:
break; informListeners(packet);
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");
}
}
break; break;
case RADIO_ERP2: case RADIO_ERP2:
break; 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.EnOceanBindingConstants;
import org.openhab.binding.enocean.internal.EnOceanException; 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;
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;
import org.openhab.binding.enocean.internal.messages.ERP1Message.RORG; 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.binding.enocean.internal.messages.Response;
import org.openhab.core.io.transport.serial.PortInUseException; import org.openhab.core.io.transport.serial.PortInUseException;
import org.openhab.core.io.transport.serial.SerialPort; import org.openhab.core.io.transport.serial.SerialPort;
@ -138,7 +142,8 @@ public abstract class EnOceanTransceiver implements SerialPortEventListener {
Request currentRequest = null; Request currentRequest = null;
protected Map<Long, HashSet<PacketListener>> listeners; protected Map<Long, HashSet<PacketListener>> listeners;
protected PacketListener teachInListener; protected HashSet<EventListener> eventListeners;
protected TeachInListener teachInListener;
protected InputStream inputStream; protected InputStream inputStream;
protected OutputStream outputStream; protected OutputStream outputStream;
@ -151,6 +156,7 @@ public abstract class EnOceanTransceiver implements SerialPortEventListener {
requestQueue = new RequestQueue(scheduler); requestQueue = new RequestQueue(scheduler);
listeners = new HashMap<>(); listeners = new HashMap<>();
eventListeners = new HashSet<>();
teachInListener = null; teachInListener = null;
this.errorListener = errorListener; this.errorListener = errorListener;
@ -192,6 +198,7 @@ public abstract class EnOceanTransceiver implements SerialPortEventListener {
} }
}); });
} }
logger.info("EnOceanSerialTransceiver RX thread started");
} }
public void ShutDown() { public void ShutDown() {
@ -266,30 +273,33 @@ public abstract class EnOceanTransceiver implements SerialPortEventListener {
} }
} }
protected void informListeners(ERP1Message msg) { protected void informListeners(BasePacket packet) {
try { try {
if (packet.getPacketType() == ESPPacketType.RADIO_ERP1) {
ERP1Message msg = (ERP1Message) packet;
byte[] senderId = msg.getSenderId(); byte[] senderId = msg.getSenderId();
byte[] d = Helper.concatAll(msg.getPayload(), msg.getOptionalPayload());
logger.debug("{} with RORG {} for {} payload {} received", packet.getPacketType().name(),
msg.getRORG().name(), HexUtils.bytesToHex(msg.getSenderId()), HexUtils.bytesToHex(d));
if (msg.getRORG() != RORG.Unknown) {
if (senderId != null) { if (senderId != null) {
if (filteredDeviceId != null && senderId[0] == filteredDeviceId[0] && senderId[1] == filteredDeviceId[1] if (filteredDeviceId != null && senderId[0] == filteredDeviceId[0]
&& senderId[2] == filteredDeviceId[2]) { && senderId[1] == filteredDeviceId[1] && senderId[2] == filteredDeviceId[2]) {
// filter away own messages which are received through a repeater // filter away own messages which are received through a repeater
return; return;
} }
if (teachInListener != null) { if (teachInListener != null && (msg.getIsTeachIn() || msg.getRORG() == RORG.RPS)) {
if (msg.getIsTeachIn() || (msg.getRORG() == RORG.RPS)) {
logger.info("Received teach in message from {}", HexUtils.bytesToHex(msg.getSenderId())); logger.info("Received teach in message from {}", HexUtils.bytesToHex(msg.getSenderId()));
teachInListener.packetReceived(msg); teachInListener.packetReceived(msg);
return; return;
} } else if (teachInListener == null && msg.getIsTeachIn()) {
} else {
if (msg.getIsTeachIn()) {
logger.info("Discard message because this is a teach-in telegram from {}!", logger.info("Discard message because this is a teach-in telegram from {}!",
HexUtils.bytesToHex(msg.getSenderId())); HexUtils.bytesToHex(msg.getSenderId()));
return; return;
} }
}
long s = Long.parseLong(HexUtils.bytesToHex(senderId), 16); long s = Long.parseLong(HexUtils.bytesToHex(senderId), 16);
HashSet<PacketListener> pl = listeners.get(s); HashSet<PacketListener> pl = listeners.get(s);
@ -297,6 +307,32 @@ public abstract class EnOceanTransceiver implements SerialPortEventListener {
pl.forEach(l -> l.packetReceived(msg)); pl.forEach(l -> l.packetReceived(msg));
} }
} }
} else {
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;
}
}
eventListeners.forEach(l -> l.eventReceived(event));
}
} catch (Exception e) { } catch (Exception e) {
logger.error("Exception in informListeners", 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; 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 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> <description>EnOceanId of device this thing belongs to</description>
<required>true</required> <required>true</required>
</parameter> </parameter>
<parameter name="senderIdOffset" type="integer"> <parameter name="senderIdOffset" type="integer" required="false" min="1" max="127">
<label>Sender Id</label> <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 <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> determined by bridge</description>

View File

@ -20,7 +20,7 @@
</channels> </channels>
<config-description> <config-description>
<parameter name="senderIdOffset" type="integer"> <parameter name="senderIdOffset" type="integer" required="false" min="1" max="127">
<label>Sender Id</label> <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 <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> determined by bridge</description>

View File

@ -18,7 +18,7 @@
<description>EnOceanId of device this thing belongs to</description> <description>EnOceanId of device this thing belongs to</description>
<required>true</required> <required>true</required>
</parameter> </parameter>
<parameter name="senderIdOffset" type="integer"> <parameter name="senderIdOffset" type="integer" required="false" min="1" max="127">
<label>Sender Id</label> <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 <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> determined by bridge</description>

View File

@ -18,7 +18,7 @@
<description>EnOceanId of device this thing belongs to</description> <description>EnOceanId of device this thing belongs to</description>
<required>true</required> <required>true</required>
</parameter> </parameter>
<parameter name="senderIdOffset" type="integer"> <parameter name="senderIdOffset" type="integer" required="false" min="1" max="127">
<label>Sender Id</label> <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 <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> determined by bridge</description>

View File

@ -18,7 +18,7 @@
<description>EnOceanId of device this thing belongs to</description> <description>EnOceanId of device this thing belongs to</description>
<required>true</required> <required>true</required>
</parameter> </parameter>
<parameter name="senderIdOffset" type="integer"> <parameter name="senderIdOffset" type="integer" required="false" min="1" max="127">
<label>Sender Id</label> <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 <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> determined by bridge</description>

View File

@ -18,7 +18,7 @@
<description>EnOceanId of device this thing belongs to</description> <description>EnOceanId of device this thing belongs to</description>
<required>true</required> <required>true</required>
</parameter> </parameter>
<parameter name="senderIdOffset" type="integer"> <parameter name="senderIdOffset" type="integer" required="false" min="1" max="127">
<label>Sender Id</label> <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 <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> determined by bridge</description>

View File

@ -18,7 +18,7 @@
<description>EnOceanId of device this thing belongs to</description> <description>EnOceanId of device this thing belongs to</description>
<required>true</required> <required>true</required>
</parameter> </parameter>
<parameter name="senderIdOffset" type="integer"> <parameter name="senderIdOffset" type="integer" required="false" min="1" max="127">
<label>Sender Id</label> <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 <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> determined by bridge</description>

View File

@ -31,6 +31,11 @@
<required>true</required> <required>true</required>
<default>ESP3</default> <default>ESP3</default>
</parameter> </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"> <parameter name="rs485" type="boolean">
<advanced>true</advanced> <advanced>true</advanced>
<label>Gateway Connected Directly to RS485 BUS</label> <label>Gateway Connected Directly to RS485 BUS</label>
@ -41,9 +46,15 @@
<label>Use following BaseId when connected directly to RS485 BUS</label> <label>Use following BaseId when connected directly to RS485 BUS</label>
<default>00000000</default> <default>00000000</default>
</parameter> </parameter>
<parameter name="nextSenderId" type="integer"> <parameter name="sendTeachOuts" type="boolean">
<advanced>true</advanced> <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> <description>Defines the next device Id, if empty, the next device id is automatically determined</description>
</parameter> </parameter>
</config-description> </config-description>

View File

@ -488,13 +488,6 @@
<event/> <event/>
</channel-type> </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"> <channel-type id="smokeDetection">
<item-type>Switch</item-type> <item-type>Switch</item-type>
<label>Smoke Detected</label> <label>Smoke Detected</label>