Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import java.net.SocketException;
import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
Expand All @@ -33,9 +34,7 @@
import org.eclipse.californium.core.coap.OptionNumberRegistry;
import org.eclipse.californium.core.coap.Request;
import org.eclipse.californium.core.coap.Response;
import org.eclipse.californium.core.coap.option.IntegerOption;
import org.eclipse.californium.core.coap.option.OpaqueOption;
import org.eclipse.californium.core.coap.option.StringOption;
import org.eclipse.californium.core.network.Endpoint;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
Expand Down Expand Up @@ -158,6 +157,27 @@ public boolean isStarted() {
return statusClient != null;
}

private byte[] readOptionBytes(Option option) {
try {
return option.encode();
} catch (RuntimeException e) {
logger.debug("{}: Failed to encode option value", thingName, e);
return EMPTY_BYTE;
}
}

private String readStringOption(Option option) {
return new String(readOptionBytes(option), StandardCharsets.UTF_8);
}

private int readIntegerOption(Option option) {
int value = 0;
for (byte b : readOptionBytes(option)) {
value = (value << 8) | (b & 0xFF);
}
return value;
}

/**
* Process an inbound Response (or mapped Request): decode CoAP options. handle discovery result or status updates
*
Expand Down Expand Up @@ -185,16 +205,14 @@ public void processResponse(@Nullable Response response) {
// We can't identify device by IP, so we need to check the CoAP header's Global Device ID
for (Option opt : options) {
if (opt.getNumber() == COIOT_OPTION_GLOBAL_DEVID) {
if (opt instanceof StringOption strOpt) {
String devid = strOpt.getStringValue();
if (devid.contains("#") && profile.device.mac != null) {
// Format: <device type>#<mac address>#<coap version>
String macid = substringBetween(devid, "#", "#");
if (getString(profile.device.mac).toUpperCase(Locale.ROOT)
.contains(macid.toUpperCase(Locale.ROOT))) {
match = true;
break;
}
String devid = readStringOption(opt);
if (devid.contains("#") && profile.device.mac != null) {
// Format: <device type>#<mac address>#<coap version>
String macid = substringBetween(devid, "#", "#");
if (getString(profile.device.mac).toUpperCase(Locale.ROOT)
.contains(macid.toUpperCase(Locale.ROOT))) {
match = true;
break;
}
}
}
Expand Down Expand Up @@ -231,63 +249,48 @@ public void processResponse(@Nullable Response response) {
for (Option opt : options) {
switch (opt.getNumber()) {
case OptionNumberRegistry.URI_PATH:
if (opt instanceof StringOption strOpt) {
uri = COLOIT_URI_BASE + strOpt.getStringValue();
} else {
logger.debug("{}: URI_PATH option is not a StringOption, type: {}", thingName,
opt.getClass().getSimpleName());
}
uri = COLOIT_URI_BASE + readStringOption(opt);
break;
case OptionNumberRegistry.URI_HOST: // ignore
break;
case OptionNumberRegistry.CONTENT_FORMAT: // ignore
break;
case COIOT_OPTION_GLOBAL_DEVID:
if (opt instanceof StringOption strOpt) {
devId = strOpt.getStringValue();
String sVersion = substringAfterLast(devId, "#");
int iVersion;
try {
iVersion = Integer.parseInt(sVersion);
} catch (NumberFormatException e) {
logger.debug("{}: Unable to parse version in CoIoT message: {}", thingName, devId);
thingHandler.incProtErrors();
devId = readStringOption(opt);
String sVersion = substringAfterLast(devId, "#");
int iVersion;
try {
iVersion = Integer.parseInt(sVersion);
} catch (NumberFormatException e) {
logger.debug("{}: Unable to parse version in CoIoT message: {}", thingName, devId);
thingHandler.incProtErrors();
return;
}
if (coiotBound && coiotVers != iVersion) {
logger.debug("{}: CoIoT version has changed from {} to {}, maybe the firmware was upgraded",
thingName, coiotVers, iVersion);
thingHandler.reinitializeThing();
coiotBound = false;
}
if (!coiotBound) {
thingHandler.updateProperties(PROPERTY_COAP_VERSION, sVersion);
logger.debug("{}: CoIoT Version {} detected", thingName, iVersion);
if (iVersion == COIOT_VERSION_1) {
coiot = new Shelly1CoIoTVersion1(thingName, thingHandler, blkMap, sensorMap);
} else if (iVersion == COIOT_VERSION_2) {
coiot = new Shelly1CoIoTVersion2(thingName, thingHandler, blkMap, sensorMap);
} else {
logger.warn("{}: Unsupported CoAP version detected: {}", thingName, sVersion);
return;
}
if (coiotBound && coiotVers != iVersion) {
logger.debug("{}: CoIoT version has changed from {} to {}, maybe the firmware was upgraded",
thingName, coiotVers, iVersion);
thingHandler.reinitializeThing();
coiotBound = false;
}
if (!coiotBound) {
thingHandler.updateProperties(PROPERTY_COAP_VERSION, sVersion);
logger.debug("{}: CoIoT Version {} detected", thingName, iVersion);
if (iVersion == COIOT_VERSION_1) {
coiot = new Shelly1CoIoTVersion1(thingName, thingHandler, blkMap, sensorMap);
} else if (iVersion == COIOT_VERSION_2) {
coiot = new Shelly1CoIoTVersion2(thingName, thingHandler, blkMap, sensorMap);
} else {
logger.warn("{}: Unsupported CoAP version detected: {}", thingName, sVersion);
return;
}
coiotVers = iVersion;
coiotBound = true;
}
} else {
logger.debug("{}: COIOT_OPTION_GLOBAL_DEVID option is not a StringOption, type: {}", thingName,
opt.getClass().getSimpleName());
coiotVers = iVersion;
coiotBound = true;
}
break;
case COIOT_OPTION_STATUS_VALIDITY:
break;
case COIOT_OPTION_STATUS_SERIAL:
if (opt instanceof IntegerOption intOpt) {
serial = intOpt.getIntegerValue();
} else {
logger.debug("{}: COIOT_OPTION_STATUS_SERIAL option is not an IntegerOption, type: {}",
thingName, opt.getClass().getSimpleName());
}
serial = readIntegerOption(opt);
break;
default:
logger.debug("{} ({}): CoAP option {} with value {} skipped", thingName, devId, opt.getNumber(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,6 @@
*/
package org.openhab.binding.shelly.internal.api1;

