added migrated 2.x add-ons

Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
Kai Kreuzer
2020-09-21 01:58:32 +02:00
parent bbf1a7fd29
commit 6df6783b60
11662 changed files with 1302875 additions and 11 deletions

View File

@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
<classpathentry kind="src" output="target/classes" path="src/main/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
<attribute name="test" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="output" path="target/classes"/>
</classpath>

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>org.openhab.binding.sinope</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.m2e.core.maven2Builder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
<nature>org.eclipse.m2e.core.maven2Nature</nature>
</natures>
</projectDescription>

View 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

View File

@@ -0,0 +1,143 @@
# Sinopé Binding
The integration happens through the Sinopé (GT150) bridge, which acts as an IP gateway to the Sinopé devices on the 916 Mhz ISM band.
This binding supports multiple gateways with multiple devices.
## Supported Things
The Sinopé bridge is required as a "bridge" for accessing any other Sinopé devices.
Right now, only the thermostat devices (3000W and 4000W) (TH1120RF) are supported.
## Discovery
The Sinopé Gateway (bridge) discovery is not supported for now.
It will be added in future release.
The Sinopé devices discovery is implemented.
## Prerequisites
### Bridge or the Sinopé Gateway
First, you will need to get your API key from your Sinopé gateway.
Grab the latest release of the [sinope-core library](<https://github.com/chaton78/sinope-core/releases>)
On Windows, you can run the SinopeProtocol.exe (in the zip release).
The gateway parameter is written on the back of the SinopéGateway (example, 002f-c2c2-dd88-aaaa).
The addr parameter is the IP given to your gateway.
```
SinopeProtocol.exe -addr [YOUR_GATEWAY_IP_OR_HOSTNAME] -gateway [YOUR_GATEWAY_ID] -login
Getting API Key - PRESS WEB Button
Your api Key is: 0x12 0x57 0x55 0xD5 0xCD 0x4A 0xD5 0x33
```
On other operating systems, using only a JVM, you can invoke directly the java command from the latest release of the [sinope-core library](<https://github.com/chaton78/sinope-core/releases>):
```
java -jar core-0.0.3-shaded.jar -addr [YOUR_GATEWAY_IP_OR_HOSTNAME] -gateway [YOUR_GATEWAY_ID] -login
Getting API Key - PRESS WEB Button
Your api Key is: 0x12 0x57 0x55 0xD5 0xCD 0x4A 0xD5 0x33
```
### Thing Discovery
You can use the same procedure to discover each device you want to use.
You will need to provide the api key from the previous step.
If you use spaces, please, use double quotes to pass the api key (i.e. "0x12 0x57 0x55 0xD5 0xCD 0x4A 0xD5 0x33")
Use the device procedure to discover it.
For a thermostat, you need to push both buttons.
The application will loop forever, press ctrl-c to exit.
```
SinopeProtocol.exe -addr [YOUR_GATEWAY_IP_OR_HOSTNAME] -gateway [YOUR_GATEWAY_ID] -api "[YOUR_API_KEY]" -discover
It is now time to push both buttons on your device!
Press crtl-c to exit!
Your device id is: 0x00 0x00 0x35 0x86
It is now time to push both buttons on your device!
Press crtl-c to exit!
```
On other operating systems, using only a JVM, you can invoke directly the java command:
```
java -jar core-0.0.3-shaded.jar -addr [YOUR_GATEWAY_IP_OR_HOSTNAME] -gateway [YOUR_GATEWAY_ID] -api "[YOUR_API_KEY]" -discover
It is now time to push both buttons on your device!
Press crtl-c to exit!
Your device id is: 0x00 0x00 0x35 0x86
It is now time to push both buttons on your device!
Press crtl-c to exit!
```
## Thing Configuration
The Sinopé bridge requires the address, the gateway id and the API key in order for the binding to know where and how to access it.
In the thing file, this looks e.g. like
```
Bridge sinope:gateway:home [ hostname="[YOUR_GATEWAY_IP_OR_HOSTNAME]", gatewayId="[YOUR_GATEWAY_ID]", apiKey="0x1F 0x5D 0xC8 0xD5 0xCD 0x3A 0xD7 0x23"]
```
The devices are identified by the ids that a Sinopé device returns when you have discovered it.
```
thermostat room [ deviceId = "0x00 0x00 0x35 0x86" ]
```
## Channels
Thermostat devices support some of the following channels:
Channel Type ID | Item Type | Description
---------------------|-------------|----------------------------------------------------------------------------------------------------------------------------------------|
insideTemperature | Number (R) | Inside Temperature |
outsideTemperature | Number (R) | Outside Temperature |
setpointTemperature | Number (RW) | Set Point Temperature |
setpointMode | String (RW) | Thermostat set point mode |
heatingLevel | Number (R) | Heating Level |
## Full Example
In this example setup the Sinopé Gateway is represented as a Bridge **Home** with thermostat **Room**
### demo.things:
```
Bridge sinope:gateway:home [ hostname="sinope", gatewayId="1234-4567-1234-1234", apiKey="0x12 0x34 0x56 0x78 0x9A 0xBC 0xDE 0xF0"] {
thermostat room [ deviceId = "00003586" ]
}
```
### demo.items:
```
Number Room_In "Room Temp. [%.2f °C]" <temperature> { channel="sinope:thermostat:home:room:insideTemperature" }
Number Room_Out "Outside Temp. [%.2f °C]" <temperature> { channel="sinope:thermostat:home:room:outsideTemperature" }
Number Room_SetPoint "Room Set Point [%.2f °C]" <temperature> { channel="sinope:thermostat:home:room:setpointTemperature" }
Number Room_SetPointMode "Room Set Point Mode" { channel="sinope:thermostat:home:room:setpointMode" }
Number Room_HeatLevel "Room Heating level [%d]" <heating> { channel="sinope:thermostat:home:room:heatingLevel" }
```
### demo.sitemap:
```
sitemap demo label="Main Menu"
{
Frame label="Sinope" {
Text item=Room_In
Text item=Room_Out
Setpoint item=Room_SetPoint label="Set Point [%.1f °C]" step=0.5 minValue=5 maxValue=35
Switch item=Room_SetPointMode mappings=[2=Manual, 3=Auto, 5=Away]
Slider item=Room_HeatLevel
}
}
```
### UI Example
![Example](doc/openhab.png)

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://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.0.0-SNAPSHOT</version>
</parent>
<artifactId>org.openhab.binding.sinope</artifactId>
<name>openHAB Add-ons :: Bundles :: Sinope Binding</name>
</project>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<features name="org.openhab.binding.sinope-${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-sinope" description="Sinope Binding" version="${project.version}">
<feature>openhab-runtime-base</feature>
<feature>openhab-transport-serial</feature>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.sinope/${project.version}</bundle>
</feature>
</features>

View File

@@ -0,0 +1,56 @@
/**
* Copyright (c) 2010-2020 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.sinope;
import java.util.HashSet;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.ThingTypeUID;
/**
* The {@link sinopeBinding} class defines common constants, which are
* used across the whole binding.
*
* @author Pascal Larin - Initial contribution
*/
@NonNullByDefault
public class SinopeBindingConstants {
public static final String BINDING_ID = "sinope";
// List of all Thing Type UIDs
public static final ThingTypeUID THING_TYPE_THERMO = new ThingTypeUID(BINDING_ID, "thermostat");
public static final ThingTypeUID THING_TYPE_GATEWAY = new ThingTypeUID(BINDING_ID, "gateway");
// List of all Channel ids
public static final String CHANNEL_HEATINGLEVEL = "heatingLevel";
public static final String CHANNEL_SETTEMP = "setpointTemperature";
public static final String CHANNEL_SETMODE = "setpointMode";
public static final String CHANNEL_INTEMP = "insideTemperature";
public static final String CHANNEL_OUTTEMP = "outsideTemperature";
public static final String CONFIG_PROPERTY_HOST = "ipAddress";
public static final String CONFIG_PROPERTY_PORT = "ipPort";
public static final String CONFIG_PROPERTY_GATEWAY_ID = "gatewayID";
public static final String CONFIG_PROPERTY_API_KEY = "apiKey";
public static final String CONFIG_PROPERTY_POLLING_INTERVAL = "pollingInterval";
public static final String CONFIG_PROPERTY_DEVICE_ID = "deviceId";
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = new HashSet<>();
static {
SUPPORTED_THING_TYPES_UIDS.add(THING_TYPE_GATEWAY);
SUPPORTED_THING_TYPES_UIDS.add(THING_TYPE_THERMO);
}
}

View File

@@ -0,0 +1,312 @@
/**
* Copyright (c) 2010-2020 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.sinope.handler;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.sinope.SinopeBindingConstants;
import org.openhab.binding.sinope.internal.SinopeConfigStatusMessage;
import org.openhab.binding.sinope.internal.config.SinopeConfig;
import org.openhab.binding.sinope.internal.core.SinopeApiLoginAnswer;
import org.openhab.binding.sinope.internal.core.SinopeApiLoginRequest;
import org.openhab.binding.sinope.internal.core.SinopeDeviceReportAnswer;
import org.openhab.binding.sinope.internal.core.base.SinopeAnswer;
import org.openhab.binding.sinope.internal.core.base.SinopeDataAnswer;
import org.openhab.binding.sinope.internal.core.base.SinopeDataRequest;
import org.openhab.binding.sinope.internal.core.base.SinopeRequest;
import org.openhab.binding.sinope.internal.discovery.SinopeThingsDiscoveryService;
import org.openhab.binding.sinope.internal.util.ByteUtil;
import org.openhab.core.config.core.status.ConfigStatusMessage;
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.ConfigStatusBridgeHandler;
import org.openhab.core.types.Command;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link SinopeGatewayHandler} is responsible for handling commands for the Sinopé Gateway.
*
* @author Pascal Larin - Initial contribution
*/
@NonNullByDefault
public class SinopeGatewayHandler extends ConfigStatusBridgeHandler {
private static final int FIRST_POLL_INTERVAL = 1; // In second
private final Logger logger = LoggerFactory.getLogger(SinopeGatewayHandler.class);
private @Nullable ScheduledFuture<?> pollFuture;
private long refreshInterval; // In seconds
private final List<SinopeThermostatHandler> thermostatHandlers = new CopyOnWriteArrayList<>();
private int seq = 1;
private @Nullable Socket clientSocket;
private boolean searching; // In searching mode..
private @Nullable ScheduledFuture<?> pollSearch;
public SinopeGatewayHandler(final Bridge bridge) {
super(bridge);
}
@Override
public void initialize() {
logger.debug("Initializing Sinope Gateway");
try {
SinopeConfig config = getConfigAs(SinopeConfig.class);
refreshInterval = config.refresh;
if (config.hostname == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"Gateway hostname must be set");
} else if (config.port == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Gateway port must be set");
} else if (config.gatewayId == null || SinopeConfig.convert(config.gatewayId) == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Gateway Id must be set");
} else if (config.apiKey == null || SinopeConfig.convert(config.apiKey) == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Api Key must be set");
} else if (connectToBridge()) {
schedulePoll();
updateStatus(ThingStatus.ONLINE);
return;
}
} catch (IOException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE,
"Can't connect to gateway. Please make sure that another instance is not connected.");
}
}
@Override
public void dispose() {
super.dispose();
stopPoll();
if (clientSocket != null) {
try {
clientSocket.close();
} catch (IOException e) {
logger.warn("Unexpected error when closing connection to gateway", e);
}
}
}
synchronized void schedulePoll() {
if (searching) {
return;
}
if (pollFuture != null) {
pollFuture.cancel(false);
}
logger.debug("Scheduling poll for {} s out, then every {} s", FIRST_POLL_INTERVAL, refreshInterval);
pollFuture = scheduler.scheduleWithFixedDelay(() -> poll(), FIRST_POLL_INTERVAL, refreshInterval,
TimeUnit.SECONDS);
}
synchronized void stopPoll() {
if (pollFuture != null && !pollFuture.isCancelled()) {
pollFuture.cancel(true);
pollFuture = null;
}
}
private synchronized void poll() {
if (!thermostatHandlers.isEmpty()) {
logger.debug("Polling for state");
try {
if (connectToBridge()) {
logger.debug("Connected to bridge");
for (SinopeThermostatHandler sinopeThermostatHandler : thermostatHandlers) {
sinopeThermostatHandler.update();
}
}
} catch (IOException e) {
setCommunicationError(true);
logger.debug("Polling issue", e);
}
} else {
logger.debug("nothing to poll");
}
}
boolean connectToBridge() throws UnknownHostException, IOException {
SinopeConfig config = getConfigAs(SinopeConfig.class);
if (this.clientSocket == null || !this.clientSocket.isConnected() || this.clientSocket.isClosed()) {
this.clientSocket = new Socket(config.hostname, config.port);
SinopeApiLoginRequest loginRequest = new SinopeApiLoginRequest(SinopeConfig.convert(config.gatewayId),
SinopeConfig.convert(config.apiKey));
SinopeApiLoginAnswer loginAnswer = (SinopeApiLoginAnswer) execute(loginRequest);
setCommunicationError(false);
return loginAnswer.getStatus() == 0;
}
return true;
}
public synchronized byte[] newSeq() {
return ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(seq++).array();
}
synchronized SinopeAnswer execute(SinopeRequest command) throws UnknownHostException, IOException {
Socket clientSocket = this.getClientSocket();
OutputStream outToServer = clientSocket.getOutputStream();
InputStream inputStream = clientSocket.getInputStream();
outToServer.write(command.getPayload());
outToServer.flush();
SinopeAnswer answ = command.getReplyAnswer(inputStream);
return answ;
}
synchronized SinopeAnswer execute(SinopeDataRequest command) throws UnknownHostException, IOException {
Socket clientSocket = this.getClientSocket();
OutputStream outToServer = clientSocket.getOutputStream();
InputStream inputStream = clientSocket.getInputStream();
if (logger.isDebugEnabled()) {
int leftBytes = inputStream.available();
if (leftBytes > 0) {
logger.debug("Hum... some leftovers: {} bytes", leftBytes);
}
}
outToServer.write(command.getPayload());
SinopeDataAnswer answ = command.getReplyAnswer(inputStream);
while (answ.getMore() == 0x01) {
answ = command.getReplyAnswer(inputStream);
}
return answ;
}
public boolean registerThermostatHandler(SinopeThermostatHandler thermostatHandler) {
return thermostatHandlers.add(thermostatHandler);
}
public boolean unregisterThermostatHandler(SinopeThermostatHandler thermostatHandler) {
return thermostatHandlers.remove(thermostatHandler);
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
}
@Override
public Collection<ConfigStatusMessage> getConfigStatus() {
Collection<ConfigStatusMessage> configStatusMessages = new LinkedList<>();
SinopeConfig config = getConfigAs(SinopeConfig.class);
if (config.hostname == null) {
configStatusMessages.add(ConfigStatusMessage.Builder.error(SinopeBindingConstants.CONFIG_PROPERTY_HOST)
.withMessageKeySuffix(SinopeConfigStatusMessage.HOST_MISSING.getMessageKey())
.withArguments(SinopeBindingConstants.CONFIG_PROPERTY_HOST).build());
}
if (config.port == null) {
configStatusMessages.add(ConfigStatusMessage.Builder.error(SinopeBindingConstants.CONFIG_PROPERTY_PORT)
.withMessageKeySuffix(SinopeConfigStatusMessage.PORT_MISSING.getMessageKey())
.withArguments(SinopeBindingConstants.CONFIG_PROPERTY_PORT).build());
}
if (config.gatewayId == null || SinopeConfig.convert(config.gatewayId) == null) {
configStatusMessages
.add(ConfigStatusMessage.Builder.error(SinopeBindingConstants.CONFIG_PROPERTY_GATEWAY_ID)
.withMessageKeySuffix(SinopeConfigStatusMessage.GATEWAY_ID_INVALID.getMessageKey())
.withArguments(SinopeBindingConstants.CONFIG_PROPERTY_GATEWAY_ID).build());
}
if (config.apiKey == null || SinopeConfig.convert(config.apiKey) == null) {
configStatusMessages.add(ConfigStatusMessage.Builder.error(SinopeBindingConstants.CONFIG_PROPERTY_API_KEY)
.withMessageKeySuffix(SinopeConfigStatusMessage.API_KEY_INVALID.getMessageKey())
.withArguments(SinopeBindingConstants.CONFIG_PROPERTY_API_KEY).build());
}
return configStatusMessages;
}
public void startSearch(final SinopeThingsDiscoveryService sinopeThingsDiscoveryService)
throws UnknownHostException, IOException {
// Stopping current polling
stopPoll();
this.searching = true;
pollSearch = scheduler.schedule(() -> search(sinopeThingsDiscoveryService), FIRST_POLL_INTERVAL,
TimeUnit.SECONDS);
}
private void search(final SinopeThingsDiscoveryService sinopeThingsDiscoveryService) {
try {
if (connectToBridge()) {
logger.debug("Successful login");
try {
while (clientSocket != null && clientSocket.isConnected() && !clientSocket.isClosed()) {
SinopeDeviceReportAnswer answ;
answ = new SinopeDeviceReportAnswer(clientSocket.getInputStream());
logger.debug("Got report answer: {}", answ);
logger.debug("Your device id is: {}", ByteUtil.toString(answ.getDeviceId()));
sinopeThingsDiscoveryService.newThermostat(answ.getDeviceId());
}
} finally {
if (clientSocket != null && !clientSocket.isClosed()) {
clientSocket.close();
clientSocket = null;
}
}
}
} catch (UnknownHostException e) {
logger.warn("Unexpected error when searching for new devices", e);
} catch (IOException e) {
logger.debug("Network connection error, expected when ending search", e);
} finally {
schedulePoll();
}
}
public void stopSearch() throws IOException {
this.searching = false;
if (this.pollSearch != null && !this.pollSearch.isCancelled()) {
this.pollSearch.cancel(true);
this.pollSearch = null;
}
if (this.clientSocket != null && this.clientSocket.isConnected()) {
this.clientSocket.close();
this.clientSocket = null;
}
schedulePoll();
}
public @Nullable Socket getClientSocket() throws UnknownHostException, IOException {
if (connectToBridge()) {
return clientSocket;
}
throw new IOException("Could not create a socket to the gateway. Check host/ip/gateway Id");
}
public void setCommunicationError(boolean hasError) {
if (hasError) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
clientSocket = null;
} else {
updateStatus(ThingStatus.ONLINE);
schedulePoll();
}
}
}

