If the actuator supports UTE teach-in, the corresponding thing can be created and paired automatically.
First you have to **start the discovery scan for a gateway**.
Then press the teach-in button of the actuator.
-If the EEP of the actuator is known, the binding sends an UTE teach-in response with a new SenderId and creates a new thing with its channels.
+If the EEP of the actuator is known, the binding sends an UTE teach-in response with a new SenderId and creates a new thing with its channels.
+
+This binding supports so called smart acknowlegde (SMACK) devices too.
+Before you can pair a SMACK device you have to configure your gateway bridge as a SMACK postmaster.
+If this option is enabled you can pair up to 20 SMACK devices with your gateway.
+
+Communication between your gateway and a SMACK device is handled through mailboxes.
+A mailbox is created for each paired SMACK device and deleted after teach out.
+You can see the paired SMACK devices and their mailbox index in the gateway properties.
+SMACK devices send periodically status updates followed by a response request.
+Whenever such a request is received a `requestAnswer` event is triggered for channel `statusRequestEvent`.
+Afterwards you have 100ms time to recalculate your items states and update them.
+A message with the updated item states is built, put into the corresponding mailbox and automatically sent upon request of the device.
+Pairing and unpairing can be done through a discovery scan.
+The corresponding thing of an unpaired device gets disabled, you have to delete it manually if you want to.
If the actuator does not support UTE teach-ins, you have to create, configure and choose the right EEP of the thing manually.
It is important to link the teach-in channel of this thing to a switch item.
| | espVersion | ESP Version of gateway | ESP3, ESP2 |
| | rs485 | If gateway is directly connected to a RS485 bus the BaseId is set to 0x00 | true, false
| | rs485BaseId | Override BaseId 0x00 if your bus contains a telegram duplicator (FTD14 for ex) | 4 byte hex value |
+| | enableSmack | Enables SMACK pairing and handling of SMACK messages | true, false |
+| | sendTeachOuts | Defines if a repeated teach in request should be answered with a learned in or teach out response | true, false |
| pushButton | receivingEEPId | EEP used for receiving msg | F6_01_01, D2_03_0A |
| | enoceanId | EnOceanId of device this thing belongs to | hex value as string |
| rockerSwitch | receivingEEPId | | F6_02_01, F6_02_02 |
| rssi | Number | Received Signal Strength Indication (dBm) of last received message |
| repeatCount | Number | Number of repeaters involved in the transmission of the telegram |
| lastReceived | DateTime | Date and time the last telegram was received |
+| statusRequestEvent | Trigger | Emits event 'requestAnswer' |
Items linked to bi-directional actuators (actuator sends status messages back) should always disable the `autoupdate`.
This is especially true for Eltako rollershutter, as their position is calculated out of the current position and the moving time.
public static final String CHANNEL_WAKEUPCYCLE = "wakeUpCycle";
public static final String CHANNEL_SERVICECOMMAND = "serviceCommand";
public static final String CHANNEL_STATUS_REQUEST_EVENT = "statusRequestEvent";
- public static final String CHANNEL_SEND_COMMAND = "sendCommand";
+ public static final String VIRTUALCHANNEL_SEND_COMMAND = "sendCommand";
public static final String CHANNEL_VENTILATIONOPERATIONMODE = "ventilationOperationMode";
public static final String CHANNEL_FIREPLACESAFETYMODE = "fireplaceSafetyMode";
Map.entry(CHANNEL_INDOORAIRANALYSIS,
new EnOceanChannelDescription(new ChannelTypeUID(BINDING_ID, CHANNEL_INDOORAIRANALYSIS),
CoreItemFactory.STRING)),
- Map.entry(CHANNEL_SETPOINT,
+ Map.entry(
+ CHANNEL_SETPOINT,
new EnOceanChannelDescription(new ChannelTypeUID(BINDING_ID, CHANNEL_SETPOINT),
CoreItemFactory.NUMBER)),
Map.entry(CHANNEL_CONTACT,
new EnOceanChannelDescription(new ChannelTypeUID(BINDING_ID, CHANNEL_SERVICECOMMAND),
CoreItemFactory.NUMBER)),
- Map.entry(CHANNEL_STATUS_REQUEST_EVENT,
- new EnOceanChannelDescription(new ChannelTypeUID(BINDING_ID, CHANNEL_STATUS_REQUEST_EVENT), null,
- "", false, true)),
- Map.entry(CHANNEL_SEND_COMMAND,
- new EnOceanChannelDescription(new ChannelTypeUID(BINDING_ID, CHANNEL_SEND_COMMAND),
- CoreItemFactory.SWITCH)),
-
Map.entry(CHANNEL_VENTILATIONOPERATIONMODE,
new EnOceanChannelDescription(new ChannelTypeUID(BINDING_ID, CHANNEL_VENTILATIONOPERATIONMODE),
CoreItemFactory.STRING)),
CoreItemFactory.NUMBER + ItemUtil.EXTENSION_SEPARATOR
+ Dimensionless.class.getSimpleName())),
+ Map.entry(CHANNEL_STATUS_REQUEST_EVENT,
+ new EnOceanChannelDescription(new ChannelTypeUID(BINDING_ID, CHANNEL_STATUS_REQUEST_EVENT), null,
+ "", false, true)),
+
Map.entry(CHANNEL_REPEATERMODE, new EnOceanChannelDescription(
new ChannelTypeUID(BINDING_ID, CHANNEL_REPEATERMODE), CoreItemFactory.STRING)));
public static final String REPEATERMODE_LEVEL_2 = "LEVEL2";
// Bridge config properties
- public static final String SENDERID = "senderId";
public static final String PATH = "path";
- public static final String HOST = "host";
- public static final String RS485 = "rs485";
- public static final String NEXTSENDERID = "nextSenderId";
+ public static final String PARAMETER_NEXT_SENDERID = "nextSenderId";
// Bridge properties
public static final String PROPERTY_BASE_ID = "Base ID";
public static final String PROPERTY_DESCRIPTION = "Description";
// Thing properties
- public static final String PROPERTY_ENOCEAN_ID = "enoceanId";
+ public static final String PROPERTY_SENDINGENOCEAN_ID = "SendingEnoceanId";
// Thing config parameter
public static final String PARAMETER_SENDERIDOFFSET = "senderIdOffset";
public static final String PARAMETER_SENDINGEEPID = "sendingEEPId";
public static final String PARAMETER_RECEIVINGEEPID = "receivingEEPId";
- public static final String PARAMETER_EEPID = "eepId";
public static final String PARAMETER_BROADCASTMESSAGES = "broadcastMessages";
public static final String PARAMETER_ENOCEANID = "enoceanId";
import org.openhab.core.io.transport.serial.SerialPortManager;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingManager;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
@Reference
ItemChannelLinkRegistry itemChannelLinkRegistry;
+ @Reference
+ ThingManager thingManager;
+
@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
}
private void registerDeviceDiscoveryService(EnOceanBridgeHandler handler) {
- EnOceanDeviceDiscoveryService discoveryService = new EnOceanDeviceDiscoveryService(handler);
+ EnOceanDeviceDiscoveryService discoveryService = new EnOceanDeviceDiscoveryService(handler, thingManager);
discoveryService.activate();
this.discoveryServiceRegs.put(handler.getThing().getUID(),
bundleContext.registerService(DiscoveryService.class.getName(), discoveryService, new Hashtable<>()));
public class EnOceanActuatorConfig extends EnOceanBaseConfig {
public int channel;
- public int senderIdOffset = -1;
+ public Integer senderIdOffset = null;
public String manufacturerId;
public String teachInType;
import java.util.ArrayList;
import java.util.List;
+import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.util.HexUtils;
/**
*
* @author Daniel Weber - Initial contribution
*/
+@NonNullByDefault
public class EnOceanBaseConfig {
+ /**
+ * EnOceanId of the physical device
+ */
public String enoceanId;
+ /**
+ * EEP used/send by physical device
+ */
public List<String> receivingEEPId = new ArrayList<>();
public boolean receivingSIGEEP = false;
public boolean rs485;
public String rs485BaseId;
- public int nextSenderId = 0;
+ public Integer nextSenderId;
+
+ public boolean enableSmack;
+ public boolean sendTeachOuts;
public EnOceanBridgeConfig() {
espVersion = "ESP3";
+ sendTeachOuts = false;
+ enableSmack = true;
+ nextSenderId = null;
}
public ESPVersion getESPVersion() {
*/
package org.openhab.binding.enocean.internal.config;
+import org.openhab.core.config.core.Configuration;
+
/**
*
* @author Daniel Weber - Initial contribution
*/
-public class EnOceanChannelTransformationConfig {
+public class EnOceanChannelTransformationConfig extends Configuration {
public String transformationType;
public String transformationFunction;
+
+ public EnOceanChannelTransformationConfig() {
+ put("transformationType", "");
+ put("transformationFunction", "");
+ }
}
import org.openhab.binding.enocean.internal.messages.BasePacket;
import org.openhab.binding.enocean.internal.messages.ERP1Message;
import org.openhab.binding.enocean.internal.messages.ERP1Message.RORG;
-import org.openhab.binding.enocean.internal.transceiver.PacketListener;
+import org.openhab.binding.enocean.internal.messages.EventMessage;
+import org.openhab.binding.enocean.internal.messages.EventMessage.EventMessageType;
+import org.openhab.binding.enocean.internal.messages.Responses.SMACKTeachInResponse;
+import org.openhab.binding.enocean.internal.transceiver.TeachInListener;
import org.openhab.core.config.discovery.AbstractDiscoveryService;
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingManager;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.util.HexUtils;
*
* @author Daniel Weber - Initial contribution
*/
-
-public class EnOceanDeviceDiscoveryService extends AbstractDiscoveryService implements PacketListener {
+public class EnOceanDeviceDiscoveryService extends AbstractDiscoveryService implements TeachInListener {
private final Logger logger = LoggerFactory.getLogger(EnOceanDeviceDiscoveryService.class);
private EnOceanBridgeHandler bridgeHandler;
+ private ThingManager thingManager;
- public EnOceanDeviceDiscoveryService(EnOceanBridgeHandler bridgeHandler) {
+ public EnOceanDeviceDiscoveryService(EnOceanBridgeHandler bridgeHandler, ThingManager thingManager) {
super(null, 60, false);
this.bridgeHandler = bridgeHandler;
+ this.thingManager = thingManager;
}
/**
}
String enoceanId = HexUtils.bytesToHex(eep.getSenderId());
- ThingTypeUID thingTypeUID = eep.getThingTypeUID();
- ThingUID thingUID = new ThingUID(thingTypeUID, bridgeHandler.getThing().getUID(), enoceanId);
-
- int senderIdOffset = 0;
- boolean broadcastMessages = true;
-
- // check for bidirectional communication => do not use broadcast in this case
- if (msg.getRORG() == RORG.UTE && (msg.getPayload(1, 1)[0]
- & UTEResponse.CommunicationType_MASK) == UTEResponse.CommunicationType_MASK) {
- broadcastMessages = false;
- }
-
- // if ute => send response if needed
- if (msg.getRORG() == RORG.UTE && (msg.getPayload(1, 1)[0] & UTEResponse.ResponseNeeded_MASK) == 0) {
- logger.info("Sending UTE response to {}", enoceanId);
- senderIdOffset = sendTeachInResponse(msg, enoceanId);
- }
-
- // if 4BS teach in variation 3 => send response
- if ((eep instanceof _4BSMessage) && ((_4BSMessage) eep).isTeachInVariation3Supported()) {
- logger.info("Sending 4BS teach in variation 3 response to {}", enoceanId);
- senderIdOffset = sendTeachInResponse(msg, enoceanId);
- }
- DiscoveryResultBuilder discoveryResultBuilder = DiscoveryResultBuilder.create(thingUID)
- .withRepresentationProperty(enoceanId).withBridge(bridgeHandler.getThing().getUID());
+ bridgeHandler.getThing().getThings().stream()
+ .filter(t -> t.getConfiguration().getProperties().getOrDefault(PARAMETER_ENOCEANID, EMPTYENOCEANID)
+ .toString().equals(enoceanId))
+ .findFirst().ifPresentOrElse(t -> {
+ // If repeated learn is not allowed => send teach out
+ // otherwise do nothing
+ if (bridgeHandler.sendTeachOuts()) {
+ sendTeachOutResponse(msg, enoceanId, t);
+ thingManager.setEnabled(t.getUID(), false);
+ }
+ }, () -> {
+ Integer senderIdOffset = null;
+ boolean broadcastMessages = true;
+
+ // check for bidirectional communication => do not use broadcast in this case
+ if (msg.getRORG() == RORG.UTE && (msg.getPayload(1, 1)[0]
+ & UTEResponse.CommunicationType_MASK) == UTEResponse.CommunicationType_MASK) {
+ broadcastMessages = false;
+ }
+
+ if (msg.getRORG() == RORG.UTE && (msg.getPayload(1, 1)[0] & UTEResponse.ResponseNeeded_MASK) == 0) {
+ // if ute => send response if needed
+ logger.debug("Sending UTE response to {}", enoceanId);
+ senderIdOffset = sendTeachInResponse(msg, enoceanId);
+ if (senderIdOffset == null) {
+ return;
+ }
+ } else if ((eep instanceof _4BSMessage) && ((_4BSMessage) eep).isTeachInVariation3Supported()) {
+ // if 4BS teach in variation 3 => send response
+ logger.debug("Sending 4BS teach in variation 3 response to {}", enoceanId);
+ senderIdOffset = sendTeachInResponse(msg, enoceanId);
+ if (senderIdOffset == null) {
+ return;
+ }
+ }
+
+ createDiscoveryResult(eep, broadcastMessages, senderIdOffset);
+ });
+ }
- eep.addConfigPropertiesTo(discoveryResultBuilder);
- discoveryResultBuilder.withProperty(PARAMETER_BROADCASTMESSAGES, broadcastMessages);
- discoveryResultBuilder.withProperty(PARAMETER_ENOCEANID, enoceanId);
+ @Override
+ public void eventReceived(EventMessage event) {
+ if (event.getEventMessageType() == EventMessageType.SA_CONFIRM_LEARN) {
+ EEP eep = EEPFactory.buildEEPFromTeachInSMACKEvent(event);
+ if (eep == null) {
+ return;
+ }
- if (senderIdOffset > 0) {
- // advance config with new device id
- discoveryResultBuilder.withProperty(PARAMETER_SENDERIDOFFSET, senderIdOffset);
+ 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()));
+ });
+ }
+ }
}
-
- 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;
+ private Integer sendTeachInResponse(ERP1Message msg, String enoceanId) {
// get new sender Id
- offset = bridgeHandler.getNextSenderId(enoceanId);
- if (offset > 0) {
+ Integer offset = bridgeHandler.getNextSenderId(enoceanId);
+ if (offset != null) {
byte[] newSenderId = bridgeHandler.getBaseId();
newSenderId[3] += offset;
// send response
- EEP response = EEPFactory.buildResponseEEPFromTeachInERP1(msg, newSenderId);
+ EEP response = EEPFactory.buildResponseEEPFromTeachInERP1(msg, newSenderId, true);
if (response != null) {
bridgeHandler.sendMessage(response.getERP1Message(), null);
- logger.info("Teach in response for {} with new senderId {} (= offset {}) sent", enoceanId,
+ 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)
+ .withRepresentationProperty(PARAMETER_ENOCEANID).withProperty(PARAMETER_ENOCEANID, enoceanId)
+ .withProperty(PARAMETER_BROADCASTMESSAGES, broadcastMessages)
+ .withBridge(bridgeHandler.getThing().getUID());
+
+ eep.addConfigPropertiesTo(discoveryResultBuilder);
+
+ if (senderIdOffset != null) {
+ // advance config with new device id
+ discoveryResultBuilder.withProperty(PARAMETER_SENDERIDOFFSET, senderIdOffset);
+ }
+
+ thingDiscovered(discoveryResultBuilder.build());
+ }
+
@Override
- public long getSenderIdToListenTo() {
+ public long getEnOceanIdToListenTo() {
// we just want teach in msg, so return zero here
return 0;
}
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.unit.SIUnits;
import org.openhab.core.types.State;
-import org.openhab.core.types.UnDefType;
/**
*
@Override
protected State convertToStateImpl(String channelId, String channelTypeId,
Function<String, State> getCurrentStateFunc, Configuration config) {
- if (!isValid()) {
- return UnDefType.UNDEF;
- }
double scaledTemp = getScaledMin()
- (((getUnscaledMin() - getUnscaledTemperatureValue()) * (getScaledMin() - getScaledMax()))
@Override
protected State convertToStateImpl(String channelId, String channelTypeId,
Function<String, State> getCurrentStateFunc, Configuration config) {
- if (!isValid()) {
- return UnDefType.UNDEF;
- }
if (channelId.equals(CHANNEL_TEMPERATURE)) {
double scaledTemp = getScaledTemperatureMin()
@Override
protected State convertToStateImpl(String channelId, String channelTypeId,
Function<String, State> getCurrentStateFunc, Configuration config) {
- if (!isValid()) {
- return UnDefType.UNDEF;
- }
if (channelId.equals(CHANNEL_ILLUMINATION)) {
return getIllumination();
@Override
protected State convertToStateImpl(String channelId, String channelTypeId,
Function<String, State> getCurrentStateFunc, Configuration config) {
- if (!isValid()) {
- return UnDefType.UNDEF;
- }
if (channelId.equals(CHANNEL_TEMPERATURE)) {
double scaledTemp = getScaledTemperatureMin()
@Override
protected State convertToStateImpl(String channelId, String channelTypeId,
Function<String, State> getCurrentStateFunc, Configuration config) {
- if (!isValid()) {
- return UnDefType.UNDEF;
- }
switch (channelId) {
case CHANNEL_FANSPEEDSTAGE:
@Override
protected void convertFromCommandImpl(String channelId, String channelTypeId, Command command,
Function<String, State> getCurrentStateFunc, Configuration config) {
- if (CHANNEL_SEND_COMMAND.equals(channelId) && (command.equals(OnOffType.ON))) {
+ if (VIRTUALCHANNEL_SEND_COMMAND.equals(channelId)) {
byte db3 = getPos(getCurrentStateFunc);
byte db2 = getTsp(getCurrentStateFunc);
byte db1 = (byte) (0x00 | getMc(getCurrentStateFunc) | getWuc(getCurrentStateFunc));
@Override
protected State convertToStateImpl(String channelId, String channelTypeId,
Function<String, State> getCurrentStateFunc, Configuration config) {
- if (!isValid()) {
- return UnDefType.UNDEF;
- }
switch (channelId) {
case CHANNEL_GENERAL_SWITCHING:
return UnDefType.UNDEF;
}
+
+ @Override
+ public boolean isValidForTeachIn() {
+ return false;
+ }
}
public static final byte ResponseNeeded_MASK = 0x40;
public static final byte TeachIn_NotSpecified = 0x20;
- public UTEResponse(ERP1Message packet) {
+ public UTEResponse(ERP1Message packet, boolean teachIn) {
int dataLength = packet.getPayload().length - ESP3_SENDERID_LENGTH - ESP3_RORG_LENGTH - ESP3_STATUS_LENGTH;
setData(packet.getPayload(ESP3_RORG_LENGTH, dataLength));
- bytes[0] = (byte) 0x91; // bidirectional communication, teach in accepted, teach in response
+ bytes[0] = (byte) (teachIn ? 0x91 : 0xA1); // bidirectional communication, teach in accepted or teach out, teach
+ // in response
setStatus((byte) 0x80);
setSuppressRepeating(true);
*/
public class _4BSTeachInVariation3Response extends _4BSMessage {
- public _4BSTeachInVariation3Response(ERP1Message packet) {
+ public _4BSTeachInVariation3Response(ERP1Message packet, boolean teachIn) {
byte[] payload = packet.getPayload(ESP3_RORG_LENGTH, RORG._4BS.getDataLength());
- payload[3] = (byte) 0xF0; // telegram with EEP number and Manufacturer ID,
- // EEP supported, Sender ID stored, Response
+ payload[3] = (byte) (teachIn ? 0xF0 : 0xD0); // telegram with EEP number and Manufacturer ID,
+ // EEP supported, Sender ID stored or deleted, Response
setData(payload);
setDestinationId(packet.getSenderId());
return this;
}
+
+ public abstract boolean isValidForTeachIn();
}
@Override
public void addConfigPropertiesTo(DiscoveryResultBuilder discoveredThingResultBuilder) {
- discoveredThingResultBuilder.withProperty(PARAMETER_EEPID, getEEPType().getId());
+ discoveredThingResultBuilder.withProperty(PARAMETER_SENDINGEEPID, getEEPType().getId())
+ .withProperty(PARAMETER_RECEIVINGEEPID, getEEPType().getId());
}
@Override
import static org.openhab.binding.enocean.internal.messages.ESP3Packet.*;
import java.lang.reflect.InvocationTargetException;
+import java.util.Arrays;
import org.openhab.binding.enocean.internal.eep.Base.UTEResponse;
import org.openhab.binding.enocean.internal.eep.Base._4BSMessage;
import org.openhab.binding.enocean.internal.eep.Base._4BSTeachInVariation3Response;
+import org.openhab.binding.enocean.internal.eep.Base._RPSMessage;
import org.openhab.binding.enocean.internal.eep.D5_00.D5_00_01;
import org.openhab.binding.enocean.internal.eep.F6_01.F6_01_01;
import org.openhab.binding.enocean.internal.eep.F6_02.F6_02_01;
+import org.openhab.binding.enocean.internal.eep.F6_05.F6_05_02;
import org.openhab.binding.enocean.internal.eep.F6_10.F6_10_00;
import org.openhab.binding.enocean.internal.eep.F6_10.F6_10_00_EltakoFPE;
import org.openhab.binding.enocean.internal.eep.F6_10.F6_10_01;
import org.openhab.binding.enocean.internal.messages.ERP1Message;
import org.openhab.binding.enocean.internal.messages.ERP1Message.RORG;
+import org.openhab.binding.enocean.internal.messages.EventMessage;
+import org.openhab.binding.enocean.internal.messages.EventMessage.EventMessageType;
+import org.openhab.binding.enocean.internal.messages.Responses.SMACKTeachInResponse;
import org.openhab.core.util.HexUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
if (cl == null) {
throw new IllegalArgumentException("Message " + eepType + " not implemented");
}
- return cl.newInstance();
- } catch (IllegalAccessException | InstantiationException e) {
+ return cl.getDeclaredConstructor().newInstance();
+ } catch (IllegalAccessException | InstantiationException | IllegalArgumentException | InvocationTargetException
+ | NoSuchMethodException | SecurityException e) {
throw new IllegalArgumentException(e);
}
}
}
}
+ private static EEPType getGenericEEPTypeFor(byte rorg) {
+ logger.info("Received unsupported EEP teach in, trying to fallback to generic thing");
+ RORG r = RORG.getRORG(rorg);
+ if (r == RORG._4BS) {
+ logger.info("Fallback to 4BS generic thing");
+ return EEPType.Generic4BS;
+ } else if (r == RORG.VLD) {
+ logger.info("Fallback to VLD generic thing");
+ return EEPType.GenericVLD;
+ } else {
+ logger.info("Fallback not possible");
+ return null;
+ }
+ }
+
public static EEP buildEEPFromTeachInERP1(ERP1Message msg) {
if (!msg.getIsTeachIn() && !(msg.getRORG() == RORG.RPS)) {
return null;
switch (msg.getRORG()) {
case RPS:
try {
- EEP result = new F6_01_01(msg);
- if (result.isValid()) { // check if t21 is set, nu not set, and data == 0x10 or 0x00
+ _RPSMessage result = new F6_10_00(msg);
+ if (result.isValidForTeachIn()) {
+ return result;
+ }
+ } catch (Exception e) {
+ }
+
+ try {
+ _RPSMessage result = new F6_10_01(msg);
+ if (result.isValidForTeachIn()) {
return result;
}
} catch (Exception e) {
}
try {
- EEP result = new F6_02_01(msg);
- if (result.isValid()) { // check if highest bit is not set
+ _RPSMessage result = new F6_02_01(msg);
+ if (result.isValidForTeachIn()) {
return result;
}
} catch (Exception e) {
}
try {
- EEP result = new F6_10_00(msg);
- if (result.isValid()) {
+ _RPSMessage result = new F6_05_02(msg);
+ if (result.isValidForTeachIn()) {
return result;
}
} catch (Exception e) {
}
+
try {
- EEP result = new F6_10_00_EltakoFPE(msg);
- if (result.isValid()) { // check if data == 0x10 or 0x00
+ _RPSMessage result = new F6_01_01(msg);
+ if (result.isValidForTeachIn()) {
return result;
}
} catch (Exception e) {
}
+
try {
- EEP result = new F6_10_01(msg);
- if (result.isValid()) {
+ _RPSMessage result = new F6_10_00_EltakoFPE(msg);
+ if (result.isValidForTeachIn()) {
return result;
}
} catch (Exception e) {
case _4BS: {
int db_0 = msg.getPayload()[4];
if ((db_0 & _4BSMessage.LRN_Type_Mask) == 0) { // Variation 1
- logger.info("Received 4BS Teach In variation 1 without EEP");
- return null;
+ logger.info("Received 4BS Teach In variation 1 without EEP, fallback to generic thing");
+ return buildEEP(EEPType.Generic4BS, msg);
}
byte db_3 = msg.getPayload()[1];
int type = ((db_3 & 0b11) << 5) + ((db_2 & 0xFF) >>> 3);
int manufId = ((db_2 & 0b111) << 8) + (db_1 & 0xff);
- logger.info("Received 4BS Teach In with EEP A5-{}-{} and manufacturerID {}",
+ logger.debug("Received 4BS Teach In with EEP A5-{}-{} and manufacturerID {}",
HexUtils.bytesToHex(new byte[] { (byte) func }),
HexUtils.bytesToHex(new byte[] { (byte) type }),
HexUtils.bytesToHex(new byte[] { (byte) manufId }));
EEPType eepType = EEPType.getType(RORG._4BS, func, type, manufId);
if (eepType == null) {
- logger.debug("Received unsupported EEP teach in, fallback to generic thing");
- eepType = EEPType.Generic4BS;
+ eepType = getGenericEEPTypeFor(RORG._4BS.getValue());
}
- return buildEEP(eepType, msg);
+ if (eepType != null) {
+ return buildEEP(eepType, msg);
+ }
}
+ break;
case UTE: {
byte[] payload = msg.getPayload();
EEPType eepType = EEPType.getType(RORG.getRORG(rorg), func, type, manufId);
if (eepType == null) {
- logger.info("Received unsupported EEP teach in, fallback to generic thing");
- RORG r = RORG.getRORG(rorg);
- if (r == RORG._4BS) {
- eepType = EEPType.Generic4BS;
- } else if (r == RORG.VLD) {
- eepType = EEPType.GenericVLD;
- } else {
- return null;
- }
+ eepType = getGenericEEPTypeFor(rorg);
}
- return buildEEP(eepType, msg);
+ if (eepType != null) {
+ return buildEEP(eepType, msg);
+ }
}
- case Unknown:
- case VLD:
- case MSC:
- case SIG:
+ break;
+ default:
return null;
}
return null;
}
- public static EEP buildResponseEEPFromTeachInERP1(ERP1Message msg, byte[] senderId) {
+ public static EEP buildEEPFromTeachInSMACKEvent(EventMessage event) {
+ if (event.getEventMessageType() != EventMessageType.SA_CONFIRM_LEARN) {
+ return null;
+ }
+
+ byte[] payload = event.getPayload();
+ byte manufIdMSB = payload[2];
+ byte manufIdLSB = payload[3];
+ int manufId = ((manufIdMSB & 0b111) << 8) + (manufIdLSB & 0xff);
+
+ byte rorg = payload[4];
+ int func = payload[5] & 0xFF;
+ int type = payload[6] & 0xFF;
+
+ byte[] senderId = Arrays.copyOfRange(payload, 12, 12 + 4);
+
+ logger.debug("Received SMACK Teach In with EEP {}-{}-{} and manufacturerID {}",
+ HexUtils.bytesToHex(new byte[] { (byte) rorg }), HexUtils.bytesToHex(new byte[] { (byte) func }),
+ HexUtils.bytesToHex(new byte[] { (byte) type }), HexUtils.bytesToHex(new byte[] { (byte) manufId }));
+
+ EEPType eepType = EEPType.getType(RORG.getRORG(rorg), func, type, manufId);
+ if (eepType == null) {
+ eepType = getGenericEEPTypeFor(rorg);
+ }
+
+ return createEEP(eepType).setSenderId(senderId);
+ }
+
+ public static EEP buildResponseEEPFromTeachInERP1(ERP1Message msg, byte[] senderId, boolean teachIn) {
switch (msg.getRORG()) {
case UTE:
- EEP result = new UTEResponse(msg);
+ EEP result = new UTEResponse(msg, teachIn);
result.setSenderId(senderId);
return result;
case _4BS:
- result = new _4BSTeachInVariation3Response(msg);
+ result = new _4BSTeachInVariation3Response(msg, teachIn);
result.setSenderId(senderId);
return result;
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;
+ }
}
import org.eclipse.jdt.annotation.NonNull;
import org.openhab.binding.enocean.internal.EnOceanChannelDescription;
+import org.openhab.binding.enocean.internal.config.EnOceanChannelTransformationConfig;
import org.openhab.binding.enocean.internal.eep.A5_02.A5_02_01;
import org.openhab.binding.enocean.internal.eep.A5_02.A5_02_02;
import org.openhab.binding.enocean.internal.eep.A5_02.A5_02_03;
UTEResponse(RORG.UTE, 0, 0, false, UTEResponse.class, null),
_4BSTeachInVariation3Response(RORG._4BS, 0, 0, false, _4BSTeachInVariation3Response.class, null),
- GenericRPS(RORG.RPS, 0xFF, 0xFF, false, GenericRPS.class, THING_TYPE_GENERICTHING, CHANNEL_GENERIC_SWITCH,
- CHANNEL_GENERIC_ROLLERSHUTTER, CHANNEL_GENERIC_DIMMER, CHANNEL_GENERIC_NUMBER, CHANNEL_GENERIC_STRING,
- CHANNEL_GENERIC_COLOR, CHANNEL_GENERIC_TEACHINCMD),
- Generic4BS(RORG._4BS, 0xFF, 0xFF, false, Generic4BS.class, THING_TYPE_GENERICTHING, CHANNEL_GENERIC_SWITCH,
- CHANNEL_GENERIC_ROLLERSHUTTER, CHANNEL_GENERIC_DIMMER, CHANNEL_GENERIC_NUMBER, CHANNEL_GENERIC_STRING,
- CHANNEL_GENERIC_COLOR, CHANNEL_GENERIC_TEACHINCMD, CHANNEL_VIBRATION),
- GenericVLD(RORG.VLD, 0xFF, 0xFF, false, GenericVLD.class, THING_TYPE_GENERICTHING, CHANNEL_GENERIC_SWITCH,
- CHANNEL_GENERIC_ROLLERSHUTTER, CHANNEL_GENERIC_DIMMER, CHANNEL_GENERIC_NUMBER, CHANNEL_GENERIC_STRING,
- CHANNEL_GENERIC_COLOR, CHANNEL_GENERIC_TEACHINCMD),
+ GenericRPS(RORG.RPS, 0xFF, 0xFF, false, GenericRPS.class, THING_TYPE_GENERICTHING),
+ Generic4BS(RORG._4BS, 0xFF, 0xFF, false, Generic4BS.class, THING_TYPE_GENERICTHING, CHANNEL_VIBRATION),
+ GenericVLD(RORG.VLD, 0xFF, 0xFF, false, GenericVLD.class, THING_TYPE_GENERICTHING),
PTM200(RORG.RPS, 0x00, 0x00, false, PTM200Message.class, null, CHANNEL_GENERAL_SWITCHING, CHANNEL_ROLLERSHUTTER,
CHANNEL_CONTACT),
// UniversalCommand(RORG._4BS, 0x3f, 0x7f, false, A5_3F_7F_Universal.class, THING_TYPE_UNIVERSALACTUATOR,
// CHANNEL_GENERIC_ROLLERSHUTTER, CHANNEL_GENERIC_LIGHT_SWITCHING, CHANNEL_GENERIC_DIMMER, CHANNEL_TEACHINCMD),
- EltakoFSB(RORG._4BS, 0x3f, 0x7f, false, "EltakoFSB", 0, A5_3F_7F_EltakoFSB.class, THING_TYPE_ROLLERSHUTTER, 0,
- new Hashtable<String, Configuration>() {
+ EltakoFSB(RORG._4BS, 0x3f, 0x7f, false, false, "EltakoFSB", 0, A5_3F_7F_EltakoFSB.class, THING_TYPE_ROLLERSHUTTER,
+ 0, new Hashtable<String, Configuration>() {
private static final long serialVersionUID = 1L;
{
put(CHANNEL_ROLLERSHUTTER, new Configuration());
}
}),
- Thermostat(RORG._4BS, 0x20, 0x04, false, A5_20_04.class, THING_TYPE_THERMOSTAT, CHANNEL_VALVE_POSITION,
+ Thermostat(RORG._4BS, 0x20, 0x04, false, true, A5_20_04.class, THING_TYPE_THERMOSTAT, CHANNEL_VALVE_POSITION,
CHANNEL_BUTTON_LOCK, CHANNEL_DISPLAY_ORIENTATION, CHANNEL_TEMPERATURE_SETPOINT, CHANNEL_TEMPERATURE,
CHANNEL_FEED_TEMPERATURE, CHANNEL_MEASUREMENT_CONTROL, CHANNEL_FAILURE_CODE, CHANNEL_WAKEUPCYCLE,
- CHANNEL_SERVICECOMMAND, CHANNEL_STATUS_REQUEST_EVENT, CHANNEL_SEND_COMMAND),
+ CHANNEL_SERVICECOMMAND),
SwitchWithEnergyMeasurment_00(RORG.VLD, 0x01, 0x00, true, D2_01_00.class, THING_TYPE_MEASUREMENTSWITCH,
CHANNEL_GENERAL_SWITCHING, CHANNEL_TOTALUSAGE),
private boolean supportsRefresh;
+ private boolean requestsResponse;
+
EEPType(RORG rorg, int func, int type, boolean supportsRefresh, Class<? extends EEP> eepClass,
ThingTypeUID thingTypeUID, String... channelIds) {
this(rorg, func, type, supportsRefresh, eepClass, thingTypeUID, -1, channelIds);
}
+ EEPType(RORG rorg, int func, int type, boolean supportsRefresh, boolean requestsResponse,
+ Class<? extends EEP> eepClass, ThingTypeUID thingTypeUID, String... channelIds) {
+ this(rorg, func, type, supportsRefresh, requestsResponse, eepClass, thingTypeUID, -1, channelIds);
+ }
+
EEPType(RORG rorg, int func, int type, boolean supportsRefresh, String manufactorSuffix, int manufId,
Class<? extends EEP> eepClass, ThingTypeUID thingTypeUID, String... channelIds) {
- this(rorg, func, type, supportsRefresh, manufactorSuffix, manufId, eepClass, thingTypeUID, 0, channelIds);
+ this(rorg, func, type, supportsRefresh, false, manufactorSuffix, manufId, eepClass, thingTypeUID, 0,
+ channelIds);
}
EEPType(RORG rorg, int func, int type, boolean supportsRefresh, Class<? extends EEP> eepClass,
ThingTypeUID thingTypeUID, int command, String... channelIds) {
- this(rorg, func, type, supportsRefresh, "", 0, eepClass, thingTypeUID, command, channelIds);
+ this(rorg, func, type, supportsRefresh, false, "", 0, eepClass, thingTypeUID, command, channelIds);
}
- EEPType(RORG rorg, int func, int type, boolean supportsRefresh, String manufactorSuffix, int manufId,
+ EEPType(RORG rorg, int func, int type, boolean supportsRefresh, boolean requestsResponse,
Class<? extends EEP> eepClass, ThingTypeUID thingTypeUID, int command, String... channelIds) {
+ this(rorg, func, type, supportsRefresh, requestsResponse, "", 0, eepClass, thingTypeUID, command, channelIds);
+ }
+
+ EEPType(RORG rorg, int func, int type, boolean supportsRefresh, boolean requestsResponse, String manufactorSuffix,
+ int manufId, Class<? extends EEP> eepClass, ThingTypeUID thingTypeUID, int command, String... channelIds) {
this.rorg = rorg;
this.func = func;
this.type = type;
this.manufactorSuffix = manufactorSuffix;
this.manufactorId = manufId;
this.supportsRefresh = supportsRefresh;
+ this.requestsResponse = requestsResponse;
for (String id : channelIds) {
this.channelIdsWithConfig.put(id, new Configuration());
this.supportedChannels.put(id, CHANNELID2CHANNELDESCRIPTION.get(id));
}
- this.channelIdsWithConfig.put(CHANNEL_RSSI, new Configuration());
- this.supportedChannels.put(CHANNEL_RSSI, CHANNELID2CHANNELDESCRIPTION.get(CHANNEL_RSSI));
-
- this.channelIdsWithConfig.put(CHANNEL_REPEATCOUNT, new Configuration());
- this.supportedChannels.put(CHANNEL_REPEATCOUNT, CHANNELID2CHANNELDESCRIPTION.get(CHANNEL_REPEATCOUNT));
-
- this.channelIdsWithConfig.put(CHANNEL_LASTRECEIVED, new Configuration());
- this.supportedChannels.put(CHANNEL_LASTRECEIVED, CHANNELID2CHANNELDESCRIPTION.get(CHANNEL_LASTRECEIVED));
+ addDefaultChannels();
}
- EEPType(RORG rorg, int func, int type, boolean supportsRefresh, String manufactorSuffix, int manufId,
- Class<? extends EEP> eepClass, ThingTypeUID thingTypeUID, int command,
+ EEPType(RORG rorg, int func, int type, boolean supportsRefresh, boolean requestsResponse, String manufactorSuffix,
+ int manufId, Class<? extends EEP> eepClass, ThingTypeUID thingTypeUID, int command,
Hashtable<String, Configuration> channelConfigs) {
this.rorg = rorg;
this.func = func;
this.manufactorSuffix = manufactorSuffix;
this.manufactorId = manufId;
this.supportsRefresh = supportsRefresh;
+ this.requestsResponse = requestsResponse;
for (String id : channelConfigs.keySet()) {
this.supportedChannels.put(id, CHANNELID2CHANNELDESCRIPTION.get(id));
}
+ addDefaultChannels();
+ }
+
+ private void addDefaultChannels() {
+
+ if (THING_TYPE_GENERICTHING.equals(this.thingTypeUID)) {
+ this.channelIdsWithConfig.put(CHANNEL_GENERIC_SWITCH, new EnOceanChannelTransformationConfig());
+ this.supportedChannels.put(CHANNEL_GENERIC_SWITCH,
+ CHANNELID2CHANNELDESCRIPTION.get(CHANNEL_GENERIC_SWITCH));
+
+ this.channelIdsWithConfig.put(CHANNEL_GENERIC_ROLLERSHUTTER, new EnOceanChannelTransformationConfig());
+ this.supportedChannels.put(CHANNEL_GENERIC_ROLLERSHUTTER,
+ CHANNELID2CHANNELDESCRIPTION.get(CHANNEL_GENERIC_ROLLERSHUTTER));
+
+ this.channelIdsWithConfig.put(CHANNEL_GENERIC_DIMMER, new EnOceanChannelTransformationConfig());
+ this.supportedChannels.put(CHANNEL_GENERIC_DIMMER,
+ CHANNELID2CHANNELDESCRIPTION.get(CHANNEL_GENERIC_DIMMER));
+
+ this.channelIdsWithConfig.put(CHANNEL_GENERIC_NUMBER, new EnOceanChannelTransformationConfig());
+ this.supportedChannels.put(CHANNEL_GENERIC_NUMBER,
+ CHANNELID2CHANNELDESCRIPTION.get(CHANNEL_GENERIC_NUMBER));
+
+ this.channelIdsWithConfig.put(CHANNEL_GENERIC_STRING, new EnOceanChannelTransformationConfig());
+ this.supportedChannels.put(CHANNEL_GENERIC_STRING,
+ CHANNELID2CHANNELDESCRIPTION.get(CHANNEL_GENERIC_STRING));
+
+ this.channelIdsWithConfig.put(CHANNEL_GENERIC_COLOR, new EnOceanChannelTransformationConfig());
+ this.supportedChannels.put(CHANNEL_GENERIC_COLOR, CHANNELID2CHANNELDESCRIPTION.get(CHANNEL_GENERIC_COLOR));
+
+ this.channelIdsWithConfig.put(CHANNEL_GENERIC_TEACHINCMD, new EnOceanChannelTransformationConfig());
+ this.supportedChannels.put(CHANNEL_GENERIC_TEACHINCMD,
+ CHANNELID2CHANNELDESCRIPTION.get(CHANNEL_GENERIC_TEACHINCMD));
+ }
+
this.channelIdsWithConfig.put(CHANNEL_RSSI, new Configuration());
this.supportedChannels.put(CHANNEL_RSSI, CHANNELID2CHANNELDESCRIPTION.get(CHANNEL_RSSI));
this.channelIdsWithConfig.put(CHANNEL_LASTRECEIVED, new Configuration());
this.supportedChannels.put(CHANNEL_LASTRECEIVED, CHANNELID2CHANNELDESCRIPTION.get(CHANNEL_LASTRECEIVED));
+
+ if (requestsResponse) {
+ this.channelIdsWithConfig.put(CHANNEL_STATUS_REQUEST_EVENT, new Configuration());
+ this.supportedChannels.put(CHANNEL_STATUS_REQUEST_EVENT,
+ CHANNELID2CHANNELDESCRIPTION.get(CHANNEL_STATUS_REQUEST_EVENT));
+ }
}
public Class<? extends EEP> getEEPClass() {
return supportsRefresh;
}
+ public boolean getRequstesResponse() {
+ return requestsResponse;
+ }
+
public Map<String, EnOceanChannelDescription> GetSupportedChannels() {
return Collections.unmodifiableMap(supportedChannels);
}
}
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));
}
@Override
protected String convertToEventImpl(String channelId, String channelTypeId, String lastEvent,
Configuration config) {
- if (!isValid()) {
- return null;
- }
return getBit(bytes[0], 4) ? CommonTriggerEvents.PRESSED : CommonTriggerEvents.RELEASED;
}
protected boolean validateData(byte[] bytes) {
return super.validateData(bytes) && !getBit(bytes[0], 7);
}
+
+ @Override
+ public boolean isValidForTeachIn() {
+ // just treat press as teach in, ignore release
+ return t21 && !nu && bytes[0] == 0x10;
+ }
}
@Override
protected String convertToEventImpl(String channelId, String channelTypeId, String lastEvent,
Configuration config) {
- if (!isValid()) {
- return null;
- }
if (t21 && nu) {
byte dir1 = channelId.equals(CHANNEL_ROCKERSWITCH_CHANNELA) ? A0 : B0;
// this method is used by the classic device listener channels to convert an rocker switch message into an
// appropriate item update
State currentState = getCurrentStateFunc.apply(channelId);
-
- if (!isValid()) {
- return UnDefType.UNDEF;
- }
-
if (t21 && nu) {
EnOceanChannelVirtualRockerSwitchConfig c = config.as(EnOceanChannelVirtualRockerSwitchConfig.class);
byte dir1 = c.getChannel() == Channel.ChannelA ? A0 : B0;
protected boolean validateData(byte[] bytes) {
return super.validateData(bytes) && !getBit(bytes[0], 7);
}
+
+ @Override
+ public boolean isValidForTeachIn() {
+ if (t21) {
+ // just treat press as teach in => DB0.4 has to be set
+ if (!getBit(bytes[0], 4)) {
+ return false;
+ }
+ // DB0.7 is never set for rocker switch message
+ if (getBit(bytes[0], 7)) {
+ return false;
+ }
+ } else {
+ return false;
+ }
+
+ return true;
+ }
}
@Override
protected String convertToEventImpl(String channelId, String channelTypeId, String lastEvent,
Configuration config) {
- if (!isValid()) {
- return null;
- }
if (t21 && nu) {
byte dir1 = channelId.equals(CHANNEL_ROCKERSWITCH_CHANNELA) ? AI : BI;
// this method is used by the classic device listener channels to convert an rocker switch message into an
// appropriate item update
State currentState = getCurrentStateFunc.apply(channelId);
-
- if (!isValid()) {
- return UnDefType.UNDEF;
- }
-
if (t21 && nu) {
EnOceanChannelVirtualRockerSwitchConfig c = config.as(EnOceanChannelVirtualRockerSwitchConfig.class);
byte dir1 = c.getChannel() == Channel.ChannelA ? AI : BI;
private State inverse(UpDownType currentState) {
return currentState == UpDownType.UP ? UpDownType.DOWN : UpDownType.UP;
}
+
+ @Override
+ public boolean isValidForTeachIn() {
+ return false; // Never treat a message as F6-02-02, let user decide which orientation of rocker switch is used
+ }
}
@Override
protected State convertToStateImpl(String channelId, String channelTypeId,
Function<String, State> getCurrentStateFunc, Configuration config) {
- if (!isValid()) {
- return UnDefType.UNDEF;
- }
switch (channelId) {
case CHANNEL_SMOKEDETECTION:
protected boolean validateData(byte[] bytes) {
return super.validateData(bytes) && (bytes[0] == ALARM_OFF || bytes[0] == ALARM_ON || bytes[0] == ENERGY_LOW);
}
+
+ @Override
+ public boolean isValidForTeachIn() {
+ // just treat the first message with ALARM_ON as teach in
+ return !t21 && !nu && bytes[0] == ALARM_ON;
+ }
}
@Override
protected State convertToStateImpl(String channelId, String channelTypeId,
Function<String, State> getCurrentStateFunc, Configuration config) {
- if (!isValid()) {
- return UnDefType.UNDEF;
- }
byte data = (byte) (bytes[0] & 0xF0);
protected boolean validateData(byte[] bytes) {
return super.validateData(bytes) && getBit(bytes[0], 7) && getBit(bytes[0], 6);
}
+
+ @Override
+ public boolean isValidForTeachIn() {
+ return t21 && !nu && getBit(bytes[0], 7) && getBit(bytes[0], 6);
+ }
}
// FPE just sends 0b00010000 or 0b00000000 value, so we apply mask 0b11101111
return super.validateData(bytes) && ((bytes[0] & (byte) 0xEF) == (byte) 0x00);
}
+
+ @Override
+ public boolean isValidForTeachIn() {
+ // just treat CLOSED as teach in
+ return bytes[0] == CLOSED;
+ }
}
@Override
protected State convertToStateImpl(String channelId, String channelTypeId,
Function<String, State> getCurrentStateFunc, Configuration config) {
- if (!isValid()) {
- return UnDefType.UNDEF;
- }
byte data = (byte) (bytes[0] & 0x0F);
protected boolean validateData(byte[] bytes) {
return super.validateData(bytes) && getBit(bytes[0], 6) && getBit(bytes[0], 3) && getBit(bytes[0], 2);
}
+
+ @Override
+ public boolean isValidForTeachIn() {
+ return !getBit(bytes[0], 7) && getBit(bytes[0], 6) && !getBit(bytes[0], 5) && !getBit(bytes[0], 4)
+ && getBit(bytes[0], 3) && getBit(bytes[0], 2);
+ }
}
*/
package org.openhab.binding.enocean.internal.eep.Generic;
-import static org.openhab.binding.enocean.internal.EnOceanBindingConstants.PARAMETER_EEPID;
+import static org.openhab.binding.enocean.internal.EnOceanBindingConstants.*;
import static org.openhab.binding.enocean.internal.messages.ESP3Packet.*;
import java.lang.reflect.InvocationTargetException;
@Override
public void addConfigPropertiesTo(DiscoveryResultBuilder discoveredThingResultBuilder) {
- discoveredThingResultBuilder.withProperty(PARAMETER_EEPID, getEEPType().getId());
+ discoveredThingResultBuilder.withProperty(PARAMETER_SENDINGEEPID, getEEPType().getId())
+ .withProperty(PARAMETER_RECEIVINGEEPID, getEEPType().getId());
}
}
import org.openhab.binding.enocean.internal.eep.EEPType;
import org.openhab.binding.enocean.internal.messages.BasePacket;
import org.openhab.core.config.core.Configuration;
+import org.openhab.core.library.types.OnOffType;
import org.openhab.core.thing.Channel;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
* @param senderIdOffset to be validated
* @return true if senderIdOffset is between ]0;128[ and is not used yet
*/
- private boolean validateSenderIdOffset(int senderIdOffset) {
- if (senderIdOffset == -1) {
+ private boolean validateSenderIdOffset(Integer senderIdOffset) {
+ if (senderIdOffset == null) {
return true;
}
}
private boolean initializeIdForSending() {
- // Generic things are treated as actuator things, however to support also generic sensors one can define a
- // senderIdOffset of -1
- // TODO: seperate generic actuators from generic sensors?
- String thingTypeId = this.getThing().getThingTypeUID().getId();
- String genericThingTypeId = THING_TYPE_GENERICTHING.getId();
-
- if (getConfiguration().senderIdOffset == -1 && thingTypeId.equals(genericThingTypeId)) {
- return true;
- }
-
EnOceanBridgeHandler bridgeHandler = getBridgeHandler();
if (bridgeHandler == null) {
return false;
}
- // if senderIdOffset is not set (=> defaults to -1) or set to -1, the next free senderIdOffset is determined
- if (getConfiguration().senderIdOffset == -1) {
+ // Generic things are treated as actuator things, however to support also generic sensors one can omit
+ // senderIdOffset
+ // TODO: seperate generic actuators from generic sensors?
+ if ((getConfiguration().senderIdOffset == null
+ && THING_TYPE_GENERICTHING.equals(this.getThing().getThingTypeUID()))) {
+ return true;
+ }
+
+ // if senderIdOffset is not set, the next free senderIdOffset is determined
+ if (getConfiguration().senderIdOffset == null) {
Configuration updateConfig = editConfiguration();
getConfiguration().senderIdOffset = bridgeHandler.getNextSenderId(thing);
- if (getConfiguration().senderIdOffset == -1) {
+ if (getConfiguration().senderIdOffset == null) {
configurationErrorDescription = "Could not get a free sender Id from Bridge";
return false;
}
}
byte[] baseId = bridgeHandler.getBaseId();
- baseId[3] = (byte) ((baseId[3] & 0xFF) + getConfiguration().senderIdOffset);
+ baseId[3] = (byte) ((baseId[3] + getConfiguration().senderIdOffset) & 0xFF);
this.senderId = baseId;
-
- this.updateProperty(PROPERTY_ENOCEAN_ID, HexUtils.bytesToHex(this.senderId));
+ this.updateProperty(PROPERTY_SENDINGENOCEAN_ID, HexUtils.bytesToHex(this.senderId));
bridgeHandler.addSender(getConfiguration().senderIdOffset, thing);
-
return true;
}
}
}
+ @Override
+ protected void sendRequestResponse() {
+ sendMessage(VIRTUALCHANNEL_SEND_COMMAND, VIRTUALCHANNEL_SEND_COMMAND, OnOffType.ON, null);
+ }
+
+ protected void sendMessage(String channelId, String channelTypeId, Command command, Configuration channelConfig) {
+ EEP eep = EEPFactory.createEEP(sendingEEPType);
+ if (eep.convertFromCommand(channelId, channelTypeId, command, id -> getCurrentState(id), channelConfig)
+ .hasData()) {
+ BasePacket msg = eep.setSenderId(senderId).setDestinationId(destinationId)
+ .setSuppressRepeating(getConfiguration().suppressRepeating).getERP1Message();
+
+ getBridgeHandler().sendMessage(msg, null);
+ }
+ }
+
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
// We must have a valid sendingEEPType and sender id to send commands
try {
Configuration channelConfig = channel.getConfiguration();
-
- EEP eep = EEPFactory.createEEP(sendingEEPType);
- if (eep.convertFromCommand(channelId, channelTypeId, command, id -> getCurrentState(id), channelConfig)
- .hasData()) {
- BasePacket msg = eep.setSenderId(senderId).setDestinationId(destinationId)
- .setSuppressRepeating(getConfiguration().suppressRepeating).getERP1Message();
-
- getBridgeHandler().sendMessage(msg, null);
- }
-
+ sendMessage(channelId, channelTypeId, command, channelConfig);
} catch (IllegalArgumentException e) {
logger.warn("Exception while sending telegram!", e);
}
@Override
public void handleRemoval() {
- if (getConfiguration().senderIdOffset > 0) {
- EnOceanBridgeHandler bridgeHandler = getBridgeHandler();
- if (bridgeHandler != null) {
+
+ EnOceanBridgeHandler bridgeHandler = getBridgeHandler();
+ if (bridgeHandler != null) {
+ if (getConfiguration().senderIdOffset != null && getConfiguration().senderIdOffset > 0) {
bridgeHandler.removeSender(getConfiguration().senderIdOffset);
}
+
+ if (bridgeHandler.isSmackClient(this.thing)) {
+ logger.warn("Removing smack client (ThingId: {}) without teach out!", this.thing.getUID().getId());
+ }
}
super.handleRemoval();
import java.util.Comparator;
import java.util.Hashtable;
import java.util.Set;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;
+import org.apache.commons.lang3.NotImplementedException;
import org.openhab.binding.enocean.internal.config.EnOceanBaseConfig;
import org.openhab.binding.enocean.internal.eep.EEP;
import org.openhab.binding.enocean.internal.eep.EEPFactory;
protected final Hashtable<RORG, EEPType> receivingEEPTypes = new Hashtable<>();
+ protected ScheduledFuture<?> responseFuture = null;
+
public EnOceanBaseSensorHandler(Thing thing, ItemChannelLinkRegistry itemChannelLinkRegistry) {
super(thing, itemChannelLinkRegistry);
}
}
@Override
- public long getSenderIdToListenTo() {
+ public long getEnOceanIdToListenTo() {
return Long.parseLong(config.enoceanId, 16);
}
};
}
+ protected void sendRequestResponse() {
+ throw new NotImplementedException("Sensor cannot send responses");
+ }
+
@Override
public void packetReceived(BasePacket packet) {
ERP1Message msg = (ERP1Message) packet;
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);
+ }
+ }
}
}
}
*/
package org.openhab.binding.enocean.internal.handler;
+import static org.openhab.binding.enocean.internal.EnOceanBindingConstants.*;
+
import java.util.AbstractMap.SimpleEntry;
import java.util.Collection;
import java.util.Hashtable;
private ItemChannelLinkRegistry itemChannelLinkRegistry;
+ protected @NonNull ChannelUID prepareAnswer;
+
public EnOceanBaseThingHandler(Thing thing, ItemChannelLinkRegistry itemChannelLinkRegistry) {
super(thing);
this.itemChannelLinkRegistry = itemChannelLinkRegistry;
+ prepareAnswer = new ChannelUID(thing.getUID(), CHANNEL_STATUS_REQUEST_EVENT);
}
- @SuppressWarnings("null")
@Override
public void initialize() {
logger.debug("Initializing enocean base thing handler.");
this.gateway = null; // reset gateway in case we change the bridge
this.config = null;
- initializeThing((getBridge() == null) ? null : getBridge().getStatus());
+ Bridge bridge = getBridge();
+ if (bridge == null) {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "A bridge is required");
+ } else {
+ initializeThing(bridge.getStatus());
+ }
}
private void initializeThing(ThingStatus bridgeStatus) {
String channelId = entry.getKey();
EnOceanChannelDescription cd = entry.getValue().GetSupportedChannels().get(channelId);
+ if (cd == null) {
+ return;
+ }
+
// if we do not need to auto create channel => skip
if (!cd.autoCreate) {
return;
import static org.openhab.binding.enocean.internal.EnOceanBindingConstants.*;
import java.io.IOException;
-import java.math.BigDecimal;
+import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedList;
import java.util.Set;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
import org.openhab.binding.enocean.internal.EnOceanConfigStatusMessage;
import org.openhab.binding.enocean.internal.config.EnOceanBaseConfig;
import org.openhab.binding.enocean.internal.config.EnOceanBridgeConfig;
+import org.openhab.binding.enocean.internal.config.EnOceanBridgeConfig.ESPVersion;
import org.openhab.binding.enocean.internal.messages.BasePacket;
-import org.openhab.binding.enocean.internal.messages.BaseResponse;
import org.openhab.binding.enocean.internal.messages.ESP3PacketFactory;
-import org.openhab.binding.enocean.internal.messages.RDBaseIdResponse;
-import org.openhab.binding.enocean.internal.messages.RDRepeaterResponse;
-import org.openhab.binding.enocean.internal.messages.RDVersionResponse;
import org.openhab.binding.enocean.internal.messages.Response;
import org.openhab.binding.enocean.internal.messages.Response.ResponseType;
+import org.openhab.binding.enocean.internal.messages.Responses.BaseResponse;
+import org.openhab.binding.enocean.internal.messages.Responses.RDBaseIdResponse;
+import org.openhab.binding.enocean.internal.messages.Responses.RDLearnedClientsResponse;
+import org.openhab.binding.enocean.internal.messages.Responses.RDLearnedClientsResponse.LearnedClient;
+import org.openhab.binding.enocean.internal.messages.Responses.RDRepeaterResponse;
+import org.openhab.binding.enocean.internal.messages.Responses.RDVersionResponse;
import org.openhab.binding.enocean.internal.transceiver.EnOceanESP2Transceiver;
import org.openhab.binding.enocean.internal.transceiver.EnOceanESP3Transceiver;
import org.openhab.binding.enocean.internal.transceiver.EnOceanTransceiver;
import org.openhab.binding.enocean.internal.transceiver.PacketListener;
import org.openhab.binding.enocean.internal.transceiver.ResponseListener;
import org.openhab.binding.enocean.internal.transceiver.ResponseListenerIgnoringTimeouts;
+import org.openhab.binding.enocean.internal.transceiver.TeachInListener;
import org.openhab.binding.enocean.internal.transceiver.TransceiverErrorListener;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.config.core.status.ConfigStatusMessage;
private byte[] baseId = null;
private Thing[] sendingThings = new Thing[128];
- private int nextSenderId = 0;
private SerialPortManager serialPortManager;
+ private boolean smackAvailable = false;
+ private boolean sendTeachOuts = true;
+ private Set<String> smackClients = Set.of();
+
public EnOceanBridgeHandler(Bridge bridge, SerialPortManager serialPortManager) {
super(bridge);
this.serialPortManager = serialPortManager;
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"SerialPortManager could not be found");
} else {
- Object devId = getConfig().get(NEXTSENDERID);
- if (devId != null) {
- nextSenderId = ((BigDecimal) devId).intValue();
- } else {
- nextSenderId = 0;
- }
-
if (connectorTask == null || connectorTask.isDone()) {
connectorTask = scheduler.scheduleWithFixedDelay(new Runnable() {
@Override
switch (c.getESPVersion()) {
case ESP2:
transceiver = new EnOceanESP2Transceiver(c.path, this, scheduler, serialPortManager);
+ smackAvailable = false;
+ sendTeachOuts = false;
break;
case ESP3:
transceiver = new EnOceanESP3Transceiver(c.path, this, scheduler, serialPortManager);
+ sendTeachOuts = c.sendTeachOuts;
break;
default:
break;
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_PENDING, "starting rx thread...");
transceiver.StartReceiving(scheduler);
+ logger.info("EnOceanSerialTransceiver RX thread up and running");
if (c.rs485) {
if (c.rs485BaseId != null && !c.rs485BaseId.isEmpty()) {
}
}
});
+
+ 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");
Collection<ConfigStatusMessage> configStatusMessages = new LinkedList<>();
// The serial port must be provided
- String path = (String) getThing().getConfiguration().get(PATH);
+ String path = getThing().getConfiguration().as(EnOceanBridgeConfig.class).path;
if (path == null || path.isEmpty()) {
configStatusMessages.add(ConfigStatusMessage.Builder.error(PATH)
.withMessageKeySuffix(EnOceanConfigStatusMessage.PORT_MISSING.getMessageKey()).withArguments(PATH)
return baseId.clone();
}
- public int getNextSenderId(Thing sender) {
- // TODO: change id to enoceanId
+ public boolean isSmackClient(Thing sender) {
+ return smackClients.contains(sender.getConfiguration().as(EnOceanBaseConfig.class).enoceanId);
+ }
+
+ public Integer getNextSenderId(Thing sender) {
return getNextSenderId(sender.getConfiguration().as(EnOceanBaseConfig.class).enoceanId);
}
- public int getNextSenderId(String senderId) {
- if (nextSenderId != 0 && sendingThings[nextSenderId] == null) {
- int result = nextSenderId;
- Configuration config = getConfig();
- config.put(NEXTSENDERID, null);
- updateConfiguration(config);
- nextSenderId = 0;
+ public Integer getNextSenderId(String enoceanId) {
+ EnOceanBridgeConfig config = getConfigAs(EnOceanBridgeConfig.class);
- return result;
+ if (config.nextSenderId != null && sendingThings[config.nextSenderId] == null) {
+ Configuration c = this.editConfiguration();
+ c.put(PARAMETER_NEXT_SENDERID, null);
+ updateConfiguration(c);
+
+ return config.nextSenderId;
}
- for (byte i = 1; i < sendingThings.length; i++) {
+ for (int i = 1; i < sendingThings.length; i++) {
if (sendingThings[i] == null || sendingThings[i].getConfiguration().as(EnOceanBaseConfig.class).enoceanId
- .equalsIgnoreCase(senderId)) {
+ .equalsIgnoreCase(enoceanId)) {
return i;
}
}
- return -1;
+ return null;
}
public boolean existsSender(int id, Thing sender) {
}
public void addPacketListener(PacketListener listener) {
- addPacketListener(listener, listener.getSenderIdToListenTo());
+ addPacketListener(listener, listener.getEnOceanIdToListenTo());
}
public void addPacketListener(PacketListener listener, long senderIdToListenTo) {
}
public void removePacketListener(PacketListener listener) {
- removePacketListener(listener, listener.getSenderIdToListenTo());
+ removePacketListener(listener, listener.getEnOceanIdToListenTo());
}
public void removePacketListener(PacketListener listener, long senderIdToListenTo) {
}
}
- public void startDiscovery(PacketListener teachInListener) {
+ public void startDiscovery(TeachInListener teachInListener) {
transceiver.startDiscovery(teachInListener);
+
+ if (smackAvailable) {
+ // activate smack teach in
+ logger.debug("activate smack teach in");
+ try {
+ transceiver.sendBasePacket(ESP3PacketFactory.SA_WR_LEARNMODE(true),
+ new ResponseListenerIgnoringTimeouts<BaseResponse>() {
+
+ @Override
+ public void responseReceived(BaseResponse response) {
+
+ if (response.isOK()) {
+ logger.debug("Smack teach in activated");
+ }
+ }
+ });
+ } catch (IOException e) {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
+ "Smack packet could not be send: " + e.getMessage());
+ }
+ }
}
public void stopDiscovery() {
transceiver.stopDiscovery();
+
+ try {
+ transceiver.sendBasePacket(ESP3PacketFactory.SA_WR_LEARNMODE(false), null);
+ refreshProperties();
+ } catch (IOException e) {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
+ "Smack packet could not be send: " + e.getMessage());
+ }
+ }
+
+ private void refreshProperties() {
+ if (getThing().getStatus() == ThingStatus.ONLINE && smackAvailable) {
+
+ logger.debug("request learned smack clients");
+ try {
+ transceiver.sendBasePacket(ESP3PacketFactory.SA_RD_LEARNEDCLIENTS,
+ new ResponseListenerIgnoringTimeouts<RDLearnedClientsResponse>() {
+
+ @Override
+ public void responseReceived(RDLearnedClientsResponse response) {
+
+ logger.debug("received response for learned smack clients");
+ if (response.isValid() && response.isOK()) {
+ LearnedClient[] clients = response.getLearnedClients();
+ updateProperty("Learned smart ack clients", Integer.toString(clients.length));
+ updateProperty("Smart ack clients",
+ Arrays.stream(clients)
+ .map(x -> String.format("%s (MB Idx: %d)",
+ HexUtils.bytesToHex(x.clientId), x.mailboxIndex))
+ .collect(Collectors.joining(", ")));
+ smackClients = Arrays.stream(clients).map(x -> HexUtils.bytesToHex(x.clientId))
+ .collect(Collectors.toSet());
+ }
+ }
+ });
+ } catch (IOException e) {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
+ "Smack packet could not be send: " + e.getMessage());
+
+ }
+ }
}
@Override
transceiver = null;
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, exception.getMessage());
}
+
+ public boolean sendTeachOuts() {
+ return sendTeachOuts;
+ }
}
}
@Override
- public long getSenderIdToListenTo() {
+ public long getEnOceanIdToListenTo() {
return 0;
}
+++ /dev/null
-/**
- * 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 org.openhab.binding.enocean.internal.Helper;
-
-/**
- *
- * @author Daniel Weber - Initial contribution
- */
-public class BaseResponse extends Response {
-
- public BaseResponse(Response response) {
- super(response.getPayload().length + response.getOptionalPayload().length, 0,
- Helper.concatAll(response.getPayload(), response.getOptionalPayload()));
- }
-}
package org.openhab.binding.enocean.internal.messages;
import org.openhab.binding.enocean.internal.EnOceanBindingConstants;
+import org.openhab.binding.enocean.internal.Helper;
import org.openhab.binding.enocean.internal.messages.BasePacket.ESPPacketType;
import org.openhab.binding.enocean.internal.messages.CCMessage.CCMessageType;
+import org.openhab.binding.enocean.internal.messages.SAMessage.SAMessageType;
import org.openhab.core.library.types.StringType;
/**
}
}
+ public static BasePacket SA_WR_LEARNMODE(boolean activate) {
+ return new SAMessage(SAMessageType.SA_WR_LEARNMODE,
+ new byte[] { SAMessageType.SA_WR_LEARNMODE.getValue(), (byte) (activate ? 1 : 0), 0, 0, 0, 0, 0 });
+ }
+
+ public final static BasePacket SA_RD_LEARNEDCLIENTS = new SAMessage(SAMessageType.SA_RD_LEARNEDCLIENTS);
+
+ public static BasePacket SA_RD_MAILBOX_STATUS(byte[] clientId, byte[] controllerId) {
+ return new SAMessage(SAMessageType.SA_RD_MAILBOX_STATUS,
+ Helper.concatAll(new byte[] { SAMessageType.SA_RD_MAILBOX_STATUS.getValue() }, clientId, controllerId));
+ }
+
+ public static BasePacket SA_WR_POSTMASTER(byte mailboxes) {
+ return new SAMessage(SAMessageType.SA_WR_POSTMASTER,
+ new byte[] { SAMessageType.SA_WR_POSTMASTER.getValue(), mailboxes });
+ }
+
+ public static BasePacket SA_WR_CLIENTLEARNRQ(byte manu1, byte manu2, byte rorg, byte func, byte type) {
+ return new SAMessage(SAMessageType.SA_WR_CLIENTLEARNRQ,
+ new byte[] { SAMessageType.SA_WR_CLIENTLEARNRQ.getValue(), manu1, manu2, rorg, func, type });
+ }
+
public static BasePacket BuildPacket(int dataLength, int optionalDataLength, byte packetType, byte[] payload) {
ESPPacketType type = ESPPacketType.getPacketType(packetType);
return new Response(dataLength, optionalDataLength, payload);
case RADIO_ERP1:
return new ERP1Message(dataLength, optionalDataLength, payload);
+ case EVENT:
+ return new EventMessage(dataLength, optionalDataLength, payload);
default:
return null;
}
--- /dev/null
+/**
+ * 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;
+ }
+}
+++ /dev/null
-/**
- * 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 org.openhab.binding.enocean.internal.Helper;
-
-/**
- *
- * @author Daniel Weber - Initial contribution
- */
-public class RDBaseIdResponse extends Response {
-
- private byte[] baseId = null;
- private int remainingWriteCycles = 0;
-
- public RDBaseIdResponse(Response response) {
- this(response.getPayload().length, response.getOptionalPayload().length,
- Helper.concatAll(response.getPayload(), response.getOptionalPayload()));
- }
-
- RDBaseIdResponse(int dataLength, int optionalDataLength, byte[] payload) {
- super(dataLength, optionalDataLength, payload);
-
- if (this.data == null || this.data.length != 5 || this.optionalData == null || this.optionalData.length != 1) {
- return;
- }
-
- baseId = getPayload(1, 4);
- remainingWriteCycles = optionalData[0] & 0xFF;
-
- _isValid = true;
- }
-
- public final byte[] getBaseId() {
- return baseId;
- }
-
- public int getRemainingWriteCycles() {
- return remainingWriteCycles;
- }
-}
+++ /dev/null
-/**
- * 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 static org.openhab.binding.enocean.internal.EnOceanBindingConstants.*;
-
-import org.eclipse.jdt.annotation.NonNull;
-import org.openhab.core.library.types.StringType;
-
-/**
- *
- * @author Daniel Weber - Initial contribution
- */
-public class RDRepeaterResponse extends Response {
-
- protected String repeaterLevel;
-
- public RDRepeaterResponse(Response response) {
- this(response.getPayload().length, 0, response.getPayload());
- }
-
- RDRepeaterResponse(int dataLength, int optionalDataLength, byte[] payload) {
- super(dataLength, optionalDataLength, payload);
-
- if (payload == null || payload.length < 3) {
- return;
- }
-
- if (payload[1] == 0) {
- repeaterLevel = REPEATERMODE_OFF;
- } else if (payload[1] == 1 || payload[1] == 2) {
- switch (payload[2]) {
- case 1:
- repeaterLevel = REPEATERMODE_LEVEL_1;
- break;
- case 2:
- repeaterLevel = REPEATERMODE_LEVEL_2;
- break;
- case 0:
- repeaterLevel = REPEATERMODE_OFF;
- break;
- default:
- return;
- }
-
- _isValid = true;
- }
- }
-
- @NonNull
- public StringType getRepeaterLevel() {
- return StringType.valueOf(repeaterLevel);
- }
-}
+++ /dev/null
-/**
- * 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.Arrays;
-
-import org.eclipse.jdt.annotation.NonNull;
-import org.openhab.core.util.HexUtils;
-
-/**
- *
- * @author Daniel Weber - Initial contribution
- */
-public class RDVersionResponse extends Response {
-
- protected String appVersion = "";
- protected String apiVersion = "";
- protected String chipId = "";
- protected String description = "";
-
- public RDVersionResponse(Response response) {
- this(response.getPayload().length, 0, response.getPayload());
- }
-
- RDVersionResponse(int dataLength, int optionalDataLength, byte[] payload) {
- super(dataLength, optionalDataLength, payload);
-
- if (payload.length < 33) {
- return;
- }
-
- try {
- appVersion = String.format("%d.%d.%d.%d", payload[1] & 0xff, payload[2] & 0xff, payload[3] & 0xff,
- payload[4] & 0xff);
- apiVersion = String.format("%d.%d.%d.%d", payload[5] & 0xff, payload[6] & 0xff, payload[7] & 0xff,
- payload[8] & 0xff);
-
- chipId = HexUtils.bytesToHex(Arrays.copyOfRange(payload, 9, 13));
-
- StringBuffer sb = new StringBuffer();
- for (int i = 17; i < payload.length; i++) {
- sb.append((char) (payload[i] & 0xff));
- }
- description = sb.toString();
- _isValid = true;
-
- } catch (Exception e) {
- responseType = ResponseType.RET_ERROR;
- }
- }
-
- @NonNull
- public String getAPPVersion() {
- return appVersion;
- }
-
- @NonNull
- public String getAPIVersion() {
- return apiVersion;
- }
-
- @NonNull
- public String getChipID() {
- return chipId;
- }
-
- @NonNull
- public String getDescription() {
- return description;
- }
-}
protected ResponseType responseType;
protected boolean _isValid = false;
- protected Response(int dataLength, int optionalDataLength, byte[] payload) {
+ public Response(int dataLength, int optionalDataLength, byte[] payload) {
super(dataLength, optionalDataLength, ESPPacketType.RESPONSE, payload);
try {
--- /dev/null
+/**
+ * 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.Helper;
+import org.openhab.binding.enocean.internal.messages.Response;
+
+/**
+ *
+ * @author Daniel Weber - Initial contribution
+ */
+public class BaseResponse extends Response {
+
+ public BaseResponse(Response response) {
+ super(response.getPayload().length + response.getOptionalPayload().length, 0,
+ Helper.concatAll(response.getPayload(), response.getOptionalPayload()));
+ }
+}
--- /dev/null
+/**
+ * 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.Helper;
+import org.openhab.binding.enocean.internal.messages.Response;
+
+/**
+ *
+ * @author Daniel Weber - Initial contribution
+ */
+public class RDBaseIdResponse extends Response {
+
+ private byte[] baseId = null;
+ private int remainingWriteCycles = 0;
+
+ public RDBaseIdResponse(Response response) {
+ this(response.getPayload().length, response.getOptionalPayload().length,
+ Helper.concatAll(response.getPayload(), response.getOptionalPayload()));
+ }
+
+ RDBaseIdResponse(int dataLength, int optionalDataLength, byte[] payload) {
+ super(dataLength, optionalDataLength, payload);
+
+ if (this.data == null || this.data.length != 5 || this.optionalData == null || this.optionalData.length != 1) {
+ return;
+ }
+
+ baseId = getPayload(1, 4);
+ remainingWriteCycles = optionalData[0] & 0xFF;
+
+ _isValid = true;
+ }
+
+ public final byte[] getBaseId() {
+ return baseId;
+ }
+
+ public int getRemainingWriteCycles() {
+ return remainingWriteCycles;
+ }
+}
--- /dev/null
+/**
+ * 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;
+ }
+}
--- /dev/null
+/**
+ * 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 static org.openhab.binding.enocean.internal.EnOceanBindingConstants.*;
+
+import org.eclipse.jdt.annotation.NonNull;
+import org.openhab.binding.enocean.internal.messages.Response;
+import org.openhab.core.library.types.StringType;
+
+/**
+ *
+ * @author Daniel Weber - Initial contribution
+ */
+public class RDRepeaterResponse extends Response {
+
+ protected String repeaterLevel;
+
+ public RDRepeaterResponse(Response response) {
+ this(response.getPayload().length, 0, response.getPayload());
+ }
+
+ RDRepeaterResponse(int dataLength, int optionalDataLength, byte[] payload) {
+ super(dataLength, optionalDataLength, payload);
+
+ if (payload == null || payload.length < 3) {
+ return;
+ }
+
+ if (payload[1] == 0) {
+ repeaterLevel = REPEATERMODE_OFF;
+ } else if (payload[1] == 1 || payload[1] == 2) {
+ switch (payload[2]) {
+ case 1:
+ repeaterLevel = REPEATERMODE_LEVEL_1;
+ break;
+ case 2:
+ repeaterLevel = REPEATERMODE_LEVEL_2;
+ break;
+ case 0:
+ repeaterLevel = REPEATERMODE_OFF;
+ break;
+ default:
+ return;
+ }
+
+ _isValid = true;
+ }
+ }
+
+ @NonNull
+ public StringType getRepeaterLevel() {
+ return StringType.valueOf(repeaterLevel);
+ }
+}
--- /dev/null
+/**
+ * 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.eclipse.jdt.annotation.NonNull;
+import org.openhab.binding.enocean.internal.messages.Response;
+import org.openhab.core.util.HexUtils;
+
+/**
+ *
+ * @author Daniel Weber - Initial contribution
+ */
+public class RDVersionResponse extends Response {
+
+ protected String appVersion = "";
+ protected String apiVersion = "";
+ protected String chipId = "";
+ protected String description = "";
+
+ public RDVersionResponse(Response response) {
+ this(response.getPayload().length, 0, response.getPayload());
+ }
+
+ RDVersionResponse(int dataLength, int optionalDataLength, byte[] payload) {
+ super(dataLength, optionalDataLength, payload);
+
+ if (payload.length < 33) {
+ return;
+ }
+
+ try {
+ appVersion = String.format("%d.%d.%d.%d", payload[1] & 0xff, payload[2] & 0xff, payload[3] & 0xff,
+ payload[4] & 0xff);
+ apiVersion = String.format("%d.%d.%d.%d", payload[5] & 0xff, payload[6] & 0xff, payload[7] & 0xff,
+ payload[8] & 0xff);
+
+ chipId = HexUtils.bytesToHex(Arrays.copyOfRange(payload, 9, 13));
+
+ StringBuffer sb = new StringBuffer();
+ for (int i = 17; i < payload.length; i++) {
+ sb.append((char) (payload[i] & 0xff));
+ }
+ description = sb.toString();
+ _isValid = true;
+
+ } catch (Exception e) {
+ responseType = ResponseType.RET_ERROR;
+ }
+ }
+
+ @NonNull
+ public String getAPPVersion() {
+ return appVersion;
+ }
+
+ @NonNull
+ public String getAPIVersion() {
+ return apiVersion;
+ }
+
+ @NonNull
+ public String getChipID() {
+ return chipId;
+ }
+
+ @NonNull
+ public String getDescription() {
+ return description;
+ }
+}
--- /dev/null
+/**
+ * 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;
+ }
+}
--- /dev/null
+/**
+ * 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;
+ }
+}
import org.openhab.binding.enocean.internal.EnOceanException;
import org.openhab.binding.enocean.internal.messages.BasePacket;
-import org.openhab.binding.enocean.internal.messages.ERP1Message;
-import org.openhab.binding.enocean.internal.messages.ERP1Message.RORG;
import org.openhab.binding.enocean.internal.messages.ESP3Packet;
import org.openhab.binding.enocean.internal.messages.ESP3PacketFactory;
import org.openhab.binding.enocean.internal.messages.Response;
HexUtils.bytesToHex(packet.getPayload()));
break;
case EVENT:
- logger.debug("Event occured: {}", HexUtils.bytesToHex(packet.getPayload()));
- break;
- case RADIO_ERP1: {
- ERP1Message msg = (ERP1Message) packet;
- logger.debug("{} with RORG {} for {} payload {} received",
- packet.getPacketType().name(), msg.getRORG().name(),
- HexUtils.bytesToHex(msg.getSenderId()), HexUtils.bytesToHex(
- Arrays.copyOf(dataBuffer, dataLength + optionalLength)));
-
- if (msg.getRORG() != RORG.Unknown) {
- informListeners(msg);
- } else {
- logger.debug("Received unknown RORG");
- }
- }
+ case RADIO_ERP1:
+ informListeners(packet);
break;
case RADIO_ERP2:
break;
import org.openhab.binding.enocean.internal.EnOceanBindingConstants;
import org.openhab.binding.enocean.internal.EnOceanException;
+import org.openhab.binding.enocean.internal.Helper;
import org.openhab.binding.enocean.internal.messages.BasePacket;
+import org.openhab.binding.enocean.internal.messages.BasePacket.ESPPacketType;
import org.openhab.binding.enocean.internal.messages.ERP1Message;
import org.openhab.binding.enocean.internal.messages.ERP1Message.RORG;
+import org.openhab.binding.enocean.internal.messages.EventMessage;
+import org.openhab.binding.enocean.internal.messages.EventMessage.EventMessageType;
import org.openhab.binding.enocean.internal.messages.Response;
import org.openhab.core.io.transport.serial.PortInUseException;
import org.openhab.core.io.transport.serial.SerialPort;
Request currentRequest = null;
protected Map<Long, HashSet<PacketListener>> listeners;
- protected PacketListener teachInListener;
+ protected HashSet<EventListener> eventListeners;
+ protected TeachInListener teachInListener;
protected InputStream inputStream;
protected OutputStream outputStream;
requestQueue = new RequestQueue(scheduler);
listeners = new HashMap<>();
+ eventListeners = new HashSet<>();
teachInListener = null;
this.errorListener = errorListener;
}
});
}
+ logger.info("EnOceanSerialTransceiver RX thread started");
}
public void ShutDown() {
}
}
- protected void informListeners(ERP1Message msg) {
+ protected void informListeners(BasePacket packet) {
try {
- byte[] senderId = msg.getSenderId();
+ if (packet.getPacketType() == ESPPacketType.RADIO_ERP1) {
+ ERP1Message msg = (ERP1Message) packet;
+ byte[] senderId = msg.getSenderId();
+ byte[] d = Helper.concatAll(msg.getPayload(), msg.getOptionalPayload());
+
+ 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 (filteredDeviceId != null && senderId[0] == filteredDeviceId[0]
+ && senderId[1] == filteredDeviceId[1] && senderId[2] == filteredDeviceId[2]) {
+ // filter away own messages which are received through a repeater
+ return;
+ }
- if (senderId != null) {
- if (filteredDeviceId != null && senderId[0] == filteredDeviceId[0] && senderId[1] == filteredDeviceId[1]
- && senderId[2] == filteredDeviceId[2]) {
- // filter away own messages which are received through a repeater
- return;
- }
+ if (teachInListener != null && (msg.getIsTeachIn() || msg.getRORG() == RORG.RPS)) {
+ logger.info("Received teach in message from {}", HexUtils.bytesToHex(msg.getSenderId()));
+ teachInListener.packetReceived(msg);
+ return;
+ } else if (teachInListener == null && msg.getIsTeachIn()) {
+ logger.info("Discard message because this is a teach-in telegram from {}!",
+ HexUtils.bytesToHex(msg.getSenderId()));
+ return;
+ }
- if (teachInListener != null) {
- if (msg.getIsTeachIn() || (msg.getRORG() == RORG.RPS)) {
- logger.info("Received teach in message from {}", HexUtils.bytesToHex(msg.getSenderId()));
- teachInListener.packetReceived(msg);
- return;
+ long s = Long.parseLong(HexUtils.bytesToHex(senderId), 16);
+ HashSet<PacketListener> pl = listeners.get(s);
+ if (pl != null) {
+ pl.forEach(l -> l.packetReceived(msg));
+ }
}
} else {
- if (msg.getIsTeachIn()) {
- logger.info("Discard message because this is a teach-in telegram from {}!",
- HexUtils.bytesToHex(msg.getSenderId()));
+ logger.debug("Received unknown RORG");
+ }
+ } else if (packet.getPacketType() == ESPPacketType.EVENT) {
+ EventMessage event = (EventMessage) packet;
+
+ byte[] d = Helper.concatAll(packet.getPayload(), packet.getOptionalPayload());
+ logger.debug("{} with type {} payload {} received", ESPPacketType.EVENT.name(),
+ event.getEventMessageType().name(), HexUtils.bytesToHex(d));
+
+ if (event.getEventMessageType() == EventMessageType.SA_CONFIRM_LEARN) {
+ byte[] senderId = event.getPayload(EventMessageType.SA_CONFIRM_LEARN.getDataLength() - 5, 4);
+
+ if (teachInListener != null) {
+ logger.info("Received smart teach in from {}", HexUtils.bytesToHex(senderId));
+ teachInListener.eventReceived(event);
+ return;
+ } else {
+ logger.info("Discard message because this is a smart teach-in telegram from {}!",
+ HexUtils.bytesToHex(senderId));
return;
}
}
- long s = Long.parseLong(HexUtils.bytesToHex(senderId), 16);
- HashSet<PacketListener> pl = listeners.get(s);
- if (pl != null) {
- pl.forEach(l -> l.packetReceived(msg));
- }
+ eventListeners.forEach(l -> l.eventReceived(event));
}
} catch (Exception e) {
logger.error("Exception in informListeners", e);
}
}
- 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;
}
--- /dev/null
+/**
+ * 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);
+}
public void packetReceived(BasePacket packet);
- public long getSenderIdToListenTo();
+ public long getEnOceanIdToListenTo();
}
--- /dev/null
+/**
+ * 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 {
+
+}
<description>EnOceanId of device this thing belongs to</description>
<required>true</required>
</parameter>
- <parameter name="senderIdOffset" type="integer">
+ <parameter name="senderIdOffset" type="integer" required="false" min="1" max="127">
<label>Sender Id</label>
<description>Id is used to generate the EnOcean Id (Int between [1-127]). If not specified the next free Id will be
determined by bridge</description>
</channels>
<config-description>
- <parameter name="senderIdOffset" type="integer">
+ <parameter name="senderIdOffset" type="integer" required="false" min="1" max="127">
<label>Sender Id</label>
<description>Id is used to generate the EnOcean Id (Int between [1-127]). If not specified the next free Id will be
determined by bridge</description>
<description>EnOceanId of device this thing belongs to</description>
<required>true</required>
</parameter>
- <parameter name="senderIdOffset" type="integer">
+ <parameter name="senderIdOffset" type="integer" required="false" min="1" max="127">
<label>Sender Id</label>
<description>Id is used to generate the EnOcean Id (Int between [1-127]). If not specified the next free Id will be
determined by bridge</description>
<description>EnOceanId of device this thing belongs to</description>
<required>true</required>
</parameter>
- <parameter name="senderIdOffset" type="integer">
+ <parameter name="senderIdOffset" type="integer" required="false" min="1" max="127">
<label>Sender Id</label>
<description>Id is used to generate the EnOcean Id (Int between [1-127]). If not specified the next free Id will be
determined by bridge</description>
<description>EnOceanId of device this thing belongs to</description>
<required>true</required>
</parameter>
- <parameter name="senderIdOffset" type="integer">
+ <parameter name="senderIdOffset" type="integer" required="false" min="1" max="127">
<label>Sender Id</label>
<description>Id is used to generate the EnOcean Id (Int between [1-127]). If not specified the next free Id will be
determined by bridge</description>
<description>EnOceanId of device this thing belongs to</description>
<required>true</required>
</parameter>
- <parameter name="senderIdOffset" type="integer">
+ <parameter name="senderIdOffset" type="integer" required="false" min="1" max="127">
<label>Sender Id</label>
<description>Id is used to generate the EnOcean Id (Int between [1-127]). If not specified the next free Id will be
determined by bridge</description>
<description>EnOceanId of device this thing belongs to</description>
<required>true</required>
</parameter>
- <parameter name="senderIdOffset" type="integer">
+ <parameter name="senderIdOffset" type="integer" required="false" min="1" max="127">
<label>Sender Id</label>
<description>Id is used to generate the EnOcean Id (Int between [1-127]). If not specified the next free Id will be
determined by bridge</description>
<required>true</required>
<default>ESP3</default>
</parameter>
+ <parameter name="enableSmack" type="boolean">
+ <label>Handle SMACK Messages</label>
+ <description>Declare Gateway as a SMACK Postmaster and handle SMACK messages</description>
+ <default>true</default>
+ </parameter>
<parameter name="rs485" type="boolean">
<advanced>true</advanced>
<label>Gateway Connected Directly to RS485 BUS</label>
<label>Use following BaseId when connected directly to RS485 BUS</label>
<default>00000000</default>
</parameter>
- <parameter name="nextSenderId" type="integer">
+ <parameter name="sendTeachOuts" type="boolean">
+ <advanced>true</advanced>
+ <label>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 Device Id</label>
+ <label>Next Sender Id Offset</label>
<description>Defines the next device Id, if empty, the next device id is automatically determined</description>
</parameter>
</config-description>
<event/>
</channel-type>
- <channel-type id="sendCommand">
- <item-type>Switch</item-type>
- <label>Send Command</label>
- <description>You can send telegrams to the device by switching this channel on</description>
- <category>thermostat</category>
- </channel-type>
-
<channel-type id="smokeDetection">
<item-type>Switch</item-type>
<label>Smoke Detected</label>