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,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>