View File

@@ -0,0 +1,279 @@
/**
* Copyright (c) 2010-2020 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.sinope.handler;
import java.io.IOException;
import java.net.UnknownHostException;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.sinope.SinopeBindingConstants;
import org.openhab.binding.sinope.internal.config.SinopeConfig;
import org.openhab.binding.sinope.internal.core.SinopeDataReadRequest;
import org.openhab.binding.sinope.internal.core.SinopeDataWriteRequest;
import org.openhab.binding.sinope.internal.core.appdata.SinopeHeatLevelData;
import org.openhab.binding.sinope.internal.core.appdata.SinopeOutTempData;
import org.openhab.binding.sinope.internal.core.appdata.SinopeRoomTempData;
import org.openhab.binding.sinope.internal.core.appdata.SinopeSetPointModeData;
import org.openhab.binding.sinope.internal.core.appdata.SinopeSetPointTempData;
import org.openhab.binding.sinope.internal.core.base.SinopeDataAnswer;
import org.openhab.binding.sinope.internal.util.ByteUtil;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.unit.SIUnits;
import org.openhab.core.thing.Bridge;
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.ThingStatusInfo;
import org.openhab.core.thing.binding.BaseThingHandler;
import org.openhab.core.types.Command;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link SinopeThermostatHandler} is responsible for handling commands, which are
* sent to one of the channels.
*
* @author Pascal Larin - Initial contribution
*/
@NonNullByDefault
public class SinopeThermostatHandler extends BaseThingHandler {
private static final int DATA_ANSWER = 0x0A;
private Logger logger = LoggerFactory.getLogger(SinopeThermostatHandler.class);
private byte[] deviceId = new byte[0];
public SinopeThermostatHandler(Thing thing) {
super(thing);
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
if (getSinopeGatewayHandler() != null) {
try {
if (SinopeBindingConstants.CHANNEL_SETTEMP.equals(channelUID.getId())
&& command instanceof QuantityType) {
setSetpointTemp(((QuantityType<?>) command).floatValue());
}
if (SinopeBindingConstants.CHANNEL_SETMODE.equals(channelUID.getId())
&& command instanceof DecimalType) {
setSetpointMode(((DecimalType) command).intValue());
}
} catch (IOException e) {
logger.debug("Cannot handle command for channel {} because of {}", channelUID.getId(),
e.getLocalizedMessage());
getSinopeGatewayHandler().setCommunicationError(true);
}
}
}
private void setSetpointTemp(float temp) throws UnknownHostException, IOException {
int newTemp = (int) (temp * 100.0);
getSinopeGatewayHandler().stopPoll();
try {
if (getSinopeGatewayHandler().connectToBridge()) {
logger.debug("Connected to bridge");
SinopeDataWriteRequest req = new SinopeDataWriteRequest(getSinopeGatewayHandler().newSeq(), deviceId,
new SinopeSetPointTempData());
((SinopeSetPointTempData) req.getAppData()).setSetPointTemp(newTemp);
SinopeDataAnswer answ = (SinopeDataAnswer) getSinopeGatewayHandler().execute(req);
if (answ.getStatus() == DATA_ANSWER) {
logger.debug("Setpoint temp is now: {} C", temp);
} else {
logger.debug("Cannot Setpoint temp, status: {}", answ.getStatus());
}
} else {
logger.debug("Could not connect to bridge to update Setpoint Temp");
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Cannot connect to bridge");
}
} finally {
getSinopeGatewayHandler().schedulePoll();
}
}
private void setSetpointMode(int mode) throws UnknownHostException, IOException {
getSinopeGatewayHandler().stopPoll();
try {
if (getSinopeGatewayHandler().connectToBridge()) {
logger.debug("Connected to bridge");
SinopeDataWriteRequest req = new SinopeDataWriteRequest(getSinopeGatewayHandler().newSeq(), deviceId,
new SinopeSetPointModeData());
((SinopeSetPointModeData) req.getAppData()).setSetPointMode((byte) mode);
SinopeDataAnswer answ = (SinopeDataAnswer) getSinopeGatewayHandler().execute(req);
if (answ.getStatus() == DATA_ANSWER) {
logger.debug("Setpoint mode is now : {}", mode);
} else {
logger.debug("Cannot Setpoint mode, status: {}", answ.getStatus());
}
} else {
logger.debug("Could not connect to bridge to update Setpoint Temp");
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Cannot connect to bridge");
}
} finally {
getSinopeGatewayHandler().schedulePoll();
}
}
@Override
public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
logger.debug("bridgeStatusChanged {}", bridgeStatusInfo);
updateDeviceId();
}
@Override
public void initialize() {
logger.debug("initializeThing thing {}", getThing().getUID());
updateDeviceId();
}
@Override
protected void updateConfiguration(Configuration configuration) {
super.updateConfiguration(configuration);
updateDeviceId();
}
public void updateOutsideTemp(double temp) {
updateState(SinopeBindingConstants.CHANNEL_OUTTEMP, new QuantityType<>(temp, SIUnits.CELSIUS));
}
public void updateRoomTemp(double temp) {
updateState(SinopeBindingConstants.CHANNEL_INTEMP, new QuantityType<>(temp, SIUnits.CELSIUS));
}
public void updateSetPointTemp(double temp) {
updateState(SinopeBindingConstants.CHANNEL_SETTEMP, new QuantityType<>(temp, SIUnits.CELSIUS));
}
public void updateSetPointMode(int mode) {
updateState(SinopeBindingConstants.CHANNEL_SETMODE, new DecimalType(mode));
}
public void updateHeatingLevel(int heatingLevel) {
updateState(SinopeBindingConstants.CHANNEL_HEATINGLEVEL, new DecimalType(heatingLevel));
}
public void update() throws UnknownHostException, IOException {
if (this.deviceId.length > 0 && getSinopeGatewayHandler() != null) {
if (isLinked(SinopeBindingConstants.CHANNEL_OUTTEMP)) {
this.updateOutsideTemp(readOutsideTemp());
}
if (isLinked(SinopeBindingConstants.CHANNEL_INTEMP)) {
this.updateRoomTemp(readRoomTemp());
}
if (isLinked(SinopeBindingConstants.CHANNEL_SETTEMP)) {
this.updateSetPointTemp(readSetpointTemp());
}
if (isLinked(SinopeBindingConstants.CHANNEL_SETMODE)) {
this.updateSetPointMode(readSetpointMode());
}
if (isLinked(SinopeBindingConstants.CHANNEL_HEATINGLEVEL)) {
this.updateHeatingLevel(readHeatLevel());
}
} else {
logger.error("Device id is null for Thing UID: {}", getThing().getUID());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR);
}
}
private double readRoomTemp() throws UnknownHostException, IOException {
logger.debug("Reading room temp for device id : {}", ByteUtil.toString(deviceId));
SinopeDataReadRequest req = new SinopeDataReadRequest(getSinopeGatewayHandler().newSeq(), deviceId,
new SinopeRoomTempData());
SinopeDataAnswer answ = (SinopeDataAnswer) getSinopeGatewayHandler().execute(req);
double temp = ((SinopeRoomTempData) answ.getAppData()).getRoomTemp() / 100.0;
logger.debug("Room temp is : {} C", temp);
return temp;
}
private double readOutsideTemp() throws UnknownHostException, IOException {
SinopeDataReadRequest req = new SinopeDataReadRequest(getSinopeGatewayHandler().newSeq(), deviceId,
new SinopeOutTempData());
logger.debug("Reading outside temp for device id: {}", ByteUtil.toString(deviceId));
SinopeDataAnswer answ = (SinopeDataAnswer) getSinopeGatewayHandler().execute(req);
double temp = ((SinopeOutTempData) answ.getAppData()).getOutTemp() / 100.0;
logger.debug("Outside temp is : {} C", temp);
return temp;
}
private double readSetpointTemp() throws UnknownHostException, IOException {
SinopeDataReadRequest req = new SinopeDataReadRequest(getSinopeGatewayHandler().newSeq(), deviceId,
new SinopeSetPointTempData());
logger.debug("Reading Set Point temp for device id: {}", ByteUtil.toString(deviceId));
SinopeDataAnswer answ = (SinopeDataAnswer) getSinopeGatewayHandler().execute(req);
double temp = ((SinopeSetPointTempData) answ.getAppData()).getSetPointTemp() / 100.0;
logger.debug("Setpoint temp is : {} C", temp);
return temp;
}
private int readSetpointMode() throws UnknownHostException, IOException {
SinopeDataReadRequest req = new SinopeDataReadRequest(getSinopeGatewayHandler().newSeq(), deviceId,
new SinopeSetPointModeData());
logger.debug("Reading Set Point mode for device id: {}", ByteUtil.toString(deviceId));
SinopeDataAnswer answ = (SinopeDataAnswer) getSinopeGatewayHandler().execute(req);
int mode = ((SinopeSetPointModeData) answ.getAppData()).getSetPointMode();
logger.debug("Setpoint mode is : {}", mode);
return mode;
}
private int readHeatLevel() throws UnknownHostException, IOException {
SinopeDataReadRequest req = new SinopeDataReadRequest(getSinopeGatewayHandler().newSeq(), deviceId,
new SinopeHeatLevelData());
logger.debug("Reading Heat Level for device id: {}", ByteUtil.toString(deviceId));
SinopeDataAnswer answ = (SinopeDataAnswer) getSinopeGatewayHandler().execute(req);
int level = ((SinopeHeatLevelData) answ.getAppData()).getHeatLevel();
logger.debug("Heating level is : {}", level);
return level;
}
private synchronized void updateDeviceId() {
String sDeviceId = (String) getConfig().get(SinopeBindingConstants.CONFIG_PROPERTY_DEVICE_ID);
this.deviceId = SinopeConfig.convert(sDeviceId);
if (this.deviceId.length == 0) {
logger.debug("Invalid Device id, cannot convert id: {}", sDeviceId);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Invalid Device id");
return;
}
Bridge bridge = getBridge();
if (bridge == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
return;
}
SinopeGatewayHandler handler = getSinopeGatewayHandler();
if (handler != null) {
handler.registerThermostatHandler(this);
}
updateStatus(ThingStatus.ONLINE);
}
private @Nullable SinopeGatewayHandler getSinopeGatewayHandler() {
Bridge bridge = this.getBridge();
if (bridge != null) {
return (SinopeGatewayHandler) bridge.getHandler();
} else {
return null;
}
}
}

