[miio] add BT Devices channel to chuangmi plug (#11715)
* [miio] add BT Devices channel to chuangmi plug * Shows the bluetooth devices connected to the plug (plug as BT gateway) * Add refresh interval functionality to reduce load on device * Change public to private for the private functions in conversions. * Add test for new conversion * Update miio.properties Signed-off-by: Marcel Verpaalen <marcel@verpaalen.com>
This commit is contained in:
parent
ba58f96d33
commit
f8a6522100
|
@ -701,6 +701,7 @@ Note, not all the values need to be in the json file, e.g. a subset of the param
|
|||
| task-switch | Switch | Imilab Timer - Task Switch | |
|
||||
| countdown-info | Switch | Imilab Timer - Countdown Info | |
|
||||
| bt-gw | String | BT Gateway | Value mapping `["disable"="Disable","enable"="Enable"]` |
|
||||
| bt-gw-devices | String | Connected BT Gateway Devices | Note, refreshes every 2nd refresh. Channel requires cloud connectivity to function. Sample widget to visualise the (json) output available from the widget market |
|
||||
|
||||
### Mi Smart Plug WiFi (<a name="chuangmi-plug-hmi205">chuangmi.plug.hmi205</a>) Channels
|
||||
|
||||
|
@ -5720,6 +5721,7 @@ Number:Time countdown "Imilab Timer - Countdown" (G_plug) {channel="miio:basic:p
|
|||
Switch task_switch "Imilab Timer - Task Switch" (G_plug) {channel="miio:basic:plug:task-switch"}
|
||||
Switch countdown_info "Imilab Timer - Countdown Info" (G_plug) {channel="miio:basic:plug:countdown-info"}
|
||||
String bt_gw "BT Gateway" (G_plug) {channel="miio:basic:plug:bt-gw"}
|
||||
String bt_gw_devices "Connected BT Gateway Devices" (G_plug) {channel="miio:basic:plug:bt-gw-devices"}
|
||||
```
|
||||
|
||||
### Mi Smart Plug WiFi (chuangmi.plug.hmi205) item file lines
|
||||
|
|
|
@ -74,7 +74,7 @@ public class MiIoSendCommand {
|
|||
}
|
||||
|
||||
public JsonElement getParams() {
|
||||
return commandJson.has("params") ? commandJson.get("params").getAsJsonArray() : new JsonArray();
|
||||
return commandJson.has("params") ? commandJson.get("params") : new JsonArray();
|
||||
}
|
||||
|
||||
public JsonObject getResponse() {
|
||||
|
|
|
@ -44,7 +44,7 @@ public class Conversions {
|
|||
* @param RGB + brightness value (note brightness in the first byte)
|
||||
* @return HSV
|
||||
*/
|
||||
public static JsonElement bRGBtoHSV(JsonElement bRGB) throws ClassCastException {
|
||||
private static JsonElement bRGBtoHSV(JsonElement bRGB) throws ClassCastException {
|
||||
if (bRGB.isJsonPrimitive() && bRGB.getAsJsonPrimitive().isNumber()) {
|
||||
Color rgb = new Color(bRGB.getAsInt());
|
||||
HSBType hsb = HSBType.fromRGB(rgb.getRed(), rgb.getGreen(), rgb.getBlue());
|
||||
|
@ -62,7 +62,7 @@ public class Conversions {
|
|||
* @param map with device variables containing the brightness info
|
||||
* @return HSV
|
||||
*/
|
||||
public static JsonElement addBrightToHSV(JsonElement rgbValue, @Nullable Map<String, Object> deviceVariables)
|
||||
private static JsonElement addBrightToHSV(JsonElement rgbValue, @Nullable Map<String, Object> deviceVariables)
|
||||
throws ClassCastException, IllegalStateException {
|
||||
int bright = 100;
|
||||
if (deviceVariables != null) {
|
||||
|
@ -79,12 +79,12 @@ public class Conversions {
|
|||
return rgbValue;
|
||||
}
|
||||
|
||||
public static JsonElement secondsToHours(JsonElement seconds) throws ClassCastException {
|
||||
private static JsonElement secondsToHours(JsonElement seconds) throws ClassCastException {
|
||||
double value = seconds.getAsDouble() / 3600;
|
||||
return new JsonPrimitive(value);
|
||||
}
|
||||
|
||||
public static JsonElement yeelightSceneConversion(JsonElement intValue)
|
||||
private static JsonElement yeelightSceneConversion(JsonElement intValue)
|
||||
throws ClassCastException, IllegalStateException {
|
||||
switch (intValue.getAsInt()) {
|
||||
case 1:
|
||||
|
@ -104,17 +104,17 @@ public class Conversions {
|
|||
}
|
||||
}
|
||||
|
||||
public static JsonElement divideTen(JsonElement value10) throws ClassCastException, IllegalStateException {
|
||||
private static JsonElement divideTen(JsonElement value10) throws ClassCastException, IllegalStateException {
|
||||
double value = value10.getAsDouble() / 10.0;
|
||||
return new JsonPrimitive(value);
|
||||
}
|
||||
|
||||
public static JsonElement divideHundred(JsonElement value10) throws ClassCastException, IllegalStateException {
|
||||
private static JsonElement divideHundred(JsonElement value10) throws ClassCastException, IllegalStateException {
|
||||
double value = value10.getAsDouble() / 100.0;
|
||||
return new JsonPrimitive(value);
|
||||
}
|
||||
|
||||
public static JsonElement tankLevel(JsonElement value12) throws ClassCastException, IllegalStateException {
|
||||
private static JsonElement tankLevel(JsonElement value12) throws ClassCastException, IllegalStateException {
|
||||
// 127 without water tank. 120 = 100% water
|
||||
if (value12.getAsInt() == 127) {
|
||||
return new JsonPrimitive(-1);
|
||||
|
@ -124,7 +124,30 @@ public class Conversions {
|
|||
}
|
||||
}
|
||||
|
||||
public static JsonElement getJsonElement(String element, JsonElement responseValue) {
|
||||
/**
|
||||
* Returns the deviceId element value from the Json response. If not found, returns the input
|
||||
*
|
||||
* @param responseValue
|
||||
* @param deviceVariables containing the deviceId
|
||||
* @return
|
||||
*/
|
||||
private static JsonElement getDidElement(JsonElement responseValue, Map<String, Object> deviceVariables) {
|
||||
String did = (String) deviceVariables.get("deviceId");
|
||||
if (did != null) {
|
||||
return getJsonElement(did, responseValue);
|
||||
}
|
||||
LOGGER.debug("deviceId not Found, no conversion");
|
||||
return responseValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the element from the Json response. If not found, returns the input
|
||||
*
|
||||
* @param element to be found
|
||||
* @param responseValue
|
||||
* @return
|
||||
*/
|
||||
private static JsonElement getJsonElement(String element, JsonElement responseValue) {
|
||||
try {
|
||||
if (responseValue.isJsonPrimitive() || responseValue.isJsonObject()) {
|
||||
JsonElement jsonElement = responseValue.isJsonObject() ? responseValue
|
||||
|
@ -143,8 +166,7 @@ public class Conversions {
|
|||
return responseValue;
|
||||
}
|
||||
|
||||
public static JsonElement execute(String transformation, JsonElement value,
|
||||
@Nullable Map<String, Object> deviceVariables) {
|
||||
public static JsonElement execute(String transformation, JsonElement value, Map<String, Object> deviceVariables) {
|
||||
try {
|
||||
if (transformation.toUpperCase().startsWith("GETJSONELEMENT")) {
|
||||
if (transformation.length() > 15) {
|
||||
|
@ -168,6 +190,8 @@ public class Conversions {
|
|||
return addBrightToHSV(value, deviceVariables);
|
||||
case "BRGBTOHSV":
|
||||
return bRGBtoHSV(value);
|
||||
case "GETDIDELEMENT":
|
||||
return getDidElement(value, deviceVariables);
|
||||
default:
|
||||
LOGGER.debug("Transformation {} not found. Returning '{}'", transformation, value.toString());
|
||||
return value;
|
||||
|
|
|
@ -22,6 +22,7 @@ import java.util.List;
|
|||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.annotations.Expose;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
|
@ -66,12 +67,21 @@ public class MiIoBasicChannel {
|
|||
@SerializedName("refresh")
|
||||
@Expose
|
||||
private @Nullable Boolean refresh;
|
||||
@SerializedName("refreshInterval")
|
||||
@Expose
|
||||
private @Nullable Integer refreshInterval;
|
||||
@SerializedName("customRefreshCommand")
|
||||
@Expose
|
||||
private @Nullable String channelCustomRefreshCommand;
|
||||
@SerializedName("customRefreshParameters")
|
||||
@Expose
|
||||
private @Nullable JsonElement customRefreshParameters;
|
||||
@SerializedName("transformation")
|
||||
@Expose
|
||||
private @Nullable String transformation;
|
||||
@SerializedName("transformations")
|
||||
@Expose
|
||||
private @Nullable List<String> transformations;
|
||||
@SerializedName("ChannelGroup")
|
||||
@Expose
|
||||
private @Nullable String channelGroup;
|
||||
|
@ -202,6 +212,23 @@ public class MiIoBasicChannel {
|
|||
this.refresh = refresh;
|
||||
}
|
||||
|
||||
public Integer getRefreshInterval() {
|
||||
Integer refreshInterval = this.refreshInterval;
|
||||
if (refreshInterval != null) {
|
||||
return refreshInterval;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
public void setRefresh(@Nullable final Integer interval) {
|
||||
final Integer refreshInterval = interval;
|
||||
if (refreshInterval != null && refreshInterval.intValue() != 1) {
|
||||
this.refreshInterval = refreshInterval;
|
||||
} else {
|
||||
this.refreshInterval = null;
|
||||
}
|
||||
}
|
||||
|
||||
public String getChannelCustomRefreshCommand() {
|
||||
final @Nullable String channelCustomRefreshCommand = this.channelCustomRefreshCommand;
|
||||
return channelCustomRefreshCommand != null ? channelCustomRefreshCommand : "";
|
||||
|
@ -211,6 +238,14 @@ public class MiIoBasicChannel {
|
|||
this.channelCustomRefreshCommand = channelCustomRefreshCommand;
|
||||
}
|
||||
|
||||
public @Nullable final JsonElement getCustomRefreshParameters() {
|
||||
return customRefreshParameters;
|
||||
}
|
||||
|
||||
public final void setCustomRefreshParameters(@Nullable JsonElement customRefreshParameters) {
|
||||
this.customRefreshParameters = customRefreshParameters;
|
||||
}
|
||||
|
||||
public String getChannelGroup() {
|
||||
final @Nullable String channelGroup = this.channelGroup;
|
||||
return channelGroup != null ? channelGroup : "";
|
||||
|
@ -237,6 +272,28 @@ public class MiIoBasicChannel {
|
|||
this.transformation = transformation;
|
||||
}
|
||||
|
||||
public final List<String> getTransformations() {
|
||||
List<String> transformations = this.transformations;
|
||||
if (transformations == null) {
|
||||
transformations = new ArrayList<>();
|
||||
}
|
||||
String transformation = this.transformation;
|
||||
if (transformation != null) {
|
||||
List<String> allTransformation = new ArrayList<>(List.of(transformation));
|
||||
allTransformation.addAll(transformations);
|
||||
return allTransformation;
|
||||
}
|
||||
return transformations;
|
||||
}
|
||||
|
||||
public final void setTransformations(@Nullable List<String> transformations) {
|
||||
if (transformations != null && !transformations.isEmpty()) {
|
||||
this.transformations = transformations;
|
||||
} else {
|
||||
this.transformations = null;
|
||||
}
|
||||
}
|
||||
|
||||
public @Nullable String getCategory() {
|
||||
return category;
|
||||
}
|
||||
|
|
|
@ -105,6 +105,7 @@ public class MiIoBasicHandler extends MiIoAbstractHandler {
|
|||
private Map<ChannelUID, MiIoBasicChannel> actions = new HashMap<>();
|
||||
private ChannelTypeRegistry channelTypeRegistry;
|
||||
private BasicChannelTypeProvider basicChannelTypeProvider;
|
||||
private Map<String, Integer> customRefreshInterval = new HashMap<>();
|
||||
|
||||
public MiIoBasicHandler(Thing thing, MiIoDatabaseWatchService miIoDatabaseWatchService,
|
||||
CloudConnector cloudConnector, ChannelTypeRegistry channelTypeRegistry,
|
||||
|
@ -352,16 +353,39 @@ public class MiIoBasicHandler extends MiIoAbstractHandler {
|
|||
}
|
||||
}
|
||||
|
||||
private boolean customRefreshIntervalCheck(MiIoBasicChannel miChannel) {
|
||||
if (miChannel.getRefreshInterval() > 1) {
|
||||
int iteration = customRefreshInterval.getOrDefault(miChannel.getChannel(), 0);
|
||||
if (iteration < 1) {
|
||||
customRefreshInterval.put(miChannel.getChannel(), miChannel.getRefreshInterval() - 1);
|
||||
} else {
|
||||
logger.debug("Skip refresh of channel {} for {}. Next refresh in {} cycles.", miChannel.getChannel(),
|
||||
getThing().getUID(), iteration);
|
||||
customRefreshInterval.put(miChannel.getChannel(), iteration - 1);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean linkedChannelCheck(MiIoBasicChannel miChannel) {
|
||||
if (!isLinked(miChannel.getChannel())) {
|
||||
logger.debug("Skip refresh of channel {} for {} as it is not linked", miChannel.getChannel(),
|
||||
getThing().getUID());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void refreshCustomProperties(MiIoBasicDevice midevice) {
|
||||
for (MiIoBasicChannel miChannel : refreshListCustomCommands.values()) {
|
||||
if (!isLinked(miChannel.getChannel())) {
|
||||
logger.debug("Skip refresh of channel {} for {} as it is not linked", miChannel.getChannel(),
|
||||
getThing().getUID());
|
||||
if (customRefreshIntervalCheck(miChannel) || !linkedChannelCheck(miChannel)) {
|
||||
continue;
|
||||
}
|
||||
String cmd = miChannel.getChannelCustomRefreshCommand();
|
||||
final JsonElement para = miChannel.getCustomRefreshParameters();
|
||||
String cmd = miChannel.getChannelCustomRefreshCommand() + (para != null ? para.toString() : "");
|
||||
if (!cmd.startsWith("/")) {
|
||||
cmds.put(sendCommand(miChannel.getChannelCustomRefreshCommand()), miChannel.getChannel());
|
||||
cmds.put(sendCommand(cmd), miChannel.getChannel());
|
||||
} else {
|
||||
if (cloudServer.isBlank()) {
|
||||
logger.debug("Cloudserver empty. Skipping refresh for {} channel '{}'", getThing().getUID(),
|
||||
|
@ -378,9 +402,7 @@ public class MiIoBasicHandler extends MiIoAbstractHandler {
|
|||
int maxProperties = device.getDevice().getMaxProperties();
|
||||
JsonArray getPropString = new JsonArray();
|
||||
for (MiIoBasicChannel miChannel : refreshList) {
|
||||
if (!isLinked(miChannel.getChannel())) {
|
||||
logger.debug("Skip refresh of channel {} for {} as it is not linked", miChannel.getChannel(),
|
||||
getThing().getUID());
|
||||
if (customRefreshIntervalCheck(miChannel) || !linkedChannelCheck(miChannel)) {
|
||||
continue;
|
||||
}
|
||||
JsonElement property;
|
||||
|
|
|
@ -182,10 +182,7 @@ public class MiIoAsyncCommunication {
|
|||
miIoSendCommand.getCloudServer());
|
||||
updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE);
|
||||
} else {
|
||||
String data = miIoSendCommand.getParams().isJsonArray()
|
||||
&& miIoSendCommand.getParams().getAsJsonArray().size() > 0
|
||||
? miIoSendCommand.getParams().getAsJsonArray().get(0).toString()
|
||||
: "";
|
||||
String data = miIoSendCommand.getParams().toString();
|
||||
logger.debug("Custom cloud request send to url '{}' with data '{}'", miIoSendCommand.getMethod(),
|
||||
data);
|
||||
decryptedResponse = cloudConnector.sendCloudCommand(miIoSendCommand.getMethod(),
|
||||
|
|
|
@ -414,6 +414,7 @@ ch.cgllc.airmonitor.s1.pm25 = PM2.5
|
|||
ch.cgllc.airmonitor.s1.temperature = Temperature
|
||||
ch.cgllc.airmonitor.s1.tvoc = tVOC
|
||||
ch.chuangmi.plug.212a01-miot.bt-gw = BT Gateway
|
||||
ch.chuangmi.plug.212a01-miot.bt-gw-devices = Connected BT Gateway Devices
|
||||
ch.chuangmi.plug.212a01-miot.countdown = Imilab Timer - Countdown
|
||||
ch.chuangmi.plug.212a01-miot.countdown-info = Imilab Timer - Countdown Info
|
||||
ch.chuangmi.plug.212a01-miot.electric-current = Power Consumption - Electric Current
|
||||
|
|
|
@ -322,7 +322,29 @@
|
|||
}
|
||||
}
|
||||
],
|
||||
"category": "bluetooth",
|
||||
"readmeComment": "Value mapping `[\"disable\"\u003d\"Disable\",\"enable\"\u003d\"Enable\"]`"
|
||||
},
|
||||
{
|
||||
"property": "",
|
||||
"friendlyName": "Connected BT Gateway Devices",
|
||||
"channel": "bt-gw-devices",
|
||||
"type": "String",
|
||||
"stateDescription": {
|
||||
"readOnly": true
|
||||
},
|
||||
"refresh": true,
|
||||
"refreshInterval": 2,
|
||||
"customRefreshCommand": "/device/get_bledevice_by_gateway",
|
||||
"customRefreshParameters": {
|
||||
"dids": [
|
||||
"$deviceId$"
|
||||
]
|
||||
},
|
||||
"transformation": "getDiDElement",
|
||||
"actions": [],
|
||||
"category": "bluetooth",
|
||||
"readmeComment": "Note, refreshes every 2nd refresh. Channel requires cloud connectivity to function. Sample widget to visualise the (json) output available from the widget market"
|
||||
}
|
||||
],
|
||||
"experimental": false
|
||||
|
|
|
@ -15,6 +15,7 @@ package org.openhab.binding.miio.internal;
|
|||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
@ -35,6 +36,48 @@ import com.google.gson.JsonPrimitive;
|
|||
@NonNullByDefault
|
||||
public class ConversionsTest {
|
||||
|
||||
@Test
|
||||
public void getDidElementTest() {
|
||||
Map<String, Object> deviceVariables = new HashMap<>();
|
||||
String transformation = "getDidElement";
|
||||
JsonElement validInput = new JsonPrimitive(
|
||||
"{\"361185596\":\"{\\\"C812105B04000400\\\":\\\"-92\\\",\\\"blt.3.17q3si5345k00\\\":\\\"-54\\\",\\\"blt.4.10heul64og400\\\":\\\"-73\\\"}\"}");
|
||||
|
||||
// test no did in deviceVariables
|
||||
JsonElement value = validInput;
|
||||
JsonElement transformedResponse = Conversions.execute(transformation, value, deviceVariables);
|
||||
assertNotNull(transformedResponse);
|
||||
assertEquals(value, transformedResponse);
|
||||
|
||||
// test valid input & response
|
||||
deviceVariables.put("deviceId", "361185596");
|
||||
value = validInput;
|
||||
transformedResponse = Conversions.execute(transformation, value, deviceVariables);
|
||||
assertNotNull(transformedResponse);
|
||||
assertEquals(new JsonPrimitive(
|
||||
"{\"C812105B04000400\":\"-92\",\"blt.3.17q3si5345k00\":\"-54\",\"blt.4.10heul64og400\":\"-73\"}"),
|
||||
transformedResponse);
|
||||
|
||||
// test non json
|
||||
value = new JsonPrimitive("some non json value");
|
||||
transformedResponse = Conversions.execute(transformation, value, deviceVariables);
|
||||
assertNotNull(transformedResponse);
|
||||
assertEquals(value, transformedResponse);
|
||||
|
||||
// test different did in deviceVariables
|
||||
deviceVariables.put("deviceId", "ABC185596");
|
||||
value = validInput;
|
||||
transformedResponse = Conversions.execute(transformation, value, deviceVariables);
|
||||
assertNotNull(transformedResponse);
|
||||
assertEquals(value, transformedResponse);
|
||||
|
||||
// test empty input
|
||||
value = new JsonPrimitive("");
|
||||
transformedResponse = Conversions.execute(transformation, value, deviceVariables);
|
||||
assertNotNull(transformedResponse);
|
||||
assertEquals(value, transformedResponse);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getJsonElementTest() {
|
||||
|
||||
|
@ -55,11 +98,6 @@ public class ConversionsTest {
|
|||
|
||||
transformation = "getJsonElement-test";
|
||||
|
||||
// test without deviceVariables
|
||||
resp = Conversions.execute(transformation, value, null);
|
||||
assertNotNull(resp);
|
||||
assertEquals(new JsonPrimitive("testresponse"), resp);
|
||||
|
||||
// test non json
|
||||
value = new JsonPrimitive("some non json value");
|
||||
resp = Conversions.execute(transformation, value, deviceVariables);
|
||||
|
|
Loading…
Reference in New Issue