import static org.openhab.binding.shelly.internal.api1.Shelly1CoapJSonDTO.COIOT_OPTION_GLOBAL_DEVID;
import static org.openhab.binding.shelly.internal.api1.Shelly1CoapJSonDTO.COIOT_OPTION_STATUS_SERIAL;
import static org.openhab.binding.shelly.internal.api1.Shelly1CoapJSonDTO.COIOT_OPTION_STATUS_VALIDITY;
import static org.openhab.binding.shelly.internal.api1.Shelly1CoapJSonDTO.COIOT_PORT;

import java.net.InetAddress;
Expand All @@ -30,11 +27,6 @@
import org.eclipse.californium.core.coap.CoAP.ResponseCode;
import org.eclipse.californium.core.coap.Request;
import org.eclipse.californium.core.coap.Response;
import org.eclipse.californium.core.coap.option.IntegerOption;
import org.eclipse.californium.core.coap.option.MapBasedOptionRegistry;
import org.eclipse.californium.core.coap.option.OptionRegistry;
import org.eclipse.californium.core.coap.option.StandardOptionRegistry;
import org.eclipse.californium.core.coap.option.StringOption;
import org.eclipse.californium.core.network.CoapEndpoint;
import org.eclipse.californium.core.network.Exchange;
import org.eclipse.californium.elements.UdpMulticastConnector;
Expand All @@ -52,27 +44,9 @@
*/
@NonNullByDefault
public class Shelly1CoapServer {
// Define custom CoIoT option definitions for Shelly devices
private static final StringOption.Definition COIOT_GLOBAL_DEVID_DEF = new StringOption.Definition(
COIOT_OPTION_GLOBAL_DEVID, "Shelly-GlobalDevId", true, 0, 255);
private static final IntegerOption.Definition COIOT_STATUS_VALIDITY_DEF = new IntegerOption.Definition(
COIOT_OPTION_STATUS_VALIDITY, "Shelly-StatusValidity", true, 0, 4);
private static final IntegerOption.Definition COIOT_STATUS_SERIAL_DEF = new IntegerOption.Definition(
COIOT_OPTION_STATUS_SERIAL, "Shelly-StatusSerial", true, 0, 4);

static {
// register configurations before Configuration.getStandard() is used
DtlsConfig.register();
// register custom CoIoT options for Shelly devices
// Merge with existing default registry to preserve options from other add-ons (e.g., Tradfri)
OptionRegistry currentRegistry = StandardOptionRegistry.getDefaultOptionRegistry();
OptionRegistry mergedRegistry = MapBasedOptionRegistry.builder() //
.add(currentRegistry) //
.add(COIOT_GLOBAL_DEVID_DEF) //
.add(COIOT_STATUS_VALIDITY_DEF) //
.add(COIOT_STATUS_SERIAL_DEF) //
.build();
StandardOptionRegistry.setDefaultOptionRegistry(mergedRegistry);
}
private final Logger logger = LoggerFactory.getLogger(Shelly1CoapServer.class);

Expand Down
Loading