View File

@@ -0,0 +1,37 @@
/**
* Copyright (c) 2010-2020 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.sinope.internal;
/**
* The {@link SinopeConfigStatusMessage} defines
* the keys to be used for {@link ConfigStatusMessage}s.
*
* @author Pascal Larin - Initial Contribution
*
*/
public enum SinopeConfigStatusMessage {
HOST_MISSING("missing-host-configuration"),
PORT_MISSING("missing-port-configuration"),
GATEWAY_ID_INVALID("invalid-gateway-id-configuration"),
API_KEY_INVALID("invalid-api-key-configuration");
private String messageKey;
private SinopeConfigStatusMessage(String messageKey) {
this.messageKey = messageKey;
}
public String getMessageKey() {
return messageKey;
}
}

View File

@@ -0,0 +1,83 @@
/**
* Copyright (c) 2010-2020 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.sinope.internal;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
import java.util.Set;
import org.openhab.binding.sinope.SinopeBindingConstants;
import org.openhab.binding.sinope.handler.SinopeGatewayHandler;
import org.openhab.binding.sinope.handler.SinopeThermostatHandler;
import org.openhab.binding.sinope.internal.discovery.SinopeThingsDiscoveryService;
import org.openhab.core.config.discovery.DiscoveryService;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerFactory;
import org.osgi.framework.ServiceRegistration;
import org.osgi.service.component.annotations.Component;
/**
* {@link SinopeHandlerFactory} is a factory for {@link SinopeThermostatHandler}s and {@link SinopeGatewayHandler}s
*
* @author Pascal Larin - Initial contribution
*/
@Component(service = ThingHandlerFactory.class, immediate = true)
public class SinopeHandlerFactory extends BaseThingHandlerFactory {
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = SinopeBindingConstants.SUPPORTED_THING_TYPES_UIDS;
private Map<ThingUID, ServiceRegistration<?>> discoveryServiceRegs = new HashMap<>();
@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
}
@Override
protected ThingHandler createHandler(Thing thing) {
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
if (SinopeBindingConstants.THING_TYPE_GATEWAY.equals(thingTypeUID)) {
SinopeGatewayHandler bridge = new SinopeGatewayHandler((Bridge) thing);
registerDiscoveryService(bridge);
return bridge;
} else if (SinopeBindingConstants.THING_TYPE_THERMO.equals(thingTypeUID)) {
return new SinopeThermostatHandler(thing);
}
return null;
}
private synchronized void registerDiscoveryService(SinopeGatewayHandler bridge) {
SinopeThingsDiscoveryService discoveryService = new SinopeThingsDiscoveryService(bridge);
this.discoveryServiceRegs.put(bridge.getThing().getUID(),
bundleContext.registerService(DiscoveryService.class.getName(), discoveryService, new Hashtable<>()));
}
@Override
protected synchronized void removeHandler(ThingHandler thingHandler) {
if (thingHandler instanceof SinopeGatewayHandler) {
ServiceRegistration<?> serviceReg = this.discoveryServiceRegs.get(thingHandler.getThing().getUID());
if (serviceReg != null) {
// remove discovery service, if bridge handler is removed
serviceReg.unregister();
discoveryServiceRegs.remove(thingHandler.getThing().getUID());
}
}
}
}

View File

@@ -0,0 +1,71 @@
/**
* Copyright (c) 2010-2020 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.sinope.internal.config;
/**
* Holds Config for the Sinope Gateway
*
* @author Pascal Larin - Initial contribution
*
*/
public class SinopeConfig {
/**
* Hostname of the Sinope Gateway
*/
public String hostname;
/**
* ip port
*/
public Integer port;
/**
* Gateway ID
*/
public String gatewayId;
/**
* API Key returned by the Gateway
*/
public String apiKey;
/**
* The number of seconds between fetches from the sinope deivces
*/
public Integer refresh;
/**
* Convert Hex Config String to byte
*/
public static byte[] convert(String value) {
if (value == null) {
return null;
}
String _value = value;
_value = _value.replace("-", "");
_value = _value.replace("0x", "");
_value = _value.replace(" ", "");
if (_value.length() == 0) {
return null;
}
if (_value.length() % 2 == 0 && _value.length() > 1) {
byte[] b = new byte[_value.length() / 2];
for (int i = 0; i < _value.length(); i = i + 2) {
b[i / 2] = (byte) Integer.parseInt(_value.substring(i, i + 2), 16);
}
return b;
} else {
return null;
}
}
}

View File

