mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-10 07:02:02 +01:00
[echonetlite] Initial contribution (#11999)
* First implementation of Echonet Lite Java Bindings. Only supports Mitsubishi Home Heat Pumps. Signed-off-by: Michael Barker <mikeb01@gmail.com>
This commit is contained in:
parent
f74f3ecd02
commit
c244391d08
@ -77,6 +77,7 @@
|
||||
/bundles/org.openhab.binding.dsmr/ @Hilbrand
|
||||
/bundles/org.openhab.binding.dwdpollenflug/ @DerOetzi
|
||||
/bundles/org.openhab.binding.dwdunwetter/ @limdul79
|
||||
/bundles/org.openhab.binding.echonetlite/ @mikeb01
|
||||
/bundles/org.openhab.binding.ecobee/ @mhilbush
|
||||
/bundles/org.openhab.binding.ecotouch/ @sibbi77
|
||||
/bundles/org.openhab.binding.ecowatt/ @lolodomo
|
||||
|
@ -381,6 +381,11 @@
|
||||
<artifactId>org.openhab.binding.easee</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.binding.echonetlite</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.binding.ecobee</artifactId>
|
||||
|
13
bundles/org.openhab.binding.echonetlite/NOTICE
Normal file
13
bundles/org.openhab.binding.echonetlite/NOTICE
Normal file
@ -0,0 +1,13 @@
|
||||
This content is produced and maintained by the openHAB project.
|
||||
|
||||
* Project home: https://www.openhab.org
|
||||
|
||||
== Declared Project Licenses
|
||||
|
||||
This program and the accompanying materials are made available under the terms
|
||||
of the Eclipse Public License 2.0 which is available at
|
||||
https://www.eclipse.org/legal/epl-2.0/.
|
||||
|
||||
== Source Code
|
||||
|
||||
https://github.com/openhab/openhab-addons
|
87
bundles/org.openhab.binding.echonetlite/README.md
Normal file
87
bundles/org.openhab.binding.echonetlite/README.md
Normal file
@ -0,0 +1,87 @@
|
||||
# EchonetLite Binding
|
||||
|
||||
This binding supports devices that make use of the Echonet Lite specification (https://echonet.jp/spec_v113_lite_en/).
|
||||
|
||||
## Supported Things
|
||||
|
||||
* Mitsubishi Electric MAC-568IF-E Wi-Fi interface (common on most Mitsubishi Heat Pumps).
|
||||
|
||||
## Discovery
|
||||
|
||||
Discovery is supported using UDP Multicast.
|
||||
When running over Wi-Fi it is advisable to run openHAB on the same network as the Echonet Lite devices.
|
||||
Multicast traffic doesn't easily route over multiple networks and will often be dropped.
|
||||
Discovery is handled via the Echonet Lite bridge, which contains the configuration of the multicast address used for discovery and asynchronous device notifications along with the port.
|
||||
It is unlikely that this configuration will require changing.
|
||||
|
||||
## Bridge Configuration
|
||||
|
||||
The bridge configuration defaults should be applicable in most scenarios.
|
||||
If device discovery is not working, this is most likely caused by the inability to receive multicast traffic from the device nodes.
|
||||
|
||||
* __port__: Port used for messaging both to and from device nodes, defaults to 3610.
|
||||
* __multicastAddress__: Multicast address used to discover device nodes and to receive asynchronous notifications from devices.
|
||||
|
||||
## Thing Configuration
|
||||
|
||||
* __hostname__: Hostname or IP address of the device node.
|
||||
* __port__: Port used to communicate with the device.
|
||||
* __groupCode__: Group code as specified in "APPENDIX Detailed Requirements for ECHONET Device objects" (https://echonet.jp/spec_object_rp1_en/).
|
||||
For Air Conditioners the value is '1'.
|
||||
* __classCode__: Class code for the device, see __groupCode__ for reference information.
|
||||
The value for Home Air Conditioners is '48' (0x30).
|
||||
* __instance__: Instance identifier if multiple instances are running on the same IP address.
|
||||
Typically, this value will be '1'.
|
||||
* __pollIntervalMs__: Interval between polls of the device for its current status.
|
||||
If multicast is not working this will determine the latency at which changes made directly on the device will be propagated back to openHAB, default is 30 000ms.
|
||||
* __retryTimeoutMs__: Length of time the bridge will wait before resubmitting a request, default is 2 000ms.
|
||||
|
||||
Because the binding uses UDP, packets can be lost on the network, so retries are necessary.
|
||||
Testing has shown that 2 000ms is a reasonable default that allows for timely retries without rejecting slow, but legitimate responses.
|
||||
|
||||
## Channels
|
||||
|
||||
Channels are derived from the Echonet Lite specification and vary from device to device depending on capabilities.
|
||||
The full set of potential channels is available from "APPENDIX Detailed Requirements for ECHONET Device objects" (https://echonet.jp/spec_object_rp1_en/)
|
||||
|
||||
The channels currently implemented are:
|
||||
|
||||
| Channel | Data Type | Description |
|
||||
|------------------------------------|-----------|-------------------------------------------------------------------------|
|
||||
| operationStatus | Switch | Switch On/Off the device |
|
||||
| installationLocation | String | Installation location (option) |
|
||||
| standardVersionInformation | String | Standard Version Information |
|
||||
| identificationNumber | String | Unique id for device (used by auto discovery for the thingId) |
|
||||
| manufacturerFaultCode | String | Manufacturer Fault Code |
|
||||
| faultStatus | Switch | Fault Status |
|
||||
| faultDescription | String | Fault Description |
|
||||
| manufacturerCode | String | Manufacturer Code |
|
||||
| businessFacilityCode | String | Business Facility Code |
|
||||
| powerSavingOperationSetting | Switch | Controls whether the unit is in power saving operation or not |
|
||||
| cumulativeOperatingTime | Number | Cumulative Operating Time |
|
||||
| airFlowRate | String | Air Flow Rate |
|
||||
| automaticControlOfAirFlowDirection | String | The type of automatic control applied to the air flow direction, if any |
|
||||
| automaticSwingOfAirFlow | String | Automatic Swing Of Air Flow |
|
||||
| airFlowDirectionVertical | String | Air Flow Direction Vertical |
|
||||
| airFlowDirectionHorizontal | String | Air Flow Direction Horizontal |
|
||||
| operationMode | String | The current mode for the Home AC unit (heating, cooling, etc.) |
|
||||
| setTemperature | Number | Desired target room temperature |
|
||||
| measuredRoomTemperature | Number | Measured Room Temperature |
|
||||
| measuredOutdoorTemperature | Number | Measured Outdoor Temperature |
|
||||
|
||||
## Full Example
|
||||
|
||||
|
||||
### Things
|
||||
|
||||
```
|
||||
Bridge echonetlite:bridge:1 [port="3610", multicastAddress="224.0.23.0"] {
|
||||
Thing device HeatPump_Bedroom1 "HeatPump Bedroom 1" @ "Bedroom 1" [hostname="192.168.0.55", port="3610", groupCode="1", classCode="48", instance="1", pollIntervalMs="30000", retryTimeoutMs="2000"]
|
||||
}
|
||||
```
|
||||
|
||||
### Items
|
||||
|
||||
```
|
||||
Switch HeatPumpBedroom1_OperationStatus "HeatPump Bedroom1 Operation Status" {channel="echonetlite:device:1:HeatPump_Bedroom1:operationStatus"}
|
||||
```
|
17
bundles/org.openhab.binding.echonetlite/pom.xml
Normal file
17
bundles/org.openhab.binding.echonetlite/pom.xml
Normal file
@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
|
||||
<version>3.4.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>org.openhab.binding.echonetlite</artifactId>
|
||||
|
||||
<name>openHAB Add-ons :: Bundles :: EchonetLite Binding</name>
|
||||
|
||||
</project>
|
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<features name="org.openhab.binding.echonetlite-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
|
||||
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
|
||||
|
||||
<feature name="openhab-binding-echonetlite" description="EchonetLite Binding" version="${project.version}">
|
||||
<feature>openhab-runtime-base</feature>
|
||||
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.echonetlite/${project.version}</bundle>
|
||||
</feature>
|
||||
</features>
|
@ -0,0 +1,34 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.echonetlite.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* The {@link EchonetBridgeConfig} class contains fields mapping thing configuration parameters.
|
||||
*
|
||||
* @author Michael Barker - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class EchonetBridgeConfig {
|
||||
|
||||
@Nullable
|
||||
public String multicastAddress;
|
||||
public int port;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "EchonetBridgeConfig{" + "multicastAddress='" + multicastAddress + '\'' + ", port=" + port + '}';
|
||||
}
|
||||
}
|
@ -0,0 +1,106 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.echonetlite.internal;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.Inet4Address;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.NetworkInterface;
|
||||
import java.net.SocketAddress;
|
||||
import java.net.StandardProtocolFamily;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.DatagramChannel;
|
||||
import java.nio.channels.SelectionKey;
|
||||
import java.nio.channels.Selector;
|
||||
import java.util.Enumeration;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Wraps a Datagram channel for sending/receiving data to/from echonet lite devices.
|
||||
*
|
||||
* @author Michael Barker - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class EchonetChannel {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(EchonetChannel.class);
|
||||
|
||||
private final DatagramChannel channel;
|
||||
private final Selector selector = Selector.open();
|
||||
|
||||
private short tid = 0;
|
||||
|
||||
public EchonetChannel(InetSocketAddress discoveryAddress) throws IOException {
|
||||
channel = DatagramChannel.open(StandardProtocolFamily.INET);
|
||||
channel.bind(new InetSocketAddress("0.0.0.0", discoveryAddress.getPort()));
|
||||
final Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();
|
||||
while (networkInterfaces.hasMoreElements()) {
|
||||
final NetworkInterface networkInterface = (NetworkInterface) networkInterfaces.nextElement();
|
||||
if (networkInterface.supportsMulticast() && hasIpV4Address(networkInterface)) {
|
||||
channel.join(discoveryAddress.getAddress(), networkInterface);
|
||||
}
|
||||
}
|
||||
channel.configureBlocking(false);
|
||||
channel.register(selector, SelectionKey.OP_READ);
|
||||
}
|
||||
|
||||
private boolean hasIpV4Address(final NetworkInterface networkInterface) {
|
||||
return networkInterface.inetAddresses().anyMatch(ia -> ia instanceof Inet4Address);
|
||||
}
|
||||
|
||||
public void close() {
|
||||
try {
|
||||
logger.debug("closing selector");
|
||||
selector.close();
|
||||
logger.debug("closing channel");
|
||||
channel.close();
|
||||
} catch (IOException ignore) {
|
||||
}
|
||||
}
|
||||
|
||||
short nextTid() {
|
||||
return tid++;
|
||||
}
|
||||
|
||||
public void sendMessage(EchonetMessageBuilder messageBuilder) throws IOException {
|
||||
messageBuilder.buffer().flip();
|
||||
channel.send(messageBuilder.buffer(), messageBuilder.address());
|
||||
}
|
||||
|
||||
public void pollMessages(EchonetMessage echonetMessage, BiConsumer<EchonetMessage, SocketAddress> consumer,
|
||||
final long timeout) throws IOException {
|
||||
selector.select(selectionKey -> {
|
||||
final DatagramChannel channel = (DatagramChannel) selectionKey.channel();
|
||||
try {
|
||||
final ByteBuffer buffer = echonetMessage.bufferForRead();
|
||||
final SocketAddress address = channel.receive(buffer);
|
||||
|
||||
echonetMessage.sourceAddress(address);
|
||||
buffer.flip();
|
||||
long t0 = System.currentTimeMillis();
|
||||
consumer.accept(echonetMessage, address);
|
||||
long t1 = System.currentTimeMillis();
|
||||
final long processingTimeMs = t1 - t0;
|
||||
if (500 < processingTimeMs) {
|
||||
logger.debug("Message took {}ms to process", processingTimeMs);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.warn("Failed to receive on channel", e);
|
||||
}
|
||||
}, timeout);
|
||||
}
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.echonetlite.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* @author Michael Barker - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public enum EchonetClass {
|
||||
AIRCON_HOMEAC(0x01, 0x30, (Epc[]) Epc.Device.values(), (Epc[]) Epc.AcGroup.values(), (Epc[]) Epc.HomeAc.values()),
|
||||
MANAGEMENT_CONTROLLER(0x05, 0xFF, new Epc[0], new Epc[0], new Epc[0]),
|
||||
NODE_PROFILE(0x0e, 0xf0, (Epc[]) Epc.Profile.values(), (Epc[]) Epc.ProfileGroup.values(),
|
||||
(Epc[]) Epc.NodeProfile.values());
|
||||
|
||||
private final int groupCode;
|
||||
private final int classCode;
|
||||
private final Epc[] deviceProperties;
|
||||
private final Epc[] groupProperties;
|
||||
private final Epc[] classProperties;
|
||||
|
||||
EchonetClass(final int groupCode, final int classCode, Epc[] deviceProperties, Epc[] groupProperties,
|
||||
Epc[] classProperties) {
|
||||
this.groupCode = groupCode;
|
||||
this.classCode = classCode;
|
||||
this.deviceProperties = deviceProperties;
|
||||
this.groupProperties = groupProperties;
|
||||
this.classProperties = classProperties;
|
||||
}
|
||||
|
||||
public static EchonetClass resolve(final int groupCode, final int classCode) {
|
||||
final EchonetClass[] values = values();
|
||||
for (EchonetClass value : values) {
|
||||
if (value.groupCode == groupCode && value.classCode == classCode) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("Unable to find class: " + groupCode + "/" + classCode);
|
||||
}
|
||||
|
||||
public int groupCode() {
|
||||
return groupCode;
|
||||
}
|
||||
|
||||
public int classCode() {
|
||||
return classCode;
|
||||
}
|
||||
|
||||
Epc[] deviceProperties() {
|
||||
return deviceProperties;
|
||||
}
|
||||
|
||||
Epc[] groupProperties() {
|
||||
return groupProperties;
|
||||
}
|
||||
|
||||
Epc[] classProperties() {
|
||||
return classProperties;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return name() + "{" + "groupCode=0x" + Integer.toHexString(groupCode) + ", classCode=0x"
|
||||
+ Integer.toHexString(0xFF & classCode) + '}';
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.echonetlite.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* @author Michael Barker - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public enum EchonetClassIndex {
|
||||
INSTANCE;
|
||||
|
||||
private static final EchonetClass[] INDEX = new EchonetClass[1 << 16];
|
||||
static {
|
||||
final EchonetClass[] values = EchonetClass.values();
|
||||
for (final EchonetClass value : values) {
|
||||
INDEX[codeToIndex(value.groupCode(), value.classCode())] = value;
|
||||
}
|
||||
}
|
||||
|
||||
public static int codeToIndex(final int groupCode, final int classCode) {
|
||||
return ((0xFF & groupCode) << 8) + (0xFF & classCode);
|
||||
}
|
||||
|
||||
public EchonetClass lookup(final int groupCode, final int classCode) {
|
||||
return INDEX[codeToIndex(groupCode, classCode)];
|
||||
}
|
||||
}
|
@ -0,0 +1,211 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.echonetlite.internal;
|
||||
|
||||
import static org.openhab.binding.echonetlite.internal.HexUtil.hex;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.types.State;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* @author Michael Barker - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class EchonetDevice extends EchonetObject {
|
||||
|
||||
private final LinkedHashMap<Epc, State> pendingSets = new LinkedHashMap<>();
|
||||
private final HashMap<Epc, State> stateFields = new HashMap<>();
|
||||
private final HashMap<String, Epc> epcByChannelId = new HashMap<>();
|
||||
private final Logger logger = LoggerFactory.getLogger(EchonetDevice.class);
|
||||
@Nullable
|
||||
private EchonetPropertyMap getPropertyMap;
|
||||
private EchonetDeviceListener listener;
|
||||
private boolean initialised = false;
|
||||
|
||||
private long lastPollMs = 0;
|
||||
|
||||
public EchonetDevice(final InstanceKey instanceKey, EchonetDeviceListener listener) {
|
||||
super(instanceKey, Epc.Device.GET_PROPERTY_MAP);
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
public void applyProperty(InstanceKey sourceInstanceKey, Esv esv, final int epcCode, final int pdc,
|
||||
final ByteBuffer edt) {
|
||||
final Epc epc = Epc.lookup(instanceKey().klass.groupCode(), instanceKey().klass.classCode(), epcCode);
|
||||
|
||||
if ((Esv.Get_Res == esv || Esv.Get_SNA == esv || Esv.INF == esv) && 0 < pdc) {
|
||||
pendingGets.remove(epc);
|
||||
|
||||
int edtPosition = edt.position();
|
||||
|
||||
final StateDecode decoder = epc.decoder();
|
||||
State state = null;
|
||||
if (null != decoder) {
|
||||
state = decoder.decodeState(edt);
|
||||
if (null == stateFields.put(epc, state)) {
|
||||
epcByChannelId.put(epc.channelId(), epc);
|
||||
}
|
||||
|
||||
final @Nullable State pendingState = lookupPendingSet(epc);
|
||||
if (null != pendingState && pendingState.equals(state)) {
|
||||
logger.debug("pendingSet - removing: {} {}", epc, state);
|
||||
pendingSets.remove(epc);
|
||||
} else if (null != pendingState) {
|
||||
logger.debug("pendingSet - state mismatch: {} {} {}", epc, pendingState, state);
|
||||
}
|
||||
|
||||
if (initialised) {
|
||||
listener.onUpdated(epc.channelId(), state);
|
||||
}
|
||||
} else if (Epc.Device.GET_PROPERTY_MAP == epc) {
|
||||
if (null == getPropertyMap) {
|
||||
final EchonetPropertyMap getPropertyMap = new EchonetPropertyMap(epc);
|
||||
getPropertyMap.update(edt);
|
||||
getPropertyMap.getProperties(instanceKey().klass.groupCode(), instanceKey().klass.classCode(),
|
||||
Set.of(Epc.Device.GET_PROPERTY_MAP), pendingGets);
|
||||
this.getPropertyMap = getPropertyMap;
|
||||
}
|
||||
}
|
||||
|
||||
if (!initialised && null != getPropertyMap && pendingGets.isEmpty()) {
|
||||
initialised = true;
|
||||
listener.onInitialised(identifier(), instanceKey, channelIds());
|
||||
stateFields.forEach((e, s) -> listener.onUpdated(e.channelId(), s));
|
||||
}
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
String value = null != state ? state.toString() : "";
|
||||
edt.position(edtPosition);
|
||||
logger.debug("Applying: {}({},{}) {} {} pending: {}", epc, hex(epc.code()), pdc, value, hex(edt),
|
||||
pendingGets.size());
|
||||
}
|
||||
} else if (esv == Esv.Set_Res) {
|
||||
pendingSets.remove(epc);
|
||||
}
|
||||
}
|
||||
|
||||
public String identifier() {
|
||||
final State identificationNumber = stateFields.get(Epc.Device.IDENTIFICATION_NUMBER);
|
||||
if (null == identificationNumber) {
|
||||
throw new IllegalStateException("Echonet devices must support identification number property");
|
||||
}
|
||||
|
||||
return identificationNumber.toString();
|
||||
}
|
||||
|
||||
public boolean buildUpdateMessage(final EchonetMessageBuilder messageBuilder, final ShortSupplier tidSupplier,
|
||||
final long nowMs, InstanceKey managementControllerKey) {
|
||||
if (pendingSets.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final InflightRequest inflightSetRequest = this.inflightSetRequest;
|
||||
|
||||
if (hasInflight(nowMs, inflightSetRequest)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final short tid = tidSupplier.getAsShort();
|
||||
messageBuilder.start(tid, managementControllerKey, instanceKey, Esv.SetC);
|
||||
|
||||
pendingSets.forEach((k, v) -> {
|
||||
final StateEncode encoder = k.encoder();
|
||||
if (null != encoder) {
|
||||
final ByteBuffer buffer = messageBuilder.edtBuffer();
|
||||
encoder.encodeState(v, buffer);
|
||||
messageBuilder.appendEpcUpdate(k.code(), buffer.flip());
|
||||
}
|
||||
});
|
||||
|
||||
inflightSetRequest.requestSent(tid, nowMs);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void update(String channelId, State state) {
|
||||
final Epc epc = epcByChannelId.get(channelId);
|
||||
if (null == epc) {
|
||||
logger.warn("Unable to find epc for channelId: {}", channelId);
|
||||
return;
|
||||
}
|
||||
|
||||
pendingSets.put(epc, state);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removed() {
|
||||
listener.onRemoved();
|
||||
}
|
||||
|
||||
public void checkTimeouts() {
|
||||
if (EchonetLiteBindingConstants.OFFLINE_TIMEOUT_COUNT <= inflightGetRequest.timeoutCount()) {
|
||||
listener.onOffline();
|
||||
}
|
||||
}
|
||||
|
||||
public void refreshAll(long nowMs) {
|
||||
final EchonetPropertyMap getPropertyMap = this.getPropertyMap;
|
||||
if (lastPollMs + pollIntervalMs <= nowMs && null != getPropertyMap) {
|
||||
getPropertyMap.getProperties(instanceKey().klass.groupCode(), instanceKey().klass.classCode(),
|
||||
Set.of(Epc.Device.GET_PROPERTY_MAP), pendingGets);
|
||||
lastPollMs = nowMs;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void refresh(String channelId) {
|
||||
final Epc epc = epcByChannelId.get(channelId);
|
||||
if (null == epc) {
|
||||
return;
|
||||
}
|
||||
|
||||
final State state = stateFields.get(epc);
|
||||
if (null == state) {
|
||||
return;
|
||||
}
|
||||
|
||||
listener.onUpdated(channelId, state);
|
||||
}
|
||||
|
||||
public void setListener(EchonetDeviceListener listener) {
|
||||
this.listener = listener;
|
||||
if (initialised) {
|
||||
listener.onInitialised(identifier(), instanceKey(), channelIds());
|
||||
stateFields.forEach((e, s) -> listener.onUpdated(e.channelId(), s));
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, String> channelIds() {
|
||||
final HashMap<String, String> channelIdAndType = new HashMap<>();
|
||||
for (Epc e : stateFields.keySet()) {
|
||||
final StateDecode decoder = e.decoder();
|
||||
if (null != decoder) {
|
||||
channelIdAndType.put(e.channelId(), decoder.itemType());
|
||||
}
|
||||
}
|
||||
return channelIdAndType;
|
||||
}
|
||||
|
||||
private @Nullable State lookupPendingSet(Epc epc) {
|
||||
return pendingSets.get(epc);
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.echonetlite.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* The {@link EchonetDeviceConfig} class contains fields mapping thing configuration parameters.
|
||||
*
|
||||
* @author Michael Barker - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class EchonetDeviceConfig {
|
||||
|
||||
/**
|
||||
* Sample configuration parameters. Replace with your own.
|
||||
*/
|
||||
@Nullable
|
||||
public String hostname;
|
||||
public int port;
|
||||
public int groupCode;
|
||||
public int classCode;
|
||||
public int instance;
|
||||
public long pollIntervalMs;
|
||||
public long retryTimeoutMs;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "EchonetLiteConfiguration{" + "hostname='" + hostname + '\'' + ", port=" + port + ", groupCode="
|
||||
+ groupCode + ", classCode=" + classCode + ", instance=" + instance + ", pollIntervalMs="
|
||||
+ pollIntervalMs + ", retryTimeoutMs=" + retryTimeoutMs + '}';
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.echonetlite.internal;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.types.State;
|
||||
|
||||
/**
|
||||
* @author Michael Barker - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface EchonetDeviceListener {
|
||||
default void onInitialised(String identifier, InstanceKey instanceKey, Map<String, String> channelIdAndType) {
|
||||
}
|
||||
|
||||
default void onUpdated(String channelId, State value) {
|
||||
}
|
||||
|
||||
default void onRemoved() {
|
||||
}
|
||||
|
||||
default void onOffline() {
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.echonetlite.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* @author Michael Barker - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface EchonetDiscoveryListener {
|
||||
void onDeviceFound(String identifier, InstanceKey instanceKey);
|
||||
}
|
@ -0,0 +1,112 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.echonetlite.internal;
|
||||
|
||||
import static org.openhab.binding.echonetlite.internal.EchonetLiteBindingConstants.PROPERTY_NAME_CLASS_CODE;
|
||||
import static org.openhab.binding.echonetlite.internal.EchonetLiteBindingConstants.PROPERTY_NAME_GROUP_CODE;
|
||||
import static org.openhab.binding.echonetlite.internal.EchonetLiteBindingConstants.PROPERTY_NAME_HOSTNAME;
|
||||
import static org.openhab.binding.echonetlite.internal.EchonetLiteBindingConstants.PROPERTY_NAME_INSTANCE;
|
||||
import static org.openhab.binding.echonetlite.internal.EchonetLiteBindingConstants.PROPERTY_NAME_INSTANCE_KEY;
|
||||
import static org.openhab.binding.echonetlite.internal.EchonetLiteBindingConstants.PROPERTY_NAME_PORT;
|
||||
import static org.openhab.binding.echonetlite.internal.EchonetLiteBindingConstants.THING_TYPE_ECHONET_DEVICE;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.config.discovery.AbstractDiscoveryService;
|
||||
import org.openhab.core.config.discovery.DiscoveryResult;
|
||||
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* @author Michael Barker - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class EchonetDiscoveryService extends AbstractDiscoveryService
|
||||
implements EchonetDiscoveryListener, ThingHandlerService {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(EchonetDiscoveryService.class);
|
||||
|
||||
@Nullable
|
||||
private EchonetLiteBridgeHandler bridgeHandler;
|
||||
|
||||
public EchonetDiscoveryService() {
|
||||
super(Set.of(THING_TYPE_ECHONET_DEVICE), 10);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startScan() {
|
||||
final EchonetLiteBridgeHandler bridgeHandler = this.bridgeHandler;
|
||||
logger.debug("startScan: {}", bridgeHandler);
|
||||
if (null != bridgeHandler) {
|
||||
bridgeHandler.startDiscovery(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected synchronized void stopScan() {
|
||||
final EchonetLiteBridgeHandler bridgeHandler = this.bridgeHandler;
|
||||
logger.debug("stopScan: {}", bridgeHandler);
|
||||
if (null != bridgeHandler) {
|
||||
bridgeHandler.stopDiscovery();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDeviceFound(String identifier, InstanceKey instanceKey) {
|
||||
final EchonetLiteBridgeHandler bridgeHandler = this.bridgeHandler;
|
||||
|
||||
if (null == bridgeHandler) {
|
||||
return;
|
||||
}
|
||||
|
||||
final DiscoveryResult discoveryResult = DiscoveryResultBuilder
|
||||
.create(new ThingUID(THING_TYPE_ECHONET_DEVICE, bridgeHandler.getThing().getUID(), identifier))
|
||||
.withProperty(PROPERTY_NAME_INSTANCE_KEY, instanceKey.representationProperty())
|
||||
.withProperty(PROPERTY_NAME_HOSTNAME, instanceKey.address.getAddress().getHostAddress())
|
||||
.withProperty(PROPERTY_NAME_PORT, instanceKey.address.getPort())
|
||||
.withProperty(PROPERTY_NAME_GROUP_CODE, instanceKey.klass.groupCode())
|
||||
.withProperty(PROPERTY_NAME_CLASS_CODE, instanceKey.klass.classCode())
|
||||
.withProperty(PROPERTY_NAME_INSTANCE, instanceKey.instance)
|
||||
.withBridge(bridgeHandler.getThing().getUID()).withRepresentationProperty(PROPERTY_NAME_INSTANCE_KEY)
|
||||
.build();
|
||||
thingDiscovered(discoveryResult);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deactivate() {
|
||||
ThingHandlerService.super.deactivate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void activate() {
|
||||
ThingHandlerService.super.activate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setThingHandler(ThingHandler thingHandler) {
|
||||
if (thingHandler instanceof EchonetLiteBridgeHandler) {
|
||||
this.bridgeHandler = (EchonetLiteBridgeHandler) thingHandler;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ThingHandler getThingHandler() {
|
||||
return bridgeHandler;
|
||||
}
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.echonetlite.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
|
||||
/**
|
||||
* The {@link EchonetLiteBindingConstants} class defines common constants, which are
|
||||
* used across the whole binding.
|
||||
*
|
||||
* @author Michael Barker - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class EchonetLiteBindingConstants {
|
||||
|
||||
public static final long DEFAULT_POLL_INTERVAL_MS = 30_000;
|
||||
public static final long DEFAULT_RETRY_TIMEOUT_MS = 2_000;
|
||||
public static final int NETWORK_WAIT_TIMEOUT = 250;
|
||||
|
||||
// List of all Thing Type UIDs
|
||||
public static final String BINDING_ID = "echonetlite";
|
||||
public static final ThingTypeUID THING_TYPE_ECHONET_DEVICE = new ThingTypeUID(BINDING_ID, "device");
|
||||
public static final ThingTypeUID THING_TYPE_ECHONET_BRIDGE = new ThingTypeUID(BINDING_ID, "bridge");
|
||||
|
||||
public static final StateCodec.OnOffCodec ON_OFF_CODEC_30_31 = new StateCodec.OnOffCodec(0x30, 0x31);
|
||||
public static final StateCodec.OnOffCodec ON_OFF_CODEC_41_42 = new StateCodec.OnOffCodec(0x41, 0x42);
|
||||
|
||||
public static final String PROPERTY_NAME_INSTANCE_KEY = "instanceKey";
|
||||
public static final String PROPERTY_NAME_HOSTNAME = "hostname";
|
||||
public static final String PROPERTY_NAME_PORT = "port";
|
||||
public static final String PROPERTY_NAME_GROUP_CODE = "groupCode";
|
||||
public static final String PROPERTY_NAME_CLASS_CODE = "classCode";
|
||||
public static final String PROPERTY_NAME_INSTANCE = "instance";
|
||||
public static final int OFFLINE_TIMEOUT_COUNT = 2;
|
||||
}
|
@ -0,0 +1,398 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.echonetlite.internal;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketAddress;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.thing.binding.BaseBridgeHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerService;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.State;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Bridge handler for echonet lite devices. By default, all messages (inbound and outbound) happen on port 3610, so
|
||||
* we can only have a single listener for echonet lite messages. Hence, using a bridge model to handle communications
|
||||
* and discovery.
|
||||
*
|
||||
* @author Michael Barker - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class EchonetLiteBridgeHandler extends BaseBridgeHandler {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(EchonetLiteBridgeHandler.class);
|
||||
private final ArrayBlockingQueue<Message> requests = new ArrayBlockingQueue<>(1024);
|
||||
private final Map<InstanceKey, EchonetObject> devicesByKey = new HashMap<>();
|
||||
private final EchonetMessageBuilder messageBuilder = new EchonetMessageBuilder();
|
||||
private final Thread networkingThread = new Thread(this::poll);
|
||||
private final EchonetMessage echonetMessage = new EchonetMessage();
|
||||
private final MonotonicClock clock = new MonotonicClock();
|
||||
|
||||
@Nullable
|
||||
private EchonetChannel echonetChannel;
|
||||
|
||||
@Nullable
|
||||
private InstanceKey managementControllerKey;
|
||||
|
||||
@Nullable
|
||||
private InstanceKey discoveryKey;
|
||||
|
||||
public EchonetLiteBridgeHandler(Bridge bridge) {
|
||||
super(bridge);
|
||||
}
|
||||
|
||||
private void start(final InstanceKey managementControllerKey, InstanceKey discoveryKey) throws IOException {
|
||||
this.managementControllerKey = managementControllerKey;
|
||||
this.discoveryKey = discoveryKey;
|
||||
|
||||
logger.debug("Binding echonet channel");
|
||||
echonetChannel = new EchonetChannel(discoveryKey.address);
|
||||
logger.debug("Starting networking thread");
|
||||
|
||||
networkingThread.setName("OH-binding-" + EchonetLiteBindingConstants.BINDING_ID);
|
||||
networkingThread.setDaemon(true);
|
||||
networkingThread.start();
|
||||
}
|
||||
|
||||
public void newDevice(InstanceKey instanceKey, long pollIntervalMs, long retryTimeoutMs,
|
||||
final EchonetDeviceListener echonetDeviceListener) {
|
||||
requests.add(new NewDeviceMessage(instanceKey, pollIntervalMs, retryTimeoutMs, echonetDeviceListener));
|
||||
}
|
||||
|
||||
private void newDeviceInternal(final NewDeviceMessage message) {
|
||||
final EchonetObject echonetObject = devicesByKey.get(message.instanceKey);
|
||||
if (null != echonetObject) {
|
||||
if (echonetObject instanceof EchonetDevice) {
|
||||
logger.debug("Update item: {} already discovered", message.instanceKey);
|
||||
EchonetDevice device = (EchonetDevice) echonetObject;
|
||||
device.setTimeouts(message.pollIntervalMs, message.retryTimeoutMs);
|
||||
device.setListener(message.echonetDeviceListener);
|
||||
} else {
|
||||
logger.debug("Item: {} already discovered, but was not a device", message.instanceKey);
|
||||
}
|
||||
} else {
|
||||
logger.debug("New Device: {}", message.instanceKey);
|
||||
final EchonetDevice device = new EchonetDevice(message.instanceKey, message.echonetDeviceListener);
|
||||
device.setTimeouts(message.pollIntervalMs, message.retryTimeoutMs);
|
||||
devicesByKey.put(message.instanceKey, device);
|
||||
}
|
||||
}
|
||||
|
||||
public void refreshDevice(final InstanceKey instanceKey, final String channelId) {
|
||||
requests.add(new RefreshMessage(instanceKey, channelId));
|
||||
}
|
||||
|
||||
private void refreshDeviceInternal(final RefreshMessage refreshMessage) {
|
||||
final EchonetObject item = devicesByKey.get(refreshMessage.instanceKey);
|
||||
if (null != item) {
|
||||
item.refresh(refreshMessage.channelId);
|
||||
}
|
||||
}
|
||||
|
||||
public void removeDevice(final InstanceKey instanceKey) {
|
||||
requests.add(new RemoveDevice(instanceKey));
|
||||
}
|
||||
|
||||
private void removeDeviceInternal(final RemoveDevice removeDevice) {
|
||||
final EchonetObject remove = devicesByKey.remove(removeDevice.instanceKey);
|
||||
|
||||
logger.debug("Removing device: {}, {}", removeDevice.instanceKey, remove);
|
||||
if (null != remove) {
|
||||
remove.removed();
|
||||
}
|
||||
}
|
||||
|
||||
public void updateDevice(final InstanceKey instanceKey, final String id, final State command) {
|
||||
requests.add(new UpdateDevice(instanceKey, id, command));
|
||||
}
|
||||
|
||||
public void updateDeviceInternal(UpdateDevice updateDevice) {
|
||||
final EchonetObject echonetObject = devicesByKey.get(updateDevice.instanceKey);
|
||||
|
||||
if (null == echonetObject) {
|
||||
logger.warn("Device not found for update: {}", updateDevice);
|
||||
return;
|
||||
}
|
||||
|
||||
echonetObject.update(updateDevice.channelId, updateDevice.state);
|
||||
}
|
||||
|
||||
public void startDiscovery(EchonetDiscoveryListener echonetDiscoveryListener) {
|
||||
requests.offer(new StartDiscoveryMessage(echonetDiscoveryListener, requireNonNull(discoveryKey)));
|
||||
}
|
||||
|
||||
public void startDiscoveryInternal(StartDiscoveryMessage startDiscovery) {
|
||||
devicesByKey.put(startDiscovery.instanceKey, new EchonetProfileNode(startDiscovery.instanceKey,
|
||||
this::onDiscoveredInstanceKey, startDiscovery.echonetDiscoveryListener));
|
||||
}
|
||||
|
||||
public void stopDiscovery() {
|
||||
requests.offer(new StopDiscoveryMessage(requireNonNull(discoveryKey)));
|
||||
}
|
||||
|
||||
private void stopDiscoveryInternal(StopDiscoveryMessage stopDiscovery) {
|
||||
devicesByKey.remove(stopDiscovery.instanceKey);
|
||||
}
|
||||
|
||||
private void onDiscoveredInstanceKey(EchonetDevice device) {
|
||||
if (null == devicesByKey.putIfAbsent(device.instanceKey(), device)) {
|
||||
logger.debug("New device discovered: {}", device.instanceKey);
|
||||
}
|
||||
}
|
||||
|
||||
private void pollDevices(long nowMs, EchonetChannel echonetChannel) {
|
||||
for (EchonetObject echonetObject : devicesByKey.values()) {
|
||||
if (echonetObject.buildUpdateMessage(messageBuilder, echonetChannel::nextTid, nowMs,
|
||||
requireNonNull(managementControllerKey))) {
|
||||
try {
|
||||
echonetChannel.sendMessage(messageBuilder);
|
||||
} catch (IOException e) {
|
||||
logger.warn("Failed to send echonet message", e);
|
||||
}
|
||||
}
|
||||
|
||||
echonetObject.refreshAll(nowMs);
|
||||
|
||||
if (echonetObject.buildPollMessage(messageBuilder, echonetChannel::nextTid, nowMs,
|
||||
requireNonNull(managementControllerKey))) {
|
||||
try {
|
||||
echonetChannel.sendMessage(messageBuilder);
|
||||
} catch (IOException e) {
|
||||
logger.warn("Failed to send echonet message", e);
|
||||
}
|
||||
} else {
|
||||
echonetObject.checkTimeouts();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void pollRequests() {
|
||||
Message message;
|
||||
while (null != (message = requestsPoll())) {
|
||||
logger.debug("Received request: {}", message);
|
||||
if (message instanceof NewDeviceMessage) {
|
||||
newDeviceInternal((NewDeviceMessage) message);
|
||||
} else if (message instanceof RefreshMessage) {
|
||||
refreshDeviceInternal((RefreshMessage) message);
|
||||
} else if (message instanceof RemoveDevice) {
|
||||
removeDeviceInternal((RemoveDevice) message);
|
||||
} else if (message instanceof UpdateDevice) {
|
||||
updateDeviceInternal((UpdateDevice) message);
|
||||
} else if (message instanceof StartDiscoveryMessage) {
|
||||
startDiscoveryInternal((StartDiscoveryMessage) message);
|
||||
} else if (message instanceof StopDiscoveryMessage) {
|
||||
stopDiscoveryInternal((StopDiscoveryMessage) message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private @Nullable Message requestsPoll() {
|
||||
return requests.poll();
|
||||
}
|
||||
|
||||
private void pollNetwork(EchonetChannel echonetChannel) {
|
||||
try {
|
||||
echonetChannel.pollMessages(echonetMessage, this::onMessage,
|
||||
EchonetLiteBindingConstants.NETWORK_WAIT_TIMEOUT);
|
||||
} catch (IOException e) {
|
||||
logger.warn("Failed to poll for messages", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void onMessage(final EchonetMessage echonetMessage, final SocketAddress sourceAddress) {
|
||||
final EchonetClass echonetClass = echonetMessage.sourceClass();
|
||||
if (null == echonetClass) {
|
||||
logger.warn("Unable to find echonetClass for message: {}, from: {}", echonetMessage.toDebug(),
|
||||
sourceAddress);
|
||||
return;
|
||||
}
|
||||
|
||||
final InstanceKey instanceKey = new InstanceKey((InetSocketAddress) sourceAddress, echonetClass,
|
||||
echonetMessage.instance());
|
||||
final Esv esv = echonetMessage.esv();
|
||||
|
||||
EchonetObject echonetObject = devicesByKey.get(instanceKey);
|
||||
if (null == echonetObject) {
|
||||
echonetObject = devicesByKey.get(discoveryKey);
|
||||
}
|
||||
|
||||
logger.debug("Message {} for: {}", esv, echonetObject);
|
||||
if (null != echonetObject) {
|
||||
echonetObject.applyHeader(esv, echonetMessage.tid(), clock.timeMs());
|
||||
while (echonetMessage.moveNext()) {
|
||||
final int epc = echonetMessage.currentEpc();
|
||||
final int pdc = echonetMessage.currentPdc();
|
||||
ByteBuffer edt = echonetMessage.currentEdt();
|
||||
echonetObject.applyProperty(instanceKey, esv, epc, pdc, edt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void poll() {
|
||||
try {
|
||||
doPoll();
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
|
||||
while (!Thread.currentThread().isInterrupted()) {
|
||||
doPoll();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void doPoll() {
|
||||
final long nowMs = clock.timeMs();
|
||||
pollRequests();
|
||||
pollDevices(nowMs, requireNonNull(echonetChannel));
|
||||
pollNetwork(requireNonNull(echonetChannel));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
final EchonetBridgeConfig bridgeConfig = getConfigAs(EchonetBridgeConfig.class);
|
||||
|
||||
final InstanceKey managementControllerKey = new InstanceKey(new InetSocketAddress(bridgeConfig.port),
|
||||
EchonetClass.MANAGEMENT_CONTROLLER, (byte) 0x01);
|
||||
final InstanceKey discoveryKey = new InstanceKey(
|
||||
new InetSocketAddress(requireNonNull(bridgeConfig.multicastAddress), bridgeConfig.port),
|
||||
EchonetClass.NODE_PROFILE, (byte) 0x01);
|
||||
|
||||
updateStatus(ThingStatus.UNKNOWN);
|
||||
|
||||
try {
|
||||
start(managementControllerKey, discoveryKey);
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException("Unable to start networking thread", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
if (networkingThread.isAlive()) {
|
||||
networkingThread.interrupt();
|
||||
try {
|
||||
networkingThread.join(TimeUnit.SECONDS.toMillis(5));
|
||||
} catch (InterruptedException e) {
|
||||
logger.debug("Interrupted while closing", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
final EchonetChannel echonetChannel = this.echonetChannel;
|
||||
if (null != echonetChannel) {
|
||||
echonetChannel.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Class<? extends ThingHandlerService>> getServices() {
|
||||
return Collections.singletonList(EchonetDiscoveryService.class);
|
||||
}
|
||||
|
||||
private abstract static class Message {
|
||||
final InstanceKey instanceKey;
|
||||
|
||||
public Message(InstanceKey instanceKey) {
|
||||
this.instanceKey = instanceKey;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class NewDeviceMessage extends Message {
|
||||
final long pollIntervalMs;
|
||||
final long retryTimeoutMs;
|
||||
final EchonetDeviceListener echonetDeviceListener;
|
||||
|
||||
public NewDeviceMessage(final InstanceKey instanceKey, long pollIntervalMs, long retryTimeoutMs,
|
||||
final EchonetDeviceListener echonetDeviceListener) {
|
||||
super(instanceKey);
|
||||
this.pollIntervalMs = pollIntervalMs;
|
||||
this.retryTimeoutMs = retryTimeoutMs;
|
||||
this.echonetDeviceListener = echonetDeviceListener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "NewDeviceMessage{" + "instanceKey=" + instanceKey + ", pollIntervalMs=" + pollIntervalMs
|
||||
+ ", retryTimeoutMs=" + retryTimeoutMs + "} " + super.toString();
|
||||
}
|
||||
}
|
||||
|
||||
private static class RefreshMessage extends Message {
|
||||
private final String channelId;
|
||||
|
||||
public RefreshMessage(InstanceKey instanceKey, String channelId) {
|
||||
super(instanceKey);
|
||||
this.channelId = channelId;
|
||||
}
|
||||
}
|
||||
|
||||
private static class RemoveDevice extends Message {
|
||||
public RemoveDevice(final InstanceKey instanceKey) {
|
||||
super(instanceKey);
|
||||
}
|
||||
}
|
||||
|
||||
private static class StartDiscoveryMessage extends Message {
|
||||
private final EchonetDiscoveryListener echonetDiscoveryListener;
|
||||
|
||||
public StartDiscoveryMessage(EchonetDiscoveryListener echonetDiscoveryListener, InstanceKey discoveryKey) {
|
||||
super(discoveryKey);
|
||||
this.echonetDiscoveryListener = echonetDiscoveryListener;
|
||||
}
|
||||
}
|
||||
|
||||
private static class StopDiscoveryMessage extends Message {
|
||||
public StopDiscoveryMessage(InstanceKey discoveryKey) {
|
||||
super(discoveryKey);
|
||||
}
|
||||
}
|
||||
|
||||
private static class UpdateDevice extends Message {
|
||||
private final String channelId;
|
||||
private final State state;
|
||||
|
||||
public UpdateDevice(final InstanceKey instanceKey, final String channelId, final State state) {
|
||||
super(instanceKey);
|
||||
this.channelId = channelId;
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "UpdateDevice{" + "instanceKey=" + instanceKey + ", channelId='" + channelId + '\'' + ", state="
|
||||
+ state + "} " + super.toString();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,188 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.echonetlite.internal;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
import static org.openhab.binding.echonetlite.internal.EchonetLiteBindingConstants.PROPERTY_NAME_INSTANCE_KEY;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.Channel;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.thing.binding.BaseThingHandler;
|
||||
import org.openhab.core.thing.binding.builder.ChannelBuilder;
|
||||
import org.openhab.core.thing.binding.builder.ThingBuilder;
|
||||
import org.openhab.core.thing.type.ChannelTypeUID;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.openhab.core.types.State;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link EchonetLiteHandler} is responsible for handling commands, which are
|
||||
* sent to one of the channels.
|
||||
*
|
||||
* @author Michael Barker - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class EchonetLiteHandler extends BaseThingHandler implements EchonetDeviceListener {
|
||||
private final Logger logger = LoggerFactory.getLogger(EchonetLiteHandler.class);
|
||||
|
||||
private @Nullable InstanceKey instanceKey;
|
||||
private final Map<String, State> stateByChannelId = new HashMap<>();
|
||||
|
||||
public EchonetLiteHandler(final Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private EchonetLiteBridgeHandler bridgeHandler() {
|
||||
@Nullable
|
||||
final Bridge bridge = getBridge();
|
||||
if (null == bridge) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
final EchonetLiteBridgeHandler handler = (EchonetLiteBridgeHandler) bridge.getHandler();
|
||||
return handler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
@Nullable
|
||||
final EchonetLiteBridgeHandler handler = bridgeHandler();
|
||||
if (null == handler) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
"@text/offline.conf-error.null-bridge-handler");
|
||||
return;
|
||||
}
|
||||
|
||||
if (command instanceof RefreshType) {
|
||||
logger.debug("Refreshing: {}", channelUID);
|
||||
|
||||
final State currentState = stateByChannelId.get(channelUID.getId());
|
||||
if (null == currentState) {
|
||||
handler.refreshDevice(requireNonNull(instanceKey), channelUID.getId());
|
||||
} else {
|
||||
updateState(channelUID, currentState);
|
||||
}
|
||||
} else if (command instanceof State) {
|
||||
logger.debug("Updating: {} to {}", channelUID, command);
|
||||
|
||||
handler.updateDevice(requireNonNull(instanceKey), channelUID.getId(), (State) command);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
final EchonetDeviceConfig config = getConfigAs(EchonetDeviceConfig.class);
|
||||
|
||||
logger.debug("Initialising: {}", config);
|
||||
|
||||
updateStatus(ThingStatus.UNKNOWN);
|
||||
|
||||
@Nullable
|
||||
final EchonetLiteBridgeHandler bridgeHandler = bridgeHandler();
|
||||
if (null == bridgeHandler) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
"@text/offline.conf-error.null-bridge-handler");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
final InetSocketAddress address = new InetSocketAddress(requireNonNull(config.hostname), config.port);
|
||||
final InstanceKey instanceKey = new InstanceKey(address,
|
||||
EchonetClass.resolve(config.groupCode, config.classCode), config.instance);
|
||||
this.instanceKey = instanceKey;
|
||||
|
||||
updateProperty(PROPERTY_NAME_INSTANCE_KEY, instanceKey.representationProperty());
|
||||
bridgeHandler.newDevice(instanceKey, config.pollIntervalMs, config.retryTimeoutMs, this);
|
||||
} catch (Exception e) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public void handleRemoval() {
|
||||
@Nullable
|
||||
final EchonetLiteBridgeHandler bridgeHandler = bridgeHandler();
|
||||
if (null == bridgeHandler) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
"@text/offline.conf-error.null-bridge-handler");
|
||||
return;
|
||||
}
|
||||
|
||||
bridgeHandler.removeDevice(requireNonNull(instanceKey));
|
||||
}
|
||||
|
||||
public void onInitialised(String identifier, InstanceKey instanceKey, Map<String, String> channelIdAndType) {
|
||||
logger.debug("Initialised Channels: {}", channelIdAndType);
|
||||
|
||||
final List<String> toAddChannelFor = new ArrayList<>();
|
||||
|
||||
for (String channelId : channelIdAndType.keySet()) {
|
||||
if (null == thing.getChannel(channelId)) {
|
||||
toAddChannelFor.add(channelId);
|
||||
}
|
||||
}
|
||||
|
||||
logger.debug("Adding Channels: {}", toAddChannelFor);
|
||||
|
||||
if (!toAddChannelFor.isEmpty()) {
|
||||
final ThingBuilder thingBuilder = editThing();
|
||||
|
||||
for (String channelId : toAddChannelFor) {
|
||||
final Channel channel = ChannelBuilder.create(new ChannelUID(thing.getUID(), channelId))
|
||||
.withAcceptedItemType(channelIdAndType.get(channelId))
|
||||
.withType(new ChannelTypeUID(thing.getThingTypeUID().getBindingId(), channelId)).build();
|
||||
thingBuilder.withChannel(channel);
|
||||
|
||||
logger.debug("Added Channel: {}", channel);
|
||||
}
|
||||
|
||||
updateThing(thingBuilder.build());
|
||||
}
|
||||
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
}
|
||||
|
||||
public void onUpdated(final String channelId, final State value) {
|
||||
stateByChannelId.put(channelId, value);
|
||||
|
||||
if (ThingStatus.ONLINE != getThing().getStatus()) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
}
|
||||
updateState(channelId, value);
|
||||
}
|
||||
|
||||
public void onRemoved() {
|
||||
updateStatus(ThingStatus.REMOVED);
|
||||
}
|
||||
|
||||
public void onOffline() {
|
||||
if (ThingStatus.OFFLINE != getThing().getStatus()) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.echonetlite.internal;
|
||||
|
||||
import static org.openhab.binding.echonetlite.internal.EchonetLiteBindingConstants.THING_TYPE_ECHONET_BRIDGE;
|
||||
import static org.openhab.binding.echonetlite.internal.EchonetLiteBindingConstants.THING_TYPE_ECHONET_DEVICE;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerFactory;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
|
||||
/**
|
||||
* The {@link EchonetLiteHandlerFactory} is responsible for creating things and thing
|
||||
* handlers.
|
||||
*
|
||||
* @author Michael Barker - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(configurationPid = "binding.echonetlite", service = ThingHandlerFactory.class)
|
||||
public class EchonetLiteHandlerFactory extends BaseThingHandlerFactory {
|
||||
|
||||
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_ECHONET_DEVICE,
|
||||
THING_TYPE_ECHONET_BRIDGE);
|
||||
|
||||
@Override
|
||||
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
|
||||
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable ThingHandler createHandler(Thing thing) {
|
||||
final ThingTypeUID thingTypeUID = thing.getThingTypeUID();
|
||||
|
||||
if (THING_TYPE_ECHONET_DEVICE.equals(thingTypeUID)) {
|
||||
return new EchonetLiteHandler(thing);
|
||||
} else if (THING_TYPE_ECHONET_BRIDGE.equals(thingTypeUID)) {
|
||||
return new EchonetLiteBridgeHandler((Bridge) thing);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,134 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.echonetlite.internal;
|
||||
|
||||
import java.net.SocketAddress;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* @author Michael Barker - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class EchonetMessage {
|
||||
public static final int TID_OFFSET = 2;
|
||||
public static final int GROUP_OFFSET = 4;
|
||||
public static final int CLASS_OFFSET = 5;
|
||||
public static final int INSTANCE_OFFSET = 6;
|
||||
public static final int ESV_OFFSET = 10;
|
||||
public static final int OPC_OFFSET = 11;
|
||||
public static final int PROPERTY_OFFSET = 12;
|
||||
|
||||
private final ByteBuffer messageData = ByteBuffer.allocateDirect(65536);
|
||||
private final ByteBuffer propertyData = messageData.duplicate();
|
||||
private int propertyCursor = 0;
|
||||
private int currentProperty = -1;
|
||||
|
||||
@Nullable
|
||||
private SocketAddress address;
|
||||
|
||||
public ByteBuffer bufferForRead() {
|
||||
reset();
|
||||
return messageData;
|
||||
}
|
||||
|
||||
private void reset() {
|
||||
messageData.clear();
|
||||
messageData.order(ByteOrder.BIG_ENDIAN);
|
||||
propertyCursor = 0;
|
||||
currentProperty = -1;
|
||||
}
|
||||
|
||||
public void sourceAddress(final SocketAddress address) {
|
||||
this.address = address;
|
||||
}
|
||||
|
||||
public @Nullable SocketAddress sourceAddress() {
|
||||
return address;
|
||||
}
|
||||
|
||||
public @Nullable EchonetClass sourceClass() {
|
||||
return EchonetClassIndex.INSTANCE.lookup(messageData.get(GROUP_OFFSET), messageData.get(CLASS_OFFSET));
|
||||
}
|
||||
|
||||
public byte instance() {
|
||||
return messageData.get(INSTANCE_OFFSET);
|
||||
}
|
||||
|
||||
public Esv esv() {
|
||||
return Esv.forCode(messageData.get(ESV_OFFSET));
|
||||
}
|
||||
|
||||
public int numProperties() {
|
||||
return 0xFF & messageData.get(OPC_OFFSET);
|
||||
}
|
||||
|
||||
public boolean moveNext() {
|
||||
if (propertyCursor < numProperties()) {
|
||||
propertyCursor++;
|
||||
if (-1 == currentProperty) {
|
||||
currentProperty = PROPERTY_OFFSET;
|
||||
} else {
|
||||
int pdc = 0xFF & messageData.get(currentProperty + 1);
|
||||
currentProperty = currentProperty + 2 + pdc;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public int currentEpc() {
|
||||
return messageData.get(currentProperty) & 0xFF;
|
||||
}
|
||||
|
||||
public int currentPdc() {
|
||||
return messageData.get(currentProperty + 1) & 0xFF;
|
||||
}
|
||||
|
||||
public ByteBuffer currentEdt() {
|
||||
propertyData.clear();
|
||||
propertyData.position(currentProperty + 2).limit(currentProperty + 2 + currentPdc());
|
||||
return propertyData;
|
||||
}
|
||||
|
||||
public short tid() {
|
||||
return messageData.getShort(TID_OFFSET);
|
||||
}
|
||||
|
||||
public String toDebug() {
|
||||
return "EchonetMessage{" + "sourceAddress=" + sourceAddress() + ", class=" + sourceClass() + ", instance="
|
||||
+ instance() + ", num properties=" + numProperties() + ", data=" + dumpData() + '}';
|
||||
}
|
||||
|
||||
private String dumpData() {
|
||||
final byte[] bs = new byte[messageData.limit()];
|
||||
final ByteBuffer duplicate = messageData.duplicate();
|
||||
duplicate.position(0).limit(messageData.limit());
|
||||
duplicate.get(bs);
|
||||
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append('[');
|
||||
for (byte b : bs) {
|
||||
sb.append("0x").append(Integer.toHexString(0xFF & b)).append(", ");
|
||||
}
|
||||
sb.setLength(sb.length() - 2);
|
||||
sb.append(']');
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
@ -0,0 +1,103 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.echonetlite.internal;
|
||||
|
||||
import static org.openhab.binding.echonetlite.internal.LangUtil.b;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketAddress;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* @author Michael Barker - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class EchonetMessageBuilder {
|
||||
private static final byte EHD_1 = 0x10;
|
||||
private static final byte EHD_2 = (byte) (0x81 & 0xFF);
|
||||
|
||||
private final ByteBuffer buffer;
|
||||
private final ByteBuffer edtBuffer = ByteBuffer.allocate(4096);
|
||||
private int opcPosition = 0;
|
||||
@Nullable
|
||||
private InetSocketAddress destAddress;
|
||||
|
||||
public EchonetMessageBuilder() {
|
||||
buffer = ByteBuffer.allocateDirect(4096).order(ByteOrder.BIG_ENDIAN);
|
||||
}
|
||||
|
||||
public void start(short tid, InstanceKey source, InstanceKey dest, Esv service) {
|
||||
// 1081000005ff010ef0006201d60100
|
||||
// 1081000105ff010ef0006201d600
|
||||
// 0000 10 81 00 00 05 ff 01 0e f0 00 62 01 d6 01 00
|
||||
// 0000 10 81 00 01 05 ff 01 0e f0 00 62 01 d6 00
|
||||
|
||||
destAddress = dest.address;
|
||||
|
||||
buffer.clear();
|
||||
buffer.put(EHD_1);
|
||||
buffer.put(EHD_2);
|
||||
buffer.putShort(tid);
|
||||
buffer.put(b(source.klass.groupCode()));
|
||||
buffer.put(b(source.klass.classCode()));
|
||||
buffer.put(b(source.instance));
|
||||
buffer.put(b(dest.klass.groupCode()));
|
||||
buffer.put(b(dest.klass.classCode()));
|
||||
buffer.put(b(dest.instance));
|
||||
buffer.put(service.code());
|
||||
|
||||
opcPosition = buffer.position();
|
||||
buffer.put((byte) 0);
|
||||
}
|
||||
|
||||
private void incrementOpc() {
|
||||
buffer.put(opcPosition, (byte) (buffer.get(opcPosition) + 1));
|
||||
}
|
||||
|
||||
public void append(final byte edt, final byte length, final byte value) {
|
||||
buffer.put(edt).put(length).put(value);
|
||||
incrementOpc();
|
||||
}
|
||||
|
||||
public void appendEpcRequest(final int epc) {
|
||||
buffer.put(b(epc)).put((byte) 0);
|
||||
incrementOpc();
|
||||
}
|
||||
|
||||
public ByteBuffer buffer() {
|
||||
return buffer;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public SocketAddress address() {
|
||||
return destAddress;
|
||||
}
|
||||
|
||||
public ByteBuffer edtBuffer() {
|
||||
edtBuffer.clear();
|
||||
return edtBuffer;
|
||||
}
|
||||
|
||||
public void appendEpcUpdate(final int epc, ByteBuffer edtBuffer) {
|
||||
if (edtBuffer.remaining() < 0 || 255 < edtBuffer.remaining()) {
|
||||
throw new IllegalArgumentException("Invalid update value, length: " + edtBuffer.remaining());
|
||||
}
|
||||
|
||||
buffer.put(b(epc)).put(b(edtBuffer.remaining())).put(edtBuffer);
|
||||
incrementOpc();
|
||||
}
|
||||
}
|
@ -0,0 +1,200 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.echonetlite.internal;
|
||||
|
||||
import static org.openhab.binding.echonetlite.internal.EchonetLiteBindingConstants.DEFAULT_RETRY_TIMEOUT_MS;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.types.State;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* @author Michael Barker - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public abstract class EchonetObject {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(EchonetObject.class);
|
||||
|
||||
protected final InstanceKey instanceKey;
|
||||
protected final HashSet<Epc> pendingGets = new HashSet<>();
|
||||
|
||||
protected InflightRequest inflightGetRequest = new InflightRequest(DEFAULT_RETRY_TIMEOUT_MS, "GET");
|
||||
protected InflightRequest inflightSetRequest = new InflightRequest(DEFAULT_RETRY_TIMEOUT_MS, "SET");
|
||||
|
||||
protected long pollIntervalMs;
|
||||
|
||||
public EchonetObject(final InstanceKey instanceKey, final Epc initialProperty) {
|
||||
this.instanceKey = instanceKey;
|
||||
pendingGets.add(initialProperty);
|
||||
}
|
||||
|
||||
public InstanceKey instanceKey() {
|
||||
return instanceKey;
|
||||
}
|
||||
|
||||
public void applyProperty(InstanceKey sourceInstanceKey, Esv esv, final int epcCode, final int pdc,
|
||||
final ByteBuffer edt) {
|
||||
}
|
||||
|
||||
public boolean buildPollMessage(final EchonetMessageBuilder messageBuilder, final ShortSupplier tidSupplier,
|
||||
long nowMs, InstanceKey managementControllerKey) {
|
||||
if (pendingGets.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (hasInflight(nowMs, this.inflightGetRequest)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final short tid = tidSupplier.getAsShort();
|
||||
messageBuilder.start(tid, managementControllerKey, instanceKey(), Esv.Get);
|
||||
|
||||
for (Epc pendingProperty : pendingGets) {
|
||||
messageBuilder.appendEpcRequest(pendingProperty.code());
|
||||
}
|
||||
|
||||
this.inflightGetRequest.requestSent(tid, nowMs);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected boolean hasInflight(long nowMs, InflightRequest inflightRequest) {
|
||||
if (inflightRequest.isInflight()) {
|
||||
return !inflightRequest.hasTimedOut(nowMs);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected void setTimeouts(long pollIntervalMs, long retryTimeoutMs) {
|
||||
this.pollIntervalMs = pollIntervalMs;
|
||||
this.inflightGetRequest = new InflightRequest(retryTimeoutMs, inflightGetRequest);
|
||||
this.inflightSetRequest = new InflightRequest(retryTimeoutMs, inflightSetRequest);
|
||||
}
|
||||
|
||||
public boolean buildUpdateMessage(final EchonetMessageBuilder messageBuilder, final ShortSupplier tid,
|
||||
final long nowMs, InstanceKey managementControllerKey) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public void refreshAll(long nowMs) {
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "ItemBase{" + "instanceKey=" + instanceKey + ", pendingProperties=" + pendingGets + '}';
|
||||
}
|
||||
|
||||
public void update(String channelId, State state) {
|
||||
}
|
||||
|
||||
public void removed() {
|
||||
}
|
||||
|
||||
public void refresh(String channelId) {
|
||||
}
|
||||
|
||||
public void applyHeader(Esv esv, short tid, long nowMs) {
|
||||
if ((esv == Esv.Get_Res || esv == Esv.Get_SNA)) {
|
||||
final long sentTimestampMs = this.inflightGetRequest.timestampMs;
|
||||
if (this.inflightGetRequest.responseReceived(tid)) {
|
||||
logger.debug("{} response time: {}ms", esv, nowMs - sentTimestampMs);
|
||||
} else {
|
||||
logger.warn("Unexpected {} response: {}", esv, tid);
|
||||
this.inflightGetRequest.checkOldResponse(tid, nowMs);
|
||||
}
|
||||
} else if ((esv == Esv.Set_Res || esv == Esv.SetC_SNA)) {
|
||||
final long sentTimestampMs = this.inflightSetRequest.timestampMs;
|
||||
if (this.inflightSetRequest.responseReceived(tid)) {
|
||||
logger.debug("{} response time: {}ms", esv, nowMs - sentTimestampMs);
|
||||
} else {
|
||||
logger.warn("Unexpected {} response: {}", esv, tid);
|
||||
this.inflightSetRequest.checkOldResponse(tid, nowMs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void checkTimeouts() {
|
||||
}
|
||||
|
||||
protected static class InflightRequest {
|
||||
private static final long NULL_TIMESTAMP = -1;
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(InflightRequest.class);
|
||||
private final long timeoutMs;
|
||||
private final String name;
|
||||
private final Map<Short, Long> oldRequests = new HashMap<>();
|
||||
|
||||
private short tid;
|
||||
private long timestampMs = NULL_TIMESTAMP;
|
||||
@SuppressWarnings("unused")
|
||||
private int timeoutCount = 0;
|
||||
|
||||
InflightRequest(long timeoutMs, InflightRequest existing) {
|
||||
this(timeoutMs, existing.name);
|
||||
this.tid = existing.tid;
|
||||
this.timestampMs = existing.timestampMs;
|
||||
}
|
||||
|
||||
InflightRequest(long timeoutMs, String name) {
|
||||
this.timeoutMs = timeoutMs;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
void requestSent(short tid, long timestampMs) {
|
||||
this.tid = tid;
|
||||
this.timestampMs = timestampMs;
|
||||
}
|
||||
|
||||
boolean responseReceived(short tid) {
|
||||
timestampMs = NULL_TIMESTAMP;
|
||||
timeoutCount = 0;
|
||||
|
||||
return this.tid == tid;
|
||||
}
|
||||
|
||||
boolean hasTimedOut(long nowMs) {
|
||||
final boolean timedOut = timestampMs + timeoutMs <= nowMs;
|
||||
if (timedOut) {
|
||||
logger.debug("Timed out {}, tid={}, timestampMs={} + timeoutMs={} <= nowMs={}", name, tid, timestampMs,
|
||||
timeoutMs, nowMs);
|
||||
timeoutCount++;
|
||||
|
||||
if (NULL_TIMESTAMP != tid) {
|
||||
oldRequests.put(tid, timestampMs);
|
||||
}
|
||||
}
|
||||
return timedOut;
|
||||
}
|
||||
|
||||
public boolean isInflight() {
|
||||
return NULL_TIMESTAMP != timestampMs;
|
||||
}
|
||||
|
||||
public void checkOldResponse(short tid, long nowMs) {
|
||||
final Long oldResponseTimestampMs = oldRequests.remove(tid);
|
||||
if (null != oldResponseTimestampMs) {
|
||||
logger.debug("Timed out request, tid={}, actually took={}", tid, nowMs - oldResponseTimestampMs);
|
||||
}
|
||||
}
|
||||
|
||||
public int timeoutCount() {
|
||||
return timeoutCount;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,82 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.echonetlite.internal;
|
||||
|
||||
import static org.openhab.binding.echonetlite.internal.EchonetLiteBindingConstants.DEFAULT_POLL_INTERVAL_MS;
|
||||
import static org.openhab.binding.echonetlite.internal.EchonetLiteBindingConstants.DEFAULT_RETRY_TIMEOUT_MS;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* @author Michael Barker - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class EchonetProfileNode extends EchonetObject implements EchonetDeviceListener {
|
||||
|
||||
private final Consumer<EchonetDevice> newDeviceListener;
|
||||
private final EchonetDiscoveryListener echonetDiscoveryListener;
|
||||
private long lastPollMs = 0;
|
||||
|
||||
public EchonetProfileNode(final InstanceKey instanceKey, Consumer<EchonetDevice> newDeviceListener,
|
||||
EchonetDiscoveryListener echonetDiscoveryListener) {
|
||||
super(instanceKey, Epc.NodeProfile.SELF_NODE_INSTANCE_LIST_S);
|
||||
this.newDeviceListener = newDeviceListener;
|
||||
this.echonetDiscoveryListener = echonetDiscoveryListener;
|
||||
setTimeouts(DEFAULT_POLL_INTERVAL_MS, DEFAULT_RETRY_TIMEOUT_MS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyProperty(InstanceKey sourceInstanceKey, Esv esv, int epcCode, int pdc, ByteBuffer edt) {
|
||||
final Epc epc = Epc.lookup(instanceKey().klass.groupCode(), instanceKey().klass.classCode(), epcCode);
|
||||
|
||||
if (EchonetClass.NODE_PROFILE == sourceInstanceKey.klass && Epc.NodeProfile.SELF_NODE_INSTANCE_LIST_S == epc) {
|
||||
final int selfNodeInstanceCount = edt.get() & 0xFF;
|
||||
|
||||
for (int i = 0; i < selfNodeInstanceCount && edt.hasRemaining(); i++) {
|
||||
final byte groupCode = edt.get();
|
||||
final byte classCode = edt.get();
|
||||
final byte instance = edt.get();
|
||||
final EchonetClass itemClass = EchonetClassIndex.INSTANCE.lookup(groupCode, classCode);
|
||||
|
||||
final InstanceKey newItemKey = new InstanceKey(sourceInstanceKey.address, itemClass, instance);
|
||||
final EchonetDevice discoveredDevice = new EchonetDevice(newItemKey, this);
|
||||
discoveredDevice.setTimeouts(DEFAULT_POLL_INTERVAL_MS, DEFAULT_RETRY_TIMEOUT_MS);
|
||||
newDeviceListener.accept(discoveredDevice);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean buildPollMessage(EchonetMessageBuilder messageBuilder, ShortSupplier tidSupplier, long nowMs,
|
||||
InstanceKey managementControllerKey) {
|
||||
boolean result = false;
|
||||
if (lastPollMs + pollIntervalMs <= nowMs) {
|
||||
result = super.buildPollMessage(messageBuilder, tidSupplier, nowMs, managementControllerKey);
|
||||
|
||||
if (result) {
|
||||
lastPollMs = nowMs;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInitialised(String identifier, InstanceKey instanceKey, Map<String, String> channelIdAndType) {
|
||||
echonetDiscoveryListener.onDeviceFound(identifier, instanceKey);
|
||||
}
|
||||
}
|
@ -0,0 +1,92 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.echonetlite.internal;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Collection;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* @author Michael Barker - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class EchonetPropertyMap {
|
||||
private static final int[][] PROPERTY_MAP = { { 0x80, 0x90, 0xA0, 0xB0, 0xC0, 0xD0, 0xE0, 0xF0, },
|
||||
{ 0x81, 0x91, 0xA1, 0xB1, 0xC1, 0xD1, 0xE1, 0xF1, }, { 0x82, 0x92, 0xA2, 0xB2, 0xC2, 0xD2, 0xE2, 0xF2, },
|
||||
{ 0x83, 0x93, 0xA3, 0xB3, 0xC3, 0xD3, 0xE3, 0xF3, }, { 0x84, 0x94, 0xA4, 0xB4, 0xC4, 0xD4, 0xE4, 0xF4, },
|
||||
{ 0x85, 0x95, 0xA5, 0xB5, 0xC5, 0xD5, 0xE5, 0xF5, }, { 0x86, 0x96, 0xA6, 0xB6, 0xC6, 0xD6, 0xE6, 0xF6, },
|
||||
{ 0x87, 0x97, 0xA7, 0xB7, 0xC7, 0xD7, 0xE7, 0xF7, }, { 0x88, 0x98, 0xA8, 0xB8, 0xC8, 0xD8, 0xE8, 0xF8, },
|
||||
{ 0x89, 0x99, 0xA9, 0xB9, 0xC9, 0xD9, 0xE9, 0xF9, }, { 0x8A, 0x9A, 0xAA, 0xBA, 0xCA, 0xDA, 0xEA, 0xFA, },
|
||||
{ 0x8B, 0x9B, 0xAB, 0xBB, 0xCB, 0xDB, 0xEB, 0xFB, }, { 0x8C, 0x9C, 0xAC, 0xBC, 0xCC, 0xDC, 0xEC, 0xFC, },
|
||||
{ 0x8D, 0x9D, 0xAD, 0xBD, 0xCD, 0xDD, 0xED, 0xFD, }, { 0x8E, 0x9E, 0xAE, 0xBE, 0xCE, 0xDE, 0xEE, 0xFE, },
|
||||
{ 0x8F, 0x9F, 0xAF, 0xBF, 0xCF, 0xDF, 0xEF, 0xFF, }, };
|
||||
|
||||
private int[] propertyMap = {};
|
||||
private final Epc epc;
|
||||
|
||||
public EchonetPropertyMap(final Epc epc) {
|
||||
this.epc = epc;
|
||||
}
|
||||
|
||||
public Epc epc() {
|
||||
return epc;
|
||||
}
|
||||
|
||||
public void update(final ByteBuffer edt) {
|
||||
propertyMap = parsePropertyMap(edt);
|
||||
}
|
||||
|
||||
public void getProperties(int groupCode, int classCode, final Set<Epc> existing, Collection<Epc> toFill) {
|
||||
for (int epcCode : propertyMap) {
|
||||
final Epc epc = Epc.lookup(groupCode, classCode, epcCode);
|
||||
if (!existing.contains(epc)) {
|
||||
toFill.add(epc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int[] parsePropertyMap(final ByteBuffer buffer) {
|
||||
final int numProperties = buffer.get() & 0xFF;
|
||||
final int[] properties = new int[numProperties];
|
||||
int propertyIndex = 0;
|
||||
if (numProperties < 16) {
|
||||
for (int i = 0; i < numProperties; i++) {
|
||||
properties[propertyIndex] = (buffer.get() & 0xFF);
|
||||
propertyIndex++;
|
||||
}
|
||||
} else {
|
||||
assert 16 == buffer.remaining();
|
||||
|
||||
for (int i = 0; i < 16; i++) {
|
||||
int b = buffer.get() & 0xFF;
|
||||
for (int j = 0; j < 8; j++) {
|
||||
if (0 != (b & (1 << j))) {
|
||||
assert propertyIndex < properties.length;
|
||||
|
||||
properties[propertyIndex] = PROPERTY_MAP[i][j];
|
||||
propertyIndex++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assert propertyIndex == properties.length;
|
||||
return properties;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "EnPropertyMap{" + "propertyMap=" + HexUtil.hex(propertyMap) + '}';
|
||||
}
|
||||
}
|
@ -0,0 +1,487 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.echonetlite.internal;
|
||||
|
||||
import static org.openhab.binding.echonetlite.internal.EchonetLiteBindingConstants.ON_OFF_CODEC_30_31;
|
||||
import static org.openhab.binding.echonetlite.internal.EchonetLiteBindingConstants.ON_OFF_CODEC_41_42;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.echonetlite.internal.StateCodec.Option;
|
||||
import org.openhab.binding.echonetlite.internal.StateCodec.OptionCodec;
|
||||
|
||||
/**
|
||||
* @author Michael Barker - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface Epc {
|
||||
int code();
|
||||
|
||||
String name();
|
||||
|
||||
@Nullable
|
||||
default String type() {
|
||||
return null;
|
||||
}
|
||||
|
||||
default String channelId() {
|
||||
return LangUtil.constantToVariable(name());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
default StateDecode decoder() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
default StateEncode encoder() {
|
||||
return null;
|
||||
}
|
||||
|
||||
static Epc lookup(int groupCode, int classCode, int epcCode) {
|
||||
return EpcLookupTable.INSTANCE.resolve(groupCode, classCode, epcCode);
|
||||
}
|
||||
|
||||
// ECHONET SPECIFICATION
|
||||
// APPENDIX Detailed Requirements for ECHONET Device objects
|
||||
// Table 2-1
|
||||
enum Device implements Epc {
|
||||
// @formatter:off
|
||||
OPERATION_STATUS(0x80, ON_OFF_CODEC_30_31),
|
||||
|
||||
INSTALLATION_LOCATION(0x81, new OptionCodec(
|
||||
new Option("Not specified", 0b00000_000),
|
||||
|
||||
new Option("Living Room", 0b00001_000),
|
||||
new Option("Living Room 1", 0b00001_001),
|
||||
new Option("Living Room 2", 0b00001_010),
|
||||
new Option("Living Room 3", 0b00001_011),
|
||||
new Option("Living Room 4", 0b00001_100),
|
||||
new Option("Living Room 5", 0b00001_101),
|
||||
new Option("Living Room 6", 0b00001_110),
|
||||
new Option("Living Room 7", 0b00001_111),
|
||||
|
||||
new Option("Dining Room", 0b00010_000),
|
||||
new Option("Dining Room 1", 0b00010_001),
|
||||
new Option("Dining Room 2", 0b00010_010),
|
||||
new Option("Dining Room 3", 0b00010_011),
|
||||
new Option("Dining Room 4", 0b00010_100),
|
||||
new Option("Dining Room 5", 0b00010_101),
|
||||
new Option("Dining Room 6", 0b00010_110),
|
||||
new Option("Dining Room 7", 0b00010_111),
|
||||
|
||||
new Option("Kitchen", 0b00011_000),
|
||||
new Option("Kitchen 1", 0b00011_001),
|
||||
new Option("Kitchen 2", 0b00011_010),
|
||||
new Option("Kitchen 3", 0b00011_011),
|
||||
new Option("Kitchen 4", 0b00011_100),
|
||||
new Option("Kitchen 5", 0b00011_101),
|
||||
new Option("Kitchen 6", 0b00011_110),
|
||||
new Option("Kitchen 7", 0b00011_111),
|
||||
|
||||
new Option("Lavatory", 0b00100_000),
|
||||
new Option("Lavatory 1", 0b00100_001),
|
||||
new Option("Lavatory 2", 0b00100_010),
|
||||
new Option("Lavatory 3", 0b00100_011),
|
||||
new Option("Lavatory 4", 0b00100_100),
|
||||
new Option("Lavatory 5", 0b00100_101),
|
||||
new Option("Lavatory 6", 0b00100_110),
|
||||
new Option("Lavatory 7", 0b00100_111),
|
||||
|
||||
new Option("Washroom/changing room", 0b00101_000),
|
||||
new Option("Washroom/changing room 1", 0b00101_001),
|
||||
new Option("Washroom/changing room 2", 0b00101_010),
|
||||
new Option("Washroom/changing room 3", 0b00101_011),
|
||||
new Option("Washroom/changing room 4", 0b00101_100),
|
||||
new Option("Washroom/changing room 5", 0b00101_101),
|
||||
new Option("Washroom/changing room 6", 0b00101_110),
|
||||
new Option("Washroom/changing room 7", 0b00101_111),
|
||||
|
||||
new Option("Passageway", 0b00111_000),
|
||||
new Option("Passageway 1", 0b00111_001),
|
||||
new Option("Passageway 2", 0b00111_010),
|
||||
new Option("Passageway 3", 0b00111_011),
|
||||
new Option("Passageway 4", 0b00111_100),
|
||||
new Option("Passageway 5", 0b00111_101),
|
||||
new Option("Passageway 6", 0b00111_110),
|
||||
new Option("Passageway 7", 0b00111_111),
|
||||
|
||||
new Option("Room", 0b01000_000),
|
||||
new Option("Room 1", 0b01000_001),
|
||||
new Option("Room 2", 0b01000_010),
|
||||
new Option("Room 3", 0b01000_011),
|
||||
new Option("Room 4", 0b01000_100),
|
||||
new Option("Room 5", 0b01000_101),
|
||||
new Option("Room 6", 0b01000_110),
|
||||
new Option("Room 7", 0b01000_111),
|
||||
|
||||
new Option("Stairway", 0b01001_000),
|
||||
new Option("Stairway 1", 0b01001_001),
|
||||
new Option("Stairway 2", 0b01001_010),
|
||||
new Option("Stairway 3", 0b01001_011),
|
||||
new Option("Stairway 4", 0b01001_100),
|
||||
new Option("Stairway 5", 0b01001_101),
|
||||
new Option("Stairway 6", 0b01001_110),
|
||||
new Option("Stairway 7", 0b01001_111),
|
||||
|
||||
new Option("Front door", 0b01010_000),
|
||||
new Option("Front door 1", 0b01010_001),
|
||||
new Option("Front door 2", 0b01010_010),
|
||||
new Option("Front door 3", 0b01010_011),
|
||||
new Option("Front door 4", 0b01010_100),
|
||||
new Option("Front door 5", 0b01010_101),
|
||||
new Option("Front door 6", 0b01010_110),
|
||||
new Option("Front door 7", 0b01010_111),
|
||||
|
||||
new Option("Storeroom", 0b01011_000),
|
||||
new Option("Storeroom 1", 0b01011_001),
|
||||
new Option("Storeroom 2", 0b01011_010),
|
||||
new Option("Storeroom 3", 0b01011_011),
|
||||
new Option("Storeroom 4", 0b01011_100),
|
||||
new Option("Storeroom 5", 0b01011_101),
|
||||
new Option("Storeroom 6", 0b01011_110),
|
||||
new Option("Storeroom 7", 0b01011_111),
|
||||
|
||||
new Option("Garden/perimeter", 0b01100_000),
|
||||
new Option("Garden/perimeter 1", 0b01100_001),
|
||||
new Option("Garden/perimeter 2", 0b01100_010),
|
||||
new Option("Garden/perimeter 3", 0b01100_011),
|
||||
new Option("Garden/perimeter 4", 0b01100_100),
|
||||
new Option("Garden/perimeter 5", 0b01100_101),
|
||||
new Option("Garden/perimeter 6", 0b01100_110),
|
||||
new Option("Garden/perimeter 7", 0b01100_111),
|
||||
|
||||
new Option("Garage", 0b01101_000),
|
||||
new Option("Garage 1", 0b01101_001),
|
||||
new Option("Garage 2", 0b01101_010),
|
||||
new Option("Garage 3", 0b01101_011),
|
||||
new Option("Garage 4", 0b01101_100),
|
||||
new Option("Garage 5", 0b01101_101),
|
||||
new Option("Garage 6", 0b01101_110),
|
||||
new Option("Garage 7", 0b01101_111),
|
||||
|
||||
new Option("Veranda/balcony", 0b01110_000),
|
||||
new Option("Veranda/balcony 1", 0b01110_001),
|
||||
new Option("Veranda/balcony 2", 0b01110_010),
|
||||
new Option("Veranda/balcony 3", 0b01110_011),
|
||||
new Option("Veranda/balcony 4", 0b01110_100),
|
||||
new Option("Veranda/balcony 5", 0b01110_101),
|
||||
new Option("Veranda/balcony 6", 0b01110_110),
|
||||
new Option("Veranda/balcony 7", 0b01110_111),
|
||||
|
||||
new Option("Others", 0b01111_000),
|
||||
new Option("Others 1", 0b01111_001),
|
||||
new Option("Others 2", 0b01111_010),
|
||||
new Option("Others 3", 0b01111_011),
|
||||
new Option("Others 4", 0b01111_100),
|
||||
new Option("Others 5", 0b01111_101),
|
||||
new Option("Others 6", 0b01111_110),
|
||||
new Option("Others 7", 0b01111_111))),
|
||||
|
||||
STANDARD_VERSION_INFORMATION(0x82, StateCodec.StandardVersionInformationCodec.INSTANCE, null),
|
||||
IDENTIFICATION_NUMBER(0x83, StateCodec.HexStringCodec.INSTANCE, null),
|
||||
MEASURED_INSTANTANEOUS_POWER_CONSUMPTION(0x84),
|
||||
MEASURED_CUMULATIVE_POWER_CONSUMPTION(0x85),
|
||||
MANUFACTURER_FAULT_CODE(0x86, StateCodec.HexStringCodec.INSTANCE, null),
|
||||
CURRENT_LIMIT_SETTING(0x87),
|
||||
FAULT_STATUS(0x88, ON_OFF_CODEC_41_42, null),
|
||||
FAULT_DESCRIPTION(0x89, StateCodec.HexStringCodec.INSTANCE, null),
|
||||
MANUFACTURER_CODE(0x8A, StateCodec.HexStringCodec.INSTANCE, null),
|
||||
BUSINESS_FACILITY_CODE(0x8B, StateCodec.HexStringCodec.INSTANCE, null),
|
||||
PRODUCT_CODE(0x8C),
|
||||
PRODUCTION_NUMBER(0x8D),
|
||||
PRODUCTION_DATE(0x8E),
|
||||
POWER_SAVING_OPERATION_SETTING(0x8F, ON_OFF_CODEC_41_42),
|
||||
REMOTE_CONTROL_SETTING(0x93),
|
||||
CURRENT_TIME_SETTING(0x97),
|
||||
CURRENT_DATE_SETTING(0x98),
|
||||
POWER_LIMIT_SETTING(0x99),
|
||||
CUMULATIVE_OPERATING_TIME(0x9A, StateCodec.OperatingTimeDecode.INSTANCE, null),
|
||||
SETM_PROPERTY_MAP(0x9B),
|
||||
GETM_PROPERTY_MAP(0x9C),
|
||||
STATUS_CHANGE_ANNOUNCEMENT_PROPERTY_MAP(0x9D),
|
||||
SET_PROPERTY_MAP(0x9E),
|
||||
GET_PROPERTY_MAP(0x9F);
|
||||
// @formatter:on
|
||||
|
||||
public final int code;
|
||||
@Nullable
|
||||
public final StateDecode stateDecode;
|
||||
@Nullable
|
||||
public final StateEncode stateEncode;
|
||||
|
||||
Device(int code) {
|
||||
this(code, null, null);
|
||||
}
|
||||
|
||||
Device(int code, @Nullable StateDecode stateDecode, @Nullable StateEncode stateEncode) {
|
||||
this.code = code;
|
||||
this.stateDecode = stateDecode;
|
||||
this.stateEncode = stateEncode;
|
||||
}
|
||||
|
||||
Device(int code, StateCodec stateCodec) {
|
||||
this(code, stateCodec, stateCodec);
|
||||
}
|
||||
|
||||
public int code() {
|
||||
return code;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public StateDecode decoder() {
|
||||
return stateDecode;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public StateEncode encoder() {
|
||||
return stateEncode;
|
||||
}
|
||||
}
|
||||
|
||||
enum AcGroup implements Epc {
|
||||
// @formatter:off
|
||||
AIR_FLOW_RATE(0xA0, new OptionCodec(
|
||||
new Option("Auto", 0x41),
|
||||
new Option("Rate 1", 0x31),
|
||||
new Option("Rate 2", 0x32),
|
||||
new Option("Rate 3", 0x33),
|
||||
new Option("Rate 4", 0x34),
|
||||
new Option("Rate 5", 0x35),
|
||||
new Option("Rate 6", 0x36),
|
||||
new Option("Rate 7", 0x37),
|
||||
new Option("Rate 8", 0x38))),
|
||||
|
||||
AUTOMATIC_CONTROL_OF_AIR_FLOW_DIRECTION(0xA1, new OptionCodec(
|
||||
new Option("Automatic", 0x41),
|
||||
new Option("Non-automatic", 0x42),
|
||||
new Option("Automatic (vertical)", 0x43),
|
||||
new Option("Automatic (horizontal)", 0x44))),
|
||||
|
||||
AUTOMATIC_SWING_OF_AIR_FLOW(0xA3, new OptionCodec(
|
||||
new Option("Not used", 0x31),
|
||||
new Option("Used (vertical)", 0x41),
|
||||
new Option("Used (horizontal)", 0x42),
|
||||
new Option("Used (vertical and horizontal)", 0x43))),
|
||||
|
||||
AIR_FLOW_DIRECTION_VERTICAL(0xA4, new OptionCodec(
|
||||
new Option("Uppermost", 0x41),
|
||||
new Option("Lowermost", 0x42),
|
||||
new Option("Mid-uppermost", 0x43),
|
||||
new Option("Mid-lowermost", 0x44),
|
||||
new Option("Central", 0x45))),
|
||||
|
||||
AIR_FLOW_DIRECTION_HORIZONTAL(0xA5, new OptionCodec(
|
||||
new Option("XXXOO", 0x41),
|
||||
new Option("OOXXX", 0x42),
|
||||
new Option("XOOOX", 0x43),
|
||||
new Option("OOXOO", 0x44),
|
||||
new Option("XXXXO", 0x51),
|
||||
new Option("XXXOX", 0x52),
|
||||
new Option("XXOXX", 0x54),
|
||||
new Option("XXOXO", 0x55),
|
||||
new Option("XXOOX", 0x56),
|
||||
new Option("XXOOO", 0x57),
|
||||
new Option("XOXXX", 0x58),
|
||||
new Option("XOXXO", 0x59),
|
||||
new Option("XOXOX", 0x5A),
|
||||
new Option("XOXOO", 0x5B),
|
||||
new Option("XOOXX", 0x5C),
|
||||
new Option("XOOXO", 0x5D),
|
||||
new Option("XOOOO", 0x5F),
|
||||
new Option("OXXXX", 0x60),
|
||||
new Option("OXXXO", 0x61),
|
||||
new Option("OXXOX", 0x62),
|
||||
new Option("OXXOO", 0x63),
|
||||
new Option("OXOXX", 0x64),
|
||||
new Option("OXOXO", 0x65),
|
||||
new Option("OXOOX", 0x66),
|
||||
new Option("OXOOO", 0x67),
|
||||
new Option("OOXXO", 0x69),
|
||||
new Option("OOXOX", 0x6A),
|
||||
new Option("OOOXX", 0x6C),
|
||||
new Option("OOOXO", 0x6D),
|
||||
new Option("OOOOX", 0x6E),
|
||||
new Option("OOOOO", 0x6F))),
|
||||
|
||||
SPECIAL_STATE(0xAA),
|
||||
NON_PRIORITY_STATE(0xAB),
|
||||
OPERATION_MODE(0xB0, new OptionCodec(
|
||||
new Option("Automatic", 0x41),
|
||||
new Option("Cooling", 0x42),
|
||||
new Option("Heating", 0x43),
|
||||
new Option("Dry", 0x44),
|
||||
new Option("Fan", 0x45),
|
||||
new Option("Other", 0x40))),
|
||||
|
||||
AUTOMATIC_TEMPERATURE_CONTROL(0xB1),
|
||||
NORMAL_HIGH_SPEED_SILENT_OPERATION(0xB2),
|
||||
SET_TEMPERATURE(0xB3, StateCodec.Temperature8bitCodec.INSTANCE),
|
||||
SET_RELATIVE_HUMIDITY(0xB4),
|
||||
SET_TEMPERATURE_COOLING_MODE(0xB5),
|
||||
SET_TEMPERATURE_HEATING_MODE(0xB6),
|
||||
SET_TEMPERATURE_DEHUMIDIFYING_MODE(0xB7),
|
||||
RATED_POWER_CONSUMPTION(0xB8),
|
||||
MEASURED_CURRENT_CONSUMPTION(0xB9),
|
||||
MEASURED_ROOM_RELATIVE_HUMIDITY(0xBA),
|
||||
MEASURED_ROOM_TEMPERATURE(0xBB, StateCodec.Temperature8bitCodec.INSTANCE, null),
|
||||
SET_TEMPERATURE_USER_REMOTE_CONTROL(0xBC),
|
||||
MEASURED_COOLED_AIR_TEMPERATURE(0xBD),
|
||||
MEASURED_OUTDOOR_TEMPERATURE(0xBE, StateCodec.Temperature8bitCodec.INSTANCE, null),
|
||||
RELATIVE_TEMPERATURE(0xBF);
|
||||
// @formatter:on
|
||||
|
||||
public final int code;
|
||||
|
||||
@Nullable
|
||||
public final StateDecode stateDecode;
|
||||
|
||||
@Nullable
|
||||
public final StateEncode stateEncode;
|
||||
|
||||
AcGroup(int code) {
|
||||
this(code, null, null);
|
||||
}
|
||||
|
||||
AcGroup(int code, @Nullable StateDecode stateDecode, @Nullable StateEncode stateEncode) {
|
||||
this.code = code;
|
||||
this.stateDecode = stateDecode;
|
||||
this.stateEncode = stateEncode;
|
||||
}
|
||||
|
||||
AcGroup(int code, StateCodec stateCodec) {
|
||||
this(code, stateCodec, stateCodec);
|
||||
}
|
||||
|
||||
public int code() {
|
||||
return code;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public StateDecode decoder() {
|
||||
return stateDecode;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public StateEncode encoder() {
|
||||
return stateEncode;
|
||||
}
|
||||
}
|
||||
|
||||
enum HomeAc implements Epc {
|
||||
VENTILATION_FUNCTION(0xC0),
|
||||
HUMIDIFIER_FUNCTION(0xC1),
|
||||
VENTILATION_AIR_FLOW_RATE(0xC3);
|
||||
|
||||
public final int code;
|
||||
|
||||
HomeAc(int code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public int code() {
|
||||
return code;
|
||||
}
|
||||
}
|
||||
|
||||
enum Profile implements Epc {
|
||||
OPERATING_STATUS(0x80, new OptionCodec(new Option("Booting", 0x30), new Option("Not booting", 0x31))),
|
||||
VERSION_INFORMATION(0x82),
|
||||
NODE_IDENTIFICATION_NUMBER(0x83),
|
||||
FAULT_CONTENT(0x89);
|
||||
|
||||
public final int code;
|
||||
|
||||
@Nullable
|
||||
public final StateDecode stateDecode;
|
||||
@Nullable
|
||||
public final StateEncode stateEncode;
|
||||
|
||||
Profile(int code) {
|
||||
this(code, null, null);
|
||||
}
|
||||
|
||||
Profile(int code, @Nullable StateDecode stateDecode, @Nullable StateEncode stateEncode) {
|
||||
this.code = code;
|
||||
this.stateDecode = stateDecode;
|
||||
this.stateEncode = stateEncode;
|
||||
}
|
||||
|
||||
Profile(int code, StateCodec stateCodec) {
|
||||
this(code, stateCodec, stateCodec);
|
||||
}
|
||||
|
||||
public int code() {
|
||||
return code;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public StateDecode decoder() {
|
||||
return stateDecode;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public StateEncode encoder() {
|
||||
return stateEncode;
|
||||
}
|
||||
}
|
||||
|
||||
enum ProfileGroup implements Epc {
|
||||
UNIQUE_IDENTIFIER_CODE(0xBF);
|
||||
|
||||
public final int code;
|
||||
|
||||
ProfileGroup(int code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public int code() {
|
||||
return code;
|
||||
}
|
||||
}
|
||||
|
||||
enum NodeProfile implements Epc {
|
||||
EA(0xE0),
|
||||
NET_ID(0xE1),
|
||||
NODE_D(0xE2),
|
||||
DEFAULT_ROUTER_DATA(0xE3),
|
||||
ALL_ROUTER_DATA(0xE4),
|
||||
LOCK_CONTROL_STATUS(0xEE),
|
||||
LOCK_CONTROL_DATA(0xEF),
|
||||
SECURE_COMMUNICATION_COMMON_KEY_SETUP_USER_KEY(0xC0),
|
||||
SECURE_COMMUNICATION_COMMON_KEY_SETUP_SERVICE_PROVIDER_KEY(0xC1),
|
||||
SECURE_COMMUNICATION_COMMON_KEY_SWITCHOVER_SETUP_USER_KEY(0xC2),
|
||||
SECURE_COMMUNICATION_COMMON_KEY_SWITCHOVER_SETUP_SERVICE_PROVIDER_KEY(0xC3),
|
||||
SECURE_COMMUNICATION_COMMON_KEY_SERIAL_KEY(0xC4),
|
||||
SELF_NODE_INSTANCE_LIST_PAGE(0xD0),
|
||||
SELF_NODE_CLASS_LIST(0xD2),
|
||||
SELF_NODE_INSTANCE_COUNT(0xD3),
|
||||
SELF_NODE_CLASS_COUNT(0xD4),
|
||||
INSTANCE_CHANGE_CLASS_COUNT(0xD5),
|
||||
SELF_NODE_INSTANCE_LIST_S(0xD6),
|
||||
SELF_NODE_CLASS_LIST_S(0xD7),
|
||||
RELATED_TO_OTHER_NODE_EA_LIST(0xD8),
|
||||
RELATED_TO_OTHER_NODE_EA_COUNT(0xD9),
|
||||
GROUP_BROADCAST_NUMBER(0xDA),;
|
||||
|
||||
public final int code;
|
||||
|
||||
NodeProfile(int code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public int code() {
|
||||
return code;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,86 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.echonetlite.internal;
|
||||
|
||||
import static org.openhab.binding.echonetlite.internal.HexUtil.hex;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* @author Michael Barker - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
enum EpcLookupTable {
|
||||
INSTANCE;
|
||||
|
||||
private static final int MAX_ENTRIES = 256;
|
||||
private final Epc[][][] lookupTable = new Epc[MAX_ENTRIES][0][0];
|
||||
|
||||
EpcLookupTable() {
|
||||
addLookupTableEntries(lookupTable, EchonetClass.AIRCON_HOMEAC);
|
||||
addLookupTableEntries(lookupTable, EchonetClass.MANAGEMENT_CONTROLLER);
|
||||
addLookupTableEntries(lookupTable, EchonetClass.NODE_PROFILE);
|
||||
}
|
||||
|
||||
public Epc resolve(int groupCode, int classCode, int epcCode) {
|
||||
if (MAX_ENTRIES <= groupCode) {
|
||||
throw new IllegalArgumentException(MAX_ENTRIES + "<= groupCode (" + groupCode + ")");
|
||||
}
|
||||
if (MAX_ENTRIES <= classCode) {
|
||||
throw new IllegalArgumentException(MAX_ENTRIES + "<= classCode (" + classCode + ")");
|
||||
}
|
||||
if (MAX_ENTRIES <= epcCode) {
|
||||
throw new IllegalArgumentException(MAX_ENTRIES + "<= epcCode (" + epcCode + ")");
|
||||
}
|
||||
|
||||
if (0 == lookupTable[groupCode].length) {
|
||||
throw new IllegalArgumentException("groupCode (" + hex(groupCode) + ") has no entries");
|
||||
}
|
||||
|
||||
if (0 == lookupTable[groupCode][classCode].length) {
|
||||
throw new IllegalArgumentException(
|
||||
"groupCode/classCode (" + hex(groupCode) + "/" + hex(classCode) + ") has no entries");
|
||||
}
|
||||
|
||||
if (null == lookupTable[groupCode][classCode][epcCode]) {
|
||||
throw new IllegalArgumentException("groupCode/classCode (" + hex(groupCode) + "/" + hex(classCode) + "/"
|
||||
+ hex(epcCode) + ") has no entry");
|
||||
}
|
||||
|
||||
return lookupTable[groupCode][classCode][epcCode];
|
||||
}
|
||||
|
||||
private static void addLookupTableEntries(Epc[][][] lookupTable, EchonetClass echonetClass) {
|
||||
final int groupCode = echonetClass.groupCode();
|
||||
final int classCode = echonetClass.classCode();
|
||||
|
||||
if (null == lookupTable[groupCode] || 0 == lookupTable[groupCode].length) {
|
||||
lookupTable[groupCode] = new Epc[MAX_ENTRIES][0];
|
||||
}
|
||||
if (null == lookupTable[groupCode][classCode] || 0 == lookupTable[groupCode][classCode].length) {
|
||||
lookupTable[groupCode][classCode] = new Epc[MAX_ENTRIES];
|
||||
}
|
||||
|
||||
for (Epc value : echonetClass.deviceProperties()) {
|
||||
lookupTable[groupCode][classCode][value.code()] = value;
|
||||
}
|
||||
|
||||
for (Epc value : echonetClass.groupProperties()) {
|
||||
lookupTable[groupCode][classCode][value.code()] = value;
|
||||
}
|
||||
|
||||
for (Epc value : echonetClass.classProperties()) {
|
||||
lookupTable[groupCode][classCode][value.code()] = value;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,88 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.echonetlite.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* @author Michael Barker - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public enum Esv {
|
||||
SetI(0x60),
|
||||
SetC(0x61),
|
||||
Get(0x62),
|
||||
INF_REQ(0x63),
|
||||
SetMI(0x64),
|
||||
SetMC(0x65),
|
||||
GetM(0x66),
|
||||
INFM_REQ(0x67),
|
||||
AddMI(0x68),
|
||||
AddMC(0x69),
|
||||
DelMI(0x6a),
|
||||
DelMC(0x6b),
|
||||
CheckM(0x6c),
|
||||
AddMSI(0x6d),
|
||||
AddMSC(0x6e),
|
||||
Set_Res(0x71),
|
||||
Get_Res(0x72),
|
||||
INF(0x73),
|
||||
INFC(0x74),
|
||||
SetM_Res(0x75),
|
||||
GetM_Res(0x76),
|
||||
INFM(0x77),
|
||||
INFMC(0x78),
|
||||
AddM_Res(0x79),
|
||||
INFC_Res(0x7a),
|
||||
DelM_Res(0x7b),
|
||||
CheckM_Res(0x7d),
|
||||
INFMC_Res(0x7d),
|
||||
AddMS_Res(0x7e),
|
||||
SetI_SNA(0x50),
|
||||
SetC_SNA(0x51),
|
||||
Get_SNA(0x52),
|
||||
INF_SNA(0x53),
|
||||
SetMI_SNA(0x54),
|
||||
SetMC_SNA(0x55),
|
||||
GetM_SNA(0x56),
|
||||
INFM_SNA(0x57),
|
||||
AddMI_SNA(0x58),
|
||||
AddMC_SNA(0x59),
|
||||
DelMI_SNA(0x5a),
|
||||
DelMC_SNA(0x5b),
|
||||
CheckM_SNA(0x5c),
|
||||
AddMSI_SNA(0x5d),
|
||||
AddMSC_SNA(0x5e),
|
||||
Unknown(0x00);
|
||||
|
||||
private final byte code;
|
||||
|
||||
Esv(int code) {
|
||||
this.code = (byte) (code & 0xFF);
|
||||
}
|
||||
|
||||
public static Esv forCode(byte b) {
|
||||
final Esv[] values = values();
|
||||
for (Esv value : values) {
|
||||
if (value.code == b) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("Unable to find Esv for: " + b);
|
||||
}
|
||||
|
||||
public byte code() {
|
||||
return code;
|
||||
}
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.echonetlite.internal;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* @author Michael Barker - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class HexUtil {
|
||||
public static String hex(ByteBuffer buffer) {
|
||||
return hex(buffer, "[", "]", "0x", ",");
|
||||
}
|
||||
|
||||
public static String hex(final ByteBuffer buffer, final String stringPrefix, final String stringSuffix,
|
||||
final String bytePrefix, final String delimiter) {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
sb.append(stringPrefix);
|
||||
for (int i = buffer.position(), n = buffer.limit(); i < n; i++) {
|
||||
final int b = buffer.get(i) & 0xFF;
|
||||
final String prefix = b < 0x10 ? "0" : "";
|
||||
sb.append(bytePrefix).append(prefix).append(Integer.toHexString(b)).append(delimiter);
|
||||
}
|
||||
sb.setLength(sb.length() - delimiter.length());
|
||||
sb.append(stringSuffix);
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static String hex(int[] array, int offset, int length) {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
sb.append('[');
|
||||
for (int i = offset; i < length; i++) {
|
||||
final int b = array[i] & 0xFF;
|
||||
hex(sb, b);
|
||||
sb.append(',');
|
||||
}
|
||||
sb.setLength(sb.length() - 1);
|
||||
sb.append(']');
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private static void hex(final StringBuilder sb, final int b) {
|
||||
final String prefix = b < 0x10 ? "0" : "";
|
||||
sb.append("0x").append(prefix).append(Integer.toHexString(b));
|
||||
}
|
||||
|
||||
public static String hex(final int b) {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
hex(sb, b);
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static String hex(int[] array) {
|
||||
return hex(array, 0, array.length);
|
||||
}
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.echonetlite.internal;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
import static org.openhab.binding.echonetlite.internal.HexUtil.hex;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* @author Michael Barker - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class InstanceKey {
|
||||
final InetSocketAddress address;
|
||||
final EchonetClass klass;
|
||||
final int instance;
|
||||
|
||||
public InstanceKey(final InetSocketAddress address, final EchonetClass klass, final int instance) {
|
||||
this.address = requireNonNull(address);
|
||||
this.klass = requireNonNull(klass);
|
||||
this.instance = instance;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "InstanceKey{" + "address=" + address + ", klass=" + klass + ", instance=" + instance + '}';
|
||||
}
|
||||
|
||||
public String representationProperty() {
|
||||
return address.getAddress().getHostAddress() + "_" + hex(klass.groupCode()) + ":" + hex(klass.classCode()) + ":"
|
||||
+ hex(instance);
|
||||
}
|
||||
|
||||
public boolean equals(@Nullable final Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
final InstanceKey that = (InstanceKey) o;
|
||||
return instance == that.instance && address.equals(that.address) && klass == that.klass;
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
return Objects.hash(address, klass, instance);
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.echonetlite.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* @author Michael Barker - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class LangUtil {
|
||||
public static byte b(int i) {
|
||||
return (byte) (i & 0xFF);
|
||||
}
|
||||
|
||||
public static String constantToVariable(CharSequence constant) {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
boolean shouldCapitalise = false;
|
||||
for (int i = 0, n = constant.length(); i < n; i++) {
|
||||
final char c = constant.charAt(i);
|
||||
if ('_' == c) {
|
||||
shouldCapitalise = true;
|
||||
} else if (shouldCapitalise) {
|
||||
sb.append(Character.toUpperCase(c));
|
||||
shouldCapitalise = false;
|
||||
} else {
|
||||
sb.append(Character.toLowerCase(c));
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.echonetlite.internal;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* @author Michael Barker - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
class MonotonicClock {
|
||||
private final long baseTimeNs;
|
||||
|
||||
MonotonicClock() {
|
||||
baseTimeNs = System.nanoTime();
|
||||
}
|
||||
|
||||
long timeMs() {
|
||||
return TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - baseTimeNs);
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.echonetlite.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* @author Michael Barker - Initial contribution
|
||||
*/
|
||||
@FunctionalInterface
|
||||
@NonNullByDefault
|
||||
public interface ShortSupplier {
|
||||
short getAsShort();
|
||||
}
|
@ -0,0 +1,216 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.echonetlite.internal;
|
||||
|
||||
import static org.openhab.binding.echonetlite.internal.HexUtil.hex;
|
||||
import static org.openhab.binding.echonetlite.internal.LangUtil.b;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.QuantityType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.library.unit.SIUnits;
|
||||
import org.openhab.core.library.unit.Units;
|
||||
import org.openhab.core.types.State;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* @author Michael Barker - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface StateCodec extends StateEncode, StateDecode {
|
||||
|
||||
class OnOffCodec implements StateCodec {
|
||||
private final int on;
|
||||
private final int off;
|
||||
|
||||
public OnOffCodec(int on, int off) {
|
||||
this.on = on;
|
||||
this.off = off;
|
||||
}
|
||||
|
||||
public State decodeState(final ByteBuffer edt) {
|
||||
return b(on) == edt.get() ? OnOffType.ON : OnOffType.OFF;
|
||||
}
|
||||
|
||||
public void encodeState(final State state, final ByteBuffer edt) {
|
||||
final OnOffType onOff = (OnOffType) state;
|
||||
edt.put(onOff == OnOffType.ON ? b(on) : b(off));
|
||||
}
|
||||
|
||||
public String itemType() {
|
||||
return "Switch";
|
||||
}
|
||||
}
|
||||
|
||||
enum StandardVersionInformationCodec implements StateDecode {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
public State decodeState(final ByteBuffer edt) {
|
||||
final int pdc = edt.remaining();
|
||||
if (pdc != 4) {
|
||||
return StringType.EMPTY;
|
||||
}
|
||||
|
||||
return new StringType("" + (char) edt.get(edt.position() + 2));
|
||||
}
|
||||
|
||||
public String itemType() {
|
||||
return "String";
|
||||
}
|
||||
}
|
||||
|
||||
enum HexStringCodec implements StateDecode {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
public State decodeState(final ByteBuffer edt) {
|
||||
return new StringType(hex(edt, "", "", "", ""));
|
||||
}
|
||||
|
||||
public String itemType() {
|
||||
return "String";
|
||||
}
|
||||
}
|
||||
|
||||
enum OperatingTimeDecode implements StateDecode {
|
||||
INSTANCE;
|
||||
|
||||
public State decodeState(final ByteBuffer edt) {
|
||||
// Specification isn't explicit about byte order, but seems to be work with testing.
|
||||
edt.order(ByteOrder.BIG_ENDIAN);
|
||||
|
||||
final int b0 = edt.get() & 0xFF;
|
||||
final long time = edt.getInt() & 0xFFFFFFFFL;
|
||||
|
||||
final TimeUnit timeUnit;
|
||||
switch (b0) {
|
||||
case 0x42:
|
||||
timeUnit = TimeUnit.MINUTES;
|
||||
break;
|
||||
|
||||
case 0x43:
|
||||
timeUnit = TimeUnit.HOURS;
|
||||
break;
|
||||
|
||||
case 0x44:
|
||||
timeUnit = TimeUnit.DAYS;
|
||||
break;
|
||||
|
||||
case 0x41:
|
||||
default:
|
||||
timeUnit = TimeUnit.SECONDS;
|
||||
break;
|
||||
}
|
||||
|
||||
return new QuantityType<>(timeUnit.toSeconds(time), Units.SECOND);
|
||||
}
|
||||
|
||||
public String itemType() {
|
||||
return "Number:Time";
|
||||
}
|
||||
}
|
||||
|
||||
class Option {
|
||||
final String name;
|
||||
final int value;
|
||||
final StringType state;
|
||||
|
||||
public Option(final String name, final int value) {
|
||||
this.name = name;
|
||||
this.value = value;
|
||||
this.state = new StringType(name);
|
||||
}
|
||||
}
|
||||
|
||||
class OptionCodec implements StateCodec {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(OptionCodec.class);
|
||||
private final Map<String, Option> optionByName = new HashMap<>();
|
||||
private final Option[] optionByValue = new Option[256]; // All options values are single bytes on the wire
|
||||
private final StringType unknown = new StringType("Unknown");
|
||||
|
||||
public OptionCodec(Option... options) {
|
||||
for (Option option : options) {
|
||||
optionByName.put(option.name, option);
|
||||
optionByValue[option.value] = option;
|
||||
}
|
||||
}
|
||||
|
||||
public String itemType() {
|
||||
return "String";
|
||||
}
|
||||
|
||||
public State decodeState(final ByteBuffer edt) {
|
||||
final int value = edt.get() & 0xFF;
|
||||
final Option option = optionByValue[value];
|
||||
return null != option ? option.state : unknown;
|
||||
}
|
||||
|
||||
public void encodeState(final State state, final ByteBuffer edt) {
|
||||
final Option option = optionByName.get(state.toFullString());
|
||||
if (null != option) {
|
||||
edt.put(b(option.value));
|
||||
} else {
|
||||
logger.warn("No option specified for: {}", state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum Decimal8bitCodec implements StateCodec {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
public String itemType() {
|
||||
return "Number";
|
||||
}
|
||||
|
||||
public State decodeState(final ByteBuffer edt) {
|
||||
final int value = edt.get(); // Should expand to typed value (mask excluded)
|
||||
return new DecimalType(value);
|
||||
}
|
||||
|
||||
public void encodeState(final State state, final ByteBuffer edt) {
|
||||
edt.put((byte) (((DecimalType) state).intValue()));
|
||||
}
|
||||
}
|
||||
|
||||
enum Temperature8bitCodec implements StateCodec {
|
||||
INSTANCE;
|
||||
|
||||
public State decodeState(final ByteBuffer edt) {
|
||||
final int value = edt.get();
|
||||
return new QuantityType<>(value, SIUnits.CELSIUS);
|
||||
}
|
||||
|
||||
public String itemType() {
|
||||
return "Number:Temperature";
|
||||
}
|
||||
|
||||
public void encodeState(final State state, final ByteBuffer edt) {
|
||||
final @Nullable QuantityType<?> tempCelsius = ((QuantityType<?>) state).toUnit(SIUnits.CELSIUS);
|
||||
edt.put((byte) (Objects.requireNonNull(tempCelsius).intValue()));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.echonetlite.internal;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.types.State;
|
||||
|
||||
/**
|
||||
* @author Michael Barker - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface StateDecode {
|
||||
State decodeState(final ByteBuffer edt);
|
||||
|
||||
String itemType();
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.echonetlite.internal;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.types.State;
|
||||
|
||||
/**
|
||||
* @author Michael Barker - Initial contribution
|
||||
*/
|
||||
@FunctionalInterface
|
||||
@NonNullByDefault
|
||||
public interface StateEncode {
|
||||
void encodeState(final State state, final ByteBuffer edt);
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<binding:binding id="echonetlite" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
|
||||
|
||||
<name>EchonetLite Binding</name>
|
||||
<description>This is the binding for EchonetLite.</description>
|
||||
|
||||
</binding:binding>
|
@ -0,0 +1,251 @@
|
||||
# binding
|
||||
|
||||
binding.echonetlite.name = EchonetLite Binding
|
||||
binding.echonetlite.description = This is the binding for EchonetLite.
|
||||
|
||||
# thing types
|
||||
|
||||
thing-type.echonetlite.bridge.label = Echonet Bridge
|
||||
thing-type.echonetlite.bridge.description = Virtual bridge to ensure that there is only a single binding to the echonet port
|
||||
thing-type.echonetlite.device.label = EchonetLite Device
|
||||
thing-type.echonetlite.device.description = Device for EchonetLite Binding
|
||||
|
||||
# thing types config
|
||||
|
||||
thing-type.config.echonetlite.bridge.multicastAddress.label = Discovery/Notification Address
|
||||
thing-type.config.echonetlite.bridge.multicastAddress.description = Address used to discover nodes and receive notifications
|
||||
thing-type.config.echonetlite.bridge.port.label = Echonet Port
|
||||
thing-type.config.echonetlite.bridge.port.description = Port used for echonet messages both outbound and inbound
|
||||
thing-type.config.echonetlite.device.classCode.label = Class Code
|
||||
thing-type.config.echonetlite.device.classCode.description = Echonet Class Code
|
||||
thing-type.config.echonetlite.device.groupCode.label = Group Code
|
||||
thing-type.config.echonetlite.device.groupCode.description = Echonet Group Code
|
||||
thing-type.config.echonetlite.device.hostname.label = Hostname
|
||||
thing-type.config.echonetlite.device.hostname.description = Hostname or IP address of the device
|
||||
thing-type.config.echonetlite.device.instance.label = Instance
|
||||
thing-type.config.echonetlite.device.instance.description = Echonet Instance
|
||||
thing-type.config.echonetlite.device.pollIntervalMs.label = Poll Interval (ms)
|
||||
thing-type.config.echonetlite.device.pollIntervalMs.description = Interval in ms between each poll of the device
|
||||
thing-type.config.echonetlite.device.port.label = Port
|
||||
thing-type.config.echonetlite.device.port.description = Port of the device (usually 3610)
|
||||
thing-type.config.echonetlite.device.retryTimeoutMs.label = Retry Timeout (ms)
|
||||
thing-type.config.echonetlite.device.retryTimeoutMs.description = Timeout in ms before a message is resent
|
||||
|
||||
# channel types
|
||||
|
||||
channel-type.echonetlite.airFlowDirectionHorizontal.label = Air Flow Direction Horizontal
|
||||
channel-type.echonetlite.airFlowDirectionHorizontal.description = Air Flow Direction Horizontal
|
||||
channel-type.echonetlite.airFlowDirectionHorizontal.state.option.XXXOO = XXXOO
|
||||
channel-type.echonetlite.airFlowDirectionHorizontal.state.option.OOXXX = OOXXX
|
||||
channel-type.echonetlite.airFlowDirectionHorizontal.state.option.XOOOX = XOOOX
|
||||
channel-type.echonetlite.airFlowDirectionHorizontal.state.option.OOXOO = OOXOO
|
||||
channel-type.echonetlite.airFlowDirectionHorizontal.state.option.XXXXO = XXXXO
|
||||
channel-type.echonetlite.airFlowDirectionHorizontal.state.option.XXXOX = XXXOX
|
||||
channel-type.echonetlite.airFlowDirectionHorizontal.state.option.XXOXX = XXOXX
|
||||
channel-type.echonetlite.airFlowDirectionHorizontal.state.option.XXOXO = XXOXO
|
||||
channel-type.echonetlite.airFlowDirectionHorizontal.state.option.XXOOX = XXOOX
|
||||
channel-type.echonetlite.airFlowDirectionHorizontal.state.option.XXOOO = XXOOO
|
||||
channel-type.echonetlite.airFlowDirectionHorizontal.state.option.XOXXX = XOXXX
|
||||
channel-type.echonetlite.airFlowDirectionHorizontal.state.option.XOXXO = XOXXO
|
||||
channel-type.echonetlite.airFlowDirectionHorizontal.state.option.XOXOX = XOXOX
|
||||
channel-type.echonetlite.airFlowDirectionHorizontal.state.option.XOXOO = XOXOO
|
||||
channel-type.echonetlite.airFlowDirectionHorizontal.state.option.XOOXX = XOOXX
|
||||
channel-type.echonetlite.airFlowDirectionHorizontal.state.option.XOOXO = XOOXO
|
||||
channel-type.echonetlite.airFlowDirectionHorizontal.state.option.XOOOO = XOOOO
|
||||
channel-type.echonetlite.airFlowDirectionHorizontal.state.option.OXXXX = OXXXX
|
||||
channel-type.echonetlite.airFlowDirectionHorizontal.state.option.OXXXO = OXXXO
|
||||
channel-type.echonetlite.airFlowDirectionHorizontal.state.option.OXXOX = OXXOX
|
||||
channel-type.echonetlite.airFlowDirectionHorizontal.state.option.OXXOO = OXXOO
|
||||
channel-type.echonetlite.airFlowDirectionHorizontal.state.option.OXOXX = OXOXX
|
||||
channel-type.echonetlite.airFlowDirectionHorizontal.state.option.OXOXO = OXOXO
|
||||
channel-type.echonetlite.airFlowDirectionHorizontal.state.option.OXOOX = OXOOX
|
||||
channel-type.echonetlite.airFlowDirectionHorizontal.state.option.OXOOO = OXOOO
|
||||
channel-type.echonetlite.airFlowDirectionHorizontal.state.option.OOXXO = OOXXO
|
||||
channel-type.echonetlite.airFlowDirectionHorizontal.state.option.OOXOX = OOXOX
|
||||
channel-type.echonetlite.airFlowDirectionHorizontal.state.option.OOOXX = OOOXX
|
||||
channel-type.echonetlite.airFlowDirectionHorizontal.state.option.OOOXO = OOOXO
|
||||
channel-type.echonetlite.airFlowDirectionHorizontal.state.option.OOOOX = OOOOX
|
||||
channel-type.echonetlite.airFlowDirectionHorizontal.state.option.OOOOO = OOOOO
|
||||
channel-type.echonetlite.airFlowDirectionVertical.label = Air Flow Direction Vertical
|
||||
channel-type.echonetlite.airFlowDirectionVertical.description = Air Flow Direction Vertical
|
||||
channel-type.echonetlite.airFlowDirectionVertical.state.option.Uppermost = Uppermost
|
||||
channel-type.echonetlite.airFlowDirectionVertical.state.option.Lowermost = Lowermost
|
||||
channel-type.echonetlite.airFlowDirectionVertical.state.option.Mid-uppermost = Mid-uppermost
|
||||
channel-type.echonetlite.airFlowDirectionVertical.state.option.Mid-lowermost = Mid-lowermost
|
||||
channel-type.echonetlite.airFlowDirectionVertical.state.option.Central = Central
|
||||
channel-type.echonetlite.airFlowRate.label = Air Flow Rate
|
||||
channel-type.echonetlite.airFlowRate.description = Air Flow Rate
|
||||
channel-type.echonetlite.airFlowRate.state.option.Auto = Auto
|
||||
channel-type.echonetlite.airFlowRate.state.option.Rate\ 1 = Rate 1
|
||||
channel-type.echonetlite.airFlowRate.state.option.Rate\ 2 = Rate 2
|
||||
channel-type.echonetlite.airFlowRate.state.option.Rate\ 3 = Rate 3
|
||||
channel-type.echonetlite.airFlowRate.state.option.Rate\ 4 = Rate 4
|
||||
channel-type.echonetlite.airFlowRate.state.option.Rate\ 5 = Rate 5
|
||||
channel-type.echonetlite.airFlowRate.state.option.Rate\ 6 = Rate 6
|
||||
channel-type.echonetlite.airFlowRate.state.option.Rate\ 7 = Rate 7
|
||||
channel-type.echonetlite.airFlowRate.state.option.Rate\ 8 = Rate 8
|
||||
channel-type.echonetlite.automaticControlOfAirFlowDirection.label = Automatic Air Flow Direction
|
||||
channel-type.echonetlite.automaticControlOfAirFlowDirection.description = The type of automatic control applied to the air flow direction, if any
|
||||
channel-type.echonetlite.automaticControlOfAirFlowDirection.state.option.Automatic = Automatic
|
||||
channel-type.echonetlite.automaticControlOfAirFlowDirection.state.option.Non-automatic = Non-automatic
|
||||
channel-type.echonetlite.automaticControlOfAirFlowDirection.state.option.Automatic\ (vertical) = Automatic (vertical)
|
||||
channel-type.echonetlite.automaticControlOfAirFlowDirection.state.option.Automatic\ (horizontal) = Automatic (horizontal)
|
||||
channel-type.echonetlite.automaticSwingOfAirFlow.label = Automatic Swing Of Air Flow
|
||||
channel-type.echonetlite.automaticSwingOfAirFlow.description = Automatic Swing Of Air Flow
|
||||
channel-type.echonetlite.automaticSwingOfAirFlow.state.option.Not\ used = Not used
|
||||
channel-type.echonetlite.automaticSwingOfAirFlow.state.option.Used\ (vertical) = Used (vertical)
|
||||
channel-type.echonetlite.automaticSwingOfAirFlow.state.option.Used\ (horizontal) = Used (horizontal)
|
||||
channel-type.echonetlite.automaticSwingOfAirFlow.state.option.Used\ (vertical\ and\ horizontal) = Used (vertical and horizontal)
|
||||
channel-type.echonetlite.businessFacilityCode.label = Business Facility Code
|
||||
channel-type.echonetlite.businessFacilityCode.description = Business Facility Code
|
||||
channel-type.echonetlite.cumulativeOperatingTime.label = Cumulative Operating Time
|
||||
channel-type.echonetlite.cumulativeOperatingTime.description = Cumulative time the unit has been operating in seconds
|
||||
channel-type.echonetlite.faultDescription.label = Fault Description
|
||||
channel-type.echonetlite.faultDescription.description = Fault Description
|
||||
channel-type.echonetlite.faultStatus.label = Fault Status
|
||||
channel-type.echonetlite.faultStatus.description = Fault Status
|
||||
channel-type.echonetlite.identificationNumber.label = Identification Number
|
||||
channel-type.echonetlite.identificationNumber.description = Identification Number
|
||||
channel-type.echonetlite.installationLocation.label = Installation Location
|
||||
channel-type.echonetlite.installationLocation.description = Installation Location
|
||||
channel-type.echonetlite.installationLocation.state.option.Not\ specified = Not specified
|
||||
channel-type.echonetlite.installationLocation.state.option.Living\ Room = Living Room
|
||||
channel-type.echonetlite.installationLocation.state.option.Living\ Room\ 1 = Living Room 1
|
||||
channel-type.echonetlite.installationLocation.state.option.Living\ Room\ 2 = Living Room 2
|
||||
channel-type.echonetlite.installationLocation.state.option.Living\ Room\ 3 = Living Room 3
|
||||
channel-type.echonetlite.installationLocation.state.option.Living\ Room\ 4 = Living Room 4
|
||||
channel-type.echonetlite.installationLocation.state.option.Living\ Room\ 5 = Living Room 5
|
||||
channel-type.echonetlite.installationLocation.state.option.Living\ Room\ 6 = Living Room 6
|
||||
channel-type.echonetlite.installationLocation.state.option.Living\ Room\ 7 = Living Room 7
|
||||
channel-type.echonetlite.installationLocation.state.option.Dining\ Room = Dining Room
|
||||
channel-type.echonetlite.installationLocation.state.option.Dining\ Room\ 1 = Dining Room 1
|
||||
channel-type.echonetlite.installationLocation.state.option.Dining\ Room\ 2 = Dining Room 2
|
||||
channel-type.echonetlite.installationLocation.state.option.Dining\ Room\ 3 = Dining Room 3
|
||||
channel-type.echonetlite.installationLocation.state.option.Dining\ Room\ 4 = Dining Room 4
|
||||
channel-type.echonetlite.installationLocation.state.option.Dining\ Room\ 5 = Dining Room 5
|
||||
channel-type.echonetlite.installationLocation.state.option.Dining\ Room\ 6 = Dining Room 6
|
||||
channel-type.echonetlite.installationLocation.state.option.Dining\ Room\ 7 = Dining Room 7
|
||||
channel-type.echonetlite.installationLocation.state.option.Kitchen = "Kitchen"
|
||||
channel-type.echonetlite.installationLocation.state.option.Kitchen\ 1 = Kitchen 1
|
||||
channel-type.echonetlite.installationLocation.state.option.Kitchen\ 2 = Kitchen 2
|
||||
channel-type.echonetlite.installationLocation.state.option.Kitchen\ 3 = Kitchen 3
|
||||
channel-type.echonetlite.installationLocation.state.option.Kitchen\ 4 = Kitchen 4
|
||||
channel-type.echonetlite.installationLocation.state.option.Kitchen\ 5 = Kitchen 5
|
||||
channel-type.echonetlite.installationLocation.state.option.Kitchen\ 6 = Kitchen 6
|
||||
channel-type.echonetlite.installationLocation.state.option.Kitchen\ 7 = Kitchen 7
|
||||
channel-type.echonetlite.installationLocation.state.option.Lavatory = "Lavatory"
|
||||
channel-type.echonetlite.installationLocation.state.option.Lavatory\ 1 = Lavatory 1
|
||||
channel-type.echonetlite.installationLocation.state.option.Lavatory\ 2 = Lavatory 2
|
||||
channel-type.echonetlite.installationLocation.state.option.Lavatory\ 3 = Lavatory 3
|
||||
channel-type.echonetlite.installationLocation.state.option.Lavatory\ 4 = Lavatory 4
|
||||
channel-type.echonetlite.installationLocation.state.option.Lavatory\ 5 = Lavatory 5
|
||||
channel-type.echonetlite.installationLocation.state.option.Lavatory\ 6 = Lavatory 6
|
||||
channel-type.echonetlite.installationLocation.state.option.Lavatory\ 7 = Lavatory 7
|
||||
channel-type.echonetlite.installationLocation.state.option.Washroom/changing\ room = Washroom/changing room
|
||||
channel-type.echonetlite.installationLocation.state.option.Washroom/changing\ room\ 1 = Washroom/changing room 1
|
||||
channel-type.echonetlite.installationLocation.state.option.Washroom/changing\ room\ 2 = Washroom/changing room 2
|
||||
channel-type.echonetlite.installationLocation.state.option.Washroom/changing\ room\ 3 = Washroom/changing room 3
|
||||
channel-type.echonetlite.installationLocation.state.option.Washroom/changing\ room\ 4 = Washroom/changing room 4
|
||||
channel-type.echonetlite.installationLocation.state.option.Washroom/changing\ room\ 5 = Washroom/changing room 5
|
||||
channel-type.echonetlite.installationLocation.state.option.Washroom/changing\ room\ 6 = Washroom/changing room 6
|
||||
channel-type.echonetlite.installationLocation.state.option.Washroom/changing\ room\ 7 = Washroom/changing room 7
|
||||
channel-type.echonetlite.installationLocation.state.option.Passageway = "Passageway"
|
||||
channel-type.echonetlite.installationLocation.state.option.Passageway\ 1 = Passageway 1
|
||||
channel-type.echonetlite.installationLocation.state.option.Passageway\ 2 = Passageway 2
|
||||
channel-type.echonetlite.installationLocation.state.option.Passageway\ 3 = Passageway 3
|
||||
channel-type.echonetlite.installationLocation.state.option.Passageway\ 4 = Passageway 4
|
||||
channel-type.echonetlite.installationLocation.state.option.Passageway\ 5 = Passageway 5
|
||||
channel-type.echonetlite.installationLocation.state.option.Passageway\ 6 = Passageway 6
|
||||
channel-type.echonetlite.installationLocation.state.option.Passageway\ 7 = Passageway 7
|
||||
channel-type.echonetlite.installationLocation.state.option.Room = "Room"
|
||||
channel-type.echonetlite.installationLocation.state.option.Room\ 1 = Room 1
|
||||
channel-type.echonetlite.installationLocation.state.option.Room\ 2 = Room 2
|
||||
channel-type.echonetlite.installationLocation.state.option.Room\ 3 = Room 3
|
||||
channel-type.echonetlite.installationLocation.state.option.Room\ 4 = Room 4
|
||||
channel-type.echonetlite.installationLocation.state.option.Room\ 5 = Room 5
|
||||
channel-type.echonetlite.installationLocation.state.option.Room\ 6 = Room 6
|
||||
channel-type.echonetlite.installationLocation.state.option.Room\ 7 = Room 7
|
||||
channel-type.echonetlite.installationLocation.state.option.Stairway = "Stairway"
|
||||
channel-type.echonetlite.installationLocation.state.option.Stairway\ 1 = Stairway 1
|
||||
channel-type.echonetlite.installationLocation.state.option.Stairway\ 2 = Stairway 2
|
||||
channel-type.echonetlite.installationLocation.state.option.Stairway\ 3 = Stairway 3
|
||||
channel-type.echonetlite.installationLocation.state.option.Stairway\ 4 = Stairway 4
|
||||
channel-type.echonetlite.installationLocation.state.option.Stairway\ 5 = Stairway 5
|
||||
channel-type.echonetlite.installationLocation.state.option.Stairway\ 6 = Stairway 6
|
||||
channel-type.echonetlite.installationLocation.state.option.Stairway\ 7 = Stairway 7
|
||||
channel-type.echonetlite.installationLocation.state.option.Front\ door = Front door
|
||||
channel-type.echonetlite.installationLocation.state.option.Front\ door\ 1 = Front door 1
|
||||
channel-type.echonetlite.installationLocation.state.option.Front\ door\ 2 = Front door 2
|
||||
channel-type.echonetlite.installationLocation.state.option.Front\ door\ 3 = Front door 3
|
||||
channel-type.echonetlite.installationLocation.state.option.Front\ door\ 4 = Front door 4
|
||||
channel-type.echonetlite.installationLocation.state.option.Front\ door\ 5 = Front door 5
|
||||
channel-type.echonetlite.installationLocation.state.option.Front\ door\ 6 = Front door 6
|
||||
channel-type.echonetlite.installationLocation.state.option.Front\ door\ 7 = Front door 7
|
||||
channel-type.echonetlite.installationLocation.state.option.Storeroom = "Storeroom"
|
||||
channel-type.echonetlite.installationLocation.state.option.Storeroom\ 1 = Storeroom 1
|
||||
channel-type.echonetlite.installationLocation.state.option.Storeroom\ 2 = Storeroom 2
|
||||
channel-type.echonetlite.installationLocation.state.option.Storeroom\ 3 = Storeroom 3
|
||||
channel-type.echonetlite.installationLocation.state.option.Storeroom\ 4 = Storeroom 4
|
||||
channel-type.echonetlite.installationLocation.state.option.Storeroom\ 5 = Storeroom 5
|
||||
channel-type.echonetlite.installationLocation.state.option.Storeroom\ 6 = Storeroom 6
|
||||
channel-type.echonetlite.installationLocation.state.option.Storeroom\ 7 = Storeroom 7
|
||||
channel-type.echonetlite.installationLocation.state.option.Garden/perimeter = Garden/perimeter
|
||||
channel-type.echonetlite.installationLocation.state.option.Garden/perimeter\ 1 = Garden/perimeter 1
|
||||
channel-type.echonetlite.installationLocation.state.option.Garden/perimeter\ 2 = Garden/perimeter 2
|
||||
channel-type.echonetlite.installationLocation.state.option.Garden/perimeter\ 3 = Garden/perimeter 3
|
||||
channel-type.echonetlite.installationLocation.state.option.Garden/perimeter\ 4 = Garden/perimeter 4
|
||||
channel-type.echonetlite.installationLocation.state.option.Garden/perimeter\ 5 = Garden/perimeter 5
|
||||
channel-type.echonetlite.installationLocation.state.option.Garden/perimeter\ 6 = Garden/perimeter 6
|
||||
channel-type.echonetlite.installationLocation.state.option.Garden/perimeter\ 7 = Garden/perimeter 7
|
||||
channel-type.echonetlite.installationLocation.state.option.Garage = "Garage"
|
||||
channel-type.echonetlite.installationLocation.state.option.Garage\ 1 = Garage 1
|
||||
channel-type.echonetlite.installationLocation.state.option.Garage\ 2 = Garage 2
|
||||
channel-type.echonetlite.installationLocation.state.option.Garage\ 3 = Garage 3
|
||||
channel-type.echonetlite.installationLocation.state.option.Garage\ 4 = Garage 4
|
||||
channel-type.echonetlite.installationLocation.state.option.Garage\ 5 = Garage 5
|
||||
channel-type.echonetlite.installationLocation.state.option.Garage\ 6 = Garage 6
|
||||
channel-type.echonetlite.installationLocation.state.option.Garage\ 7 = Garage 7
|
||||
channel-type.echonetlite.installationLocation.state.option.Veranda/balcony = Veranda/balcony
|
||||
channel-type.echonetlite.installationLocation.state.option.Veranda/balcony\ 1 = Veranda/balcony 1
|
||||
channel-type.echonetlite.installationLocation.state.option.Veranda/balcony\ 2 = Veranda/balcony 2
|
||||
channel-type.echonetlite.installationLocation.state.option.Veranda/balcony\ 3 = Veranda/balcony 3
|
||||
channel-type.echonetlite.installationLocation.state.option.Veranda/balcony\ 4 = Veranda/balcony 4
|
||||
channel-type.echonetlite.installationLocation.state.option.Veranda/balcony\ 5 = Veranda/balcony 5
|
||||
channel-type.echonetlite.installationLocation.state.option.Veranda/balcony\ 6 = Veranda/balcony 6
|
||||
channel-type.echonetlite.installationLocation.state.option.Veranda/balcony\ 7 = Veranda/balcony 7
|
||||
channel-type.echonetlite.installationLocation.state.option.Others = "Others"
|
||||
channel-type.echonetlite.installationLocation.state.option.Others\ 1 = Others 1
|
||||
channel-type.echonetlite.installationLocation.state.option.Others\ 2 = Others 2
|
||||
channel-type.echonetlite.installationLocation.state.option.Others\ 3 = Others 3
|
||||
channel-type.echonetlite.installationLocation.state.option.Others\ 4 = Others 4
|
||||
channel-type.echonetlite.installationLocation.state.option.Others\ 5 = Others 5
|
||||
channel-type.echonetlite.installationLocation.state.option.Others\ 6 = Others 6
|
||||
channel-type.echonetlite.installationLocation.state.option.Others\ 7 = Others 7
|
||||
channel-type.echonetlite.manufacturerCode.label = Manufacturer Code
|
||||
channel-type.echonetlite.manufacturerCode.description = Manufacturer Code
|
||||
channel-type.echonetlite.manufacturerFaultCode.label = Manufacturer Fault Code
|
||||
channel-type.echonetlite.manufacturerFaultCode.description = Manufacturer Fault Code
|
||||
channel-type.echonetlite.measuredOutdoorTemperature.label = Measured Outdoor Temperature
|
||||
channel-type.echonetlite.measuredOutdoorTemperature.description = Measured Outdoor Temperature
|
||||
channel-type.echonetlite.measuredRoomTemperature.label = Measured Room Temperature
|
||||
channel-type.echonetlite.measuredRoomTemperature.description = Measured Room Temperature
|
||||
channel-type.echonetlite.operationMode.label = Operation Mode
|
||||
channel-type.echonetlite.operationMode.description = The current mode for the Home AC unit (heating, cooling, etc.)
|
||||
channel-type.echonetlite.operationMode.state.option.Automatic = Automatic
|
||||
channel-type.echonetlite.operationMode.state.option.Cooling = Cooling
|
||||
channel-type.echonetlite.operationMode.state.option.Heating = Heating
|
||||
channel-type.echonetlite.operationMode.state.option.Dry = Dry
|
||||
channel-type.echonetlite.operationMode.state.option.Fan = Fan
|
||||
channel-type.echonetlite.operationMode.state.option.Other = Other
|
||||
channel-type.echonetlite.operationStatus.label = Operation Status
|
||||
channel-type.echonetlite.operationStatus.description = Operation Status
|
||||
channel-type.echonetlite.powerSavingOperationSetting.label = Power Saving
|
||||
channel-type.echonetlite.powerSavingOperationSetting.description = Controls whether the unit is in power saving operation or not
|
||||
channel-type.echonetlite.setTemperature.label = Set Temperature
|
||||
channel-type.echonetlite.setTemperature.description = Desired target room temperature
|
||||
channel-type.echonetlite.standardVersionInformation.label = Standard Version Information
|
||||
channel-type.echonetlite.standardVersionInformation.description = Standard Version Information
|
||||
|
||||
# thing status descriptions
|
||||
|
||||
offline.conf-error.null-bridge-handler = Bridge is null
|
@ -0,0 +1,378 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="echonetlite"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<channel-type id="operationStatus">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Operation Status</label>
|
||||
<description>Operation Status</description>
|
||||
<category>Switch</category>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="installationLocation">
|
||||
<item-type>String</item-type>
|
||||
<label>Installation Location</label>
|
||||
<description>Installation Location</description>
|
||||
<category>Text</category>
|
||||
<state>
|
||||
<options>
|
||||
<option value="Not specified">Not specified</option>
|
||||
|
||||
<option value="Living Room">Living Room</option>
|
||||
<option value="Living Room 1">Living Room 1</option>
|
||||
<option value="Living Room 2">Living Room 2</option>
|
||||
<option value="Living Room 3">Living Room 3</option>
|
||||
<option value="Living Room 4">Living Room 4</option>
|
||||
<option value="Living Room 5">Living Room 5</option>
|
||||
<option value="Living Room 6">Living Room 6</option>
|
||||
<option value="Living Room 7">Living Room 7</option>
|
||||
|
||||
<option value="Dining Room">Dining Room</option>
|
||||
<option value="Dining Room 1">Dining Room 1</option>
|
||||
<option value="Dining Room 2">Dining Room 2</option>
|
||||
<option value="Dining Room 3">Dining Room 3</option>
|
||||
<option value="Dining Room 4">Dining Room 4</option>
|
||||
<option value="Dining Room 5">Dining Room 5</option>
|
||||
<option value="Dining Room 6">Dining Room 6</option>
|
||||
<option value="Dining Room 7">Dining Room 7</option>
|
||||
|
||||
<option value="Kitchen">"Kitchen"</option>
|
||||
<option value="Kitchen 1">Kitchen 1</option>
|
||||
<option value="Kitchen 2">Kitchen 2</option>
|
||||
<option value="Kitchen 3">Kitchen 3</option>
|
||||
<option value="Kitchen 4">Kitchen 4</option>
|
||||
<option value="Kitchen 5">Kitchen 5</option>
|
||||
<option value="Kitchen 6">Kitchen 6</option>
|
||||
<option value="Kitchen 7">Kitchen 7</option>
|
||||
|
||||
<option value="Lavatory">"Lavatory"</option>
|
||||
<option value="Lavatory 1">Lavatory 1</option>
|
||||
<option value="Lavatory 2">Lavatory 2</option>
|
||||
<option value="Lavatory 3">Lavatory 3</option>
|
||||
<option value="Lavatory 4">Lavatory 4</option>
|
||||
<option value="Lavatory 5">Lavatory 5</option>
|
||||
<option value="Lavatory 6">Lavatory 6</option>
|
||||
<option value="Lavatory 7">Lavatory 7</option>
|
||||
|
||||
<option value="Washroom/changing room">Washroom/changing room</option>
|
||||
<option value="Washroom/changing room 1">Washroom/changing room 1</option>
|
||||
<option value="Washroom/changing room 2">Washroom/changing room 2</option>
|
||||
<option value="Washroom/changing room 3">Washroom/changing room 3</option>
|
||||
<option value="Washroom/changing room 4">Washroom/changing room 4</option>
|
||||
<option value="Washroom/changing room 5">Washroom/changing room 5</option>
|
||||
<option value="Washroom/changing room 6">Washroom/changing room 6</option>
|
||||
<option value="Washroom/changing room 7">Washroom/changing room 7</option>
|
||||
|
||||
<option value="Passageway">"Passageway"</option>
|
||||
<option value="Passageway 1">Passageway 1</option>
|
||||
<option value="Passageway 2">Passageway 2</option>
|
||||
<option value="Passageway 3">Passageway 3</option>
|
||||
<option value="Passageway 4">Passageway 4</option>
|
||||
<option value="Passageway 5">Passageway 5</option>
|
||||
<option value="Passageway 6">Passageway 6</option>
|
||||
<option value="Passageway 7">Passageway 7</option>
|
||||
|
||||
<option value="Room">"Room"</option>
|
||||
<option value="Room 1">Room 1</option>
|
||||
<option value="Room 2">Room 2</option>
|
||||
<option value="Room 3">Room 3</option>
|
||||
<option value="Room 4">Room 4</option>
|
||||
<option value="Room 5">Room 5</option>
|
||||
<option value="Room 6">Room 6</option>
|
||||
<option value="Room 7">Room 7</option>
|
||||
|
||||
<option value="Stairway">"Stairway"</option>
|
||||
<option value="Stairway 1">Stairway 1</option>
|
||||
<option value="Stairway 2">Stairway 2</option>
|
||||
<option value="Stairway 3">Stairway 3</option>
|
||||
<option value="Stairway 4">Stairway 4</option>
|
||||
<option value="Stairway 5">Stairway 5</option>
|
||||
<option value="Stairway 6">Stairway 6</option>
|
||||
<option value="Stairway 7">Stairway 7</option>
|
||||
|
||||
<option value="Front door">Front door</option>
|
||||
<option value="Front door 1">Front door 1</option>
|
||||
<option value="Front door 2">Front door 2</option>
|
||||
<option value="Front door 3">Front door 3</option>
|
||||
<option value="Front door 4">Front door 4</option>
|
||||
<option value="Front door 5">Front door 5</option>
|
||||
<option value="Front door 6">Front door 6</option>
|
||||
<option value="Front door 7">Front door 7</option>
|
||||
|
||||
<option value="Storeroom">"Storeroom"</option>
|
||||
<option value="Storeroom 1">Storeroom 1</option>
|
||||
<option value="Storeroom 2">Storeroom 2</option>
|
||||
<option value="Storeroom 3">Storeroom 3</option>
|
||||
<option value="Storeroom 4">Storeroom 4</option>
|
||||
<option value="Storeroom 5">Storeroom 5</option>
|
||||
<option value="Storeroom 6">Storeroom 6</option>
|
||||
<option value="Storeroom 7">Storeroom 7</option>
|
||||
|
||||
<option value="Garden/perimeter">Garden/perimeter</option>
|
||||
<option value="Garden/perimeter 1">Garden/perimeter 1</option>
|
||||
<option value="Garden/perimeter 2">Garden/perimeter 2</option>
|
||||
<option value="Garden/perimeter 3">Garden/perimeter 3</option>
|
||||
<option value="Garden/perimeter 4">Garden/perimeter 4</option>
|
||||
<option value="Garden/perimeter 5">Garden/perimeter 5</option>
|
||||
<option value="Garden/perimeter 6">Garden/perimeter 6</option>
|
||||
<option value="Garden/perimeter 7">Garden/perimeter 7</option>
|
||||
|
||||
<option value="Garage">"Garage"</option>
|
||||
<option value="Garage 1">Garage 1</option>
|
||||
<option value="Garage 2">Garage 2</option>
|
||||
<option value="Garage 3">Garage 3</option>
|
||||
<option value="Garage 4">Garage 4</option>
|
||||
<option value="Garage 5">Garage 5</option>
|
||||
<option value="Garage 6">Garage 6</option>
|
||||
<option value="Garage 7">Garage 7</option>
|
||||
|
||||
<option value="Veranda/balcony">Veranda/balcony</option>
|
||||
<option value="Veranda/balcony 1">Veranda/balcony 1</option>
|
||||
<option value="Veranda/balcony 2">Veranda/balcony 2</option>
|
||||
<option value="Veranda/balcony 3">Veranda/balcony 3</option>
|
||||
<option value="Veranda/balcony 4">Veranda/balcony 4</option>
|
||||
<option value="Veranda/balcony 5">Veranda/balcony 5</option>
|
||||
<option value="Veranda/balcony 6">Veranda/balcony 6</option>
|
||||
<option value="Veranda/balcony 7">Veranda/balcony 7</option>
|
||||
|
||||
<option value="Others">"Others"</option>
|
||||
<option value="Others 1">Others 1</option>
|
||||
<option value="Others 2">Others 2</option>
|
||||
<option value="Others 3">Others 3</option>
|
||||
<option value="Others 4">Others 4</option>
|
||||
<option value="Others 5">Others 5</option>
|
||||
<option value="Others 6">Others 6</option>
|
||||
<option value="Others 7">Others 7</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="standardVersionInformation">
|
||||
<item-type>String</item-type>
|
||||
<label>Standard Version Information</label>
|
||||
<description>Standard Version Information</description>
|
||||
<category>Text</category>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="identificationNumber">
|
||||
<item-type>String</item-type>
|
||||
<label>Identification Number</label>
|
||||
<description>Identification Number</description>
|
||||
<category>Text</category>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="manufacturerFaultCode">
|
||||
<item-type>String</item-type>
|
||||
<label>Manufacturer Fault Code</label>
|
||||
<description>Manufacturer Fault Code</description>
|
||||
<category>Text</category>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="faultStatus">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Fault Status</label>
|
||||
<description>Fault Status</description>
|
||||
<category>Alarm</category>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="faultDescription">
|
||||
<item-type>String</item-type>
|
||||
<label>Fault Description</label>
|
||||
<description>Fault Description</description>
|
||||
<category>Text</category>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="manufacturerCode">
|
||||
<item-type>String</item-type>
|
||||
<label>Manufacturer Code</label>
|
||||
<description>Manufacturer Code</description>
|
||||
<category>Text</category>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="businessFacilityCode">
|
||||
<item-type>String</item-type>
|
||||
<label>Business Facility Code</label>
|
||||
<description>Business Facility Code</description>
|
||||
<category>Text</category>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="powerSavingOperationSetting">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Power Saving</label>
|
||||
<description>Controls whether the unit is in power saving operation or not</description>
|
||||
<category>Switch</category>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="cumulativeOperatingTime">
|
||||
<item-type>Number:Time</item-type>
|
||||
<label>Cumulative Operating Time</label>
|
||||
<description>Cumulative time the unit has been operating in seconds</description>
|
||||
<category>Time</category>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="airFlowRate">
|
||||
<item-type>String</item-type>
|
||||
<label>Air Flow Rate</label>
|
||||
<description>Air Flow Rate</description>
|
||||
<category>Flow</category>
|
||||
<state>
|
||||
<options>
|
||||
<option value="Auto">Auto</option>
|
||||
<option value="Rate 1">Rate 1</option>
|
||||
<option value="Rate 2">Rate 2</option>
|
||||
<option value="Rate 3">Rate 3</option>
|
||||
<option value="Rate 4">Rate 4</option>
|
||||
<option value="Rate 5">Rate 5</option>
|
||||
<option value="Rate 6">Rate 6</option>
|
||||
<option value="Rate 7">Rate 7</option>
|
||||
<option value="Rate 8">Rate 8</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="automaticControlOfAirFlowDirection">
|
||||
<item-type>String</item-type>
|
||||
<label>Automatic Air Flow Direction</label>
|
||||
<description>The type of automatic control applied to the air flow direction, if any</description>
|
||||
<state>
|
||||
<options>
|
||||
<option value="Automatic">Automatic</option>
|
||||
<option value="Non-automatic">Non-automatic</option>
|
||||
<option value="Automatic (vertical)">Automatic (vertical)</option>
|
||||
<option value="Automatic (horizontal)">Automatic (horizontal)</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="automaticSwingOfAirFlow">
|
||||
<item-type>String</item-type>
|
||||
<label>Automatic Swing Of Air Flow</label>
|
||||
<description>Automatic Swing Of Air Flow</description>
|
||||
<state>
|
||||
<options>
|
||||
<option value="Not used">Not used</option>
|
||||
<option value="Used (vertical)">Used (vertical)</option>
|
||||
<option value="Used (horizontal)">Used (horizontal)</option>
|
||||
<option value="Used (vertical and horizontal)">Used (vertical and horizontal)</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="airFlowDirectionVertical">
|
||||
<item-type>String</item-type>
|
||||
<label>Air Flow Direction Vertical</label>
|
||||
<description>Air Flow Direction Vertical</description>
|
||||
<state>
|
||||
<options>
|
||||
<option value="Uppermost">Uppermost</option>
|
||||
<option value="Lowermost">Lowermost</option>
|
||||
<option value="Mid-uppermost">Mid-uppermost</option>
|
||||
<option value="Mid-lowermost">Mid-lowermost</option>
|
||||
<option value="Central">Central</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="airFlowDirectionHorizontal">
|
||||
<item-type>String</item-type>
|
||||
<label>Air Flow Direction Horizontal</label>
|
||||
<description>Air Flow Direction Horizontal</description>
|
||||
<state>
|
||||
<options>
|
||||
<option value="XXXOO">XXXOO</option>
|
||||
<option value="OOXXX">OOXXX</option>
|
||||
<option value="XOOOX">XOOOX</option>
|
||||
<option value="OOXOO">OOXOO</option>
|
||||
<option value="XXXXO">XXXXO</option>
|
||||
<option value="XXXOX">XXXOX</option>
|
||||
<option value="XXOXX">XXOXX</option>
|
||||
<option value="XXOXO">XXOXO</option>
|
||||
<option value="XXOOX">XXOOX</option>
|
||||
<option value="XXOOO">XXOOO</option>
|
||||
<option value="XOXXX">XOXXX</option>
|
||||
<option value="XOXXO">XOXXO</option>
|
||||
<option value="XOXOX">XOXOX</option>
|
||||
<option value="XOXOO">XOXOO</option>
|
||||
<option value="XOOXX">XOOXX</option>
|
||||
<option value="XOOXO">XOOXO</option>
|
||||
<option value="XOOOO">XOOOO</option>
|
||||
<option value="OXXXX">OXXXX</option>
|
||||
<option value="OXXXO">OXXXO</option>
|
||||
<option value="OXXOX">OXXOX</option>
|
||||
<option value="OXXOO">OXXOO</option>
|
||||
<option value="OXOXX">OXOXX</option>
|
||||
<option value="OXOXO">OXOXO</option>
|
||||
<option value="OXOOX">OXOOX</option>
|
||||
<option value="OXOOO">OXOOO</option>
|
||||
<option value="OOXXO">OOXXO</option>
|
||||
<option value="OOXOX">OOXOX</option>
|
||||
<option value="OOOXX">OOOXX</option>
|
||||
<option value="OOOXO">OOOXO</option>
|
||||
<option value="OOOOX">OOOOX</option>
|
||||
<option value="OOOOO">OOOOO</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="operationMode">
|
||||
<item-type>String</item-type>
|
||||
<label>Operation Mode</label>
|
||||
<description>The current mode for the Home AC unit (heating, cooling, etc.)</description>
|
||||
<state>
|
||||
<options>
|
||||
<option value="Automatic">Automatic</option>
|
||||
<option value="Cooling">Cooling</option>
|
||||
<option value="Heating">Heating</option>
|
||||
<option value="Dry">Dry</option>
|
||||
<option value="Fan">Fan</option>
|
||||
<option value="Other">Other</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="setTemperature">
|
||||
<item-type>Number:Temperature</item-type>
|
||||
<label>Set Temperature</label>
|
||||
<description>Desired target room temperature</description>
|
||||
<category>Temperature</category>
|
||||
<tags>
|
||||
<tag>Setpoint</tag>
|
||||
<tag>Temperature</tag>
|
||||
</tags>
|
||||
<state min="0" max="50" pattern="%d %unit%" readOnly="false"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="measuredRoomTemperature">
|
||||
<item-type>Number:Temperature</item-type>
|
||||
<label>Measured Room Temperature</label>
|
||||
<description>Measured Room Temperature</description>
|
||||
<category>Temperature</category>
|
||||
<tags>
|
||||
<tag>Measurement</tag>
|
||||
<tag>Temperature</tag>
|
||||
</tags>
|
||||
<state min="-127" max="125" pattern="%d %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="measuredOutdoorTemperature">
|
||||
<item-type>Number:Temperature</item-type>
|
||||
<label>Measured Outdoor Temperature</label>
|
||||
<description>Measured Outdoor Temperature</description>
|
||||
<category>Temperature</category>
|
||||
<tags>
|
||||
<tag>Measurement</tag>
|
||||
<tag>Temperature</tag>
|
||||
</tags>
|
||||
<state min="-127" max="125" pattern="%d %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
</thing:thing-descriptions>
|
@ -0,0 +1,73 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="echonetlite"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<bridge-type id="bridge">
|
||||
<label>Echonet Bridge</label>
|
||||
<description>Virtual bridge to ensure that there is only a single binding to the echonet port</description>
|
||||
|
||||
<representation-property>port</representation-property>
|
||||
|
||||
<config-description>
|
||||
<parameter name="multicastAddress" type="text" required="true">
|
||||
<context>network-address</context>
|
||||
<label>Discovery/Notification Address</label>
|
||||
<description>Address used to discover nodes and receive notifications</description>
|
||||
<default>224.0.23.0</default>
|
||||
</parameter>
|
||||
<parameter name="port" type="integer" required="true">
|
||||
<label>Echonet Port</label>
|
||||
<description>Port used for echonet messages both outbound and inbound</description>
|
||||
<default>3610</default>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</bridge-type>
|
||||
|
||||
<thing-type id="device">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="bridge"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>EchonetLite Device</label>
|
||||
<description>Device for EchonetLite Binding</description>
|
||||
<representation-property>instanceKey</representation-property>
|
||||
|
||||
<config-description>
|
||||
<parameter name="hostname" type="text" required="true">
|
||||
<context>network-address</context>
|
||||
<label>Hostname</label>
|
||||
<description>Hostname or IP address of the device</description>
|
||||
</parameter>
|
||||
<parameter name="port" type="integer" required="true">
|
||||
<default>3610</default>
|
||||
<label>Port</label>
|
||||
<description>Port of the device (usually 3610)</description>
|
||||
</parameter>
|
||||
<parameter name="groupCode" type="integer" required="true">
|
||||
<label>Group Code</label>
|
||||
<description>Echonet Group Code</description>
|
||||
</parameter>
|
||||
<parameter name="classCode" type="integer" required="true">
|
||||
<label>Class Code</label>
|
||||
<description>Echonet Class Code</description>
|
||||
</parameter>
|
||||
<parameter name="instance" type="integer" required="true">
|
||||
<label>Instance</label>
|
||||
<description>Echonet Instance</description>
|
||||
</parameter>
|
||||
<parameter name="pollIntervalMs" type="integer" required="true">
|
||||
<default>30000</default>
|
||||
<label>Poll Interval (ms)</label>
|
||||
<description>Interval in ms between each poll of the device</description>
|
||||
</parameter>
|
||||
<parameter name="retryTimeoutMs" type="integer" required="true">
|
||||
<default>2000</default>
|
||||
<label>Retry Timeout (ms)</label>
|
||||
<description>Timeout in ms before a message is resent</description>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</thing-type>
|
||||
|
||||
</thing:thing-descriptions>
|
@ -0,0 +1,43 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.echonetlite.internal;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.openhab.binding.echonetlite.internal.EchonetClass.AIRCON_HOMEAC;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* @author Michael Barker - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
class EpcTest {
|
||||
|
||||
@Test
|
||||
void shouldLookupEpc() {
|
||||
final EchonetClass echonetClass = AIRCON_HOMEAC;
|
||||
|
||||
for (Epc epc : Epc.Device.values()) {
|
||||
assertEquals(epc, Epc.lookup(echonetClass.groupCode(), echonetClass.classCode(), epc.code()));
|
||||
}
|
||||
|
||||
for (Epc epc : Epc.AcGroup.values()) {
|
||||
assertEquals(epc, Epc.lookup(echonetClass.groupCode(), echonetClass.classCode(), epc.code()));
|
||||
}
|
||||
|
||||
for (Epc epc : Epc.HomeAc.values()) {
|
||||
assertEquals(epc, Epc.lookup(echonetClass.groupCode(), echonetClass.classCode(), epc.code()));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.echonetlite.internal.protocol;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.openhab.binding.echonetlite.internal.LangUtil.constantToVariable;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* @author Michael Barker - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
class LangUtilTest {
|
||||
|
||||
@Test
|
||||
void shouldConvertConstantToVariable() {
|
||||
assertEquals("operationStatus", constantToVariable("OPERATION_STATUS"));
|
||||
}
|
||||
}
|
@ -0,0 +1,130 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.echonetlite.internal.protocol;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.openhab.binding.echonetlite.internal.LangUtil.b;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.openhab.binding.echonetlite.internal.StateCodec;
|
||||
import org.openhab.binding.echonetlite.internal.StateCodec.HexStringCodec;
|
||||
import org.openhab.binding.echonetlite.internal.StateCodec.OperatingTimeDecode;
|
||||
import org.openhab.binding.echonetlite.internal.StateCodec.Option;
|
||||
import org.openhab.binding.echonetlite.internal.StateCodec.OptionCodec;
|
||||
import org.openhab.binding.echonetlite.internal.StateCodec.StandardVersionInformationCodec;
|
||||
import org.openhab.binding.echonetlite.internal.StateDecode;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.QuantityType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.library.unit.SIUnits;
|
||||
import org.openhab.core.types.State;
|
||||
|
||||
/**
|
||||
* @author Michael Barker - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
class StateCodecTest {
|
||||
private void assertEncodeDecode(StateCodec stateCodec, State state, byte[] expectedOutput) {
|
||||
final ByteBuffer buffer = ByteBuffer.allocate(1024);
|
||||
stateCodec.encodeState(state, buffer);
|
||||
buffer.flip();
|
||||
|
||||
final byte[] encoded = new byte[buffer.remaining()];
|
||||
buffer.get(encoded);
|
||||
assertArrayEquals(expectedOutput, encoded);
|
||||
|
||||
buffer.flip();
|
||||
|
||||
assertEquals(state, stateCodec.decodeState(buffer));
|
||||
}
|
||||
|
||||
private void assertDecode(StateDecode stateDecode, State expectedState, byte[] data) {
|
||||
assertEquals(expectedState, stateDecode.decodeState(ByteBuffer.wrap(data)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldEncodeOnOff() {
|
||||
final int on = 34;
|
||||
final int off = 27;
|
||||
final StateCodec.OnOffCodec onOffCodec = new StateCodec.OnOffCodec(on, off);
|
||||
|
||||
assertEncodeDecode(onOffCodec, OnOffType.ON, new byte[] { b(on) });
|
||||
assertEncodeDecode(onOffCodec, OnOffType.OFF, new byte[] { b(off) });
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldDecodeStandardVersionInformation() {
|
||||
assertDecode(StandardVersionInformationCodec.INSTANCE, StringType.EMPTY, new byte[0]);
|
||||
assertDecode(StandardVersionInformationCodec.INSTANCE, StringType.EMPTY, new byte[1]);
|
||||
assertDecode(StandardVersionInformationCodec.INSTANCE, StringType.EMPTY, new byte[2]);
|
||||
assertDecode(StandardVersionInformationCodec.INSTANCE, StringType.EMPTY, new byte[3]);
|
||||
assertDecode(StandardVersionInformationCodec.INSTANCE, StringType.EMPTY, new byte[5]);
|
||||
assertDecode(StandardVersionInformationCodec.INSTANCE, new StringType("A"), new byte[] { 0, 0, 'A', 0 });
|
||||
assertDecode(StandardVersionInformationCodec.INSTANCE, new StringType("Z"), new byte[] { 0, 0, 'Z', 0 });
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldDecodeHexString() {
|
||||
assertDecode(HexStringCodec.INSTANCE, new StringType("000102030467"), new byte[] { 0, 1, 2, 3, 4, b(0x67) });
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldDecodeCumulativeOperatingTime() {
|
||||
final ByteBuffer buffer = ByteBuffer.wrap(new byte[5]);
|
||||
buffer.order(ByteOrder.BIG_ENDIAN);
|
||||
|
||||
final int valueInSeconds = 484260;
|
||||
final long valueInMinutes = TimeUnit.SECONDS.toMinutes(valueInSeconds);
|
||||
buffer.put(b(0x42));
|
||||
buffer.putInt((int) valueInMinutes);
|
||||
|
||||
buffer.flip();
|
||||
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||
assertEquals(valueInSeconds, ((QuantityType<?>) OperatingTimeDecode.INSTANCE.decodeState(buffer)).intValue());
|
||||
|
||||
buffer.flip();
|
||||
buffer.order(ByteOrder.BIG_ENDIAN);
|
||||
assertEquals(valueInSeconds, ((QuantityType<?>) OperatingTimeDecode.INSTANCE.decodeState(buffer)).intValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldEncodeDecodeOption() {
|
||||
final OptionCodec optionCodec = new OptionCodec(new Option("ABC", 123), new Option("DEF", 101));
|
||||
assertEncodeDecode(optionCodec, new StringType("ABC"), new byte[] { 123 });
|
||||
assertEncodeDecode(optionCodec, new StringType("DEF"), new byte[] { 101 });
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldEncodeAndDecode8Bit() {
|
||||
assertEncodeDecode(StateCodec.Decimal8bitCodec.INSTANCE, new DecimalType(123), new byte[] { 123 });
|
||||
assertEncodeDecode(StateCodec.Decimal8bitCodec.INSTANCE, new DecimalType(1), new byte[] { 1 });
|
||||
assertEncodeDecode(StateCodec.Decimal8bitCodec.INSTANCE, new DecimalType(-1), new byte[] { b(255) });
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldEncodeAndDecodeTemperature() {
|
||||
assertEncodeDecode(StateCodec.Temperature8bitCodec.INSTANCE, new QuantityType<>(123, SIUnits.CELSIUS),
|
||||
new byte[] { 123 });
|
||||
assertEncodeDecode(StateCodec.Temperature8bitCodec.INSTANCE, new QuantityType<>(1, SIUnits.CELSIUS),
|
||||
new byte[] { 1 });
|
||||
assertEncodeDecode(StateCodec.Temperature8bitCodec.INSTANCE, new QuantityType<>(-1, SIUnits.CELSIUS),
|
||||
new byte[] { b(255) });
|
||||
}
|
||||
}
|
@ -110,6 +110,7 @@
|
||||
<module>org.openhab.binding.dwdpollenflug</module>
|
||||
<module>org.openhab.binding.dwdunwetter</module>
|
||||
<module>org.openhab.binding.easee</module>
|
||||
<module>org.openhab.binding.echonetlite</module>
|
||||
<module>org.openhab.binding.ecobee</module>
|
||||
<module>org.openhab.binding.ecotouch</module>
|
||||
<module>org.openhab.binding.ecowatt</module>
|
||||
|
Loading…
Reference in New Issue
Block a user