[shelly] Add support for Shelly BLU series of devices (#15031)
* support for Pro 3EM (WIP) * Support for Plug-S and Smoke added * new channel resetTotals for emeters, new channel sensor#mute for Smoke * Validate Temp reported by CoAP before updating channel, ignore 999 * Add support for Shelly BLU Button and Door/Window Signed-off-by: Markus Michels <markus7017@gmail.com>
This commit is contained in:
parent
46039efd0a
commit
631148320f
|
@ -77,7 +77,7 @@ The binding provides the same feature set across all devices as good as possible
|
|||
### Generation 2 Plus series
|
||||
|
||||
| thing-type | Model | Vendor ID |
|
||||
| -------------------- | -------------------------------------------------------- | --------------------------------------------- |
|
||||
| -------------------- | -------------------------------------------------------- | ---------------------------- |
|
||||
| shellyplus1 | Shelly Plus 1 with 1x relay | SNSW-001X16EU |
|
||||
| shellyplus1pm | Shelly Plus 1PM with 1x relay + power meter | SNSW-001P16EU |
|
||||
| shellyplus2pm-relay | Shelly Plus 2PM with 2x relay + power meter, relay mode | SNSW-002P16EU, SNSW-102P16EU |
|
||||
|
@ -104,6 +104,14 @@ The binding provides the same feature set across all devices as good as possible
|
|||
| shellypro3em | Shelly Pro 3 with 3 integrated power meters | SPEM-003CEBEU |
|
||||
| shellypro4pm | Shelly Pro 4 PM with 4x relay + power meter | SPSW-004PE16EU, SPSW-104PE16EU |
|
||||
|
||||
### Shelly BLU
|
||||
|
||||
| thing-type | Model | Vendor ID |
|
||||
| ----------------- | ------------------------------------------------------ | --------- |
|
||||
| shellyblubutton | Shelly BLU Button 1 | SBBT |
|
||||
| shellybludw | Shelly BLU Door/Windows | SBDW |
|
||||
|
||||
|
||||
## Binding Configuration
|
||||
|
||||
The binding has the following configuration options:
|
||||
|
@ -160,6 +168,29 @@ This allows routing the CoIoT/CoAP messages across multiple IP subnets without s
|
|||
You could use Shelly Manager (doc/ShellyManager.md) to easily do the setup (configuring the openHAB host as CoAP peer address).
|
||||
Keep Multicast mode if you have multiple hosts, which should receive the CoAP updates.
|
||||
|
||||
### Discovery of BLU Devices
|
||||
|
||||
The BLU devices use Bluetooth Low Energy (BLE).
|
||||
The binding can't communicate directly with the device, but the Plus/Pro series with firmware 0.14.1 or newer could be used as a gateway.
|
||||
The binding automatically installs a script on the Shelly Device (oh-blu-scanner), which forwards the BLU events to the binding using the WebSocket channel.
|
||||
|
||||
Follow these steps to add the Shelly BLU Device to openHAB
|
||||
- Make sure a Shelly is near by the BLU device, enable Bluetooh on this device (the Bluetooth Gateway mode is not required)
|
||||
- Add this thing to openHAB, make sure thing gets online
|
||||
- Enable "BLU Gateway Support" in the thing configuration of the Shelly device acting as gateway.
|
||||
- Now press the button on your BLU device, this wakes up the device and the script forwards this event to the binding
|
||||
- As a result the corresponding thing should show up in the Inbox
|
||||
- Add the thing (at this point no channels are created), the new thing will show status CONFIG_PENDING
|
||||
- Click the device button again, the binding gets another event and creates the channels and thing changes status to ONLINE
|
||||
- Finally link the channels to the equipment in the model
|
||||
|
||||
Note: During initialization the script 'oh-blu-scanner.js' gets installed and activated on the Shelly Gateway device.
|
||||
|
||||
Every time an event is received sensors#lastUpdate and channels are updated with the reported values.
|
||||
device#wifiSignal indicates the Bluetooth signal strength and gets updated when the device sends an event.
|
||||
|
||||
The binding supports multiple Shelly Plus/Pro as gateway devices unless they are added as thing and are ONLINE.
|
||||
|
||||
### Password Protected Devices
|
||||
|
||||
The Shelly devices can be configured to require authorization through a user id and password.
|
||||
|
@ -251,6 +282,7 @@ You could also create a rule to catch those status changes or device alarms (see
|
|||
| eventsRoller | true: register event "trigger" when the roller updates status | no | true for roller devices |
|
||||
| favoriteUP | 0-4: Favorite id for UP (see Roller Favorites) | no | 0 = no favorite id |
|
||||
| favoriteDOWN | 0-4: Favorite id for DOWN (see Roller Favorites) | no | 0 = no favorite id |
|
||||
| enableBluGateway | true: Active BLU gateway support (install script) | no | false ]
|
||||
|
||||
### General Notes
|
||||
|
||||
|
@ -380,7 +412,7 @@ A new alarm will be triggered on a new condition or every 5 minutes if the condi
|
|||
| TEMP_OVER | Above "temperature over" threshold |
|
||||
| VIBRATION | A vibration/tamper was detected (DW2 only) |
|
||||
|
||||
Refer to section [Full Example](#full-example) for examples how to catch alarm triggers in openHAB rules
|
||||
Refer to section [Full Example](#full-example) for examples how to catch alarm triggers in openHAB rules.
|
||||
|
||||
## Channels
|
||||
|
||||
|
@ -1341,6 +1373,38 @@ Channels lastEvent and eventCount are only available if input type is set to mom
|
|||
| | timerActive | Switch | yes | Relay #1: ON: An auto-on/off timer is active |
|
||||
| | button | Trigger | yes | Event trigger, see section Button Events |
|
||||
|
||||
## Shelly BLU Devices
|
||||
|
||||
### Shelly BLU Button 1 (thing-type: shellyblubutton)
|
||||
|
||||
See notes on discovery of Shelly BLU devices above.
|
||||
|
||||
| Group | Channel | Type | read-only | Description |
|
||||
| ------- | ------------- | -------- | --------- | ----------------------------------------------------------------------------------- |
|
||||
| status | lastEvent | String | yes | Last event type (S/SS/SSS/L) |
|
||||
| | eventCount | Number | yes | Counter gets incremented every time the device issues a button event. |
|
||||
| | button | Trigger | yes | Event trigger with payload, see SHORT_PRESSED or LONG_PRESSED |
|
||||
| | lastUpdate | DateTime | yes | Timestamp of the last measurement |
|
||||
| battery | batteryLevel | Number | yes | Battery Level in % |
|
||||
| | lowBattery | Switch | yes | Low battery alert (< 20%) |
|
||||
| device | gatewayDevice | String | yes | Shelly forwarded last status update (BLU gateway), could vary from packet to packet |
|
||||
|
||||
|
||||
|
||||
### Shelly BLU Door/Window Sensor (thing-type: shellybludw)
|
||||
|
||||
See notes on discovery of Shelly BLU devices above.
|
||||
|
||||
| Group | Channel | Type | read-only | Description |
|
||||
| ------- | ------------- | -------- | --------- | ----------------------------------------------------------------------------------- |
|
||||
| sensors | state | Contact | yes | OPEN: Contact is open, CLOSED: Contact is closed |
|
||||
| | lux | Number | yes | Brightness in Lux |
|
||||
| | tilt | Number | yes | Tilt in ° (angle), -1 indicates that the sensor is not calibrated |
|
||||
| | lastUpdate | DateTime | yes | Timestamp of the last update (any sensor value changed) |
|
||||
| battery | batteryLevel | Number | yes | Battery Level in % |
|
||||
| | lowBattery | Switch | yes | Low battery alert (< 20%) |
|
||||
| device | gatewayDevice | String | yes | Shelly forwarded last status update (BLU gateway), could vary from packet to packet |
|
||||
|
||||
## Full Example
|
||||
|
||||
### shelly.things
|
||||
|
@ -1582,4 +1646,3 @@ sitemap demo label="Home"
|
|||
Number item=Shelly_Power
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
|
@ -85,11 +85,14 @@ public class ShellyBindingConstants {
|
|||
THING_TYPE_SHELLYPLUSSMOKE, //
|
||||
THING_TYPE_SHELLYPLUSPLUGS, //
|
||||
THING_TYPE_SHELLYPLUSPLUGUS, //
|
||||
THING_TYPE_SHELLYBLUBUTTON, //
|
||||
THING_TYPE_SHELLYBLUDW, //
|
||||
THING_TYPE_SHELLYPROTECTED, //
|
||||
THING_TYPE_SHELLYUNKNOWN);
|
||||
|
||||
// Thing Configuration Properties
|
||||
public static final String CONFIG_DEVICEIP = "deviceIp";
|
||||
public static final String CONFIG_DEVICEADDRESS = "deviceAddress";
|
||||
public static final String CONFIG_HTTP_USERID = "userId";
|
||||
public static final String CONFIG_HTTP_PASSWORD = "password";
|
||||
public static final String CONFIG_UPDATE_INTERVAL = "updateInterval";
|
||||
|
@ -99,6 +102,7 @@ public class ShellyBindingConstants {
|
|||
public static final String PROPERTY_DEV_TYPE = "deviceType";
|
||||
public static final String PROPERTY_DEV_MODE = "deviceMode";
|
||||
public static final String PROPERTY_DEV_GEN = "deviceGeneration";
|
||||
public static final String PROPERTY_GW_DEVICE = "gatewayDevice";
|
||||
public static final String PROPERTY_HWREV = "deviceHwRev";
|
||||
public static final String PROPERTY_HWBATCH = "deviceHwBatch";
|
||||
public static final String PROPERTY_UPDATE_PERIOD = "devUpdatePeriod";
|
||||
|
@ -230,6 +234,7 @@ public class ShellyBindingConstants {
|
|||
// Device Status
|
||||
public static final String CHANNEL_GROUP_DEV_STATUS = "device";
|
||||
public static final String CHANNEL_DEVST_NAME = "deviceName";
|
||||
public static final String CHANNEL_DEVST_GATEWAY = "gatewayDevice";
|
||||
public static final String CHANNEL_DEVST_UPTIME = "uptime";
|
||||
public static final String CHANNEL_DEVST_HEARTBEAT = "heartBeat";
|
||||
public static final String CHANNEL_DEVST_RSSI = "wifiSignal";
|
||||
|
@ -311,4 +316,7 @@ public class ShellyBindingConstants {
|
|||
public static final int UPDATE_SETTINGS_INTERVAL_SECONDS = 60; // check for updates every x sec
|
||||
public static final int HEALTH_CHECK_INTERVAL_SEC = 300; // Health check interval, 5min
|
||||
public static final int VIBRATION_FILTER_SEC = 5; // Absore duplicate vibration events for xx sec
|
||||
|
||||
public static final String BUNDLE_RESOURCE_SNIPLETS = "sniplets"; // where to find code sniplets in the bundle
|
||||
public static final String BUNDLE_RESOURCE_SCRIPTS = "scripts"; // where to find scrips in the bundle
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ import org.eclipse.jetty.client.HttpClient;
|
|||
import org.openhab.binding.shelly.internal.api1.Shelly1CoapServer;
|
||||
import org.openhab.binding.shelly.internal.config.ShellyBindingConfiguration;
|
||||
import org.openhab.binding.shelly.internal.handler.ShellyBaseHandler;
|
||||
import org.openhab.binding.shelly.internal.handler.ShellyBluSensorHandler;
|
||||
import org.openhab.binding.shelly.internal.handler.ShellyLightHandler;
|
||||
import org.openhab.binding.shelly.internal.handler.ShellyManagerInterface;
|
||||
import org.openhab.binding.shelly.internal.handler.ShellyProtectedHandler;
|
||||
|
@ -103,6 +104,11 @@ public class ShellyHandlerFactory extends BaseThingHandlerFactory {
|
|||
this.coapServer = new Shelly1CoapServer();
|
||||
}
|
||||
|
||||
@Activate
|
||||
void activate() {
|
||||
thingTable.startDiscoveryService(bundleContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
|
||||
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
|
||||
|
@ -123,11 +129,15 @@ public class ShellyHandlerFactory extends BaseThingHandlerFactory {
|
|||
|| thingType.equals(THING_TYPE_SHELLYRGBW2_WHITE_STR)
|
||||
|| thingType.equals(THING_TYPE_SHELLYRGBW2_WHITE_STR) || thingType.equals(THING_TYPE_SHELLYDUORGBW_STR)
|
||||
|| thingType.equals(THING_TYPE_SHELLYVINTAGE_STR)) {
|
||||
logger.debug("{}: Create new thing of type {} using ShellyLightHandler", thing.getLabel(),
|
||||
logger.debug("{}: Create new thing of type {} using ShellyLightHandler", thing.getLabel(),
|
||||
thingTypeUID.toString());
|
||||
handler = new ShellyLightHandler(thing, messages, bindingConfig, thingTable, coapServer, httpClient);
|
||||
} else if (thingType.startsWith("shellyblu")) {
|
||||
logger.debug("{}: Create new thing of type {} using ShellyBluSensorHandler", thing.getLabel(),
|
||||
thingTypeUID.toString());
|
||||
handler = new ShellyBluSensorHandler(thing, messages, bindingConfig, thingTable, coapServer, httpClient);
|
||||
} else if (SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID)) {
|
||||
logger.debug("{}: Create new thing of type {} using ShellyRelayHandler", thing.getLabel(),
|
||||
logger.debug("{}: Create new thing of type {} using ShellyRelayHandler", thing.getLabel(),
|
||||
thingTypeUID.toString());
|
||||
handler = new ShellyRelayHandler(thing, messages, bindingConfig, thingTable, coapServer, httpClient);
|
||||
}
|
||||
|
@ -143,20 +153,13 @@ public class ShellyHandlerFactory extends BaseThingHandlerFactory {
|
|||
return null;
|
||||
}
|
||||
|
||||
public Map<String, ShellyManagerInterface> getThingHandlers() {
|
||||
Map<String, ShellyManagerInterface> table = new HashMap<>();
|
||||
for (Map.Entry<String, ShellyThingInterface> entry : thingTable.getTable().entrySet()) {
|
||||
table.put(entry.getKey(), (ShellyManagerInterface) entry.getValue());
|
||||
}
|
||||
return table;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove handler of things.
|
||||
*/
|
||||
@Override
|
||||
protected synchronized void removeHandler(@NonNull ThingHandler thingHandler) {
|
||||
if (thingHandler instanceof ShellyBaseHandler) {
|
||||
((ShellyBaseHandler) thingHandler).stop();
|
||||
String uid = thingHandler.getThing().getUID().getAsString();
|
||||
thingTable.removeThing(uid);
|
||||
}
|
||||
|
@ -185,4 +188,12 @@ public class ShellyHandlerFactory extends BaseThingHandlerFactory {
|
|||
public ShellyBindingConfiguration getBindingConfig() {
|
||||
return bindingConfig;
|
||||
}
|
||||
|
||||
public Map<String, ShellyManagerInterface> getThingHandlers() {
|
||||
Map<String, ShellyManagerInterface> table = new HashMap<>();
|
||||
for (Map.Entry<String, ShellyThingInterface> entry : thingTable.getTable().entrySet()) {
|
||||
table.put(entry.getKey(), (ShellyManagerInterface) entry.getValue());
|
||||
}
|
||||
return table;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -83,6 +83,8 @@ public class ShellyApiException extends Exception {
|
|||
string[1]);
|
||||
} else if (isMalformedURL()) {
|
||||
message = "Invalid URL: " + url;
|
||||
} else if (isJsonError()) {
|
||||
message = getString(getMessage());
|
||||
} else if (isTimeout()) {
|
||||
message = "API Timeout for " + url;
|
||||
} else if (!isConnectionError()) {
|
||||
|
|
|
@ -46,7 +46,7 @@ public interface ShellyApiInterface {
|
|||
|
||||
ShellySettingsStatus getStatus() throws ShellyApiException;
|
||||
|
||||
void setLedStatus(String ledName, Boolean value) throws ShellyApiException;
|
||||
void setLedStatus(String ledName, boolean value) throws ShellyApiException;
|
||||
|
||||
void setSleepTime(int value) throws ShellyApiException;
|
||||
|
||||
|
@ -54,9 +54,9 @@ public interface ShellyApiInterface {
|
|||
|
||||
void setRelayTurn(int id, String turnMode) throws ShellyApiException;
|
||||
|
||||
public void resetMeterTotal(int id) throws ShellyApiException;
|
||||
void resetMeterTotal(int id) throws ShellyApiException;
|
||||
|
||||
public ShellyRollerStatus getRollerStatus(int rollerIndex) throws ShellyApiException;
|
||||
ShellyRollerStatus getRollerStatus(int rollerIndex) throws ShellyApiException;
|
||||
|
||||
void setRollerTurn(int relayIndex, String turnMode) throws ShellyApiException;
|
||||
|
||||
|
@ -80,7 +80,6 @@ public interface ShellyApiInterface {
|
|||
|
||||
void setBrightness(int id, int brightness, boolean autoOn) throws ShellyApiException;
|
||||
|
||||
// Valve
|
||||
void setValveMode(int id, boolean auto) throws ShellyApiException;
|
||||
|
||||
void setValveTemperature(int valveId, int value) throws ShellyApiException;
|
||||
|
@ -137,5 +136,9 @@ public interface ShellyApiInterface {
|
|||
|
||||
void sendIRKey(String keyCode) throws ShellyApiException, IllegalArgumentException;
|
||||
|
||||
void postEvent(String device, String index, String event, Map<String, String> parms) throws ShellyApiException;
|
||||
|
||||
void close();
|
||||
|
||||
void startScan();
|
||||
}
|
||||
|
|
|
@ -67,6 +67,8 @@ public class ShellyDeviceProfile {
|
|||
public boolean auth = false;
|
||||
public boolean alwaysOn = true;
|
||||
public boolean isGen2 = false;
|
||||
public boolean isBlu = false;
|
||||
public String gateway = "";
|
||||
|
||||
public String hwRev = "";
|
||||
public String hwBatchId = "";
|
||||
|
@ -125,6 +127,10 @@ public class ShellyDeviceProfile {
|
|||
// Shelly UNI uses ext_temperature array, reformat to avoid GSON exception
|
||||
json = json.replace("ext_temperature", "ext_temperature_array");
|
||||
}
|
||||
if (json.contains("\"ext_humidity\":{\"0\":[{")) {
|
||||
// Shelly UNI uses ext_humidity array, reformat to avoid GSON exception
|
||||
json = json.replace("ext_humidity", "ext_humidity_array");
|
||||
}
|
||||
settingsJson = json;
|
||||
settings = fromJson(gson, json, ShellySettingsGlobal.class);
|
||||
|
||||
|
@ -185,6 +191,8 @@ public class ShellyDeviceProfile {
|
|||
return;
|
||||
}
|
||||
|
||||
isBlu = thingType.startsWith("shellyblu"); // e.g. SBBT for BU Button
|
||||
|
||||
isDimmer = deviceType.equalsIgnoreCase(SHELLYDT_DIMMER) || deviceType.equalsIgnoreCase(SHELLYDT_DIMMER2);
|
||||
isBulb = thingType.equals(THING_TYPE_SHELLYBULB_STR);
|
||||
isDuo = thingType.equals(THING_TYPE_SHELLYDUO_STR) || thingType.equals(THING_TYPE_SHELLYVINTAGE_STR)
|
||||
|
@ -201,12 +209,14 @@ public class ShellyDeviceProfile {
|
|||
boolean isGas = thingType.equals(THING_TYPE_SHELLYGAS_STR);
|
||||
boolean isUNI = thingType.equals(THING_TYPE_SHELLYUNI_STR);
|
||||
isHT = thingType.equals(THING_TYPE_SHELLYHT_STR) || thingType.equals(THING_TYPE_SHELLYPLUSHT_STR);
|
||||
isDW = thingType.equals(THING_TYPE_SHELLYDOORWIN_STR) || thingType.equals(THING_TYPE_SHELLYDOORWIN2_STR);
|
||||
isDW = thingType.equals(THING_TYPE_SHELLYDOORWIN_STR) || thingType.equals(THING_TYPE_SHELLYDOORWIN2_STR)
|
||||
|| thingType.equals(THING_TYPE_SHELLYBLUDW_STR);
|
||||
isMotion = thingType.startsWith(THING_TYPE_SHELLYMOTION_STR);
|
||||
isSense = thingType.equals(THING_TYPE_SHELLYSENSE_STR);
|
||||
isIX = thingType.equals(THING_TYPE_SHELLYIX3_STR) || thingType.equals(THING_TYPE_SHELLYPLUSI4_STR)
|
||||
|| thingType.equals(THING_TYPE_SHELLYPLUSI4DC_STR);
|
||||
isButton = thingType.equals(THING_TYPE_SHELLYBUTTON1_STR) || thingType.equals(THING_TYPE_SHELLYBUTTON2_STR);
|
||||
isButton = thingType.equals(THING_TYPE_SHELLYBUTTON1_STR) || thingType.equals(THING_TYPE_SHELLYBUTTON2_STR)
|
||||
|| thingType.equals(THING_TYPE_SHELLYBLUBUTTON_STR);
|
||||
isSensor = isHT || isFlood || isDW || isSmoke || isGas || isButton || isUNI || isMotion || isSense || isTRV;
|
||||
hasBattery = isHT || isFlood || isDW || isSmoke || isButton || isMotion || isTRV;
|
||||
isTRV = thingType.equals(THING_TYPE_SHELLYTRV_STR);
|
||||
|
@ -241,7 +251,8 @@ public class ShellyDeviceProfile {
|
|||
} else if (hasRelays) {
|
||||
return numRelays <= 1 ? CHANNEL_GROUP_RELAY_CONTROL : CHANNEL_GROUP_RELAY_CONTROL + idx;
|
||||
} else if (isRGBW2) {
|
||||
return settings.lights == null || settings.lights.size() <= 1 ? CHANNEL_GROUP_LIGHT_CONTROL
|
||||
return settings.lights == null || settings.lights != null && settings.lights.size() <= 1
|
||||
? CHANNEL_GROUP_LIGHT_CONTROL
|
||||
: CHANNEL_GROUP_LIGHT_CHANNEL + idx;
|
||||
} else if (isLight) {
|
||||
return CHANNEL_GROUP_LIGHT_CONTROL;
|
||||
|
|
|
@ -146,14 +146,15 @@ public class ShellyHttpClient {
|
|||
HTTP_AUTH_TYPE_BASIC + " " + Base64.getEncoder().encodeToString(value.getBytes()));
|
||||
}
|
||||
fillPostData(request, data);
|
||||
logger.trace("{}: HTTP {} for {} {}", thingName, method, url, data);
|
||||
logger.trace("{}: HTTP {} for {} {}\n{}", thingName, method, url, data, request.getHeaders());
|
||||
|
||||
// Do request and get response
|
||||
ContentResponse contentResponse = request.send();
|
||||
apiResult = new ShellyApiResult(contentResponse);
|
||||
apiResult.httpCode = contentResponse.getStatus();
|
||||
String response = contentResponse.getContentAsString().replace("\t", "").replace("\r\n", "").trim();
|
||||
logger.trace("{}: HTTP Response {}: {}", thingName, contentResponse.getStatus(), response);
|
||||
logger.trace("{}: HTTP Response {}: {}\n{}", thingName, contentResponse.getStatus(), response,
|
||||
contentResponse.getHeaders());
|
||||
|
||||
if (response.contains("\"error\":{")) { // Gen2
|
||||
Shelly2RpcBaseMessage message = gson.fromJson(response, Shelly2RpcBaseMessage.class);
|
||||
|
@ -204,7 +205,7 @@ public class ShellyHttpClient {
|
|||
StringContentProvider postData;
|
||||
postData = new StringContentProvider(type, data, StandardCharsets.UTF_8);
|
||||
request.content(postData);
|
||||
request.header(HttpHeader.CONTENT_LENGTH, Long.toString(postData.getLength()));
|
||||
// request.header(HttpHeader.CONTENT_LENGTH, Long.toString(postData.getLength()));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -253,4 +254,8 @@ public class ShellyHttpClient {
|
|||
public int getTimeoutsRecovered() {
|
||||
return timeoutsRecovered;
|
||||
}
|
||||
|
||||
public void postEvent(String device, String index, String event, Map<String, String> parms)
|
||||
throws ShellyApiException {
|
||||
}
|
||||
}
|
||||
|
|
|
@ -739,7 +739,12 @@ public class Shelly1ApiJsonDTO {
|
|||
public ArrayList<ShellyRollerStatus> rollers;
|
||||
public ArrayList<ShellySettingsLight> lights;
|
||||
public ArrayList<ShellySettingsMeter> meters;
|
||||
|
||||
public ArrayList<ShellySettingsEMeter> emeters;
|
||||
public Double totalCurrent;
|
||||
public Double totalPower;
|
||||
public Double totalReturned;
|
||||
|
||||
@SerializedName("ext_temperature")
|
||||
public ShellyStatusSensor.ShellyExtTemperature extTemperature; // Shelly 1/1PM: sensor values
|
||||
@SerializedName("ext_humidity")
|
||||
|
|
|
@ -30,14 +30,14 @@ import org.openhab.core.types.State;
|
|||
*/
|
||||
@NonNullByDefault
|
||||
public interface Shelly1CoIoTInterface {
|
||||
int getVersion();
|
||||
public int getVersion();
|
||||
|
||||
CoIotDescrSen fixDescription(@Nullable CoIotDescrSen sen, Map<String, CoIotDescrBlk> blkMap);
|
||||
public CoIotDescrSen fixDescription(@Nullable CoIotDescrSen sen, Map<String, CoIotDescrBlk> blkMap);
|
||||
|
||||
void completeMissingSensorDefinition(Map<String, CoIotDescrSen> sensorMap);
|
||||
public void completeMissingSensorDefinition(Map<String, CoIotDescrSen> sensorMap);
|
||||
|
||||
boolean handleStatusUpdate(List<CoIotSensor> sensorUpdates, CoIotDescrSen sen, int serial, CoIotSensor s,
|
||||
public boolean handleStatusUpdate(List<CoIotSensor> sensorUpdates, CoIotDescrSen sen, int serial, CoIotSensor s,
|
||||
Map<String, State> updates, ShellyColorUtils col);
|
||||
|
||||
String getLastWakeup();
|
||||
public String getLastWakeup();
|
||||
}
|
||||
|
|
|
@ -210,8 +210,8 @@ public class Shelly1CoIoTProtocol {
|
|||
"{}: Check button[{}] for event trigger (inButtonMode={}, isButton={}, hasBattery={}, serial={}, count={}, lastEventCount[{}]={}",
|
||||
thingName, idx, profile.inButtonMode(idx), profile.isButton, profile.hasBattery, serial, count, idx,
|
||||
lastEventCount[idx]);
|
||||
if (profile.inButtonMode(idx) && ((profile.hasBattery && count == 1)
|
||||
|| (lastEventCount[idx] != -1 && count != lastEventCount[idx]))) {
|
||||
if (profile.inButtonMode(idx) && ((profile.hasBattery && count == 1) || lastEventCount[idx] == -1
|
||||
|| count != lastEventCount[idx])) {
|
||||
if (!profile.isButton || (profile.isButton && (serial != 0x200))) { // skip duplicate on wake-up
|
||||
logger.debug("{}: Trigger event {}", thingName, inputEvent[idx]);
|
||||
thingHandler.triggerButton(group, idx, inputEvent[idx]);
|
||||
|
|
|
@ -176,7 +176,7 @@ public class Shelly1CoapHandler implements Shelly1CoapListener {
|
|||
|
||||
List<Option> options = response.getOptions().asSortedList();
|
||||
String ip = response.getSourceContext().getPeerAddress().toString();
|
||||
boolean match = ip.contains(config.deviceIp);
|
||||
boolean match = ip.contains("/" + config.deviceIp + ":");
|
||||
if (!match) {
|
||||
// We can't identify device by IP, so we need to check the CoAP header's Global Device ID
|
||||
for (Option opt : options) {
|
||||
|
|
|
@ -23,5 +23,5 @@ import org.eclipse.jdt.annotation.Nullable;
|
|||
*/
|
||||
@NonNullByDefault
|
||||
public interface Shelly1CoapListener {
|
||||
void processResponse(@Nullable Response response);
|
||||
public void processResponse(@Nullable Response response);
|
||||
}
|
||||
|
|
|
@ -177,7 +177,7 @@ public class Shelly1HttpApi extends ShellyHttpClient implements ShellyApiInterfa
|
|||
|
||||
@Override
|
||||
public void resetMeterTotal(int id) throws ShellyApiException {
|
||||
callApi(SHELLY_URL_STATUS_EMETER + "/" + id + "/reset_totals=1", ShellyStatusRelay.class);
|
||||
callApi(SHELLY_URL_STATUS_EMETER + "/" + id + "?reset_totals=true", ShellyStatusRelay.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -245,7 +245,7 @@ public class Shelly1HttpApi extends ShellyHttpClient implements ShellyApiInterfa
|
|||
} else if (profile.isLight) {
|
||||
type = SHELLY_CLASS_LIGHT;
|
||||
}
|
||||
String uri = SHELLY_URL_SETTINGS + "/" + type + "/" + index + "?" + timerName + "=" + (int) value;
|
||||
String uri = SHELLY_URL_SETTINGS + "/" + type + "/" + index + "?" + timerName + "=" + value;
|
||||
httpRequest(uri);
|
||||
}
|
||||
|
||||
|
@ -294,7 +294,7 @@ public class Shelly1HttpApi extends ShellyHttpClient implements ShellyApiInterfa
|
|||
}
|
||||
|
||||
@Override
|
||||
public void setLedStatus(String ledName, Boolean value) throws ShellyApiException {
|
||||
public void setLedStatus(String ledName, boolean value) throws ShellyApiException {
|
||||
httpRequest(SHELLY_URL_SETTINGS + "?" + ledName + "=" + (value ? SHELLY_API_TRUE : SHELLY_API_FALSE));
|
||||
}
|
||||
|
||||
|
@ -752,4 +752,8 @@ public class Shelly1HttpApi extends ShellyHttpClient implements ShellyApiInterfa
|
|||
@Override
|
||||
public void close() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startScan() {
|
||||
}
|
||||
}
|
||||
|
|
|
@ -289,6 +289,16 @@ public class Shelly2ApiClient extends ShellyHttpClient {
|
|||
return false;
|
||||
}
|
||||
|
||||
if (em.totalCurrent != null) {
|
||||
status.totalCurrent = em.totalCurrent;
|
||||
}
|
||||
if (em.totalActPower != null) {
|
||||
status.totalPower = em.totalActPower;
|
||||
}
|
||||
if (em.totalAprtPower != null) {
|
||||
status.totalReturned = em.totalAprtPower;
|
||||
}
|
||||
|
||||
ShellySettingsMeter sm = new ShellySettingsMeter();
|
||||
ShellySettingsEMeter emeter = status.emeters.get(0);
|
||||
sm.isValid = emeter.isValid = true;
|
||||
|
@ -683,7 +693,7 @@ public class Shelly2ApiClient extends ShellyHttpClient {
|
|||
throw new ShellyApiException("Thing/profile not initialized!");
|
||||
}
|
||||
|
||||
ShellyDeviceProfile getProfile() throws ShellyApiException {
|
||||
protected ShellyDeviceProfile getProfile() throws ShellyApiException {
|
||||
if (thing != null) {
|
||||
return thing.getProfile();
|
||||
}
|
||||
|
|
|
@ -58,6 +58,15 @@ public class Shelly2ApiJsonDTO {
|
|||
public static final String SHELLYRPC_METHOD_WSSETCONFIG = "WS.SetConfig";
|
||||
public static final String SHELLYRPC_METHOD_SMOKE_SETCONFIG = "Smoke.SetConfig";
|
||||
public static final String SHELLYRPC_METHOD_SMOKE_MUTE = "Smoke.Mute";
|
||||
public static final String SHELLYRPC_METHOD_SCRIPT_LIST = "Script.List";
|
||||
public static final String SHELLYRPC_METHOD_SCRIPT_SETCONFIG = "Script.SetConfig";
|
||||
public static final String SHELLYRPC_METHOD_SCRIPT_GETSTATUS = "Script.GetStatus";
|
||||
public static final String SHELLYRPC_METHOD_SCRIPT_DELETE = "Script.Delete";
|
||||
public static final String SHELLYRPC_METHOD_SCRIPT_CREATE = "Script.Create";
|
||||
public static final String SHELLYRPC_METHOD_SCRIPT_GETCODE = "Script.GetCode";
|
||||
public static final String SHELLYRPC_METHOD_SCRIPT_PUTCODE = "Script.PutCode";
|
||||
public static final String SHELLYRPC_METHOD_SCRIPT_START = "Script.Start";
|
||||
public static final String SHELLYRPC_METHOD_SCRIPT_STOP = "Script.Stop";
|
||||
|
||||
public static final String SHELLYRPC_METHOD_NOTIFYSTATUS = "NotifyStatus"; // inbound status
|
||||
public static final String SHELLYRPC_METHOD_NOTIFYFULLSTATUS = "NotifyFullStatus"; // inbound status from bat device
|
||||
|
@ -118,6 +127,12 @@ public class Shelly2ApiJsonDTO {
|
|||
public static final String SHELLY2_EVENT_WIFICONNFAILED = "sta_connect_fail";
|
||||
public static final String SHELLY2_EVENT_WIFIDISCONNECTED = "sta_disconnected";
|
||||
|
||||
// BLU events
|
||||
public static final String SHELLY2_BLU_GWSCRIPT = "oh-blu-scanner.js";
|
||||
public static final String SHELLY2_EVENT_BLUPREFIX = "oh-blu.";
|
||||
public static final String SHELLY2_EVENT_BLUSCAN = SHELLY2_EVENT_BLUPREFIX + "scan_result";
|
||||
public static final String SHELLY2_EVENT_BLUDATA = SHELLY2_EVENT_BLUPREFIX + "data";
|
||||
|
||||
// Error Codes
|
||||
public static final String SHELLY2_ERROR_OVERPOWER = "overpower";
|
||||
public static final String SHELLY2_ERROR_OVERTEMP = "overtemp";
|
||||
|
@ -549,6 +564,13 @@ public class Shelly2ApiJsonDTO {
|
|||
|
||||
@SerializedName("n_current")
|
||||
public Double nCurrent;
|
||||
|
||||
@SerializedName("total_current")
|
||||
public Double totalCurrent;
|
||||
@SerializedName("total_act_power")
|
||||
public Double totalActPower;
|
||||
@SerializedName("total_aprt_power")
|
||||
public Double totalAprtPower;
|
||||
}
|
||||
|
||||
public static class Shelly2DeviceStatusEmData {
|
||||
|
@ -754,6 +776,9 @@ public class Shelly2ApiJsonDTO {
|
|||
// Cloud.SetConfig
|
||||
public Shelly2ConfigParms config;
|
||||
|
||||
// Script
|
||||
public String name;
|
||||
|
||||
public Shelly2RpcRequestParams withConfig() {
|
||||
config = new Shelly2ConfigParms();
|
||||
return this;
|
||||
|
@ -779,6 +804,11 @@ public class Shelly2ApiJsonDTO {
|
|||
params.pos = pos;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Shelly2RpcRequest withName(String name) {
|
||||
params.name = name;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Shelly2WsConfigResponse {
|
||||
|
@ -793,6 +823,30 @@ public class Shelly2ApiJsonDTO {
|
|||
public Shelly2WsConfigResult result;
|
||||
}
|
||||
|
||||
public static class ShellyScriptListResponse {
|
||||
public static class ShellyScriptListEntry {
|
||||
public Integer id;
|
||||
public String name;
|
||||
public Boolean enable;
|
||||
public Boolean running;
|
||||
}
|
||||
|
||||
public ArrayList<ShellyScriptListEntry> scripts;
|
||||
}
|
||||
|
||||
public static class ShellyScriptResponse {
|
||||
public Integer id;
|
||||
public Boolean running;
|
||||
public Integer len;
|
||||
public String data;
|
||||
}
|
||||
|
||||
public static class ShellyScriptPutCodeParams {
|
||||
public Integer id;
|
||||
public String code;
|
||||
public Boolean append;
|
||||
}
|
||||
|
||||
public static class Shelly2RpcBaseMessage {
|
||||
// Basic message format, e.g.
|
||||
// {"id":1,"src":"localweb528","method":"Shelly.GetConfig"}
|
||||
|
@ -804,8 +858,10 @@ public class Shelly2ApiJsonDTO {
|
|||
public Integer id;
|
||||
public String src;
|
||||
public String dst;
|
||||
public String component;
|
||||
public String method;
|
||||
public Object params;
|
||||
public String event;
|
||||
public Object result;
|
||||
public Shelly2AuthRequest auth;
|
||||
public Shelly2RpcMessageError error;
|
||||
|
@ -852,11 +908,49 @@ public class Shelly2ApiJsonDTO {
|
|||
public String algorithm;
|
||||
}
|
||||
|
||||
// BTHome samples
|
||||
// BLU Button 1
|
||||
// {"component":"script:2", "id":2, "event":"oh-blu.scan_result",
|
||||
// "data":{"addr":"bc:02:6e:c3:a6:c7","rssi":-62,"tx_power":-128}, "ts":1682877414.21}
|
||||
// {"component":"script:2", "id":2, "event":"oh-blu.data",
|
||||
// "data":{"encryption":false,"BTHome_version":2,"pid":205,"Battery":100,"Button":1,"addr":"b4:35:22:fd:b3:81","rssi":-68},
|
||||
// "ts":1682877399.22}
|
||||
//
|
||||
// BLU Door Window
|
||||
// {"component":"script:2", "id":2, "event":"oh-blu.scan_result",
|
||||
// "data":{"addr":"bc:02:6e:c3:a6:c7","rssi":-62,"tx_power":-128}, "ts":1682877414.21}
|
||||
// {"component":"script:2", "id":2, "event":"oh-blu.data",
|
||||
// "data":{"encryption":false,"BTHome_version":2,"pid":38,"Battery":100,"Illuminance":0,"Window":1,"Rotation":0,"addr":"bc:02:6e:c3:a6:c7","rssi":-62},
|
||||
// "ts":1682877414.25}
|
||||
|
||||
public class Shelly2NotifyEventMessage {
|
||||
public String addr;
|
||||
public String name;
|
||||
public Boolean encryption;
|
||||
@SerializedName("BTHome_version")
|
||||
public Integer bthVersion;
|
||||
public Integer pid;
|
||||
@SerializedName("Battery")
|
||||
public Integer battery;
|
||||
@SerializedName("Button")
|
||||
public Integer buttonEvent;
|
||||
@SerializedName("Illuminance")
|
||||
public Integer illuminance;
|
||||
@SerializedName("Window")
|
||||
public Integer windowState;
|
||||
@SerializedName("Rotation")
|
||||
public Double rotation;
|
||||
|
||||
public Integer rssi;
|
||||
public Integer tx_power;
|
||||
}
|
||||
|
||||
public class Shelly2NotifyEvent {
|
||||
public Integer id;
|
||||
public Double ts;
|
||||
public String component;
|
||||
public String event;
|
||||
public Shelly2NotifyEventMessage data;
|
||||
public String msg;
|
||||
public Integer reason;
|
||||
@SerializedName("cfg_rev")
|
||||
|
@ -869,7 +963,8 @@ public class Shelly2ApiJsonDTO {
|
|||
}
|
||||
|
||||
public static class Shelly2RpcNotifyEvent {
|
||||
public String src;
|
||||
public Double ts;
|
||||
Shelly2NotifyEventData params;
|
||||
public Shelly2NotifyEventData params;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,9 +17,15 @@ import static org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.*;
|
|||
import static org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.*;
|
||||
import static org.openhab.binding.shelly.internal.util.ShellyUtils.*;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
@ -65,6 +71,10 @@ import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2RpcRequ
|
|||
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2RpcRequest.Shelly2RpcRequestParams;
|
||||
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2WsConfigResponse;
|
||||
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2WsConfigResponse.Shelly2WsConfigResult;
|
||||
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.ShellyScriptListResponse;
|
||||
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.ShellyScriptListResponse.ShellyScriptListEntry;
|
||||
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.ShellyScriptPutCodeParams;
|
||||
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.ShellyScriptResponse;
|
||||
import org.openhab.binding.shelly.internal.config.ShellyThingConfiguration;
|
||||
import org.openhab.binding.shelly.internal.handler.ShellyThingInterface;
|
||||
import org.openhab.binding.shelly.internal.handler.ShellyThingTable;
|
||||
|
@ -83,7 +93,7 @@ public class Shelly2ApiRpc extends Shelly2ApiClient implements ShellyApiInterfac
|
|||
private final Logger logger = LoggerFactory.getLogger(Shelly2ApiRpc.class);
|
||||
private final @Nullable ShellyThingTable thingTable;
|
||||
|
||||
private boolean initialized = false;
|
||||
protected boolean initialized = false;
|
||||
private boolean discovery = false;
|
||||
private Shelly2RpcSocket rpcSocket = new Shelly2RpcSocket();
|
||||
private Shelly2AuthResponse authInfo = new Shelly2AuthResponse();
|
||||
|
@ -139,6 +149,17 @@ public class Shelly2ApiRpc extends Shelly2ApiClient implements ShellyApiInterfac
|
|||
return initialized;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startScan() {
|
||||
if (config.enableBluGateway) {
|
||||
try {
|
||||
installScript(SHELLY2_BLU_GWSCRIPT);
|
||||
} catch (ShellyApiException e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("null")
|
||||
@Override
|
||||
public ShellyDeviceProfile getDeviceProfile(String thingType) throws ShellyApiException {
|
||||
ShellyDeviceProfile profile = thing != null ? getProfile() : new ShellyDeviceProfile();
|
||||
|
@ -269,6 +290,19 @@ public class Shelly2ApiRpc extends Shelly2ApiClient implements ShellyApiInterfac
|
|||
if (!discovery) {
|
||||
getStatus(); // make sure profile.status is initialized (e.g,. relay/meter status)
|
||||
asyncApiRequest(SHELLYRPC_METHOD_GETSTATUS); // request periodic status updates from device
|
||||
|
||||
try {
|
||||
logger.debug("{}: BLU Gateway support enabled for this device: {}", thingName, config.enableBluGateway);
|
||||
if (config.enableBluGateway) {
|
||||
if (getBool(profile.settings.bluetooth)) {
|
||||
installScript(SHELLY2_BLU_GWSCRIPT);
|
||||
} else {
|
||||
logger.debug("{}: Bluetooth needs to be enabled to activate BLU Gateway mode", thingName);
|
||||
}
|
||||
}
|
||||
} catch (ShellyApiException e) {
|
||||
logger.debug("{}: Device config failed", thingName, e);
|
||||
}
|
||||
}
|
||||
|
||||
return profile;
|
||||
|
@ -306,6 +340,117 @@ public class Shelly2ApiRpc extends Shelly2ApiClient implements ShellyApiInterfac
|
|||
}
|
||||
}
|
||||
|
||||
protected void installScript(String script) throws ShellyApiException {
|
||||
String json = apiRequest(new Shelly2RpcRequest().withMethod(SHELLYRPC_METHOD_SCRIPT_LIST));
|
||||
ShellyScriptListResponse scriptList = gson.fromJson(json, ShellyScriptListResponse.class);
|
||||
Integer ourId = -1;
|
||||
String code = "";
|
||||
|
||||
logger.debug("{}: Install or restart script {} on Shelly Device", thingName, script);
|
||||
boolean running = false, upload = false;
|
||||
if (scriptList != null) {
|
||||
for (ShellyScriptListEntry s : scriptList.scripts) {
|
||||
if (s.name.startsWith(script)) {
|
||||
ourId = s.id;
|
||||
running = s.running;
|
||||
logger.debug("{}: Script {} is already installed, id={}", thingName, script, ourId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// get script code from bundle resources
|
||||
String file = BUNDLE_RESOURCE_SCRIPTS + "/" + script;
|
||||
ClassLoader cl = Shelly2ApiRpc.class.getClassLoader();
|
||||
if (cl != null) {
|
||||
try (InputStream inputStream = cl.getResourceAsStream(file)) {
|
||||
if (inputStream != null) {
|
||||
code = new BufferedReader(new InputStreamReader(inputStream)).lines()
|
||||
.collect(Collectors.joining("\n"));
|
||||
}
|
||||
} catch (IOException | UncheckedIOException e) {
|
||||
logger.debug("{}: Installation of script {} failed: Unable to read {} from bundle resources!",
|
||||
thingName, script, file, e);
|
||||
}
|
||||
}
|
||||
|
||||
boolean restart = false;
|
||||
if (ourId == -1) {
|
||||
// script not installed -> install it
|
||||
upload = true;
|
||||
} else {
|
||||
try {
|
||||
// verify that the same code version is active (avoid unnesesary flash updates)
|
||||
json = apiRequest(new Shelly2RpcRequest().withMethod(SHELLYRPC_METHOD_SCRIPT_GETCODE).withId(ourId));
|
||||
ShellyScriptResponse rsp = gson.fromJson(json, ShellyScriptResponse.class);
|
||||
if (!rsp.data.trim().equals(code.trim())) {
|
||||
logger.debug("{}: A script version was found, update to newest one", thingName);
|
||||
upload = true;
|
||||
} else {
|
||||
logger.debug("{}: Same script version was found, restart", thingName);
|
||||
restart = true;
|
||||
}
|
||||
} catch (ShellyApiException e) {
|
||||
logger.debug("{}: Unable to read current script code -> force update (deviced returned: {})", thingName,
|
||||
e.getMessage());
|
||||
upload = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (restart || (running && upload)) {
|
||||
json = apiRequest(new Shelly2RpcRequest().withMethod(SHELLYRPC_METHOD_SCRIPT_STOP).withId(ourId));
|
||||
// first stop running script
|
||||
running = false;
|
||||
}
|
||||
if (upload && ourId != -1) {
|
||||
// Delete existing script
|
||||
logger.debug("{}: Delete existing script", thingName);
|
||||
json = apiRequest(new Shelly2RpcRequest().withMethod(SHELLYRPC_METHOD_SCRIPT_DELETE).withId(ourId));
|
||||
}
|
||||
|
||||
if (upload) {
|
||||
logger.debug("{}: Script will be installed...", thingName);
|
||||
|
||||
// Create new script, get id
|
||||
json = apiRequest(new Shelly2RpcRequest().withMethod(SHELLYRPC_METHOD_SCRIPT_CREATE).withName(script));
|
||||
ShellyScriptResponse rsp = gson.fromJson(json, ShellyScriptResponse.class);
|
||||
if (rsp != null) {
|
||||
ourId = rsp.id;
|
||||
logger.debug("{}: Script has been created, id={}", thingName, ourId);
|
||||
upload = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (upload) {
|
||||
// Put script code for generated id
|
||||
ShellyScriptPutCodeParams parms = new ShellyScriptPutCodeParams();
|
||||
parms.id = ourId;
|
||||
parms.append = false;
|
||||
int length = code.length(), processed = 0, chunk = 1;
|
||||
do {
|
||||
int nextlen = Math.min(1024, length - processed);
|
||||
parms.code = code.substring(processed, processed + nextlen);
|
||||
logger.debug("{}: Uploading chunk {} of script (total {} chars, {} processed)", thingName, chunk,
|
||||
length, processed);
|
||||
apiRequest(SHELLYRPC_METHOD_SCRIPT_PUTCODE, parms, String.class);
|
||||
processed += nextlen;
|
||||
chunk++;
|
||||
parms.append = true;
|
||||
} while (processed < length);
|
||||
running = false;
|
||||
|
||||
Shelly2RpcRequestParams params = new Shelly2RpcRequestParams().withConfig();
|
||||
params.config.enable = true;
|
||||
apiRequest(SHELLYRPC_METHOD_SCRIPT_SETCONFIG, params, String.class);
|
||||
}
|
||||
|
||||
if (!running) {
|
||||
// Script was created or is there and stopped -> start it
|
||||
json = apiRequest(new Shelly2RpcRequest().withMethod(SHELLYRPC_METHOD_SCRIPT_START).withId(ourId));
|
||||
logger.debug("{}: Script {} was {} successful", thingName, script,
|
||||
restart ? "restarted" : "installed and started");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConnect(String deviceIp, boolean connected) {
|
||||
if (thing == null && thingTable != null) {
|
||||
|
@ -755,7 +900,7 @@ public class Shelly2ApiRpc extends Shelly2ApiClient implements ShellyApiInterfac
|
|||
* categories (e.g. bulbs)
|
||||
*/
|
||||
@Override
|
||||
public void setLedStatus(String ledName, Boolean value) throws ShellyApiException {
|
||||
public void setLedStatus(String ledName, boolean value) throws ShellyApiException {
|
||||
throw new ShellyApiException("API call not implemented");
|
||||
}
|
||||
|
||||
|
@ -866,6 +1011,7 @@ public class Shelly2ApiRpc extends Shelly2ApiClient implements ShellyApiInterfac
|
|||
rpcSocket.sendMessage(gson.toJson(request)); // submit, result wull be async
|
||||
}
|
||||
|
||||
@SuppressWarnings("null")
|
||||
public <T> T apiRequest(String method, @Nullable Object params, Class<T> classOfT) throws ShellyApiException {
|
||||
String json = "";
|
||||
Shelly2RpcBaseMessage req = buildRequest(method, params);
|
||||
|
@ -905,8 +1051,18 @@ public class Shelly2ApiRpc extends Shelly2ApiClient implements ShellyApiInterfac
|
|||
throw e;
|
||||
}
|
||||
}
|
||||
Shelly2RpcBaseMessage response = gson.fromJson(json, Shelly2RpcBaseMessage.class);
|
||||
if (response == null) {
|
||||
throw new IllegalArgumentException("Unable to cover API result to obhect");
|
||||
}
|
||||
if (response.result != null) {
|
||||
// return sub element result as requested class type
|
||||
json = gson.toJson(gson.fromJson(json, Shelly2RpcBaseMessage.class).result);
|
||||
return fromJson(gson, json, classOfT);
|
||||
} else {
|
||||
// return direct format
|
||||
return gson.fromJson(json, classOfT);
|
||||
}
|
||||
}
|
||||
|
||||
public <T> T apiRequest(Shelly2RpcRequest request, Class<T> classOfT) throws ShellyApiException {
|
||||
|
|
|
@ -34,9 +34,11 @@ import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
|
|||
import org.eclipse.jetty.websocket.client.WebSocketClient;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyApiException;
|
||||
import org.openhab.binding.shelly.internal.api1.Shelly1HttpApi;
|
||||
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2NotifyEvent;
|
||||
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2RpcBaseMessage;
|
||||
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2RpcNotifyEvent;
|
||||
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2RpcNotifyStatus;
|
||||
import org.openhab.binding.shelly.internal.handler.ShellyBluSensorHandler;
|
||||
import org.openhab.binding.shelly.internal.handler.ShellyThingInterface;
|
||||
import org.openhab.binding.shelly.internal.handler.ShellyThingTable;
|
||||
import org.slf4j.Logger;
|
||||
|
@ -250,8 +252,34 @@ public class Shelly2RpcSocket {
|
|||
handler.onNotifyStatus(status);
|
||||
return;
|
||||
case SHELLYRPC_METHOD_NOTIFYEVENT:
|
||||
handler.onNotifyEvent(fromJson(gson, receivedMessage, Shelly2RpcNotifyEvent.class));
|
||||
return;
|
||||
Shelly2RpcNotifyEvent events = fromJson(gson, receivedMessage, Shelly2RpcNotifyEvent.class);
|
||||
events.src = message.src;
|
||||
if (events.params == null || events.params.events == null) {
|
||||
logger.debug("{}: Malformed event data: {}", thingName, receivedMessage);
|
||||
} else {
|
||||
for (Shelly2NotifyEvent e : events.params.events) {
|
||||
if (getString(e.event).startsWith(SHELLY2_EVENT_BLUPREFIX)) {
|
||||
String address = getString(e.data.addr).replaceAll(":", "");
|
||||
if (thingTable != null && thingTable.findThing(address) != null) {
|
||||
if (thingTable != null) { // known device
|
||||
ShellyThingInterface thing = thingTable.getThing(address);
|
||||
Shelly2ApiRpc api = (Shelly2ApiRpc) thing.getApi();
|
||||
handler = api.getRpcHandler();
|
||||
handler.onNotifyEvent(
|
||||
fromJson(gson, receivedMessage, Shelly2RpcNotifyEvent.class));
|
||||
}
|
||||
} else { // new device
|
||||
if (e.event.equals(SHELLY2_EVENT_BLUSCAN)) {
|
||||
ShellyBluSensorHandler.addBluThing(message.src, e, thingTable);
|
||||
} else {
|
||||
logger.debug("{}: NotifyEvent {} for unknown device {}", message.src,
|
||||
e.event, e.data.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
handler.onMessage(receivedMessage);
|
||||
}
|
||||
|
@ -259,7 +287,9 @@ public class Shelly2RpcSocket {
|
|||
logger.debug("{}: No Rpc listener registered for device {}, skip message: {}", thingName,
|
||||
getString(message.src), receivedMessage);
|
||||
}
|
||||
} catch (ShellyApiException | IllegalArgumentException | NullPointerException e) {
|
||||
} catch (ShellyApiException | IllegalArgumentException e) {
|
||||
logger.debug("{}: Unable to process Rpc message ({}): {}", thingName, e.getMessage(), receivedMessage);
|
||||
} catch (NullPointerException e) {
|
||||
logger.debug("{}: Unable to process Rpc message: {}", thingName, receivedMessage, e);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,15 +24,15 @@ import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2RpcNoti
|
|||
@NonNullByDefault
|
||||
public interface Shelly2RpctInterface {
|
||||
|
||||
void onConnect(String deviceIp, boolean connected);
|
||||
public void onConnect(String deviceIp, boolean connected);
|
||||
|
||||
void onMessage(String decodedmessage);
|
||||
public void onMessage(String decodedmessage);
|
||||
|
||||
void onNotifyStatus(Shelly2RpcNotifyStatus message);
|
||||
public void onNotifyStatus(Shelly2RpcNotifyStatus message);
|
||||
|
||||
void onNotifyEvent(Shelly2RpcNotifyEvent message);
|
||||
public void onNotifyEvent(Shelly2RpcNotifyEvent message);
|
||||
|
||||
void onClose(int statusCode, String reason);
|
||||
public void onClose(int statusCode, String reason);
|
||||
|
||||
void onError(Throwable cause);
|
||||
public void onError(Throwable cause);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,323 @@
|
|||
/**
|
||||
* Copyright (c) 2010-2023 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.shelly.internal.api2;
|
||||
|
||||
import static org.openhab.binding.shelly.internal.ShellyBindingConstants.*;
|
||||
import static org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.*;
|
||||
import static org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.*;
|
||||
import static org.openhab.binding.shelly.internal.discovery.ShellyThingCreator.*;
|
||||
import static org.openhab.binding.shelly.internal.util.ShellyUtils.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.openhab.binding.shelly.internal.api.ShellyApiException;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyDeviceProfile;
|
||||
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyInputState;
|
||||
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySensorSleepMode;
|
||||
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsDevice;
|
||||
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsInput;
|
||||
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsStatus;
|
||||
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyStatusSensor;
|
||||
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyStatusSensor.ShellySensorAccel;
|
||||
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyStatusSensor.ShellySensorBat;
|
||||
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyStatusSensor.ShellySensorLux;
|
||||
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyStatusSensor.ShellySensorState;
|
||||
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2NotifyEvent;
|
||||
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2RpcNotifyEvent;
|
||||
import org.openhab.binding.shelly.internal.config.ShellyThingConfiguration;
|
||||
import org.openhab.binding.shelly.internal.handler.ShellyBluSensorHandler;
|
||||
import org.openhab.binding.shelly.internal.handler.ShellyComponents;
|
||||
import org.openhab.binding.shelly.internal.handler.ShellyThingInterface;
|
||||
import org.openhab.binding.shelly.internal.handler.ShellyThingTable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* {@link ShellyBluApi} implementsBLU interface
|
||||
*
|
||||
* @author Markus Michels - Initial contribution
|
||||
*/
|
||||
public class ShellyBluApi extends Shelly2ApiRpc {
|
||||
private static final Logger logger = LoggerFactory.getLogger(ShellyBluApi.class);
|
||||
private boolean connected = false; // true = BLU devices has connected
|
||||
private ShellySettingsStatus deviceStatus = new ShellySettingsStatus();
|
||||
private int lastPid = -1;
|
||||
|
||||
private static final Map<String, String> MAP_INPUT_EVENT_TYPE = new HashMap<>();
|
||||
static {
|
||||
MAP_INPUT_EVENT_TYPE.put(SHELLY2_EVENT_1PUSH, SHELLY_BTNEVENT_1SHORTPUSH);
|
||||
MAP_INPUT_EVENT_TYPE.put(SHELLY2_EVENT_2PUSH, SHELLY_BTNEVENT_2SHORTPUSH);
|
||||
MAP_INPUT_EVENT_TYPE.put(SHELLY2_EVENT_3PUSH, SHELLY_BTNEVENT_3SHORTPUSH);
|
||||
MAP_INPUT_EVENT_TYPE.put(SHELLY2_EVENT_LPUSH, SHELLY_BTNEVENT_LONGPUSH);
|
||||
MAP_INPUT_EVENT_TYPE.put(SHELLY2_EVENT_LSPUSH, SHELLY_BTNEVENT_LONGSHORTPUSH);
|
||||
MAP_INPUT_EVENT_TYPE.put(SHELLY2_EVENT_SLPUSH, SHELLY_BTNEVENT_SHORTLONGPUSH);
|
||||
MAP_INPUT_EVENT_TYPE.put("1", SHELLY_BTNEVENT_1SHORTPUSH);
|
||||
MAP_INPUT_EVENT_TYPE.put("2", SHELLY_BTNEVENT_2SHORTPUSH);
|
||||
MAP_INPUT_EVENT_TYPE.put("3", SHELLY_BTNEVENT_3SHORTPUSH);
|
||||
MAP_INPUT_EVENT_TYPE.put("4", SHELLY_BTNEVENT_LONGPUSH);
|
||||
}
|
||||
|
||||
/**
|
||||
* Regular constructor - called by Thing handler
|
||||
*
|
||||
* @param thingName Symbolic thing name
|
||||
* @param thing Thing Handler (ThingHandlerInterface)
|
||||
*/
|
||||
public ShellyBluApi(String thingName, ShellyThingTable thingTable, ShellyThingInterface thing) {
|
||||
super(thingName, thingTable, thing);
|
||||
|
||||
ShellyInputState input = new ShellyInputState();
|
||||
deviceStatus.inputs = new ArrayList<>();
|
||||
input.input = 0;
|
||||
input.event = "";
|
||||
input.eventCount = 0;
|
||||
deviceStatus.inputs.add(input);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() throws ShellyApiException {
|
||||
if (!initialized) {
|
||||
initialized = true;
|
||||
connected = false;
|
||||
} else {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInitialized() {
|
||||
return initialized;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setConfig(String thingName, ShellyThingConfiguration config) {
|
||||
this.thingName = thingName;
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ShellySettingsDevice getDeviceInfo() throws ShellyApiException {
|
||||
ShellySettingsDevice info = new ShellySettingsDevice();
|
||||
info.hostname = !config.serviceName.isEmpty() ? config.serviceName : "";
|
||||
info.fw = "1234";
|
||||
info.type = "SBBT";
|
||||
info.mac = config.deviceAddress;
|
||||
info.auth = false;
|
||||
info.gen = 99;
|
||||
return info;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ShellyDeviceProfile getDeviceProfile(String thingType) throws ShellyApiException {
|
||||
ShellyDeviceProfile profile = thing != null ? getProfile() : new ShellyDeviceProfile();
|
||||
|
||||
profile.isBlu = true;
|
||||
profile.settingsJson = "{}";
|
||||
profile.thingName = thingName;
|
||||
profile.name = getString(profile.settings.name);
|
||||
if (profile.gateway.isEmpty()) {
|
||||
profile.gateway = getThing().getProperty(PROPERTY_GW_DEVICE);
|
||||
}
|
||||
|
||||
ShellySettingsDevice device = getDeviceInfo();
|
||||
profile.settings.device = device;
|
||||
profile.hostname = device.hostname;
|
||||
profile.deviceType = device.type;
|
||||
profile.mac = device.mac;
|
||||
profile.auth = device.auth;
|
||||
if (config.serviceName.isEmpty()) {
|
||||
config.serviceName = getString(profile.hostname);
|
||||
}
|
||||
profile.fwDate = substringBefore(device.fw, "/");
|
||||
profile.fwVersion = substringBefore(ShellyDeviceProfile.extractFwVersion(device.fw.replace("/", "/v")), "-");
|
||||
profile.status.update.oldVersion = profile.fwVersion;
|
||||
profile.status.hasUpdate = profile.status.update.hasUpdate = false;
|
||||
|
||||
if (profile.hasBattery) {
|
||||
profile.settings.sleepMode = new ShellySensorSleepMode();
|
||||
profile.settings.sleepMode.unit = "m";
|
||||
profile.settings.sleepMode.period = 720;
|
||||
}
|
||||
|
||||
if (profile.isButton) {
|
||||
ShellySettingsInput settings = new ShellySettingsInput();
|
||||
profile.numInputs = 1;
|
||||
settings.btnType = SHELLY_BTNT_MOMENTARY;
|
||||
|
||||
if (profile.settings.inputs != null) {
|
||||
profile.settings.inputs.set(0, settings);
|
||||
} else {
|
||||
profile.settings.inputs = new ArrayList<>();
|
||||
profile.settings.inputs.add(settings);
|
||||
}
|
||||
profile.status = deviceStatus;
|
||||
}
|
||||
|
||||
if (!connected) {
|
||||
throw new ShellyApiException("BLU Device not yet connected");
|
||||
}
|
||||
|
||||
profile.initialized = true;
|
||||
return profile;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ShellySettingsStatus getStatus() throws ShellyApiException {
|
||||
if (!connected) {
|
||||
throw new ShellyApiException("Thing is not yet initialized -> status not available");
|
||||
}
|
||||
return deviceStatus;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ShellyStatusSensor getSensorStatus() throws ShellyApiException {
|
||||
if (!connected) {
|
||||
throw new ShellyApiException("Thing is not yet initialized -> sensor data not available");
|
||||
}
|
||||
|
||||
return sensorData;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNotifyEvent(Shelly2RpcNotifyEvent message) {
|
||||
logger.trace("{}: ShellyEvent received: {}", thingName, gson.toJson(message));
|
||||
|
||||
boolean updated = false;
|
||||
ShellyBluSensorHandler t = (ShellyBluSensorHandler) thing;
|
||||
if (t == null) {
|
||||
logger.debug("{}: Thing is not initialized -> ignore event", thingName);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
ShellyDeviceProfile profile = getProfile();
|
||||
|
||||
t.incProtMessages();
|
||||
|
||||
if (!connected) {
|
||||
connected = true;
|
||||
t.setThingOnline();
|
||||
} else {
|
||||
t.restartWatchdog();
|
||||
}
|
||||
|
||||
for (Shelly2NotifyEvent e : message.params.events) {
|
||||
logger.debug("{}: BluEvent received: {}", thingName, gson.toJson(message));
|
||||
String event = getString(e.event);
|
||||
if (event.startsWith(SHELLY2_EVENT_BLUPREFIX)) {
|
||||
logger.debug("{}: BLU event {} received from address {}, pid={}", thingName, event,
|
||||
getString(e.data.addr), getInteger(e.data.pid));
|
||||
if (e.data.pid != null) {
|
||||
int pid = e.data.pid;
|
||||
if (pid == lastPid) {
|
||||
logger.debug("{}: Duplicate packet for PID={} received, ignore", thingName, pid);
|
||||
break;
|
||||
}
|
||||
lastPid = pid;
|
||||
}
|
||||
getThing().getProfile().gateway = message.src;
|
||||
}
|
||||
|
||||
switch (event) {
|
||||
case SHELLY2_EVENT_BLUSCAN:
|
||||
if (e.data == null || e.data.addr == null) {
|
||||
logger.debug("{}: Inconsistent BLU scan result ignored: {}", thingName,
|
||||
gson.toJson(message));
|
||||
break;
|
||||
}
|
||||
logger.debug("{}: BLU Device discovered", thingName);
|
||||
if (e.data.name != null) {
|
||||
profile.settings.name = buildBluServiceName(e.data.name, e.data.addr);
|
||||
}
|
||||
break;
|
||||
case SHELLY2_EVENT_BLUDATA:
|
||||
if (e.data == null || e.data.addr == null || e.data.pid == null) {
|
||||
logger.debug("{}: Inconsistent BLU packet ignored: {}", thingName, gson.toJson(message));
|
||||
break;
|
||||
}
|
||||
|
||||
if (e.data.battery != null) {
|
||||
if (sensorData.bat == null) {
|
||||
sensorData.bat = new ShellySensorBat();
|
||||
}
|
||||
sensorData.bat.value = (double) e.data.battery;
|
||||
}
|
||||
if (e.data.rssi != null) {
|
||||
deviceStatus.wifiSta.rssi = e.data.rssi;
|
||||
}
|
||||
if (e.data.windowState != null) {
|
||||
if (sensorData.sensor == null) {
|
||||
sensorData.sensor = new ShellySensorState();
|
||||
}
|
||||
sensorData.sensor.isValid = true;
|
||||
sensorData.sensor.state = e.data.windowState == 1 ? SHELLY_API_DWSTATE_OPEN
|
||||
: SHELLY_API_DWSTATE_CLOSE;
|
||||
}
|
||||
if (e.data.illuminance != null) {
|
||||
if (sensorData.lux == null) {
|
||||
sensorData.lux = new ShellySensorLux();
|
||||
}
|
||||
sensorData.lux.isValid = true;
|
||||
sensorData.lux.value = (double) e.data.illuminance;
|
||||
}
|
||||
if (e.data.rotation != null) {
|
||||
if (sensorData.accel == null) {
|
||||
sensorData.accel = new ShellySensorAccel();
|
||||
}
|
||||
sensorData.accel.tilt = e.data.rotation.intValue();
|
||||
}
|
||||
|
||||
if (e.data.buttonEvent != null) {
|
||||
ShellyInputState input = deviceStatus.inputs != null ? deviceStatus.inputs.get(0)
|
||||
: new ShellyInputState();
|
||||
input.event = mapValue(MAP_INPUT_EVENT_TYPE, e.data.buttonEvent + "");
|
||||
input.eventCount++;
|
||||
deviceStatus.inputs.set(0, input);
|
||||
// sensorData.inputs.set(0, input);
|
||||
|
||||
String group = getProfile().getInputGroup(0);
|
||||
String suffix = profile.getInputSuffix(0);
|
||||
t.updateChannel(group, CHANNEL_STATUS_EVENTTYPE + suffix, getStringType(input.event));
|
||||
t.updateChannel(group, CHANNEL_STATUS_EVENTCOUNT + suffix, getDecimal(input.eventCount));
|
||||
t.triggerButton(profile.getInputGroup(0), 0, input.event);
|
||||
}
|
||||
updated |= ShellyComponents.updateDeviceStatus(t, deviceStatus);
|
||||
updated |= ShellyComponents.updateSensors(getThing(), deviceStatus);
|
||||
break;
|
||||
default:
|
||||
super.onNotifyEvent(message);
|
||||
}
|
||||
}
|
||||
} catch (ShellyApiException e) {
|
||||
logger.debug("{}: Unable to process event", thingName, e);
|
||||
t.incProtErrors();
|
||||
}
|
||||
|
||||
if (updated) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public static String buildBluServiceName(String name, String mac) throws IllegalArgumentException {
|
||||
String model = name.contains("-") ? substringBefore(name, "-") : name; // e.g. SBBT-02C or just SBDW
|
||||
switch (model) {
|
||||
case SHELLYDT_BLUBUTTON:
|
||||
return (THING_TYPE_SHELLYBLUBUTTON_STR + "-" + mac).toLowerCase();
|
||||
case SHELLYDT_BLUDW:
|
||||
return (THING_TYPE_SHELLYBLUDW_STR + "-" + mac).toLowerCase();
|
||||
default:
|
||||
throw new IllegalArgumentException("Unsupported BLU device model " + model);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -22,6 +22,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
|
|||
@NonNullByDefault
|
||||
public class ShellyThingConfiguration {
|
||||
public String deviceIp = ""; // ip address of thedevice
|
||||
public String deviceAddress = ""; // IP address or MAC address for BLU devices
|
||||
public String userId = ""; // userid for http basic auth
|
||||
public String password = ""; // password for http basic auth
|
||||
|
||||
|
@ -42,4 +43,6 @@ public class ShellyThingConfiguration {
|
|||
public String localIp = ""; // local ip addresses used to create callback url
|
||||
public String localPort = "8080";
|
||||
public String serviceName = "";
|
||||
|
||||
public boolean enableBluGateway = false;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,93 @@
|
|||
/**
|
||||
* Copyright (c) 2010-2023 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.shelly.internal.discovery;
|
||||
|
||||
import static org.openhab.binding.shelly.internal.ShellyBindingConstants.*;
|
||||
import static org.openhab.core.thing.Thing.PROPERTY_MAC_ADDRESS;
|
||||
|
||||
import java.util.Hashtable;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.shelly.internal.handler.ShellyThingTable;
|
||||
import org.openhab.core.config.discovery.AbstractDiscoveryService;
|
||||
import org.openhab.core.config.discovery.DiscoveryResult;
|
||||
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
|
||||
import org.openhab.core.config.discovery.DiscoveryService;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.osgi.framework.BundleContext;
|
||||
import org.osgi.framework.ServiceRegistration;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Device discovery creates a thing in the inbox for each vehicle
|
||||
* found in the data received from {@link ShellyBluDiscoveryService}.
|
||||
*
|
||||
* @author Markus Michels - Initial Contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ShellyBluDiscoveryService extends AbstractDiscoveryService {
|
||||
private final Logger logger = LoggerFactory.getLogger(ShellyBluDiscoveryService.class);
|
||||
|
||||
private final BundleContext bundleContext;
|
||||
private final ShellyThingTable thingTable;
|
||||
private static final int TIMEOUT = 10;
|
||||
private @Nullable ServiceRegistration<?> discoveryService;
|
||||
|
||||
public ShellyBluDiscoveryService(BundleContext bundleContext, ShellyThingTable thingTable) {
|
||||
super(SUPPORTED_THING_TYPES_UIDS, TIMEOUT);
|
||||
this.bundleContext = bundleContext;
|
||||
this.thingTable = thingTable;
|
||||
}
|
||||
|
||||
@SuppressWarnings("null")
|
||||
public void registerDeviceDiscoveryService() {
|
||||
if (discoveryService == null) {
|
||||
discoveryService = bundleContext.registerService(DiscoveryService.class.getName(), this,
|
||||
new Hashtable<String, Object>());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startScan() {
|
||||
logger.debug("Starting BLU Discovery");
|
||||
thingTable.startScan();
|
||||
}
|
||||
|
||||
public void discoveredResult(ThingTypeUID tuid, String model, String serviceName, String address,
|
||||
Map<String, Object> properties) {
|
||||
ThingUID uid = ShellyThingCreator.getThingUID(serviceName, model, "", true);
|
||||
logger.debug("Adding discovered thing with id {}", uid.toString());
|
||||
properties.put(PROPERTY_MAC_ADDRESS, address);
|
||||
String thingLabel = "Shelly BLU " + model + " (" + serviceName + ")";
|
||||
DiscoveryResult result = DiscoveryResultBuilder.create(uid).withProperties(properties)
|
||||
.withRepresentationProperty(PROPERTY_DEV_NAME).withLabel(thingLabel).build();
|
||||
thingDiscovered(result);
|
||||
}
|
||||
|
||||
public void unregisterDeviceDiscoveryService() {
|
||||
if (discoveryService != null) {
|
||||
discoveryService.unregister();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deactivate() {
|
||||
super.deactivate();
|
||||
unregisterDeviceDiscoveryService();
|
||||
}
|
||||
}
|
|
@ -101,6 +101,10 @@ public class ShellyThingCreator {
|
|||
public static final String SHELLYDT_PRO4PM = "SPSW-004PE16EU";
|
||||
public static final String SHELLYDT_PRO4PM_2 = "SPSW-104PE16EU";
|
||||
|
||||
// Shelly BLU Series
|
||||
public static final String SHELLYDT_BLUBUTTON = "SBBT";
|
||||
public static final String SHELLYDT_BLUDW = "SBDW";
|
||||
|
||||
// Thing names
|
||||
public static final String THING_TYPE_SHELLY1_STR = "shelly1";
|
||||
public static final String THING_TYPE_SHELLY1L_STR = "shelly1l";
|
||||
|
@ -164,6 +168,12 @@ public class ShellyThingCreator {
|
|||
public static final String THING_TYPE_SHELLYPRO3EM_STR = "shellypro3em";
|
||||
public static final String THING_TYPE_SHELLYPRO4PM_STR = "shellypro4pm";
|
||||
|
||||
// Shelly BLU Series
|
||||
public static final String THING_TYPE_SHELLYBLU_PREFIX = "shellyblu";
|
||||
public static final String THING_TYPE_SHELLYBLUBUTTON_STR = THING_TYPE_SHELLYBLU_PREFIX + "button";
|
||||
public static final String THING_TYPE_SHELLYBLUDW_STR = THING_TYPE_SHELLYBLU_PREFIX + "dw";
|
||||
|
||||
// Password protected or unknown device
|
||||
public static final String THING_TYPE_SHELLYPROTECTED_STR = "shellydevice";
|
||||
public static final String THING_TYPE_SHELLYUNKNOWN_STR = "shellyunknown";
|
||||
|
||||
|
@ -258,6 +268,11 @@ public class ShellyThingCreator {
|
|||
public static final ThingTypeUID THING_TYPE_SHELLYPRO4PM = new ThingTypeUID(BINDING_ID,
|
||||
THING_TYPE_SHELLYPRO4PM_STR);
|
||||
|
||||
// Shelly Blu series
|
||||
public static final ThingTypeUID THING_TYPE_SHELLYBLUBUTTON = new ThingTypeUID(BINDING_ID,
|
||||
THING_TYPE_SHELLYBLUBUTTON_STR);
|
||||
public static final ThingTypeUID THING_TYPE_SHELLYBLUDW = new ThingTypeUID(BINDING_ID, THING_TYPE_SHELLYBLUDW_STR);
|
||||
|
||||
private static final Map<String, String> THING_TYPE_MAPPING = new LinkedHashMap<>();
|
||||
static {
|
||||
// mapping by device type id
|
||||
|
@ -324,6 +339,10 @@ public class ShellyThingCreator {
|
|||
THING_TYPE_MAPPING.put(SHELLYDT_PRO4PM, THING_TYPE_SHELLYPRO4PM_STR);
|
||||
THING_TYPE_MAPPING.put(SHELLYDT_PRO4PM_2, THING_TYPE_SHELLYPRO4PM_STR);
|
||||
|
||||
// Blu Series
|
||||
THING_TYPE_MAPPING.put(SHELLYDT_BLUBUTTON, THING_TYPE_SHELLYBLUBUTTON_STR);
|
||||
THING_TYPE_MAPPING.put(SHELLYDT_BLUDW, THING_TYPE_SHELLYBLUDW_STR);
|
||||
|
||||
// mapping by thing type
|
||||
THING_TYPE_MAPPING.put(THING_TYPE_SHELLY1_STR, THING_TYPE_SHELLY1_STR);
|
||||
THING_TYPE_MAPPING.put(THING_TYPE_SHELLY1PM_STR, THING_TYPE_SHELLY1PM_STR);
|
||||
|
|
|
@ -46,6 +46,7 @@ import org.openhab.binding.shelly.internal.api1.Shelly1CoapJSonDTO;
|
|||
import org.openhab.binding.shelly.internal.api1.Shelly1CoapServer;
|
||||
import org.openhab.binding.shelly.internal.api1.Shelly1HttpApi;
|
||||
import org.openhab.binding.shelly.internal.api2.Shelly2ApiRpc;
|
||||
import org.openhab.binding.shelly.internal.api2.ShellyBluApi;
|
||||
import org.openhab.binding.shelly.internal.config.ShellyBindingConfiguration;
|
||||
import org.openhab.binding.shelly.internal.config.ShellyThingConfiguration;
|
||||
import org.openhab.binding.shelly.internal.discovery.ShellyThingCreator;
|
||||
|
@ -104,6 +105,7 @@ public abstract class ShellyBaseHandler extends BaseThingHandler
|
|||
private final int cacheCount = UPDATE_SETTINGS_INTERVAL_SECONDS / UPDATE_STATUS_INTERVAL_SECONDS;
|
||||
|
||||
private final boolean gen2;
|
||||
private final boolean blu;
|
||||
protected boolean autoCoIoT = false;
|
||||
|
||||
// Thing status
|
||||
|
@ -151,7 +153,9 @@ public abstract class ShellyBaseHandler extends BaseThingHandler
|
|||
gen = "2";
|
||||
}
|
||||
gen2 = "2".equals(gen);
|
||||
this.api = !gen2 ? new Shelly1HttpApi(thingName, this) : new Shelly2ApiRpc(thingName, thingTable, this);
|
||||
blu = thingType.startsWith("shellyblu");
|
||||
this.api = !blu ? !gen2 ? new Shelly1HttpApi(thingName, this) : new Shelly2ApiRpc(thingName, thingTable, this)
|
||||
: new ShellyBluApi(thingName, thingTable, this);
|
||||
if (gen2) {
|
||||
config.eventsCoIoT = false;
|
||||
}
|
||||
|
@ -162,7 +166,7 @@ public abstract class ShellyBaseHandler extends BaseThingHandler
|
|||
|
||||
@Override
|
||||
public boolean checkRepresentation(String key) {
|
||||
return key.equalsIgnoreCase(getUID()) || key.equalsIgnoreCase(config.deviceIp)
|
||||
return key.equalsIgnoreCase(getUID()) || key.equalsIgnoreCase(config.deviceAddress)
|
||||
|| key.equalsIgnoreCase(config.serviceName) || key.equalsIgnoreCase(getThingName());
|
||||
}
|
||||
|
||||
|
@ -176,15 +180,13 @@ public abstract class ShellyBaseHandler extends BaseThingHandler
|
|||
boolean start = true;
|
||||
try {
|
||||
initializeThingConfig();
|
||||
logger.debug("{}: Device config: IP address={}, HTTP user/password={}/{}, update interval={}",
|
||||
thingName, config.deviceIp, config.userId.isEmpty() ? "<non>" : config.userId,
|
||||
logger.debug("{}: Device config: Device address={}, HTTP user/password={}/{}, update interval={}",
|
||||
thingName, config.deviceAddress, config.userId.isEmpty() ? "<non>" : config.userId,
|
||||
config.password.isEmpty() ? "<none>" : "***", config.updateInterval);
|
||||
logger.debug(
|
||||
"{}: Configured Events: Button: {}, Switch (on/off): {}, Push: {}, Roller: {}, Sensor: {}, CoIoT: {}, Enable AutoCoIoT: {}",
|
||||
thingName, config.eventsButton, config.eventsSwitch, config.eventsPush, config.eventsRoller,
|
||||
config.eventsSensorReport, config.eventsCoIoT, bindingConfig.autoCoIoT);
|
||||
updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.CONFIGURATION_PENDING,
|
||||
messages.get("status.unknown.initializing"));
|
||||
start = initializeThing();
|
||||
} catch (ShellyApiException e) {
|
||||
ShellyApiResult res = e.getApiResult();
|
||||
|
@ -225,6 +227,13 @@ public abstract class ShellyBaseHandler extends BaseThingHandler
|
|||
return httpClient;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startScan() {
|
||||
if (api.isInitialized()) {
|
||||
api.startScan();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This routine is called every time the Thing configuration has been changed
|
||||
*/
|
||||
|
@ -257,12 +266,15 @@ public abstract class ShellyBaseHandler extends BaseThingHandler
|
|||
resetStats();
|
||||
|
||||
logger.debug("{}: Start initializing for thing {}, type {}, IP address {}, Gen2: {}, CoIoT: {}", thingName,
|
||||
getThing().getLabel(), thingType, config.deviceIp, gen2, config.eventsCoIoT);
|
||||
if (config.deviceIp.isEmpty()) {
|
||||
setThingOffline(ThingStatusDetail.CONFIGURATION_ERROR, "config-status.error.missing-device-ip");
|
||||
getThing().getLabel(), thingType, config.deviceAddress, gen2, config.eventsCoIoT);
|
||||
if (config.deviceAddress.isEmpty()) {
|
||||
setThingOffline(ThingStatusDetail.CONFIGURATION_ERROR, "config-status.error.missing-device-address");
|
||||
return false;
|
||||
}
|
||||
|
||||
updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.CONFIGURATION_PENDING,
|
||||
messages.get("status.unknown.initializing"));
|
||||
|
||||
profile.initFromThingType(thingType); // do some basic initialization
|
||||
|
||||
// Gen 1 only: Setup CoAP listener to we get the CoAP message, which triggers initialization even the thing
|
||||
|
@ -326,7 +338,7 @@ public abstract class ShellyBaseHandler extends BaseThingHandler
|
|||
checkVersion(tmpPrf, tmpPrf.status);
|
||||
|
||||
startCoap(config, tmpPrf);
|
||||
if (!gen2) {
|
||||
if (!gen2 && !blu) {
|
||||
api.setActionURLs(); // register event urls
|
||||
}
|
||||
|
||||
|
@ -358,7 +370,7 @@ public abstract class ShellyBaseHandler extends BaseThingHandler
|
|||
return;
|
||||
}
|
||||
|
||||
if (!profile.isInitialized() || (isThingOffline() && profile.alwaysOn)) {
|
||||
if (!profile.isInitialized()) {
|
||||
logger.debug("{}: {}", thingName, messages.get("command.init", command));
|
||||
initializeThing();
|
||||
} else {
|
||||
|
@ -468,6 +480,15 @@ public abstract class ShellyBaseHandler extends BaseThingHandler
|
|||
logger.warn("{}: {} - {}", thingName, messages.get("command.failed", command, channelUID),
|
||||
e.toString());
|
||||
}
|
||||
|
||||
String group = getString(channelUID.getGroupId());
|
||||
String channel = getString(channelUID.getIdWithoutGroup());
|
||||
State oldValue = getChannelValue(group, channel);
|
||||
if (oldValue != UnDefType.NULL) {
|
||||
logger.info("{}: Restore channel value to {}", thingName, oldValue);
|
||||
updateChannel(group, channel, oldValue);
|
||||
}
|
||||
|
||||
} catch (IllegalArgumentException e) {
|
||||
logger.debug("{}: {}", thingName, messages.get("command.failed", command, channelUID));
|
||||
}
|
||||
|
@ -518,8 +539,11 @@ public abstract class ShellyBaseHandler extends BaseThingHandler
|
|||
postEvent(ALARM_TYPE_RESTARTED, true);
|
||||
}
|
||||
|
||||
// If status update was successful the thing must be online
|
||||
// If status update was successful the thing must be online,
|
||||
// but not while firmware update is in progress
|
||||
if (getThingStatusDetail() != ThingStatusDetail.FIRMWARE_UPDATING) {
|
||||
setThingOnline();
|
||||
}
|
||||
|
||||
// map status to channels
|
||||
updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_NAME, getStringType(profile.settings.name));
|
||||
|
@ -587,13 +611,13 @@ public abstract class ShellyBaseHandler extends BaseThingHandler
|
|||
logger.debug("{}: Shelly settings info for {}: {}", thingName, profile.hostname, profile.settingsJson);
|
||||
logger.debug("{}: Device "
|
||||
+ "hasRelays:{} (numRelays={}),isRoller:{} (numRoller={}),isDimmer:{},numMeter={},isEMeter:{}), ext. Switch Add-On: {}"
|
||||
+ ",isSensor:{},isDS:{},hasBattery:{}{},isSense:{},isMotion:{},isLight:{},isBulb:{},isDuo:{},isRGBW2:{},inColor:{}"
|
||||
+ ",isSensor:{},isDS:{},hasBattery:{}{},isSense:{},isMotion:{},isLight:{},isBulb:{},isDuo:{},isRGBW2:{},inColor:{}, BLU Gateway support: {}"
|
||||
+ ",alwaysOn:{}, updatePeriod:{}sec", thingName, profile.hasRelays, profile.numRelays, profile.isRoller,
|
||||
profile.numRollers, profile.isDimmer, profile.numMeters, profile.isEMeter,
|
||||
profile.settings.extSwitch != null ? "installed" : "n/a", profile.isSensor, profile.isDW,
|
||||
profile.hasBattery, profile.hasBattery ? " (low battery threshold=" + config.lowBattery + "%)" : "",
|
||||
profile.isSense, profile.isMotion, profile.isLight, profile.isBulb, profile.isDuo, profile.isRGBW2,
|
||||
profile.inColor, profile.alwaysOn, profile.updatePeriod);
|
||||
profile.inColor, profile.alwaysOn, profile.updatePeriod, config.enableBluGateway);
|
||||
if (profile.status.extTemperature != null || profile.status.extHumidity != null
|
||||
|| profile.status.extVoltage != null || profile.status.extAnalogInput != null) {
|
||||
logger.debug("{}: Shelly Add-On detected with at least 1 external sensor", thingName);
|
||||
|
@ -715,7 +739,7 @@ public abstract class ShellyBaseHandler extends BaseThingHandler
|
|||
|
||||
// Update uptime and WiFi, internal temp
|
||||
ShellyComponents.updateDeviceStatus(this, status);
|
||||
stats.wifiRssi = status.wifiSta.rssi;
|
||||
stats.wifiRssi = getInteger(status.wifiSta.rssi);
|
||||
|
||||
if (api.isInitialized()) {
|
||||
stats.timeoutErrors = api.getTimeoutErrors();
|
||||
|
@ -829,9 +853,10 @@ public abstract class ShellyBaseHandler extends BaseThingHandler
|
|||
* @return true if event was processed
|
||||
*/
|
||||
@Override
|
||||
public boolean onEvent(String ipAddress, String deviceName, String deviceIndex, String type,
|
||||
public boolean onEvent(String address, String deviceName, String deviceIndex, String type,
|
||||
Map<String, String> parameters) {
|
||||
if (thingName.equalsIgnoreCase(deviceName) || config.deviceIp.equals(ipAddress)) {
|
||||
if (thingName.equalsIgnoreCase(deviceName) || config.deviceAddress.equals(address)
|
||||
|| config.serviceName.equals(deviceName)) {
|
||||
logger.debug("{}: Event received: class={}, index={}, parameters={}", deviceName, type, deviceIndex,
|
||||
parameters);
|
||||
int idx = !deviceIndex.isEmpty() ? Integer.parseInt(deviceIndex) : 1;
|
||||
|
@ -851,7 +876,7 @@ public abstract class ShellyBaseHandler extends BaseThingHandler
|
|||
String payload = "";
|
||||
String parmType = getString(parameters.get("type"));
|
||||
String event = !parmType.isEmpty() ? parmType : type;
|
||||
boolean isButton = profile.inButtonMode(idx - 1);
|
||||
boolean isButton = profile.inButtonMode(idx - 1) || type.equals("button");
|
||||
switch (event) {
|
||||
case SHELLY_EVENT_SHORTPUSH:
|
||||
case SHELLY_EVENT_DOUBLE_SHORTPUSH:
|
||||
|
@ -959,14 +984,21 @@ public abstract class ShellyBaseHandler extends BaseThingHandler
|
|||
thingName = getString(properties.get(PROPERTY_SERVICE_NAME));
|
||||
if (thingName.isEmpty()) {
|
||||
thingName = getString(thingType + "-" + getString(getThing().getUID().getId())).toLowerCase();
|
||||
logger.debug("{}: Thing name derived from UID {}", thingName, getString(getThing().getUID().toString()));
|
||||
}
|
||||
|
||||
config = getConfigAs(ShellyThingConfiguration.class);
|
||||
if (config.deviceIp.isEmpty()) {
|
||||
logger.debug("{}: IP address for the device must not be empty", thingName); // may not set in .things file
|
||||
if (config.deviceAddress.isEmpty()) {
|
||||
config.deviceAddress = config.deviceIp;
|
||||
}
|
||||
if (config.deviceAddress.isEmpty()) {
|
||||
logger.debug("{}: IP/MAC address for the device must not be empty", thingName); // may not set in .things
|
||||
// file
|
||||
return;
|
||||
}
|
||||
|
||||
config.deviceAddress = config.deviceAddress.toLowerCase().replaceAll(":", ""); // remove : from MAC address and
|
||||
// convert to lower case
|
||||
if (!config.deviceIp.isEmpty()) {
|
||||
try {
|
||||
InetAddress addr = InetAddress.getByName(config.deviceIp);
|
||||
String saddr = addr.getHostAddress();
|
||||
|
@ -977,6 +1009,7 @@ public abstract class ShellyBaseHandler extends BaseThingHandler
|
|||
} catch (UnknownHostException e) {
|
||||
logger.debug("{}: Unable to resolve hostname {}", thingName, config.deviceIp);
|
||||
}
|
||||
}
|
||||
|
||||
config.serviceName = getString(properties.get(PROPERTY_SERVICE_NAME));
|
||||
config.localIp = bindingConfig.localIP;
|
||||
|
@ -1406,9 +1439,11 @@ public abstract class ShellyBaseHandler extends BaseThingHandler
|
|||
properties.put(PROPERTY_MAC_ADDRESS, profile.mac);
|
||||
properties.put(PROPERTY_FIRMWARE_VERSION, profile.fwVersion + "/" + profile.fwDate);
|
||||
properties.put(PROPERTY_DEV_MODE, profile.mode);
|
||||
if (profile.hasRelays) {
|
||||
properties.put(PROPERTY_NUM_RELAYS, String.valueOf(profile.numRelays));
|
||||
properties.put(PROPERTY_NUM_ROLLERS, String.valueOf(profile.numRollers));
|
||||
properties.put(PROPERTY_NUM_METER, String.valueOf(profile.numMeters));
|
||||
}
|
||||
properties.put(PROPERTY_UPDATE_PERIOD, String.valueOf(profile.updatePeriod));
|
||||
if (!profile.hwRev.isEmpty()) {
|
||||
properties.put(PROPERTY_HWREV, profile.hwRev);
|
||||
|
|
|
@ -0,0 +1,93 @@
|
|||
/**
|
||||
* Copyright (c) 2010-2023 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.shelly.internal.handler;
|
||||
|
||||
import static org.openhab.binding.shelly.internal.ShellyBindingConstants.*;
|
||||
import static org.openhab.binding.shelly.internal.api2.ShellyBluApi.buildBluServiceName;
|
||||
import static org.openhab.binding.shelly.internal.discovery.ShellyThingCreator.*;
|
||||
import static org.openhab.binding.shelly.internal.util.ShellyUtils.*;
|
||||
import static org.openhab.core.thing.Thing.PROPERTY_MODEL_ID;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.openhab.binding.shelly.internal.api1.Shelly1CoapServer;
|
||||
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2NotifyEvent;
|
||||
import org.openhab.binding.shelly.internal.config.ShellyBindingConfiguration;
|
||||
import org.openhab.binding.shelly.internal.provider.ShellyTranslationProvider;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* {@link ShellyBluSensorHandler} implements the thing handler for the BLU devices
|
||||
*
|
||||
* @author Markus Michels - Initial contribution
|
||||
*/
|
||||
public class ShellyBluSensorHandler extends ShellyBaseHandler {
|
||||
private final static Logger logger = LoggerFactory.getLogger(ShellyBluSensorHandler.class);
|
||||
|
||||
public ShellyBluSensorHandler(final Thing thing, final ShellyTranslationProvider translationProvider,
|
||||
final ShellyBindingConfiguration bindingConfig, final ShellyThingTable thingTable,
|
||||
final Shelly1CoapServer coapServer, final HttpClient httpClient) {
|
||||
super(thing, translationProvider, bindingConfig, thingTable, coapServer, httpClient);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
logger.debug("Thing is using {}", this.getClass());
|
||||
super.initialize();
|
||||
}
|
||||
|
||||
public static void addBluThing(String gateway, Shelly2NotifyEvent e, ShellyThingTable thingTable) {
|
||||
String model = substringBefore(getString(e.data.name), "-").toUpperCase();
|
||||
String mac = e.data.addr.replaceAll(":", "");
|
||||
String ttype = "";
|
||||
logger.debug("{}: Create thing for new BLU device {}: {} / {}", gateway, e.data.name, model, mac);
|
||||
ThingTypeUID tuid;
|
||||
switch (model) {
|
||||
case SHELLYDT_BLUBUTTON:
|
||||
ttype = THING_TYPE_SHELLYBLUBUTTON_STR;
|
||||
tuid = THING_TYPE_SHELLYBLUBUTTON;
|
||||
break;
|
||||
case SHELLYDT_BLUDW:
|
||||
ttype = THING_TYPE_SHELLYBLUDW_STR;
|
||||
tuid = THING_TYPE_SHELLYBLUDW;
|
||||
break;
|
||||
default:
|
||||
logger.debug("{}: Unsupported BLU device model {}, MAC={}", gateway, model, mac);
|
||||
return;
|
||||
}
|
||||
String serviceName = buildBluServiceName(model, mac);
|
||||
|
||||
Map<String, Object> properties = new TreeMap<>();
|
||||
addProperty(properties, PROPERTY_MODEL_ID, model);
|
||||
addProperty(properties, PROPERTY_SERVICE_NAME, serviceName);
|
||||
addProperty(properties, PROPERTY_DEV_NAME, e.data.name);
|
||||
addProperty(properties, PROPERTY_DEV_TYPE, ttype);
|
||||
addProperty(properties, PROPERTY_DEV_GEN, "BLU");
|
||||
addProperty(properties, PROPERTY_GW_DEVICE, gateway);
|
||||
addProperty(properties, CONFIG_DEVICEADDRESS, mac);
|
||||
|
||||
if (thingTable != null) {
|
||||
thingTable.discoveredResult(tuid, model, serviceName, mac, properties);
|
||||
}
|
||||
}
|
||||
|
||||
private static void addProperty(Map<String, Object> properties, String key, @Nullable String value) {
|
||||
properties.put(key, value != null ? value : "");
|
||||
}
|
||||
}
|
|
@ -54,6 +54,9 @@ public class ShellyComponents {
|
|||
public static boolean updateDeviceStatus(ShellyThingInterface thingHandler, ShellySettingsStatus status) {
|
||||
ShellyDeviceProfile profile = thingHandler.getProfile();
|
||||
|
||||
if (!profile.gateway.isEmpty()) {
|
||||
thingHandler.updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_GATEWAY, getStringType(profile.gateway));
|
||||
}
|
||||
if (!thingHandler.areChannelsCreated()) {
|
||||
thingHandler.updateChannelDefinitions(ShellyChannelDefinitions.createDeviceChannels(thingHandler.getThing(),
|
||||
thingHandler.getProfile(), status));
|
||||
|
@ -177,6 +180,8 @@ public class ShellyComponents {
|
|||
break;
|
||||
}
|
||||
if (pos != -1) {
|
||||
thingHandler.logger.debug("{}: Update roller position to {}/{}, state={}", thingHandler.thingName, pos,
|
||||
SHELLY_MAX_ROLLER_POS - pos, state);
|
||||
updated |= thingHandler.updateChannel(groupName, CHANNEL_ROL_CONTROL_CONTROL,
|
||||
toQuantityType((double) (SHELLY_MAX_ROLLER_POS - pos), Units.PERCENT));
|
||||
updated |= thingHandler.updateChannel(groupName, CHANNEL_ROL_CONTROL_POS,
|
||||
|
@ -238,7 +243,7 @@ public class ShellyComponents {
|
|||
|
||||
// convert Watt/Min to kw/h
|
||||
if (meter.total != null) {
|
||||
double kwh = getDouble(meter.total) / 60 / 1000;
|
||||
double kwh = getDouble(meter.total) / 1000 / 60;
|
||||
updated |= thingHandler.updateChannel(groupName, CHANNEL_METER_TOTALKWH,
|
||||
toQuantityType(kwh, DIGITS_KWH, Units.KILOWATT_HOUR));
|
||||
accumulatedTotal += kwh;
|
||||
|
@ -263,13 +268,15 @@ public class ShellyComponents {
|
|||
.createEMeterChannels(thingHandler.getThing(), profile, emeter, groupName));
|
||||
}
|
||||
|
||||
// convert Watt/Hour tok w/h
|
||||
// convert Watt/Hour to kw/h
|
||||
double total = getDouble(emeter.total) / 1000 / 60;
|
||||
double totalReturned = getDouble(emeter.totalReturned) / 1000;
|
||||
updated |= thingHandler.updateChannel(groupName, CHANNEL_METER_CURRENTWATTS,
|
||||
toQuantityType(getDouble(emeter.power), DIGITS_WATT, Units.WATT));
|
||||
updated |= thingHandler.updateChannel(groupName, CHANNEL_METER_TOTALKWH,
|
||||
toQuantityType(getDouble(emeter.total) / 1000, DIGITS_KWH, Units.KILOWATT_HOUR));
|
||||
updated |= thingHandler.updateChannel(groupName, CHANNEL_EMETER_TOTALRET, toQuantityType(
|
||||
getDouble(emeter.totalReturned) / 1000, DIGITS_KWH, Units.KILOWATT_HOUR));
|
||||
toQuantityType(total, DIGITS_KWH, Units.KILOWATT_HOUR));
|
||||
updated |= thingHandler.updateChannel(groupName, CHANNEL_EMETER_TOTALRET,
|
||||
toQuantityType(totalReturned, DIGITS_KWH, Units.KILOWATT_HOUR));
|
||||
updated |= thingHandler.updateChannel(groupName, CHANNEL_EMETER_REACTWATTS,
|
||||
toQuantityType(getDouble(emeter.reactive), DIGITS_WATT, Units.WATT));
|
||||
updated |= thingHandler.updateChannel(groupName, CHANNEL_EMETER_VOLTAGE,
|
||||
|
@ -280,8 +287,8 @@ public class ShellyComponents {
|
|||
toQuantityType(computePF(emeter), Units.PERCENT));
|
||||
|
||||
accumulatedWatts += getDouble(emeter.power);
|
||||
accumulatedTotal += getDouble(emeter.total) / 1000;
|
||||
accumulatedReturned += getDouble(emeter.totalReturned) / 1000;
|
||||
accumulatedTotal += total;
|
||||
accumulatedReturned += totalReturned;
|
||||
if (updated) {
|
||||
thingHandler.updateChannel(groupName, CHANNEL_LAST_UPDATE, getTimestamp());
|
||||
}
|
||||
|
@ -327,7 +334,7 @@ public class ShellyComponents {
|
|||
updated |= thingHandler.updateChannel(groupName, CHANNEL_METER_CURRENTWATTS,
|
||||
toQuantityType(getDouble(currentWatts), DIGITS_WATT, Units.WATT));
|
||||
updated |= thingHandler.updateChannel(groupName, CHANNEL_METER_TOTALKWH,
|
||||
toQuantityType(getDouble(totalWatts), DIGITS_KWH, Units.KILOWATT_HOUR));
|
||||
toQuantityType(totalWatts, DIGITS_KWH, Units.KILOWATT_HOUR));
|
||||
|
||||
if (updated && timestamp > 0) {
|
||||
thingHandler.updateChannel(groupName, CHANNEL_LAST_UPDATE,
|
||||
|
@ -336,12 +343,14 @@ public class ShellyComponents {
|
|||
}
|
||||
|
||||
if (!profile.isRoller && !profile.isRGBW2) {
|
||||
thingHandler.updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_ACCUWATTS,
|
||||
toQuantityType(accumulatedWatts, DIGITS_WATT, Units.WATT));
|
||||
thingHandler.updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_ACCUWATTS, toQuantityType(
|
||||
status.totalPower != null ? status.totalPower : accumulatedWatts, DIGITS_WATT, Units.WATT));
|
||||
thingHandler.updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_ACCUTOTAL,
|
||||
toQuantityType(accumulatedTotal, DIGITS_KWH, Units.KILOWATT_HOUR));
|
||||
toQuantityType(status.totalCurrent != null ? status.totalCurrent / 1000 : accumulatedTotal,
|
||||
DIGITS_KWH, Units.KILOWATT_HOUR));
|
||||
thingHandler.updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_ACCURETURNED,
|
||||
toQuantityType(accumulatedReturned, DIGITS_KWH, Units.KILOWATT_HOUR));
|
||||
toQuantityType(status.totalReturned != null ? status.totalReturned / 1000 : accumulatedReturned,
|
||||
DIGITS_KWH, Units.KILOWATT_HOUR));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -28,6 +28,6 @@ public interface ShellyDeviceListener {
|
|||
/**
|
||||
* This method is called when new device information is received.
|
||||
*/
|
||||
boolean onEvent(String ipAddress, String deviceName, String deviceIndex, String eventType,
|
||||
public boolean onEvent(String ipAddress, String deviceName, String deviceIndex, String eventType,
|
||||
Map<String, String> parameters);
|
||||
}
|
||||
|
|
|
@ -56,15 +56,6 @@ public class ShellyLightHandler extends ShellyBaseHandler {
|
|||
private final Logger logger = LoggerFactory.getLogger(ShellyLightHandler.class);
|
||||
private final Map<Integer, ShellyColorUtils> channelColors;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param thing The thing passed by the HandlerFactory
|
||||
* @param bindingConfig configuration of the binding
|
||||
* @param coapServer coap server instance
|
||||
* @param localIP local IP of the openHAB host
|
||||
* @param httpPort port of the openHAB HTTP API
|
||||
*/
|
||||
public ShellyLightHandler(final Thing thing, final ShellyTranslationProvider translationProvider,
|
||||
final ShellyBindingConfiguration bindingConfig, final ShellyThingTable thingTable,
|
||||
final Shelly1CoapServer coapServer, final HttpClient httpClient) {
|
||||
|
@ -230,6 +221,7 @@ public class ShellyLightHandler extends ShellyBaseHandler {
|
|||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
private boolean handleColorPicker(ShellyDeviceProfile profile, Integer lightId, ShellyColorUtils col,
|
||||
Command command) throws ShellyApiException {
|
||||
boolean updated = false;
|
||||
|
|
|
@ -28,29 +28,29 @@ import org.openhab.core.types.State;
|
|||
@NonNullByDefault
|
||||
public interface ShellyManagerInterface {
|
||||
|
||||
Thing getThing();
|
||||
public Thing getThing();
|
||||
|
||||
String getThingName();
|
||||
public String getThingName();
|
||||
|
||||
ShellyDeviceProfile getProfile();
|
||||
public ShellyDeviceProfile getProfile();
|
||||
|
||||
ShellyDeviceProfile getProfile(boolean forceRefresh) throws ShellyApiException;
|
||||
public ShellyDeviceProfile getProfile(boolean forceRefresh) throws ShellyApiException;
|
||||
|
||||
ShellyApiInterface getApi();
|
||||
public ShellyApiInterface getApi();
|
||||
|
||||
ShellyDeviceStats getStats();
|
||||
public ShellyDeviceStats getStats();
|
||||
|
||||
void resetStats();
|
||||
public void resetStats();
|
||||
|
||||
State getChannelValue(String group, String channel);
|
||||
public State getChannelValue(String group, String channel);
|
||||
|
||||
void setThingOnline();
|
||||
public void setThingOnline();
|
||||
|
||||
void setThingOffline(ThingStatusDetail detail, String messageKey, Object... arguments);
|
||||
public void setThingOffline(ThingStatusDetail detail, String messageKey, Object... arguments);
|
||||
|
||||
boolean requestUpdates(int requestCount, boolean refreshSettings);
|
||||
public boolean requestUpdates(int requestCount, boolean refreshSettings);
|
||||
|
||||
void incProtMessages();
|
||||
public void incProtMessages();
|
||||
|
||||
void incProtErrors();
|
||||
public void incProtErrors();
|
||||
}
|
||||
|
|
|
@ -39,84 +39,85 @@ import org.openhab.core.types.StateOption;
|
|||
@NonNullByDefault
|
||||
public interface ShellyThingInterface {
|
||||
|
||||
ShellyDeviceProfile getProfile(boolean forceRefresh) throws ShellyApiException;
|
||||
public ShellyDeviceProfile getProfile(boolean forceRefresh) throws ShellyApiException;
|
||||
|
||||
@Nullable
|
||||
List<StateOption> getStateOptions(ChannelTypeUID uid);
|
||||
public @Nullable List<StateOption> getStateOptions(ChannelTypeUID uid);
|
||||
|
||||
double getChannelDouble(String group, String channel);
|
||||
public double getChannelDouble(String group, String channel);
|
||||
|
||||
boolean updateChannel(String group, String channel, State value);
|
||||
public boolean updateChannel(String group, String channel, State value);
|
||||
|
||||
boolean updateChannel(String channelId, State value, boolean force);
|
||||
public boolean updateChannel(String channelId, State value, boolean force);
|
||||
|
||||
void setThingOnline();
|
||||
public void setThingOnline();
|
||||
|
||||
void setThingOffline(ThingStatusDetail detail, String messageKey, Object... arguments);
|
||||
public void setThingOffline(ThingStatusDetail detail, String messageKey, Object... arguments);
|
||||
|
||||
boolean isStopping();
|
||||
public boolean isStopping();
|
||||
|
||||
String getThingType();
|
||||
public String getThingType();
|
||||
|
||||
ThingStatus getThingStatus();
|
||||
public ThingStatus getThingStatus();
|
||||
|
||||
ThingStatusDetail getThingStatusDetail();
|
||||
public ThingStatusDetail getThingStatusDetail();
|
||||
|
||||
boolean isThingOnline();
|
||||
public boolean isThingOnline();
|
||||
|
||||
boolean requestUpdates(int requestCount, boolean refreshSettings);
|
||||
public boolean requestUpdates(int requestCount, boolean refreshSettings);
|
||||
|
||||
void triggerUpdateFromCoap();
|
||||
public void triggerUpdateFromCoap();
|
||||
|
||||
void reinitializeThing();
|
||||
public void reinitializeThing();
|
||||
|
||||
void restartWatchdog();
|
||||
public void restartWatchdog();
|
||||
|
||||
void publishState(String channelId, State value);
|
||||
public void publishState(String channelId, State value);
|
||||
|
||||
boolean areChannelsCreated();
|
||||
public boolean areChannelsCreated();
|
||||
|
||||
State getChannelValue(String group, String channel);
|
||||
public State getChannelValue(String group, String channel);
|
||||
|
||||
boolean updateInputs(ShellySettingsStatus status);
|
||||
public boolean updateInputs(ShellySettingsStatus status);
|
||||
|
||||
void updateChannelDefinitions(Map<String, Channel> dynChannels);
|
||||
public void updateChannelDefinitions(Map<String, Channel> dynChannels);
|
||||
|
||||
void postEvent(String event, boolean force);
|
||||
public void postEvent(String event, boolean force);
|
||||
|
||||
void triggerChannel(String group, String channelID, String event);
|
||||
public void triggerChannel(String group, String channelID, String event);
|
||||
|
||||
void triggerButton(String group, int idx, String value);
|
||||
public void triggerButton(String group, int idx, String value);
|
||||
|
||||
ShellyDeviceStats getStats();
|
||||
public ShellyDeviceStats getStats();
|
||||
|
||||
void resetStats();
|
||||
public void resetStats();
|
||||
|
||||
Thing getThing();
|
||||
public Thing getThing();
|
||||
|
||||
String getThingName();
|
||||
public String getThingName();
|
||||
|
||||
ShellyThingConfiguration getThingConfig();
|
||||
public ShellyThingConfiguration getThingConfig();
|
||||
|
||||
HttpClient getHttpClient();
|
||||
public HttpClient getHttpClient();
|
||||
|
||||
String getProperty(String key);
|
||||
public String getProperty(String key);
|
||||
|
||||
void updateProperties(String key, String value);
|
||||
public void updateProperties(String key, String value);
|
||||
|
||||
boolean updateWakeupReason(@Nullable List<Object> valueArray);
|
||||
public boolean updateWakeupReason(@Nullable List<Object> valueArray);
|
||||
|
||||
ShellyApiInterface getApi();
|
||||
public ShellyApiInterface getApi();
|
||||
|
||||
ShellyDeviceProfile getProfile();
|
||||
public ShellyDeviceProfile getProfile();
|
||||
|
||||
long getScheduledUpdates();
|
||||
public long getScheduledUpdates();
|
||||
|
||||
void fillDeviceStatus(ShellySettingsStatus status, boolean updated);
|
||||
public void fillDeviceStatus(ShellySettingsStatus status, boolean updated);
|
||||
|
||||
boolean checkRepresentation(String key);
|
||||
public boolean checkRepresentation(String key);
|
||||
|
||||
void incProtMessages();
|
||||
public void incProtMessages();
|
||||
|
||||
void incProtErrors();
|
||||
public void incProtErrors();
|
||||
|
||||
public void startScan();
|
||||
}
|
||||
|
|
|
@ -16,8 +16,13 @@ import java.util.Map;
|
|||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.shelly.internal.discovery.ShellyBluDiscoveryService;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.osgi.framework.BundleContext;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.ConfigurationPolicy;
|
||||
import org.osgi.service.component.annotations.Deactivate;
|
||||
|
||||
/***
|
||||
* The{@link ShellyThingTable} implements a simple table to allow dispatching incoming events to the proper thing
|
||||
|
@ -29,6 +34,7 @@ import org.osgi.service.component.annotations.ConfigurationPolicy;
|
|||
@Component(service = ShellyThingTable.class, configurationPolicy = ConfigurationPolicy.OPTIONAL)
|
||||
public class ShellyThingTable {
|
||||
private Map<String, ShellyThingInterface> thingTable = new ConcurrentHashMap<>();
|
||||
private @Nullable ShellyBluDiscoveryService bluDiscoveryService;
|
||||
|
||||
public void addThing(String key, ShellyThingInterface thing) {
|
||||
if (thingTable.containsKey(key)) {
|
||||
|
@ -37,7 +43,7 @@ public class ShellyThingTable {
|
|||
thingTable.put(key, thing);
|
||||
}
|
||||
|
||||
public ShellyThingInterface getThing(String key) {
|
||||
public @Nullable ShellyThingInterface findThing(String key) {
|
||||
ShellyThingInterface t = thingTable.get(key);
|
||||
if (t != null) {
|
||||
return t;
|
||||
|
@ -48,8 +54,16 @@ public class ShellyThingTable {
|
|||
return t;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public ShellyThingInterface getThing(String key) {
|
||||
ShellyThingInterface t = findThing(key);
|
||||
if (t == null) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
return t;
|
||||
}
|
||||
|
||||
public void removeThing(String key) {
|
||||
if (thingTable.containsKey(key)) {
|
||||
|
@ -64,4 +78,36 @@ public class ShellyThingTable {
|
|||
public int size() {
|
||||
return thingTable.size();
|
||||
}
|
||||
|
||||
public void startDiscoveryService(BundleContext bundleContext) {
|
||||
if (bluDiscoveryService == null) {
|
||||
bluDiscoveryService = new ShellyBluDiscoveryService(bundleContext, this);
|
||||
bluDiscoveryService.registerDeviceDiscoveryService();
|
||||
}
|
||||
}
|
||||
|
||||
public void startScan() {
|
||||
for (Map.Entry<String, ShellyThingInterface> thing : thingTable.entrySet()) {
|
||||
(thing.getValue()).startScan();
|
||||
}
|
||||
}
|
||||
|
||||
public void stopDiscoveryService() {
|
||||
if (bluDiscoveryService != null) {
|
||||
bluDiscoveryService.unregisterDeviceDiscoveryService();
|
||||
bluDiscoveryService = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void discoveredResult(ThingTypeUID uid, String model, String serviceName, String address,
|
||||
Map<String, Object> properties) {
|
||||
if (bluDiscoveryService != null) {
|
||||
bluDiscoveryService.discoveredResult(uid, model, serviceName, address, properties);
|
||||
}
|
||||
}
|
||||
|
||||
@Deactivate
|
||||
public void deactivate() {
|
||||
stopDiscoveryService();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -87,6 +87,7 @@ public class ShellyManagerCache<K, V> extends ConcurrentHashMap<K, V> {
|
|||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("null")
|
||||
private void cleanMap() {
|
||||
long currentTime = new Date().getTime();
|
||||
for (K key : timeMap.keySet()) {
|
||||
|
|
|
@ -61,7 +61,6 @@ public class ShellyManagerConstants {
|
|||
public static final String ACTION_GETDEB1 = "getdebug1";
|
||||
public static final String ACTION_NONE = "-";
|
||||
|
||||
public static final String TEMPLATE_PATH = "sniplets/";
|
||||
public static final String HEADER_HTML = "header.html";
|
||||
public static final String OVERVIEW_HTML = "overview.html";
|
||||
public static final String OVERVIEW_HEADER = "ov_header.html";
|
||||
|
|
|
@ -164,7 +164,7 @@ public class ShellyManagerPage {
|
|||
}
|
||||
|
||||
String html = "";
|
||||
String file = TEMPLATE_PATH + template;
|
||||
String file = BUNDLE_RESOURCE_SNIPLETS + "/" + template;
|
||||
logger.debug("Read HTML from {}", file);
|
||||
ClassLoader cl = ShellyManagerInterface.class.getClassLoader();
|
||||
if (cl != null) {
|
||||
|
|
|
@ -84,6 +84,7 @@ public class ShellyManagerServlet extends HttpServlet {
|
|||
logger.debug("{} stopped", className);
|
||||
}
|
||||
|
||||
@SuppressWarnings("resource")
|
||||
@Override
|
||||
protected void service(@Nullable HttpServletRequest request, @Nullable HttpServletResponse response)
|
||||
throws ServletException, IOException, IllegalArgumentException {
|
||||
|
|
|
@ -125,6 +125,7 @@ public class ShellyChannelDefinitions {
|
|||
CHANNEL_DEFINITIONS
|
||||
// Device
|
||||
.add(new ShellyChannel(m, CHGR_DEVST, CHANNEL_DEVST_NAME, "deviceName", ITEMT_STRING))
|
||||
.add(new ShellyChannel(m, CHGR_DEVST, CHANNEL_DEVST_GATEWAY, "gatewayDevice", ITEMT_STRING))
|
||||
.add(new ShellyChannel(m, CHGR_DEVST, CHANNEL_DEVST_ITEMP, "deviceTemp", ITEMT_TEMP))
|
||||
.add(new ShellyChannel(m, CHGR_DEVST, CHANNEL_DEVST_WAKEUP, "sensorWakeup", ITEMT_STRING))
|
||||
.add(new ShellyChannel(m, CHGR_DEVST, CHANNEL_DEVST_ACCUWATTS, "meterAccuWatts", ITEMT_POWER))
|
||||
|
@ -297,9 +298,11 @@ public class ShellyChannelDefinitions {
|
|||
Map<String, Channel> add = new LinkedHashMap<>();
|
||||
|
||||
addChannel(thing, add, profile.settings.name != null, CHGR_DEVST, CHANNEL_DEVST_NAME);
|
||||
addChannel(thing, add, !profile.gateway.isEmpty() || profile.isBlu, CHGR_DEVST, CHANNEL_DEVST_GATEWAY);
|
||||
|
||||
if (!profile.isSensor && !profile.isIX && status.temperature != null
|
||||
&& status.temperature != SHELLY_API_INVTEMP) {
|
||||
if (!profile.isSensor && !profile.isIX
|
||||
&& ((status.temperature != null && getDouble(status.temperature) != SHELLY_API_INVTEMP)
|
||||
|| (status.tmp != null && getDouble(status.tmp.tC) != SHELLY_API_INVTEMP))) {
|
||||
// Only some devices report the internal device temp
|
||||
addChannel(thing, add, status.tmp != null || status.temperature != null, CHGR_DEVST, CHANNEL_DEVST_ITEMP);
|
||||
}
|
||||
|
@ -355,6 +358,8 @@ public class ShellyChannelDefinitions {
|
|||
addChannel(thing, add, profile.status.extTemperature.sensor1 != null, CHGR_SENSOR, CHANNEL_ESENSOR_TEMP1);
|
||||
addChannel(thing, add, profile.status.extTemperature.sensor2 != null, CHGR_SENSOR, CHANNEL_ESENSOR_TEMP2);
|
||||
addChannel(thing, add, profile.status.extTemperature.sensor3 != null, CHGR_SENSOR, CHANNEL_ESENSOR_TEMP3);
|
||||
addChannel(thing, add, profile.status.extTemperature.sensor4 != null, CHGR_SENSOR, CHANNEL_ESENSOR_TEMP4);
|
||||
addChannel(thing, add, profile.status.extTemperature.sensor5 != null, CHGR_SENSOR, CHANNEL_ESENSOR_TEMP5);
|
||||
}
|
||||
addChannel(thing, add, profile.status.extHumidity != null, CHGR_SENSOR, CHANNEL_ESENSOR_HUMIDITY);
|
||||
addChannel(thing, add, profile.status.extVoltage != null, CHGR_SENSOR, CHANNEL_ESENSOR_VOLTAGE);
|
||||
|
@ -411,7 +416,7 @@ public class ShellyChannelDefinitions {
|
|||
for (int i = 0; i < profile.numInputs; i++) {
|
||||
String group = profile.getInputGroup(i);
|
||||
String suffix = profile.getInputSuffix(i); // multi ? String.valueOf(i + 1) : "";
|
||||
addChannel(thing, add, true, group, CHANNEL_INPUT + suffix);
|
||||
addChannel(thing, add, !profile.isButton, group, CHANNEL_INPUT + suffix);
|
||||
addChannel(thing, add, true, group,
|
||||
(!profile.isRoller ? CHANNEL_BUTTON_TRIGGER + suffix : CHANNEL_EVENT_TRIGGER));
|
||||
if (profile.inButtonMode(i)) {
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
<type>binding</type>
|
||||
<name>@text/addon.shelly.name</name>
|
||||
<description>@text/addon.shelly.description</description>
|
||||
<connection>local</connection>
|
||||
|
||||
<config-description>
|
||||
<parameter name="defaultUserId" type="text">
|
||||
|
|
|
@ -22,6 +22,11 @@
|
|||
<unitLabel>seconds</unitLabel>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="enableBluGateway" type="boolean" required="false">
|
||||
<label>@text/thing-type.config.shelly.enableBluGateway.label</label>
|
||||
<description>@text/thing-type.config.shelly.enableBluGateway.description</description>
|
||||
<default>false</default>
|
||||
</parameter>
|
||||
</config-description>
|
||||
|
||||
<config-description uri="thing-type:shelly:roller-gen2">
|
||||
|
@ -52,6 +57,11 @@
|
|||
<unitLabel>seconds</unitLabel>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="enableBluGateway" type="boolean" required="false">
|
||||
<label>@text/thing-type.config.shelly.enableBluGateway.label</label>
|
||||
<description>@text/thing-type.config.shelly.enableBluGateway.description</description>
|
||||
<default>false</default>
|
||||
</parameter>
|
||||
</config-description>
|
||||
|
||||
<config-description uri="thing-type:shelly:battery-gen2">
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<config-description:config-descriptions
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:config-description="https://openhab.org/schemas/config-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0 https://openhab.org/schemas/config-description-1.0.0.xsd">
|
||||
|
||||
<config-description uri="thing-type:shelly:blubattery">
|
||||
<parameter name="deviceAddress" type="text" required="true">
|
||||
<label>@text/thing-type.config.shelly.deviceAddress.label</label>
|
||||
<description>@text/thing-type.config.shelly.@text/thing-type.config.shelly.deviceAddress.label.description</description>
|
||||
</parameter>
|
||||
<parameter name="lowBattery" type="integer" required="false">
|
||||
<label>@text/thing-type.config.shelly.battery.lowBattery.label</label>
|
||||
<description>@text/thing-type.config.shelly.battery.lowBattery.description</description>
|
||||
<default>20</default>
|
||||
<unitLabel>%</unitLabel>
|
||||
</parameter>
|
||||
</config-description>
|
||||
|
||||
</config-description:config-descriptions>
|
|
@ -11,7 +11,7 @@ addon.shelly.config.autoCoIoT.label = Auto-CoIoT
|
|||
addon.shelly.config.autoCoIoT.description = If enabled CoIoT will be automatically used when the devices runs a firmware version 1.6 or newer; false: Use thing configuration to enabled/disable CoIoT events.
|
||||
|
||||
# Config status messages
|
||||
message.config-status.error.missing-device-ip = IP address of the Shelly device is missing.
|
||||
message.config-status.error.missing-device-address = IP/MAC Address of the Shelly device is missing.
|
||||
message.config-status.error.missing-userid = No user ID in the Thing configuration
|
||||
|
||||
# Thing status descriptions
|
||||
|
@ -43,7 +43,7 @@ message.event.filtered = Event filtered: {0}
|
|||
message.coap.init.failed = Unable to start CoIoT: {0}
|
||||
message.discovery.disabled = Device is marked as non-discoverable, will be skipped
|
||||
message.discovery.protected = Device {0} reported 'Access defined' (missing userid/password or incorrect).
|
||||
message.discovery.failed = Device discovery of device with IP address {0} failed: {1}
|
||||
message.discovery.failed = Device discovery of device with address {0} failed: {1}
|
||||
message.roller.calibrating = Device is not calibrated, use Shelly App to perform initial roller calibration.
|
||||
message.roller.favmissing = Roller position favorites are not supported by installed firmware or not configured in the Shelly App
|
||||
|
||||
|
@ -88,16 +88,6 @@ thing-type.shelly.shellytrv.description = Shelly TRV (Radiator value, battery po
|
|||
thing-type.shelly.shellyix3.description = Shelly ix3 (Activation Device with 3 inputs)
|
||||
thing-type.shelly.shellypludht.description = Shelly Plus HT - Temperature and Humidity Sensor
|
||||
|
||||
# Plus Devices
|
||||
thing-type.shelly.shellyplus1.description = Shelly Plus 1 (Single Relay Switch)
|
||||
thing-type.shelly.shellyplus1pm.description = Shelly Plus 1PM - Single Relay Switch with Power Meter
|
||||
thing-type.shelly.shellyplus2-relay.description = Shelly Plus 2PM - Dual Relay Switch with Power Meter
|
||||
thing-type.shelly.shellyplus2pm-roller.description = Shelly Plus 2PM - Roller Control with Power Meter
|
||||
thing-type.shelly.shellyplusplug.description = Shelly Plus Plug S/IT/UK/US . Outlet with Power Meter
|
||||
thing-type.shelly.shellyplusht.description = Shelly Plus HT - Humidity and Temperature sensor with display
|
||||
thing-type.shelly.shellyplusi4.description = Shelly Plus i4 - 4xInput Device
|
||||
thing-type.shelly.shellyplusi4dc.description = Shelly Plus i4DC - 4xDC Input Device
|
||||
|
||||
# Pro Devices
|
||||
thing-type.shelly.shellypro1.description = Shelly Pro 1 - Single Relay Switch
|
||||
thing-type.shelly.shellypro1pm.description = Shelly Pro 1PM - Single Relay Switch with Power Meter
|
||||
|
@ -108,16 +98,18 @@ thing-type.shelly.shellypro3.description = Shelly Pro 3 - 3xRelay Switch
|
|||
thing-type.shelly.shellypro3em.description = Shelly Pro 3EM - 3xPower Meter
|
||||
thing-type.shelly.shellypro4pm.description = Shelly Pro 4PM - 4xRelay Switch with Power Meter
|
||||
|
||||
|
||||
# Plus/Pro devices
|
||||
# Plus devices
|
||||
thing-type.shelly.shellyplus1.description = Shelly Plus 1 (Single Relay Switch)
|
||||
thing-type.shelly.shellyplus1pm.description = Shelly Plus 1PM - Single Relay Switch with Power Meter
|
||||
thing-type.shelly.shellyplus2-relay.description = Shelly Plus 2PM - Dual Relay Switch with Power Meter
|
||||
thing-type.shelly.shellyplus2pm-roller.description = Shelly Plus 2PM - Roller Control with Power Meter
|
||||
thing-type.shelly.shellyplusplug.description = Shelly Plus Plug S/IT/UK/US . Outlet with Power Meter
|
||||
thing-type.shelly.shellyplusht.description = Shelly Plus HT - Humidity and Temperature sensor with display
|
||||
thing-type.shelly.shellyplussmoke.description = Shelly Plus Smoke - Smoke Detector with Alarm
|
||||
thing-type.shelly.shellyplusi4.description = Shelly Plus i4 - 4xInput Device
|
||||
thing-type.shelly.shellyplusi4dc.description = Shelly Plus i4DC - 4xDC Input Device
|
||||
|
||||
# Pro devices
|
||||
thing-type.shelly.shellypro1.description = Shelly Pro 1 - Single Relay Switch
|
||||
thing-type.shelly.shellypro1pm.description = Shelly Pro 1PM - Single Relay Switch with Power Meter
|
||||
thing-type.shelly.shellypro2-relay.description = Shelly Pro 2 - Dual Relay Switch
|
||||
|
@ -127,15 +119,23 @@ thing-type.shelly.shellypro4pm.description = Shelly Pro 4PM - 4xRelay Switch wit
|
|||
thing-type.shelly.shellypro3.description = Shelly Pro 3 - 3xRelay Switch
|
||||
thing-type.shelly.shellypro4pm.description = Shelly Pro 4PM - 4xRelay Switch with Power Meter
|
||||
|
||||
# BLU devices
|
||||
thing-type.shelly.shellypblubutton.description = Shelly BLU Button
|
||||
thing-type.shelly.shellybludw.description = Shelly BLU Door/Window Sensor
|
||||
|
||||
# thing config - shellydevice
|
||||
thing-type.config.shelly.deviceIp.label = IP Address
|
||||
thing-type.config.shelly.deviceIp.description = IP Address of the Shelly device
|
||||
thing-type.config.shelly.deviceAddress.label = MAC Address
|
||||
thing-type.config.shelly.deviceAddress.description = MAC Address of the Shelly device
|
||||
thing-type.config.shelly.userId.label = User ID
|
||||
thing-type.config.shelly.userId.description = User ID for API access
|
||||
thing-type.config.shelly.password.label = Password
|
||||
thing-type.config.shelly.password.description = Password for API access
|
||||
thing-type.config.shelly.updateInterval.label = Status Interval
|
||||
thing-type.config.shelly.updateInterval.description = Interval for the device status update
|
||||
thing-type.config.shelly.enableBluGateway.label = Enable BLU Gateway Support
|
||||
thing-type.config.shelly.enableBluGateway.description = Enables BLU Gateway support incl- auto-upload of the required script
|
||||
thing-type.config.shelly.eventsButton.label = Button Events
|
||||
thing-type.config.shelly.eventsButton.description = Activates the Button Action URLS
|
||||
thing-type.config.shelly.eventsPush.label = Push Events
|
||||
|
@ -287,8 +287,8 @@ channel-type.shelly.meterAccuWatts.label = Accumulated Power Consumption
|
|||
channel-type.shelly.meterAccuWatts.description = Accumulated Power Consumption in Watts of the device (including all meters)
|
||||
channel-type.shelly.meterAccuTotal.label = Accumulated Total Power
|
||||
channel-type.shelly.meterAccuTotal.description = Accumulated Total Power in kW/h of the device (including all meters)
|
||||
channel-type.shelly.meterAccuReturned.label = Accumulated Returned Power
|
||||
channel-type.shelly.meterAccuReturned.description = Accumulated Returned Power in kW/h of the device (including all meters)
|
||||
channel-type.shelly.meterAccuReturned.label = Accumulated Apparent Power
|
||||
channel-type.shelly.meterAccuReturned.description = Accumulated Apparent Power in kW/h of the device (including all meters)
|
||||
channel-type.shelly.meterReactive.label = Reactive Energy
|
||||
channel-type.shelly.meterReactive.description = Instantaneous reactive power in Watts (W)
|
||||
channel-type.shelly.lastPower1.label = Last Power
|
||||
|
@ -450,6 +450,8 @@ channel-type.shelly.senseKey.label = IR Key to Send
|
|||
channel-type.shelly.senseKey.description = Send a defined key code
|
||||
channel-type.shelly.deviceName.label = Device Name
|
||||
channel-type.shelly.deviceName.description = Symbolic Device Name as configured in the Shelly App
|
||||
channel-type.shelly.gatewayDevice.label = Gateway Device
|
||||
channel-type.shelly.gatewayDevice.description = Last Shelly Device, which forwarded the event
|
||||
channel-type.shelly.uptime.label = Uptime
|
||||
channel-type.shelly.uptime.description = Number of seconds since the device was powered up
|
||||
channel-type.shelly.heartBeat.label = Last Heartbeat
|
||||
|
|
|
@ -59,6 +59,13 @@
|
|||
<state readOnly="true">
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="gatewayDevice" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>@text/channel-type.shelly.gatewayDevice.label</label>
|
||||
<description>@text/channel-type.shelly.gatewayDevice.description</description>
|
||||
<state readOnly="true">
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="calibrated" advanced="true">
|
||||
<item-type>Switch</item-type>
|
||||
<label>@text/channel-type.shelly.calibrated.label</label>
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="shelly"
|
||||
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">
|
||||
|
||||
<thing-type id="shellyblubutton">
|
||||
<label>Shelly BLU Button</label>
|
||||
<description>@text/thing-type.shelly.shellyblubutton.description</description>
|
||||
<category>WallSwitch</category>
|
||||
<channel-groups>
|
||||
<channel-group id="status" typeId="buttonState"/>
|
||||
<channel-group id="battery" typeId="batteryStatus"/>
|
||||
<channel-group id="device" typeId="deviceStatus"/>
|
||||
</channel-groups>
|
||||
|
||||
<representation-property>serviceName</representation-property>
|
||||
<config-description-ref uri="thing-type:shelly:blubattery"/>
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="shellybludw">
|
||||
<label>Shelly BLU Door/Window</label>
|
||||
<description>@text/thing-type.shelly.shellybludw.description</description>
|
||||
<category>Sensor</category>
|
||||
<channel-groups>
|
||||
<channel-group id="sensors" typeId="sensorData"/>
|
||||
<channel-group id="battery" typeId="batteryStatus"/>
|
||||
<channel-group id="device" typeId="deviceStatus"/>
|
||||
</channel-groups>
|
||||
|
||||
<representation-property>serviceName</representation-property>
|
||||
<config-description-ref uri="thing-type:shelly:blubattery"/>
|
||||
</thing-type>
|
||||
|
||||
</thing:thing-descriptions>
|
|
@ -0,0 +1,167 @@
|
|||
/*
|
||||
* This script uses the BLE scan functionality in scripting to pass scan reults to openHAB
|
||||
* Supported BLU Devices: SBBT , SBDW
|
||||
*/
|
||||
|
||||
let ALLTERCO_DEVICE_NAME_PREFIX = ["SBBT", "SBDW"];
|
||||
let ALLTERCO_MFD_ID_STR = "0ba9";
|
||||
let BTHOME_SVC_ID_STR = "fcd2";
|
||||
|
||||
let ALLTERCO_MFD_ID = JSON.parse("0x" + ALLTERCO_MFD_ID_STR);
|
||||
let BTHOME_SVC_ID = JSON.parse("0x" + BTHOME_SVC_ID_STR);
|
||||
let SCAN_DURATION = BLE.Scanner.INFINITE_SCAN;
|
||||
|
||||
let SHELLY_BLU_CACHE = {};
|
||||
let LAST_PID = {};
|
||||
|
||||
let uint8 = 0;
|
||||
let int8 = 1;
|
||||
let uint16 = 2;
|
||||
let int16 = 3;
|
||||
let uint24 = 4;
|
||||
let int24 = 5;
|
||||
|
||||
let BTH = [];
|
||||
BTH[0x00] = { n: "pid", t: uint8 };
|
||||
BTH[0x01] = { n: "Battery", t: uint8, u: "%" };
|
||||
BTH[0x05] = { n: "Illuminance", t: uint24, f: 0.01 };
|
||||
BTH[0x1a] = { n: "Door", t: uint8 };
|
||||
BTH[0x20] = { n: "Moisture", t: uint8 };
|
||||
BTH[0x2d] = { n: "Window", t: uint8 };
|
||||
BTH[0x3a] = { n: "Button", t: uint8 };
|
||||
BTH[0x3f] = { n: "Rotation", t: int16, f: 0.1 };
|
||||
|
||||
function getByteSize(type) {
|
||||
if (type === uint8 || type === int8) return 1;
|
||||
if (type === uint16 || type === int16) return 2;
|
||||
if (type === uint24 || type === int24) return 3;
|
||||
//impossible as advertisements are much smaller;
|
||||
return 255;
|
||||
}
|
||||
|
||||
let BTHomeDecoder = {
|
||||
utoi: function (num, bitsz) {
|
||||
let mask = 1 << (bitsz - 1);
|
||||
return num & mask ? num - (1 << bitsz) : num;
|
||||
},
|
||||
getUInt8: function (buffer) {
|
||||
return buffer.at(0);
|
||||
},
|
||||
getInt8: function (buffer) {
|
||||
return this.utoi(this.getUInt8(buffer), 8);
|
||||
},
|
||||
getUInt16LE: function (buffer) {
|
||||
return 0xffff & ((buffer.at(1) << 8) | buffer.at(0));
|
||||
},
|
||||
getInt16LE: function (buffer) {
|
||||
return this.utoi(this.getUInt16LE(buffer), 16);
|
||||
},
|
||||
getUInt24LE: function (buffer) {
|
||||
return (
|
||||
0x00ffffff & ((buffer.at(2) << 16) | (buffer.at(1) << 8) | buffer.at(0))
|
||||
);
|
||||
},
|
||||
getInt24LE: function (buffer) {
|
||||
return this.utoi(this.getUInt24LE(buffer), 24);
|
||||
},
|
||||
getBufValue: function (type, buffer) {
|
||||
if (buffer.length < getByteSize(type)) return null;
|
||||
let res = null;
|
||||
if (type === uint8) res = this.getUInt8(buffer);
|
||||
if (type === int8) res = this.getInt8(buffer);
|
||||
if (type === uint16) res = this.getUInt16LE(buffer);
|
||||
if (type === int16) res = this.getInt16LE(buffer);
|
||||
if (type === uint24) res = this.getUInt24LE(buffer);
|
||||
if (type === int24) res = this.getInt24LE(buffer);
|
||||
return res;
|
||||
},
|
||||
unpack: function (buffer) {
|
||||
// beacons might not provide BTH service data
|
||||
if (typeof buffer !== "string" || buffer.length === 0) return null;
|
||||
let result = {};
|
||||
let _dib = buffer.at(0);
|
||||
result["encryption"] = _dib & 0x1 ? true : false;
|
||||
result["BTHome_version"] = _dib >> 5;
|
||||
if (result["BTHome_version"] !== 2) return null;
|
||||
//Can not handle encrypted data
|
||||
if (result["encryption"]) return result;
|
||||
buffer = buffer.slice(1);
|
||||
|
||||
let _bth;
|
||||
let _value;
|
||||
while (buffer.length > 0) {
|
||||
_bth = BTH[buffer.at(0)];
|
||||
if (_bth === "undefined") {
|
||||
console.log("BTH: unknown type");
|
||||
break;
|
||||
}
|
||||
buffer = buffer.slice(1);
|
||||
_value = this.getBufValue(_bth.t, buffer);
|
||||
if (_value === null) break;
|
||||
if (typeof _bth.f !== "undefined") _value = _value * _bth.f;
|
||||
result[_bth.n] = _value;
|
||||
buffer = buffer.slice(getByteSize(_bth.t));
|
||||
}
|
||||
return result;
|
||||
},
|
||||
};
|
||||
|
||||
let ShellyBLUParser = {
|
||||
getData: function (res) {
|
||||
let result = BTHomeDecoder.unpack(res.service_data[BTHOME_SVC_ID_STR]);
|
||||
result.addr = res.addr;
|
||||
result.rssi = res.rssi;
|
||||
return result;
|
||||
},
|
||||
};
|
||||
|
||||
function scanCB(ev, res) {
|
||||
if (ev !== BLE.Scanner.SCAN_RESULT) return;
|
||||
// skip if there is no service_data member
|
||||
if (typeof res.service_data === 'undefined' || typeof res.service_data[BTHOME_SVC_ID_STR] === 'undefined') return;
|
||||
// skip if we have already found this device
|
||||
if (typeof SHELLY_BLU_CACHE[res.addr] === 'undefined') {
|
||||
if (typeof res.local_name === "undefined") console.log("res.local_name undefined")
|
||||
if (typeof res.local_name !== 'string') return;
|
||||
let shellyBluNameIdx = 0;
|
||||
for (shellyBluNameIdx in ALLTERCO_DEVICE_NAME_PREFIX) {
|
||||
if (res.local_name.indexOf(ALLTERCO_DEVICE_NAME_PREFIX[shellyBluNameIdx]) === 0) {
|
||||
console.log('New device found: address=', res.addr, ', name=', res.local_name);
|
||||
Shelly.emitEvent("oh-blu.scan_result", {"addr":res.addr, "name":res.local_name, "rssi":res.rssi, "tx_power":res.tx_power_level});
|
||||
SHELLY_BLU_CACHE[res.addr] = res.local_name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let BTHparsed = ShellyBLUParser.getData(res); // skip if parsing failed
|
||||
if (BTHparsed === null) {
|
||||
console.log("Failed to parse BTH data");
|
||||
return;
|
||||
}
|
||||
|
||||
// skip, we are deduping results
|
||||
if (typeof LAST_PID[res.addr] === 'undefined' ||
|
||||
BTHparsed.pid !== LAST_PID[res.addr]) {
|
||||
Shelly.emitEvent("oh-blu.data", BTHparsed);
|
||||
LAST_PID[res.addr] = BTHparsed.pid;
|
||||
}
|
||||
}
|
||||
|
||||
// retry several times to start the scanner if script was started before
|
||||
// BLE infrastructure was up in the Shelly
|
||||
function startBLEScan() {
|
||||
let bleScanSuccess = BLE.Scanner.Start({ duration_ms: SCAN_DURATION, active: true }, scanCB);
|
||||
if( bleScanSuccess === false ) {
|
||||
Timer.set(1000, false, startBLEScan);
|
||||
} else {
|
||||
console.log('Success: OH-BLU Event Gateway running');
|
||||
}
|
||||
}
|
||||
|
||||
let BLEConfig = Shelly.getComponentConfig('ble');
|
||||
if(BLEConfig.enable === false) {
|
||||
console.log('Error: BLE not enabled');
|
||||
} else {
|
||||
Timer.set(1000, false, startBLEScan);
|
||||
}
|
||||
|
Loading…
Reference in New Issue