@@ -0,0 +1,145 @@
/**
* Copyright (c) 2010-2020 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.sinope.internal.core;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import org.openhab.binding.sinope.internal.core.base.SinopeAnswer;
import org.openhab.binding.sinope.internal.util.ByteUtil;
/**
* The Class SinopeApiLoginAnswer.
*
* @author Pascal Larin - Initial contribution
*/
public class SinopeApiLoginAnswer extends SinopeAnswer {
/** The Constant STATUS_SIZE. */
protected static final int STATUS_SIZE = 1;
/** The Constant BACKOFF_SIZE. */
protected static final int BACKOFF_SIZE = 2;
/** The Constant SW_REV_MAJ_SIZE. */
protected static final int SW_REV_MAJ_SIZE = 1;
/** The Constant SW_REV_MIN_SIZE. */
protected static final int SW_REV_MIN_SIZE = 1;
/** The Constant SW_REV_BUG_SIZE. */
protected static final int SW_REV_BUG_SIZE = 1;
/** The Constant DEVICE_ID_SIZE. */
protected static final int DEVICE_ID_SIZE = 4;
/**
* Instantiates a new sinope api login answer.
*
* @param r the r
* @throws IOException Signals that an I/O exception has occurred.
*/
public SinopeApiLoginAnswer(InputStream r) throws IOException {
super(r);
}
/**
* @see org.openhab.binding.sinope.internal.core.base.SinopeFrame#getCommand()
*/
@Override
protected byte[] getCommand() {
return new byte[] { 0x01, 0x11 };
}
/**
* Gets the status.
*
* @return the status
*/
public byte getStatus() {
byte[] b = this.getFrameData();
return b[0];
}
/**
* Gets the backoff.
*
* @return the backoff
*/
public byte[] getBackoff() {
byte[] b = this.getFrameData();
b = Arrays.copyOfRange(b, STATUS_SIZE, STATUS_SIZE + BACKOFF_SIZE);
return b;
}
/**
* Gets the sw rev maj.
*
* @return the sw rev maj
*/
public byte getSwRevMaj() {
byte[] b = this.getFrameData();
return b[STATUS_SIZE + BACKOFF_SIZE];
}
/**
* Gets the sw rev min.
*
* @return the sw rev min
*/
public byte getSwRevMin() {
byte[] b = this.getFrameData();
return b[STATUS_SIZE + BACKOFF_SIZE + SW_REV_MAJ_SIZE];
}
/**
* Gets the sw rev bug.
*
* @return the sw rev bug
*/
public byte getSwRevBug() {
byte[] b = this.getFrameData();
return b[STATUS_SIZE + BACKOFF_SIZE + SW_REV_MAJ_SIZE + SW_REV_MIN_SIZE];
}
/**
* Gets the device id.
*
* @return the device id
*/
public byte[] getDeviceId() {
byte[] b = this.getFrameData();
b = Arrays.copyOfRange(b, STATUS_SIZE + BACKOFF_SIZE + SW_REV_MAJ_SIZE + SW_REV_MIN_SIZE + SW_REV_BUG_SIZE,
STATUS_SIZE + BACKOFF_SIZE + SW_REV_MAJ_SIZE + SW_REV_MIN_SIZE + SW_REV_BUG_SIZE + DEVICE_ID_SIZE);
return ByteUtil.reverse(b);
}
/**
* @see org.openhab.binding.sinope.internal.core.base.SinopeFrame#toString()
*/
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(super.toString());
sb.append(String.format("\nData: %s", ByteUtil.toString(getFrameData())));
sb.append(String.format("\n\tStatus: 0x%02X ", getStatus()));
sb.append(String.format("\n\tBackoff: %s", ByteUtil.toString(getBackoff())));
sb.append(String.format("\n\tSwRevMaj: 0x%02X ", getSwRevMaj()));
sb.append(String.format("\n\tSwRevMin: 0x%02X ", getSwRevMin()));
sb.append(String.format("\n\tSwRevBug: 0x%02X ", getSwRevBug()));
sb.append(String.format("\n\tDeviceID: %s", ByteUtil.toString(getDeviceId())));
return sb.toString();
}
}

View File

@@ -0,0 +1,86 @@
/**
* Copyright (c) 2010-2020 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.sinope.internal.core;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import org.openhab.binding.sinope.internal.core.base.SinopeRequest;
import org.openhab.binding.sinope.internal.util.ByteUtil;
/**
* The Class SinopeApiLoginRequest.
*
* @author Pascal Larin - Initial contribution
*/
public class SinopeApiLoginRequest extends SinopeRequest {
/** The api key. */
private byte[] apiKey;
/** The id. */
private byte[] id;
/**
* Instantiates a new sinope api login request.
*
* @param id the id
* @param apiKey the api key
*/
public SinopeApiLoginRequest(byte[] id, byte[] apiKey) {
this.id = id;
this.apiKey = apiKey;
}
/**
* @see org.openhab.binding.sinope.internal.core.base.SinopeFrame#getCommand()
*/
@Override
protected byte[] getCommand() {
return new byte[] { 0x01, 0x10 };
}
/**
* @see org.openhab.binding.sinope.internal.core.base.SinopeFrame#getFrameData()
*/
@Override
protected byte[] getFrameData() {
byte[] b = new byte[id.length + apiKey.length];
ByteBuffer bb = ByteBuffer.wrap(b);
bb.put(ByteUtil.reverse(id));
bb.put(ByteUtil.reverse(apiKey));
// System.out.println(toString(bb.array()));
return bb.array();
}
/**
* Gets the id.
*
* @return the id
*/
public byte[] getId() {
return id;
}
/**
* @see org.openhab.binding.sinope.internal.core.base.SinopeRequest#getReplyAnswer(java.io.InputStream)
*/
@Override
public SinopeApiLoginAnswer getReplyAnswer(InputStream r) throws IOException {
return new SinopeApiLoginAnswer(r);
}
}

View File

@@ -0,0 +1,101 @@
/**
* Copyright (c) 2010-2020 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.sinope.internal.core;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import org.openhab.binding.sinope.internal.core.base.SinopeAnswer;
import org.openhab.binding.sinope.internal.util.ByteUtil;
/**
* The Class SinopeAuthenticationKeyAnswer.
*
* @author Pascal Larin - Initial contribution
*/
public class SinopeAuthenticationKeyAnswer extends SinopeAnswer {
/** The Constant STATUS_SIZE. */
protected static final int STATUS_SIZE = 1;
/** The Constant BACKOFF_SIZE. */
protected static final int BACKOFF_SIZE = 2;
/** The Constant API_KEY_SIZE. */
protected static final int API_KEY_SIZE = 8;
/**
* Instantiates a new sinope authentication key answer.
*
* @param r the r
* @throws IOException Signals that an I/O exception has occurred.
*/
public SinopeAuthenticationKeyAnswer(InputStream r) throws IOException {
super(r);
}
/**
* @see org.openhab.binding.sinope.internal.core.base.SinopeFrame#getCommand()
*/
@Override
protected byte[] getCommand() {
return new byte[] { 0x01, 0x11 };
}
/**
* Gets the status.
*
* @return the status
*/
public int getStatus() {
byte[] b = this.getFrameData();
return b[0];
}
/**
* Gets the backoff.
*
* @return the backoff
*/
public byte[] getBackoff() {
byte[] b = this.getFrameData();
b = Arrays.copyOfRange(b, STATUS_SIZE, STATUS_SIZE + BACKOFF_SIZE);
return b;
}
/**
* Gets the api key.
*
* @return the api key
*/
public byte[] getApiKey() {
byte[] b = this.getFrameData();
b = Arrays.copyOfRange(b, STATUS_SIZE + BACKOFF_SIZE, STATUS_SIZE + BACKOFF_SIZE + API_KEY_SIZE);
return ByteUtil.reverse(b);
}
/**
* @see org.openhab.binding.sinope.internal.core.base.SinopeFrame#toString()
*/
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(super.toString());
sb.append(String.format("\nData: %s", ByteUtil.toString(getFrameData())));
sb.append(String.format("\n\tStatus: 0x%02X ", getStatus()));
sb.append(String.format("\n\tApi Key: %s", ByteUtil.toString(getApiKey())));
sb.append(String.format("\n\tBackoff: %s", ByteUtil.toString(getBackoff())));
return sb.toString();
}
}

View File

@@ -0,0 +1,72 @@
/**
* Copyright (c) 2010-2020 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.sinope.internal.core;
import java.io.IOException;
import java.io.InputStream;
import org.openhab.binding.sinope.internal.core.base.SinopeRequest;
import org.openhab.binding.sinope.internal.util.ByteUtil;
/**
* The Class SinopeAuthenticationKeyRequest.
*
* @author Pascal Larin - Initial contribution
*/
public class SinopeAuthenticationKeyRequest extends SinopeRequest {
/** The id. */
private byte[] id;
/**
* Instantiates a new sinope authentication key request.
*
* @param id the id
*/
public SinopeAuthenticationKeyRequest(byte[] id) {
this.id = id;
}
/**
* @see org.openhab.binding.sinope.internal.core.base.SinopeFrame#getCommand()
*/
@Override
protected byte[] getCommand() {
return new byte[] { 0x01, 0x0a };
}
/**
* @see org.openhab.binding.sinope.internal.core.base.SinopeFrame#getFrameData()
*/
@Override
protected byte[] getFrameData() {
return ByteUtil.reverse(this.id);
}
/**
* @see org.openhab.binding.sinope.internal.core.base.SinopeRequest#getReplyAnswer(java.io.InputStream)
*/
@Override
public SinopeAuthenticationKeyAnswer getReplyAnswer(InputStream r) throws IOException {
return new SinopeAuthenticationKeyAnswer(r);
}
/**
* Gets the id.
*
* @return the id
*/
public byte[] getId() {
return id;
}
}

View File

@@ -0,0 +1,46 @@
/**
* Copyright (c) 2010-2020 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.sinope.internal.core;
import java.io.IOException;
import java.io.InputStream;
import org.openhab.binding.sinope.internal.core.appdata.SinopeAppData;
import org.openhab.binding.sinope.internal.core.base.SinopeDataAnswer;
/**
* The Class SinopeDataReadAnswer.
*
* @author Pascal Larin - Initial contribution
*/
public class SinopeDataReadAnswer extends SinopeDataAnswer {
/**
* Instantiates a new sinope data read answer.
*
* @param r the r
* @param appData the app data
* @throws IOException Signals that an I/O exception has occurred.
*/
public SinopeDataReadAnswer(InputStream r, SinopeAppData appData) throws IOException {
super(r, appData);
}
/**
* @see org.openhab.binding.sinope.internal.core.base.SinopeFrame#getCommand()
*/
@Override
protected byte[] getCommand() {
return new byte[] { 0x02, 0x41 };
}
}

View File

@@ -0,0 +1,56 @@
/**
* Copyright (c) 2010-2020 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.sinope.internal.core;
import java.io.IOException;
import java.io.InputStream;
import org.openhab.binding.sinope.internal.core.appdata.SinopeAppData;
import org.openhab.binding.sinope.internal.core.base.SinopeDataRequest;
/**
* The Class SinopeDataReadRequest.
*
* @author Pascal Larin - Initial contribution
*/
public class SinopeDataReadRequest extends SinopeDataRequest {
/**
* Instantiates a new sinope data read request.
*
* @param seq the seq
* @param dstDeviceId the dst device id
* @param appData the app data
*/
public SinopeDataReadRequest(byte[] seq, byte[] dstDeviceId, SinopeAppData appData) {
super(seq, dstDeviceId, appData);
// Read Request, as per spec.. zap data part
appData.cleanData();
}
/**
* @see org.openhab.binding.sinope.internal.core.base.SinopeFrame#getCommand()
*/
@Override
protected byte[] getCommand() {
return new byte[] { 0x02, 0x40 };
}
/**
* @see org.openhab.binding.sinope.internal.core.base.SinopeDataRequest#getReplyAnswer(java.io.InputStream)
*/
@Override
public SinopeDataReadAnswer getReplyAnswer(InputStream r) throws IOException {
return new SinopeDataReadAnswer(r, this.getAppData());
}
}

View File

@@ -0,0 +1,46 @@
/**
* Copyright (c) 2010-2020 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.sinope.internal.core;
import java.io.IOException;
import java.io.InputStream;
import org.openhab.binding.sinope.internal.core.appdata.SinopeAppData;
import org.openhab.binding.sinope.internal.core.base.SinopeDataAnswer;
/**
* The Class SinopeDataReadAnswer.
*
* @author Pascal Larin - Initial contribution
*/
public class SinopeDataWriteAnswer extends SinopeDataAnswer {
/**
* Instantiates a new sinope data read answer.
*
* @param r the r
* @param appData the app data
* @throws IOException Signals that an I/O exception has occurred.
*/
public SinopeDataWriteAnswer(InputStream r, SinopeAppData appData) throws IOException {
super(r, appData);
}
/**
* @see org.openhab.binding.sinope.internal.core.base.SinopeFrame#getCommand()
*/
@Override
protected byte[] getCommand() {
return new byte[] { 0x02, 0x45 };
}
}

View File

@@ -0,0 +1,54 @@
/**
* Copyright (c) 2010-2020 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.sinope.internal.core;
import java.io.IOException;
import java.io.InputStream;
import org.openhab.binding.sinope.internal.core.appdata.SinopeAppData;
import org.openhab.binding.sinope.internal.core.base.SinopeDataRequest;
/**
* The Class SinopeDataReadRequest.
*
* @author Pascal Larin - Initial contribution
*/
public class SinopeDataWriteRequest extends SinopeDataRequest {
/**
* Instantiates a new sinope data read request.
*
* @param seq the seq
* @param dstDeviceId the dst device id
* @param appData the app data
*/
public SinopeDataWriteRequest(byte[] seq, byte[] dstDeviceId, SinopeAppData appData) {
super(seq, dstDeviceId, appData);
}
/**
* @see org.openhab.binding.sinope.internal.core.base.SinopeFrame#getCommand()
*/
@Override
protected byte[] getCommand() {
return new byte[] { 0x02, 0x44 };
}
/**
* @see org.openhab.binding.sinope.internal.core.base.SinopeDataRequest#getReplyAnswer(java.io.InputStream)
*/
@Override
public SinopeDataReadAnswer getReplyAnswer(InputStream r) throws IOException {
return new SinopeDataReadAnswer(r, this.getAppData());
}
}

View File

@@ -0,0 +1,85 @@
/**
* Copyright (c) 2010-2020 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.sinope.internal.core;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import org.openhab.binding.sinope.internal.core.base.SinopeAnswer;
import org.openhab.binding.sinope.internal.util.ByteUtil;
/**
* The Class SinopeDeviceReportAnswer.
*
* @author Pascal Larin - Initial contribution
*/
public class SinopeDeviceReportAnswer extends SinopeAnswer {
/** The Constant STATUS_SIZE. */
protected static final int STATUS_SIZE = 1;
/** The Constant DEVICE_ID_SIZE. */
protected static final int DEVICE_ID_SIZE = 4;
/**
* Instantiates a new sinope device report answer.
*
* @param r the r
* @throws IOException Signals that an I/O exception has occurred.
*/
public SinopeDeviceReportAnswer(InputStream r) throws IOException {
super(r);
}
/**
* @see org.openhab.binding.sinope.internal.core.base.SinopeFrame#getCommand()
*/
@Override
protected byte[] getCommand() {
return new byte[] { 0x01, 0x16 };
}
/**
* Gets the status.
*
* @return the status
*/
public byte getStatus() {
byte[] b = this.getFrameData();
return b[0];
}
/**
* Gets the device id.
*
* @return the device id
*/
public byte[] getDeviceId() {
byte[] b = this.getFrameData();
b = Arrays.copyOfRange(b, STATUS_SIZE, STATUS_SIZE + DEVICE_ID_SIZE);
return ByteUtil.reverse(b);
}
/**
* @see org.openhab.binding.sinope.internal.core.base.SinopeFrame#toString()
*/
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(super.toString());
sb.append(String.format("\nStatus: 0x%02X ", getStatus()));
sb.append(String.format("\n\tDeviceId: %s", ByteUtil.toString(getDeviceId())));
return sb.toString();
}
}

View File

@@ -0,0 +1,44 @@
/**
* Copyright (c) 2010-2020 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.sinope.internal.core;
import java.io.IOException;
import java.io.InputStream;
import org.openhab.binding.sinope.internal.core.base.SinopeAnswer;
/**
* The Class SinopePingAnswer.
*
* @author Pascal Larin - Initial contribution
*/
public class SinopePingAnswer extends SinopeAnswer {
/**
* Instantiates a new sinope ping answer.
*
* @param r the r
* @throws IOException Signals that an I/O exception has occurred.
*/
public SinopePingAnswer(InputStream r) throws IOException {
super(r);
}
/**
* @see org.openhab.binding.sinope.internal.core.base.SinopeFrame#getCommand()
*/
@Override
protected byte[] getCommand() {
return new byte[] { 0x00, 0x13 };
}
}

View File

@@ -0,0 +1,50 @@
/**
* Copyright (c) 2010-2020 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.sinope.internal.core;
import java.io.IOException;
import java.io.InputStream;
import org.openhab.binding.sinope.internal.core.base.SinopeRequest;
/**
* The Class SinopePingRequest.
*
* @author Pascal Larin - Initial contribution
*/
public class SinopePingRequest extends SinopeRequest {
/**
* @see org.openhab.binding.sinope.internal.core.base.SinopeFrame#getCommand()
*/
@Override
protected byte[] getCommand() {
return new byte[] { 0x00, 0x12 };
}
/**
* @see org.openhab.binding.sinope.internal.core.base.SinopeFrame#getFrameData()
*/
@Override
protected byte[] getFrameData() {
return new byte[0];
}
/**
* @see org.openhab.binding.sinope.internal.core.base.SinopeRequest#getReplyAnswer(java.io.InputStream)
*/
@Override
public SinopePingAnswer getReplyAnswer(InputStream r) throws IOException {
return new SinopePingAnswer(r);
}
}

View File

@@ -0,0 +1,135 @@
/**
* Copyright (c) 2010-2020 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.sinope.internal.core.appdata;
import java.nio.ByteBuffer;
import org.openhab.binding.sinope.internal.util.ByteUtil;
/**
* The Class SinopeAppData.
*
* @author Pascal Larin - Initial contribution
*/
public class SinopeAppData {
/** The Constant DATA_ID_SIZE. */
protected static final int DATA_ID_SIZE = 4;
/** The Constant DATA_SIZE. */
protected static final int DATA_SIZE = 1;
/** The internal data. */
protected byte[] internal_data; // Full App object
/** The data id. */
private byte[] dataId;
/**
* The data.
* data == null ? Do not send data size and payload
*/
private byte[] data; // Data field of the app object
/**
* Instantiates a new sinope app data.
*
* @param dataId the data id
* @param data the data
*/
public SinopeAppData(byte[] dataId, byte[] data) {
this.dataId = dataId;
this.data = data;
}
/**
* Read.
*
* @param d the d
*/
public void read(byte[] d) {
this.internal_data = d;
if (d.length >= DATA_ID_SIZE) {
ByteBuffer bb = ByteBuffer.wrap(d);
this.dataId = new byte[DATA_ID_SIZE];
bb.get(dataId);
this.dataId = ByteUtil.reverse(dataId);
if (d.length > DATA_ID_SIZE) {
int len = bb.get() & 0xff;
this.data = new byte[len];
bb.get(this.data);
}
}
}
/**
* Gets the internal data.
*
* @return the internal data
*/
public byte[] getInternalData() {
if (internal_data == null) {
int len = data != null ? 1 + data.length : 0;
byte[] b = new byte[DATA_ID_SIZE + len];
ByteBuffer bb = ByteBuffer.wrap(b);
bb.put(ByteUtil.reverse(dataId));
if (data != null) { // Special case, Data null, don't put app size
bb.put((byte) data.length);
bb.put(data);
}
this.internal_data = bb.array();
}
return this.internal_data;
}
/**
* Gets the data.
*
* @return the data
*/
public byte[] getData() {
return this.data;
}
/**
* @see java.lang.Object#toString()
*/
/*
*
*
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(String.format("\n\tData ID: %s", ByteUtil.toString(this.dataId)));
if (data != null) {
sb.append(String.format("\n\t\tData Size: 0x%02X ", getData().length));
sb.append(String.format("\n\t\tData: %s", ByteUtil.toString(getData())));
}
return sb.toString();
}
/**
* Clean data.
* It nullifies the data field. Tells to not send data size and payload.
*/
public void cleanData() {
this.data = null;
}
}

View File

@@ -0,0 +1,53 @@
/**
* Copyright (c) 2010-2020 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.sinope.internal.core.appdata;
/**
* The Class SinopeRoomTempData.
*
* @author Pascal Larin - Initial contribution
*/
public class SinopeHeatLevelData extends SinopeAppData {
/**
* Instantiates a new sinope set point temp data.
*/
public SinopeHeatLevelData() {
super(new byte[] { 0x00, 0x00, 0x02, 0x20 }, new byte[] { 0 });
}
/**
* Gets the room temp.
*
* @return the room temp
*/
public int getHeatLevel() {
if (getData() != null) {
return getData()[0] & 0xFF;
}
return -273;
}
/**
* @see org.openhab.binding.sinope.internal.core.appdata.SinopeAppData#toString()
*/
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(super.toString());
if (getData() != null) {
sb.append(String.format("\nHeat Level is %d %%", this.getHeatLevel()));
}
return sb.toString();
}
}

View File

@@ -0,0 +1,28 @@
/**
* Copyright (c) 2010-2020 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.sinope.internal.core.appdata;
/**
* The Class SinopeLocalTimeData.
*
* @author Pascal Larin - Initial contribution
*/
public class SinopeLocalTimeData extends SinopeAppData {
/**
* Instantiates a new sinope local time data.
*/
public SinopeLocalTimeData() {
super(new byte[] { 0x00, 0x00, 0x06, 0x00 }, new byte[] { 0, 0, 0 });
}
}

View File

@@ -0,0 +1,58 @@
/**
* Copyright (c) 2010-2020 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.sinope.internal.core.appdata;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
/**
* The Class SinopeOutTempData.
*
* @author Pascal Larin - Initial contribution
*/
public class SinopeOutTempData extends SinopeAppData {
/**
* Instantiates a new sinope out temp data.
*/
public SinopeOutTempData() {
super(new byte[] { 0x00, 0x00, 0x02, 0x04 }, new byte[] { 0, 0 });
}
/**
* Gets the out temp.
*
* @return the out temp
*/
public int getOutTemp() {
if (getData() != null) {
ByteBuffer bb = ByteBuffer.wrap(getData());
bb.order(ByteOrder.LITTLE_ENDIAN);
return bb.getShort();
}
return -273;
}
/**
* @see org.openhab.binding.sinope.internal.core.appdata.SinopeAppData#toString()
*/
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(super.toString());
if (getData() != null) {
sb.append(String.format("\n\tOutside temperature is %2.2f C", this.getOutTemp() / 100.0));
}
return sb.toString();
}
}

View File

@@ -0,0 +1,58 @@
/**
* Copyright (c) 2010-2020 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.sinope.internal.core.appdata;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
/**
* The Class SinopeRoomTempData.
*
* @author Pascal Larin - Initial contribution
*/
public class SinopeRoomTempData extends SinopeAppData {
/**
* Instantiates a new sinope room temp data.
*/
public SinopeRoomTempData() {
super(new byte[] { 0x00, 0x00, 0x02, 0x03 }, new byte[] { 0, 0 });
}
/**
* Gets the room temp.
*
* @return the room temp
*/
public int getRoomTemp() {
if (getData() != null) {
ByteBuffer bb = ByteBuffer.wrap(getData());
bb.order(ByteOrder.LITTLE_ENDIAN);
return bb.getShort();
}
return -273;
}
/**
* @see org.openhab.binding.sinope.internal.core.appdata.SinopeAppData#toString()
*/
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(super.toString());
if (getData() != null) {
sb.append(String.format("\n\tRoom temperature is %2.2f C", this.getRoomTemp() / 100.0));
}
return sb.toString();
}
}

View File

@@ -0,0 +1,57 @@
/**
* Copyright (c) 2010-2020 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.sinope.internal.core.appdata;
/**
* The Class SinopeRoomTempData.
*
* @author Pascal Larin - Initial contribution
*/
public class SinopeSetPointModeData extends SinopeAppData {
/**
* Instantiates a new sinope set point temp data.
*/
public SinopeSetPointModeData() {
super(new byte[] { 0x00, 0x00, 0x02, 0x11 }, new byte[] { 0 });
}
/**
* Gets the room temp.
*
* @return the room temp
*/
public int getSetPointMode() {
if (getData() != null) {
return getData()[0] & 0xFF;
}
return 0;
}
/**
* @see org.openhab.binding.sinope.internal.core.appdata.SinopeAppData#toString()
*/
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(super.toString());
if (getData() != null) {
sb.append(String.format("\nSet point mode is ", this.getSetPointMode()));
}
return sb.toString();
}
public void setSetPointMode(byte mode) {
getData()[0] = mode;
}
}

View File

@@ -0,0 +1,64 @@
/**
* Copyright (c) 2010-2020 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.sinope.internal.core.appdata;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
/**
* The Class SinopeRoomTempData.
*
* @author Pascal Larin - Initial contribution
*/
public class SinopeSetPointTempData extends SinopeAppData {
/**
* Instantiates a new sinope set point temp data.
*/
public SinopeSetPointTempData() {
super(new byte[] { 0x00, 0x00, 0x02, 0x08 }, new byte[] { 0, 0 });
}
/**
* Gets the room temp.
*
* @return the room temp
*/
public int getSetPointTemp() {
if (getData() != null) {
ByteBuffer bb = ByteBuffer.wrap(getData());
bb.order(ByteOrder.LITTLE_ENDIAN);
return bb.getShort();
}
return -273;
}
/**
* @see org.openhab.binding.sinope.internal.core.appdata.SinopeAppData#toString()
*/
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(super.toString());
if (getData() != null) {
sb.append(String.format("\nSet point temperature is %2.2f C", this.getSetPointTemp() / 100.0));
}
return sb.toString();
}
public void setSetPointTemp(int newTemp) {
ByteBuffer bb = ByteBuffer.wrap(getData());
bb.order(ByteOrder.LITTLE_ENDIAN);
bb.putShort((short) newTemp);
}
}

View File

@@ -0,0 +1,24 @@
/**
* Copyright (c) 2010-2020 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.sinope.internal.core.base;
/**
* The Class NotSupportedException.
*
* @author Pascal Larin - Initial contribution
*/
public class NotSupportedException extends RuntimeException {
/** The Constant serialVersionUID. */
private static final long serialVersionUID = 1L;
}

View File

@@ -0,0 +1,117 @@
/**
* Copyright (c) 2010-2020 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.sinope.internal.core.base;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Arrays;
import org.openhab.binding.sinope.internal.util.ByteUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The Class SinopeAnswer.
*
* @author Pascal Larin - Initial contribution
*/
public abstract class SinopeAnswer extends SinopeRequest {
/** The Constant logger. */
private static final Logger logger = LoggerFactory.getLogger(SinopeAnswer.class);
/**
* Instantiates a new sinope answer.
*
* @param r the r
* @throws IOException Signals that an I/O exception has occurred.
*/
public SinopeAnswer(InputStream r) throws IOException {
byte[] header = new byte[SinopeFrame.PREAMBLE_SIZE + SinopeFrame.FRAME_CTL_SIZE + SinopeFrame.SIZE_SIZE];
r.read(header, 0, header.length);
if (header[0] != 0x55) {
throw new IOException(String.format("Invalid header PREAMBLE: %02x", header[0]));
}
int startSizeIndex = SinopeFrame.PREAMBLE_SIZE + SinopeFrame.FRAME_CTL_SIZE;
int endSizeIndex = startSizeIndex + SinopeFrame.SIZE_SIZE;
byte[] sizeInByte = Arrays.copyOfRange(header, startSizeIndex, endSizeIndex);
ByteBuffer bb = ByteBuffer.allocate(SIZE_SIZE);
bb.order(ByteOrder.LITTLE_ENDIAN);
bb.put(sizeInByte);
short size = bb.getShort(0);
byte[] payload = new byte[SinopeRequest.HEADER_COMMAND_CRC_SIZE + size];
byte[] remain = new byte[size + 1];
r.read(remain, 0, size + 1);
bb = ByteBuffer.wrap(payload);
bb.put(header);
bb.put(remain);
this.setInternal_payload(bb.array());
}
/**
* @see org.openhab.binding.sinope.internal.core.base.SinopeRequest#getPayload()
*/
/*
*
*
* @see ca.tulip.sinope.core.internal.SinopeRequest#getPayload()
*/
@Override
public byte[] getPayload() {
return getInternal_payload();
}
/**
* @see org.openhab.binding.sinope.internal.core.base.SinopeRequest#getReplyAnswer(java.io.InputStream)
*/
/*
*
*
* @see ca.tulip.sinope.core.internal.SinopeRequest#getReplyAnswer(java.io.InputStream)
*/
@Override
public SinopeAnswer getReplyAnswer(InputStream r) {
throw new NotSupportedException();
}
/**
* @see org.openhab.binding.sinope.internal.core.base.SinopeFrame#getFrameData()
*/
/*
*
*
* @see ca.tulip.sinope.core.internal.SinopeFrame#getFrameData()
*/
@Override
protected final byte[] getFrameData() {
byte[] b = this.getInternal_payload();
int headerSize = SinopeFrame.PREAMBLE_SIZE + SinopeFrame.FRAME_CTL_SIZE + SinopeFrame.COMMAND_SIZE
+ SinopeFrame.SIZE_SIZE;
return Arrays.copyOfRange(b, headerSize, b.length - SinopeFrame.CRC_SIZE);
}
/**
* @see org.openhab.binding.sinope.internal.core.base.SinopeRequest#setInternal_payload(byte[])
*/
@Override
protected void setInternal_payload(byte[] internal_payload) {
logger.debug("Answer Frame: {}", ByteUtil.toString(internal_payload));
super.setInternal_payload(internal_payload);
}
}

View File

@@ -0,0 +1,161 @@
/**
* Copyright (c) 2010-2020 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.sinope.internal.core.base;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import org.openhab.binding.sinope.internal.core.appdata.SinopeAppData;
import org.openhab.binding.sinope.internal.util.ByteUtil;
/**
* The Class SinopeDataAnswer.
*
* @author Pascal Larin - Initial contribution
*/
public abstract class SinopeDataAnswer extends SinopeAnswer {
/** The Constant SEQ_SIZE. */
protected static final int SEQ_SIZE = 4;
/** The Constant STATUS_SIZE. */
protected static final int STATUS_SIZE = 1;
/** The Constant ATTEMPT_NBR_SIZE. */
protected static final int ATTEMPT_NBR_SIZE = 1;
/** The Constant MORE_SIZE. */
protected static final int MORE_SIZE = 1;
/** The Constant SRC_DEVICE_ID_SIZE. */
protected static final int SRC_DEVICE_ID_SIZE = 4;
/** The Constant APP_DATA_SIZE_SIZE. */
protected static final int APP_DATA_SIZE_SIZE = 1;
/** The app data. */
private SinopeAppData appData;
/**
* Instantiates a new sinope data answer.
*
* @param r the r
* @param appData the app data
* @throws IOException Signals that an I/O exception has occurred.
*/
public SinopeDataAnswer(InputStream r, SinopeAppData appData) throws IOException {
super(r);
byte[] data = getData();
this.appData = appData;
this.appData.read(data);
}
/**
* Gets the app data.
*
* @return the app data
*/
public SinopeAppData getAppData() {
return appData;
}
/**
* Gets the seq.
*
* @return the seq
*/
public byte[] getSeq() {
byte[] b = this.getFrameData();
return Arrays.copyOfRange(b, 0, SEQ_SIZE);
}
/**
* Gets the status.
*
* @return the status
*/
public byte getStatus() {
byte[] b = this.getFrameData();
return b[SEQ_SIZE];
}
/**
* Gets the attempt nbr.
*
* @return the attempt nbr
*/
public byte getAttemptNbr() {
byte[] b = this.getFrameData();
return b[SEQ_SIZE + STATUS_SIZE];
}
/**
* Gets the more.
*
* @return the more
*/
public byte getMore() {
byte[] b = this.getFrameData();
return b[SEQ_SIZE + STATUS_SIZE + ATTEMPT_NBR_SIZE];
}
/**
* Gets the src device id.
*
* @return the src device id
*/
public byte[] getSrcDeviceId() {
byte[] b = this.getFrameData();
int start = SEQ_SIZE + STATUS_SIZE + ATTEMPT_NBR_SIZE + MORE_SIZE;
int end = start + SRC_DEVICE_ID_SIZE;
return ByteUtil.reverse(Arrays.copyOfRange(b, start, end));
}
/**
* Gets the data.
*
* @return the data
*/
public byte[] getData() {
byte[] b = this.getFrameData();
int start = SEQ_SIZE + STATUS_SIZE + ATTEMPT_NBR_SIZE + MORE_SIZE + SRC_DEVICE_ID_SIZE;
int end = start + 1 + (b[start] & 0xff);
return Arrays.copyOfRange(b, start + 1, end);
}
/**
* @see org.openhab.binding.sinope.internal.core.base.SinopeFrame#toString()
*/
/*
*
*
* @see ca.tulip.sinope.core.internal.SinopeFrame#toString()
*/
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(super.toString());
sb.append(String.format("\nData: %s", ByteUtil.toString(getFrameData())));
sb.append(String.format("\n\tSeq: %s", ByteUtil.toString(getSeq())));
sb.append(String.format("\n\tStatus: 0x%02X ", getStatus()));
sb.append(String.format("\n\tAttempt Nbr: 0x%02X ", getAttemptNbr()));
sb.append(String.format("\n\tMore: 0x%02X ", getMore()));
sb.append(String.format("\n\tSrcDeviceId: %s", ByteUtil.toString(getSrcDeviceId())));
sb.append(appData);
return sb.toString();
}
}

View File

@@ -0,0 +1,219 @@
/**
* Copyright (c) 2010-2020 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.sinope.internal.core.base;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import org.openhab.binding.sinope.internal.core.appdata.SinopeAppData;
import org.openhab.binding.sinope.internal.util.ByteUtil;
/**
* The Class SinopeDataRequest.
*
* @author Pascal Larin - Initial contribution
*/
public abstract class SinopeDataRequest extends SinopeRequest {
/** The seq. */
private byte[] seq;
/** The request type. */
private byte requestType;
/** The res 1. */
private byte res1;
/** The res 2. */
private byte res2;
/** The res 3. */
private byte[] res3;
/** The res 4. */
private byte[] res4;
/** The dst device id. */
private byte[] dstDeviceId;
/** The app data. */
private SinopeAppData appData;
/**
* Instantiates a new sinope data request.
*
* @param seq the seq
* @param dstDeviceId the dst device id
* @param appData the app data
*/
public SinopeDataRequest(byte[] seq, byte[] dstDeviceId, SinopeAppData appData) {
super();
this.seq = seq;
this.requestType = 0;
this.res1 = 0;
this.res2 = 0;
this.res3 = new byte[] { 0, 0 };
this.res4 = new byte[] { 0, 0 };
this.dstDeviceId = dstDeviceId;
this.appData = appData;
}
/**
* Gets the seq.
*
* @return the seq
*/
public byte[] getSeq() {
return seq;
}
/**
* Gets the request type.
*
* @return the request type
*/
public byte getRequestType() {
return requestType;
}
/**
* Gets the res 1.
*
* @return the res 1
*/
public byte getRes1() {
return res1;
}
/**
* Gets the res 2.
*
* @return the res 2
*/
public byte getRes2() {
return res2;
}
/**
* Gets the res 3.
*
* @return the res 3
*/
public byte[] getRes3() {
return res3;
}
/**
* Gets the res 4.
*
* @return the res 4
*/
public byte[] getRes4() {
return res4;
}
/**
* Gets the dst device id.
*
* @return the dst device id
*/
public byte[] getDstDeviceId() {
return dstDeviceId;
}
/**
* Gets the app data size.
*
* @return the app data size
*/
public int getAppDataSize() {
return getAppData().getInternalData().length;
}
/**
* Gets the app data.
*
* @return the app data
*/
public SinopeAppData getAppData() {
return appData;
}
/**
* @see org.openhab.binding.sinope.internal.core.base.SinopeFrame#getFrameData()
*/
/*
*
*
* @see ca.tulip.sinope.core.internal.SinopeFrame#getFrameData()
*/
@Override
protected byte[] getFrameData() {
int appDataLen = getAppDataSize();
byte b[] = new byte[seq.length + 1 + 1 + 1 + res3.length + res4.length + dstDeviceId.length + 1 + appDataLen];
ByteBuffer bb = ByteBuffer.wrap(b);
bb.put(ByteUtil.reverse(seq));
bb.put(requestType);
bb.put(res1);
bb.put(res2);
bb.put(ByteUtil.reverse(res3));
bb.put(ByteUtil.reverse(res4));
bb.put(ByteUtil.reverse(dstDeviceId));
bb.put((byte) appDataLen);
bb.put(getAppData().getInternalData());
//
// System.out.println(toString(bb.array()));
return bb.array();
}
/**
* @see org.openhab.binding.sinope.internal.core.base.SinopeFrame#toString()
*/
/*
*
*
* @see ca.tulip.sinope.core.internal.SinopeFrame#toString()
*/
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(String.format("\nData: %s", ByteUtil.toString(getFrameData())));
sb.append(String.format("\n\tSeq: %s", ByteUtil.toString(getSeq())));
sb.append(String.format("\n\tRequest Type: 0x%02X ", getRequestType()));
sb.append(String.format("\n\tRes1: 0x%02X ", getRes1()));
sb.append(String.format("\n\tRes2: 0x%02X ", getRes2()));
sb.append(String.format("\n\tRes3: %s", ByteUtil.toString(getRes3())));
sb.append(String.format("\n\tRes4: %s", ByteUtil.toString(getRes4())));
sb.append(String.format("\n\tDstDeviceId: %s", ByteUtil.toString(getDstDeviceId())));
sb.append(String.format("\n\tAppDataSize: 0x%02X ", getAppDataSize()));
sb.append(String.format("\n\tAppData: %s", getAppData()));
return sb.toString();
}
/**
* @see org.openhab.binding.sinope.internal.core.base.SinopeRequest#getReplyAnswer(java.io.InputStream)
*/
/*
*
*
* @see ca.tulip.sinope.core.internal.SinopeRequest#getReplyAnswer(java.io.InputStream)
*/
@Override
public abstract SinopeDataAnswer getReplyAnswer(InputStream r) throws IOException;
}

View File

@@ -0,0 +1,127 @@
/**
* Copyright (c) 2010-2020 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.sinope.internal.core.base;
import org.openhab.binding.sinope.internal.util.ByteUtil;
import org.openhab.binding.sinope.internal.util.CRC8;
/**
* The Class SinopeFrame.
*
* @author Pascal Larin - Initial contribution
*/
abstract class SinopeFrame {
/** The Constant PREAMBLE. */
protected static final byte PREAMBLE = 0x55;
/** The Constant FRAME_CTL. */
protected static final byte FRAME_CTL = 0x00;
/** The Constant PREAMBLE_SIZE. */
protected static final int PREAMBLE_SIZE = 1;
/** The Constant FRAME_CTL_SIZE. */
protected static final int FRAME_CTL_SIZE = 1;
/** The Constant CRC_SIZE. */
protected static final int CRC_SIZE = 1;
/** The Constant SIZE_SIZE. */
protected static final int SIZE_SIZE = 2;
/** The Constant COMMAND_SIZE. */
protected static final byte COMMAND_SIZE = 2;
/** The crc 8. */
private final CRC8 crc8 = new CRC8();
/** The internal payload. */
protected byte[] internal_payload;
/**
* Gets the command.
*
* @return the command
*/
protected abstract byte[] getCommand();
/**
* Gets the frame data.
*
* @return the frame data
*/
protected abstract byte[] getFrameData();
/**
* Gets the payload.
*
* @return the payload
*/
protected abstract byte[] getPayload();
/**
* Gets the crc8.
*
* @param buffer the buffer
* @return the crc8
*/
protected byte getCRC8(byte[] buffer) {
crc8.reset();
crc8.update(buffer, 0, buffer.length - 1);
return (byte) (crc8.getValue());
}
/**
* @see java.lang.Object#toString()
*/
/*
*
*
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
getPayload();
sb.append(ByteUtil.toString(internal_payload));
return sb.toString();
}
/**
* Sets the payload.
*
* @param payload the new payload
*/
public void setPayload(byte[] payload) {
setInternal_payload(payload);
}
/**
* Gets the internal payload.
*
* @return the internal payload
*/
protected byte[] getInternal_payload() {
return internal_payload;
}
/**
* Sets the internal payload.
*
* @param internal_payload the new internal payload
*/
protected void setInternal_payload(byte[] internal_payload) {
this.internal_payload = internal_payload;
}
}

View File

@@ -0,0 +1,86 @@
/**
* Copyright (c) 2010-2020 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.sinope.internal.core.base;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import org.openhab.binding.sinope.internal.util.ByteUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The Class SinopeRequest.
*
* @author Pascal Larin - Initial contribution
*/
public abstract class SinopeRequest extends SinopeFrame {
/** The Constant HEADER_COMMAND_CRC_SIZE. */
protected static final int HEADER_COMMAND_CRC_SIZE = SinopeFrame.PREAMBLE_SIZE + SinopeFrame.FRAME_CTL_SIZE
+ SinopeFrame.SIZE_SIZE + SinopeFrame.COMMAND_SIZE + SinopeFrame.CRC_SIZE;
/** The Constant logger. */
private static final Logger logger = LoggerFactory.getLogger(SinopeRequest.class);
/**
* @see org.openhab.binding.sinope.internal.core.base.SinopeFrame#getPayload()
*/
/*
*
*
* @see ca.tulip.sinope.core.internal.SinopeFrame#getPayload()
*/
@Override
public byte[] getPayload() {
if (getInternal_payload() == null) {
byte[] command = getCommand();
byte[] data = getFrameData();
int len = HEADER_COMMAND_CRC_SIZE + data.length;
byte[] buffer = new byte[len];
ByteBuffer bb = ByteBuffer.wrap(buffer);
bb.put(PREAMBLE);
bb.put(FRAME_CTL);
bb.order(ByteOrder.LITTLE_ENDIAN);
bb.putShort((short) (SinopeFrame.COMMAND_SIZE + data.length));
bb.put(ByteUtil.reverse(command));
bb.put(data);
bb.put(getCRC8(bb.array()));
setInternal_payload(bb.array());
}
return getInternal_payload();
}
/**
* Gets the reply answer.
*
* @param r the r
* @return the reply answer
* @throws IOException Signals that an I/O exception has occurred.
*/
public abstract SinopeAnswer getReplyAnswer(InputStream r) throws IOException;
/**
* @see org.openhab.binding.sinope.internal.core.base.SinopeFrame#setInternal_payload(byte[])
*/
@Override
protected void setInternal_payload(byte[] internal_payload) {
logger.debug("Request Frame: {}", ByteUtil.toString(internal_payload));
super.setInternal_payload(internal_payload);
}
}

View File

@@ -0,0 +1,101 @@
/**
* Copyright (c) 2010-2020 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.sinope.internal.discovery;
import java.io.IOException;
import java.util.Set;
import org.openhab.binding.sinope.SinopeBindingConstants;
import org.openhab.binding.sinope.handler.SinopeGatewayHandler;
import org.openhab.binding.sinope.internal.util.ByteUtil;
import org.openhab.core.config.discovery.AbstractDiscoveryService;
import org.openhab.core.config.discovery.DiscoveryListener;
import org.openhab.core.config.discovery.DiscoveryResult;
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.ThingUID;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link AbstractDiscoveryService} provides methods which handle the {@link DiscoveryListener}s.
*
* Subclasses do not have to care about adding and removing those listeners.
* They can use the protected methods {@link #thingDiscovered(DiscoveryResult)} and {@link #thingRemoved(String)} in
* order to notify the registered {@link DiscoveryListener}s.
*
* @author Pascal Larin - Initial contribution
*
*/
public class SinopeThingsDiscoveryService extends AbstractDiscoveryService {
private final Logger logger = LoggerFactory.getLogger(SinopeThingsDiscoveryService.class);
private static final int SEARCH_TIME = 120;
private SinopeGatewayHandler sinopeGatewayHandler;
public SinopeThingsDiscoveryService(SinopeGatewayHandler sinopeGatewayHandler) {
super(SinopeBindingConstants.SUPPORTED_THING_TYPES_UIDS, SEARCH_TIME);
this.sinopeGatewayHandler = sinopeGatewayHandler;
}
@Override
public Set<ThingTypeUID> getSupportedThingTypes() {
return SinopeBindingConstants.SUPPORTED_THING_TYPES_UIDS;
}
@Override
public void startScan() {
logger.debug("Sinope Things starting scan");
try {
sinopeGatewayHandler.startSearch(this);
} catch (IOException e) {
logger.debug("Search failed with an exception", e);
}
}
@Override
protected synchronized void stopScan() {
super.stopScan();
removeOlderResults(getTimestampOfLastScan());
try {
sinopeGatewayHandler.stopSearch();
} catch (IOException e) {
logger.debug("Can't stop search with an exception", e);
} finally {
logger.debug("Sinope Things scan stopped");
}
}
public void newThermostat(byte[] deviceId) {
logger.debug("Sinope Things service discovered a new device with id: {}", ByteUtil.toString(deviceId));
ThingTypeUID thingTypeUID = SinopeBindingConstants.THING_TYPE_THERMO;
ThingUID bridgeUID = sinopeGatewayHandler.getThing().getUID();
ThingUID thingUID = new ThingUID(thingTypeUID, bridgeUID, toUID(deviceId));
DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withThingType(thingTypeUID)
.withBridge(bridgeUID).withLabel("Device-Sinope")
.withProperty(SinopeBindingConstants.CONFIG_PROPERTY_DEVICE_ID, ByteUtil.toString(deviceId)).build();
thingDiscovered(discoveryResult);
}
private static String toUID(byte[] deviceId) {
StringBuilder sb = new StringBuilder();
for (byte b : deviceId) {
sb.append(String.format("%02X", b));
}
return sb.toString();
}
}

View File

@@ -0,0 +1,61 @@
/**
* Copyright (c) 2010-2020 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.sinope.internal.util;
import java.util.Arrays;
/**
* The Class ByteUtil.
*
* @author Pascal Larin - Initial contribution
*/
public class ByteUtil {
/**
* Reverse.
*
* @param array to reverse
* @return the reserved in byte[]
*/
public static byte[] reverse(byte[] array) {
if (array == null) {
return null;
}
byte[] r = Arrays.copyOf(array, array.length);
int i = 0;
int j = r.length - 1;
byte tmp;
while (j > i) {
tmp = r[j];
r[j] = r[i];
r[i] = tmp;
j--;
i++;
}
return r;
}
/**
* To string.
*
* @param buf the buf
* @return the string
*/
public static String toString(byte[] buf) {
StringBuilder sb = new StringBuilder();
for (byte b : buf) {
sb.append(String.format("0x%02X ", b));
}
return sb.toString();
}
}

View File

@@ -0,0 +1,97 @@
/**
* Copyright (c) 2010-2020 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.sinope.internal.util;
import java.util.zip.Checksum;
/**
* The Class CRC8.
*
* @author Pascal Larin - Initial contribution
*/
public class CRC8 implements Checksum {
/** The init. */
private final int init;
/** The Constant crcTable. */
private static final int crcTable[] = { 0x00, 0x07, 0x0e, 0x09, 0x1c, 0x1b, 0x12, 0x15, 0x38, 0x3f, 0x36, 0x31,
0x24, 0x23, 0x2a, 0x2d, 0x70, 0x77, 0x7e, 0x79, 0x6c, 0x6b, 0x62, 0x65, 0x48, 0x4f, 0x46, 0x41, 0x54, 0x53,
0x5a, 0x5d, 0xe0, 0xe7, 0xee, 0xe9, 0xfc, 0xfb, 0xf2, 0xf5, 0xd8, 0xdf, 0xd6, 0xd1, 0xc4, 0xc3, 0xca, 0xcd,
0x90, 0x97, 0x9e, 0x99, 0x8c, 0x8b, 0x82, 0x85, 0xa8, 0xaf, 0xa6, 0xa1, 0xb4, 0xb3, 0xba, 0xbd, 0xc7, 0xc0,
0xc9, 0xce, 0xdb, 0xdc, 0xd5, 0xd2, 0xff, 0xf8, 0xf1, 0xf6, 0xe3, 0xe4, 0xed, 0xea, 0xb7, 0xb0, 0xb9, 0xbe,
0xab, 0xac, 0xa5, 0xa2, 0x8f, 0x88, 0x81, 0x86, 0x93, 0x94, 0x9d, 0x9a, 0x27, 0x20, 0x29, 0x2e, 0x3b, 0x3c,
0x35, 0x32, 0x1f, 0x18, 0x11, 0x16, 0x03, 0x04, 0x0d, 0x0a, 0x57, 0x50, 0x59, 0x5e, 0x4b, 0x4c, 0x45, 0x42,
0x6f, 0x68, 0x61, 0x66, 0x73, 0x74, 0x7d, 0x7a, 0x89, 0x8e, 0x87, 0x80, 0x95, 0x92, 0x9b, 0x9c, 0xb1, 0xb6,
0xbf, 0xb8, 0xad, 0xaa, 0xa3, 0xa4, 0xf9, 0xfe, 0xf7, 0xf0, 0xe5, 0xe2, 0xeb, 0xec, 0xc1, 0xc6, 0xcf, 0xc8,
0xdd, 0xda, 0xd3, 0xd4, 0x69, 0x6e, 0x67, 0x60, 0x75, 0x72, 0x7b, 0x7c, 0x51, 0x56, 0x5f, 0x58, 0x4d, 0x4a,
0x43, 0x44, 0x19, 0x1e, 0x17, 0x10, 0x05, 0x02, 0x0b, 0x0c, 0x21, 0x26, 0x2f, 0x28, 0x3d, 0x3a, 0x33, 0x34,
0x4e, 0x49, 0x40, 0x47, 0x52, 0x55, 0x5c, 0x5b, 0x76, 0x71, 0x78, 0x7f, 0x6a, 0x6d, 0x64, 0x63, 0x3e, 0x39,
0x30, 0x37, 0x22, 0x25, 0x2c, 0x2b, 0x06, 0x01, 0x08, 0x0f, 0x1a, 0x1d, 0x14, 0x13, 0xae, 0xa9, 0xa0, 0xa7,
0xb2, 0xb5, 0xbc, 0xbb, 0x96, 0x91, 0x98, 0x9f, 0x8a, 0x8d, 0x84, 0x83, 0xde, 0xd9, 0xd0, 0xd7, 0xc2, 0xc5,
0xcc, 0xcb, 0xe6, 0xe1, 0xe8, 0xef, 0xfa, 0xfd, 0xf4, 0xf3 };
/** The value. */
private int value;
/**
* Instantiates a new crc8.
*/
public CRC8() {
this.value = this.init = 0;
}
/**
* @see java.util.zip.Checksum#update(byte[], int, int)
*/
@Override
public void update(byte[] buffer, int offset, int len) {
for (int i = 0; i < len; i++) {
byte data = buffer[offset + i];
value = crcTable[value ^ data & 0xff];
}
}
/**
* Update.
*
* @param buffer the buffer
*/
public void update(byte[] buffer) {
update(buffer, 0, buffer.length);
}
/**
* @see java.util.zip.Checksum#update(int)
*/
@Override
public void update(int b) {
update(new byte[] { (byte) b }, 0, 1);
}
/**
* @see java.util.zip.Checksum#getValue()
*/
@Override
public long getValue() {
return value & 0xFF;
}
/**
* @see java.util.zip.Checksum#reset()
*/
@Override
public void reset() {
value = init;
}
}

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<binding:binding id="sinope" 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>Sinopé Binding</name>
<description>This is the binding for Sinopé. Sinopé provides high-quality, energy-efficient products and with
innovative technology solutions that will meet customer's current and future needs:
low and high voltage thermostats,
light switches, water leak detectors and load controllers.</description>
<author>Pascal Larin</author>
</binding:binding>

View File

@@ -0,0 +1,4 @@
config-status.error.missing-host-configuration=No host for the sinopé gateway has been provided.
config-status.error.missing-port-configuration=No port for the sinopé gateway has been provided.
invalid-gateway-id-configuration=Missing or invalid gateway id.
invalid-api-key-configuration=Missing or invalid API key.

View File

@@ -0,0 +1,111 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="sinope"
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="gateway">
<label>Sinopé Gateway</label>
<description>A Sinopé Gateway</description>
<config-description>
<parameter name="hostname" type="text">
<label>Hostname</label>
<description>Hostname of the Sinopé Gateway</description>
<context>network-address</context>
<required>true</required>
</parameter>
<parameter name="port" type="integer">
<label>Port</label>
<description>The port that the Sinopé Gateway listens on</description>
<default>4550</default>
</parameter>
<parameter name="gatewayId" type="text">
<label>Gateway ID</label>
<description>The Sinopé gateway ID (as printed on the back-side, e.g. "xxxx xxxx xxxx xxxx")</description>
<required>true</required>
</parameter>
<parameter name="apiKey" type="text">
<label>API Key</label>
<description>Use sinope-core application to generate your api key</description>
<required>true</required>
</parameter>
<parameter name="refresh" type="integer" required="false">
<label>Refresh Interval</label>
<description>The number of seconds between fetches from the Sinopé Gateway.</description>
<default>60</default>
</parameter>
</config-description>
</bridge-type>
<thing-type id="thermostat">
<supported-bridge-type-refs>
<bridge-type-ref id="gateway"/>
</supported-bridge-type-refs>
<label>Sinopé Thermostat</label>
<description>Sinopé Thermostat control</description>
<channels>
<channel id="insideTemperature" typeId="insideTemperature"/>
<channel id="outsideTemperature" typeId="outsideTemperature"/>
<channel id="setpointTemperature" typeId="setpointTemperature"/>
<channel id="setpointMode" typeId="setpointMode"/>
<channel id="heatingLevel" typeId="heatingLevel"/>
</channels>
<config-description>
<parameter name="deviceId" type="text" required="true">
<label>Thermostat Device ID</label>
</parameter>
</config-description>
</thing-type>
<channel-type id="insideTemperature">
<item-type>Number:Temperature</item-type>
<label>Temperature</label>
<description>Current inside temperature</description>
<category>Temperature</category>
<!-- As specified by manufacturer -->
<state min="-10" max="70" step="0.01" pattern="%.2f %unit%" readOnly="true"/>
</channel-type>
<channel-type id="outsideTemperature">
<item-type>Number:Temperature</item-type>
<label>Temperature</label>
<description>Current outside temperature</description>
<category>Temperature</category>
<state step="0.01" pattern="%.2f %unit%" readOnly="true"/>
</channel-type>
<channel-type id="setpointTemperature">
<item-type>Number:Temperature</item-type>
<label>Temperature</label>
<description>Setpoint temperature</description>
<category>Temperature</category>
<state min="5" max="30" step="0.01" pattern="%.2f %unit%" readOnly="false"/>
</channel-type>
<channel-type id="setpointMode">
<item-type>Number</item-type>
<label>Setpoint Mode</label>
<description>Thermostat setpoint mode</description>
<category>Temperature</category>
<state pattern="%s" readOnly="false">
<options>
<option value="0">Off</option>
<option value="1">Freeze Protect</option>
<option value="2">Manual</option>
<option value="3">Auto</option>
<option value="5">Away</option>
<option value="129">Bypass Freeze Protect</option>
<option value="131">Bypass Auto</option>
<option value="133">Bypass Away</option>
</options>
</state>
</channel-type>
<channel-type id="heatingLevel">
<item-type>Dimmer</item-type>
<label>Heat Level</label>
<description>Heating Level</description>
<category>Heating</category>
<state min="0" max="100" step="1" pattern="%d %%" readOnly="true"/>
</channel-type>
</thing:thing-descriptions>