added migrated 2.x add-ons
Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<features name="org.openhab.binding.lghombot-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
|
||||
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
|
||||
|
||||
<feature name="openhab-binding-lghombot" description="LG HomBot Binding" version="${project.version}">
|
||||
<feature>openhab-runtime-base</feature>
|
||||
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.lghombot/${project.version}</bundle>
|
||||
</feature>
|
||||
</features>
|
||||
@@ -0,0 +1,83 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.lghombot.internal;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.library.types.RawType;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link CameraUtil} is responsible for parsing the raw yuv 422 image from a LG HomBot.
|
||||
*
|
||||
* @author Fredrik Ahlström - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class CameraUtil {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(CameraUtil.class);
|
||||
|
||||
private CameraUtil() {
|
||||
// No need to instance this class.
|
||||
}
|
||||
|
||||
/**
|
||||
* This converts a non-interleaved YUV-422 image to a JPEG image.
|
||||
*
|
||||
* @param yuvData The uncompressed YUV data
|
||||
* @param width The width of image.
|
||||
* @param height The height of the image.
|
||||
* @return A JPEG image as a State
|
||||
*/
|
||||
static State parseImageFromBytes(byte[] yuvData, int width, int height) {
|
||||
final int size = width * height;
|
||||
|
||||
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
|
||||
|
||||
for (int i = 0; i < size; i++) {
|
||||
double y = yuvData[i] & 0xFF;
|
||||
double u = yuvData[size + i / 2] & 0xFF;
|
||||
double v = yuvData[(int) (size * 1.5 + i / 2.0)] & 0xFF;
|
||||
|
||||
int r = Math.min(Math.max((int) (y + 1.371 * (v - 128)), 0), 255); // red
|
||||
int g = Math.min(Math.max((int) (y - 0.336 * (u - 128) - 0.698 * (v - 128)), 0), 255); // green
|
||||
int b = Math.min(Math.max((int) (y + 1.732 * (u - 128)), 0), 255); // blue
|
||||
|
||||
int p = (r << 16) | (g << 8) | b; // pixel
|
||||
image.setRGB(i % width, i / width, p);
|
||||
}
|
||||
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
try {
|
||||
if (!ImageIO.write(image, "jpg", baos)) {
|
||||
logger.debug("Couldn't find JPEG writer.");
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.info("IOException creating JPEG image.", e);
|
||||
}
|
||||
byte[] byteArray = baos.toByteArray();
|
||||
if (byteArray != null && byteArray.length > 0) {
|
||||
return new RawType(byteArray, "image/jpeg");
|
||||
} else {
|
||||
return UnDefType.UNDEF;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.lghombot.internal;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
|
||||
/**
|
||||
* The {@link LGHomBotBindingConstants} class defines common constants, which are
|
||||
* used across the whole binding.
|
||||
*
|
||||
* @author Fredrik Ahlström - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public final class LGHomBotBindingConstants {
|
||||
|
||||
private static final String BINDING_ID = "lghombot";
|
||||
|
||||
// List of all Thing Type UIDs
|
||||
public static final ThingTypeUID THING_TYPE_LGHOMBOT = new ThingTypeUID(BINDING_ID, "LGHomBot");
|
||||
|
||||
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections.singleton(THING_TYPE_LGHOMBOT);
|
||||
|
||||
// List of all Channel ids
|
||||
static final String CHANNEL_STATE = "state";
|
||||
static final String CHANNEL_BATTERY = "battery";
|
||||
static final String CHANNEL_CPU_LOAD = "cpuLoad";
|
||||
static final String CHANNEL_SRV_MEM = "srvMem";
|
||||
static final String CHANNEL_CLEAN = "clean";
|
||||
static final String CHANNEL_START = "start";
|
||||
static final String CHANNEL_HOME = "home";
|
||||
static final String CHANNEL_PAUSE = "pause";
|
||||
static final String CHANNEL_MODE = "mode";
|
||||
static final String CHANNEL_TURBO = "turbo";
|
||||
static final String CHANNEL_REPEAT = "repeat";
|
||||
static final String CHANNEL_NICKNAME = "nickname";
|
||||
static final String CHANNEL_MOVE = "move";
|
||||
static final String CHANNEL_CAMERA = "camera";
|
||||
static final String CHANNEL_LAST_CLEAN = "lastClean";
|
||||
static final String CHANNEL_MAP = "map";
|
||||
static final String CHANNEL_MONDAY = "monday";
|
||||
static final String CHANNEL_TUESDAY = "tuesday";
|
||||
static final String CHANNEL_WEDNESDAY = "wednesday";
|
||||
static final String CHANNEL_THURSDAY = "thursday";
|
||||
static final String CHANNEL_FRIDAY = "friday";
|
||||
static final String CHANNEL_SATURDAY = "saturday";
|
||||
static final String CHANNEL_SUNDAY = "sunday";
|
||||
|
||||
// List of all HomBot states
|
||||
static final String HBSTATE_UNKNOWN = "UNKNOWN";
|
||||
static final String HBSTATE_WORKING = "WORKING";
|
||||
static final String HBSTATE_BACKMOVING = "BACKMOVING";
|
||||
static final String HBSTATE_BACKMOVING_INIT = "BACKMOVING_INIT";
|
||||
static final String HBSTATE_BACKMOVING_JOY = "BACKMOVING_JOY";
|
||||
static final String HBSTATE_PAUSE = "PAUSE";
|
||||
static final String HBSTATE_STANDBY = "STANDBY";
|
||||
static final String HBSTATE_HOMING = "HOMING";
|
||||
static final String HBSTATE_DOCKING = "DOCKING";
|
||||
static final String HBSTATE_CHARGING = "CHARGING";
|
||||
static final String HBSTATE_DIAGNOSIS = "DIAGNOSIS";
|
||||
static final String HBSTATE_RESERVATION = "RESERVATION";
|
||||
static final String HBSTATE_ERROR = "ERROR";
|
||||
|
||||
/**
|
||||
* Default port number HomBot uses.
|
||||
*/
|
||||
public static final int DEFAULT_HOMBOT_PORT = 6260;
|
||||
|
||||
private LGHomBotBindingConstants() {
|
||||
// No need to instance this class.
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.lghombot.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.lghombot.internal.discovery.LGHomBotDiscovery;
|
||||
|
||||
/**
|
||||
* The {@link LGHomBotConfiguration} class contains fields mapping thing configuration parameters.
|
||||
*
|
||||
* @author Fredrik Ahlström - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class LGHomBotConfiguration {
|
||||
|
||||
/**
|
||||
* Constant field used in {@link LGHomBotDiscovery} to set the configuration property during discovery. Value of
|
||||
* this field needs to match {@link #ipAddress}
|
||||
*/
|
||||
public static final String IP_ADDRESS = "ipAddress";
|
||||
|
||||
/**
|
||||
* IP Address (or host name) of HomBot
|
||||
*/
|
||||
public String ipAddress = "";
|
||||
|
||||
/**
|
||||
* Port used by the HomBot
|
||||
*/
|
||||
public int port = LGHomBotBindingConstants.DEFAULT_HOMBOT_PORT;
|
||||
|
||||
/**
|
||||
* Polling time (in seconds) to refresh state from the HomBot itself.
|
||||
*/
|
||||
public int pollingPeriod = 3;
|
||||
}
|
||||
@@ -0,0 +1,677 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.lghombot.internal;
|
||||
|
||||
import static org.openhab.binding.lghombot.internal.LGHomBotBindingConstants.*;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.time.DateTimeException;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.util.UrlEncoded;
|
||||
import org.openhab.core.io.net.http.HttpUtil;
|
||||
import org.openhab.core.library.types.DateTimeType;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.RawType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.thing.binding.BaseThingHandler;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link LGHomBotHandler} is responsible for handling commands, which are
|
||||
* sent to one of the channels.
|
||||
*
|
||||
* @author Fredrik Ahlström - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class LGHomBotHandler extends BaseThingHandler {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(LGHomBotHandler.class);
|
||||
|
||||
// This is setup in initialize().
|
||||
private LGHomBotConfiguration config = new LGHomBotConfiguration();
|
||||
|
||||
private @Nullable ScheduledFuture<?> refreshTimer;
|
||||
|
||||
// State of HomBot
|
||||
private String currentState = "";
|
||||
private String currentMode = "";
|
||||
private String currentNickname = "";
|
||||
private String currentSrvMem = "";
|
||||
private DecimalType currentBattery = DecimalType.ZERO;
|
||||
private DecimalType currentCPULoad = DecimalType.ZERO;
|
||||
private OnOffType currentCleanState = OnOffType.OFF;
|
||||
private OnOffType currentStartState = OnOffType.OFF;
|
||||
private OnOffType currentHomeState = OnOffType.OFF;
|
||||
private OnOffType currentTurbo = OnOffType.OFF;
|
||||
private OnOffType currentRepeat = OnOffType.OFF;
|
||||
private State currentImage = UnDefType.UNDEF;
|
||||
private State currentMap = UnDefType.UNDEF;
|
||||
private DateTimeType currentLastClean = new DateTimeType();
|
||||
private String currentMonday = "";
|
||||
private String currentTuesday = "";
|
||||
private String currentWednesday = "";
|
||||
private String currentThursday = "";
|
||||
private String currentFriday = "";
|
||||
private String currentSaturday = "";
|
||||
private String currentSunday = "";
|
||||
|
||||
private final DateTimeFormatter formatterLG = DateTimeFormatter.ofPattern("yyyy/MM/dd/HH/mm/ss");
|
||||
private boolean disposed = false;
|
||||
private boolean refreshSchedule = false;
|
||||
|
||||
public LGHomBotHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
super.dispose();
|
||||
disposed = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
if (command.equals(RefreshType.REFRESH)) {
|
||||
refreshFromState(channelUID);
|
||||
} else {
|
||||
switch (channelUID.getId()) {
|
||||
case CHANNEL_CLEAN:
|
||||
if (command == OnOffType.ON) {
|
||||
if (currentState.equals(HBSTATE_HOMING)) {
|
||||
sendHomBotCommand("PAUSE");
|
||||
}
|
||||
sendHomBotCommand("CLEAN_START");
|
||||
} else if (command == OnOffType.OFF) {
|
||||
sendHomBotCommand("HOMING");
|
||||
}
|
||||
break;
|
||||
case CHANNEL_START:
|
||||
if (command == OnOffType.ON) {
|
||||
sendHomBotCommand("CLEAN_START");
|
||||
}
|
||||
break;
|
||||
case CHANNEL_HOME:
|
||||
if (command == OnOffType.ON) {
|
||||
sendHomBotCommand("HOMING");
|
||||
}
|
||||
break;
|
||||
case CHANNEL_PAUSE:
|
||||
if (command instanceof OnOffType) {
|
||||
sendHomBotCommand("PAUSE");
|
||||
}
|
||||
break;
|
||||
case CHANNEL_TURBO:
|
||||
if (command == OnOffType.ON) {
|
||||
sendHomBotCommand("TURBO", "true");
|
||||
} else if (command == OnOffType.OFF) {
|
||||
sendHomBotCommand("TURBO", "false");
|
||||
}
|
||||
break;
|
||||
case CHANNEL_REPEAT:
|
||||
if (command == OnOffType.ON) {
|
||||
sendHomBotCommand("REPEAT", "true");
|
||||
} else if (command == OnOffType.OFF) {
|
||||
sendHomBotCommand("REPEAT", "false");
|
||||
}
|
||||
break;
|
||||
case CHANNEL_MODE:
|
||||
if (command instanceof StringType) {
|
||||
switch (command.toString()) {
|
||||
case "SB":
|
||||
sendHomBotCommand("CLEAN_MODE", "CLEAN_SB");
|
||||
break;
|
||||
case "ZZ":
|
||||
sendHomBotCommand("CLEAN_MODE", "CLEAN_ZZ");
|
||||
break;
|
||||
case "SPOT":
|
||||
sendHomBotCommand("CLEAN_MODE", "CLEAN_SPOT");
|
||||
break;
|
||||
case "MACRO_SECTOR":
|
||||
sendHomBotCommand("CLEAN_MODE", "CLEAN_MACRO_SECTOR");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case CHANNEL_MOVE:
|
||||
if (command instanceof StringType) {
|
||||
String commandString = command.toString();
|
||||
switch (commandString) {
|
||||
case "FORWARD":
|
||||
case "FORWARD_LEFT":
|
||||
case "FORWARD_RIGHT":
|
||||
case "LEFT":
|
||||
case "RIGHT":
|
||||
case "BACKWARD":
|
||||
case "BACKWARD_LEFT":
|
||||
case "BACKWARD_RIGHT":
|
||||
case "RELEASE":
|
||||
sendHomBotJoystick(commandString);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
logger.debug("Command received for unknown channel {}: {}", channelUID.getId(), command);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
disposed = false;
|
||||
logger.debug("Initializing handler for LG HomBot");
|
||||
config = getConfigAs(LGHomBotConfiguration.class);
|
||||
|
||||
setupRefreshTimer(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleRemoval() {
|
||||
ScheduledFuture<?> localTimer = refreshTimer;
|
||||
if (localTimer != null) {
|
||||
localTimer.cancel(false);
|
||||
refreshTimer = null;
|
||||
}
|
||||
updateStatus(ThingStatus.REMOVED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up a refresh timer (using the scheduler) with the given interval.
|
||||
*
|
||||
* @param initialWaitTime The delay before the first refresh. Maybe 0 to immediately
|
||||
* initiate a refresh.
|
||||
*/
|
||||
private void setupRefreshTimer(int initialWaitTime) {
|
||||
ScheduledFuture<?> localTimer = refreshTimer;
|
||||
if (localTimer != null) {
|
||||
localTimer.cancel(false);
|
||||
}
|
||||
refreshTimer = scheduler.scheduleWithFixedDelay(this::updateAllChannels, initialWaitTime, config.pollingPeriod,
|
||||
TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
private String buildHttpAddress(String path) {
|
||||
return "http://" + config.ipAddress + ":" + config.port + path;
|
||||
}
|
||||
|
||||
private void sendHomBotCommand(String command) {
|
||||
String fullCmd = "/json.cgi?" + UrlEncoded.encodeString("{\"COMMAND\":\"" + command + "\"}");
|
||||
sendCommand(fullCmd);
|
||||
}
|
||||
|
||||
private void sendHomBotCommand(String command, String argument) {
|
||||
String fullCmd = "/json.cgi?"
|
||||
+ UrlEncoded.encodeString("{\"COMMAND\":{\"" + command + "\":\"" + argument + "\"}}");
|
||||
sendCommand(fullCmd);
|
||||
}
|
||||
|
||||
private void sendHomBotJoystick(String command) {
|
||||
String fullCmd = "/json.cgi?" + UrlEncoded.encodeString("{\"JOY\":\"" + command + "\"}");
|
||||
sendCommand(fullCmd);
|
||||
}
|
||||
|
||||
private @Nullable String sendCommand(String path) {
|
||||
String url = buildHttpAddress(path);
|
||||
logger.trace("Executing: {}", url);
|
||||
String status = null;
|
||||
try {
|
||||
status = HttpUtil.executeUrl("GET", url, 1000);
|
||||
if (getThing().getStatus() != ThingStatus.ONLINE) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
|
||||
}
|
||||
logger.trace("Status received: {}", status);
|
||||
return status;
|
||||
}
|
||||
|
||||
private void refreshFromState(ChannelUID channelUID) {
|
||||
switch (channelUID.getId()) {
|
||||
case CHANNEL_STATE:
|
||||
updateState(channelUID, StringType.valueOf(currentState));
|
||||
break;
|
||||
case CHANNEL_CLEAN:
|
||||
updateState(channelUID, currentCleanState);
|
||||
break;
|
||||
case CHANNEL_START:
|
||||
updateState(channelUID, currentStartState);
|
||||
break;
|
||||
case CHANNEL_HOME:
|
||||
updateState(channelUID, currentHomeState);
|
||||
break;
|
||||
case CHANNEL_BATTERY:
|
||||
updateState(channelUID, currentBattery);
|
||||
break;
|
||||
case CHANNEL_CPU_LOAD:
|
||||
updateState(channelUID, currentCPULoad);
|
||||
break;
|
||||
case CHANNEL_SRV_MEM:
|
||||
updateState(channelUID, StringType.valueOf(currentSrvMem));
|
||||
break;
|
||||
case CHANNEL_TURBO:
|
||||
updateState(channelUID, currentTurbo);
|
||||
break;
|
||||
case CHANNEL_REPEAT:
|
||||
updateState(channelUID, currentRepeat);
|
||||
break;
|
||||
case CHANNEL_MODE:
|
||||
updateState(channelUID, StringType.valueOf(currentMode));
|
||||
break;
|
||||
case CHANNEL_NICKNAME:
|
||||
updateState(channelUID, StringType.valueOf(currentNickname));
|
||||
break;
|
||||
case CHANNEL_CAMERA:
|
||||
parseImage();
|
||||
updateState(channelUID, currentImage);
|
||||
break;
|
||||
case CHANNEL_LAST_CLEAN:
|
||||
updateState(channelUID, currentLastClean);
|
||||
break;
|
||||
case CHANNEL_MAP:
|
||||
parseMap();
|
||||
updateState(channelUID, currentMap);
|
||||
break;
|
||||
case CHANNEL_MONDAY:
|
||||
updateState(channelUID, StringType.valueOf(currentMonday));
|
||||
refreshSchedule = true;
|
||||
break;
|
||||
case CHANNEL_TUESDAY:
|
||||
updateState(channelUID, StringType.valueOf(currentTuesday));
|
||||
refreshSchedule = true;
|
||||
break;
|
||||
case CHANNEL_WEDNESDAY:
|
||||
updateState(channelUID, StringType.valueOf(currentWednesday));
|
||||
refreshSchedule = true;
|
||||
break;
|
||||
case CHANNEL_THURSDAY:
|
||||
updateState(channelUID, StringType.valueOf(currentThursday));
|
||||
refreshSchedule = true;
|
||||
break;
|
||||
case CHANNEL_FRIDAY:
|
||||
updateState(channelUID, StringType.valueOf(currentFriday));
|
||||
refreshSchedule = true;
|
||||
break;
|
||||
case CHANNEL_SATURDAY:
|
||||
updateState(channelUID, StringType.valueOf(currentSaturday));
|
||||
refreshSchedule = true;
|
||||
break;
|
||||
case CHANNEL_SUNDAY:
|
||||
updateState(channelUID, StringType.valueOf(currentSunday));
|
||||
refreshSchedule = true;
|
||||
break;
|
||||
default:
|
||||
logger.warn("Channel refresh for {} not implemented!", channelUID.getId());
|
||||
}
|
||||
}
|
||||
|
||||
private void updateAllChannels() {
|
||||
if (disposed) {
|
||||
return;
|
||||
}
|
||||
if (refreshSchedule) {
|
||||
refreshSchedule = false;
|
||||
fetchSchedule();
|
||||
return;
|
||||
}
|
||||
|
||||
String status = sendCommand("/status.txt");
|
||||
if (status != null && !status.isEmpty()) {
|
||||
boolean parsingOk = true;
|
||||
String[] rows = status.split("\\r?\\n");
|
||||
for (String row : rows) {
|
||||
int idx = row.indexOf('=');
|
||||
if (idx == -1) {
|
||||
continue;
|
||||
}
|
||||
final String key = row.substring(0, idx);
|
||||
String value = row.substring(idx + 1).replace("\"", "");
|
||||
switch (key) {
|
||||
case "JSON_ROBOT_STATE":
|
||||
if (value.isEmpty()) {
|
||||
value = HBSTATE_UNKNOWN;
|
||||
}
|
||||
if (!value.equals(currentState)) {
|
||||
currentState = value;
|
||||
updateState(CHANNEL_STATE, StringType.valueOf(value));
|
||||
|
||||
switch (value) {
|
||||
case HBSTATE_WORKING:
|
||||
case HBSTATE_BACKMOVING:
|
||||
case HBSTATE_BACKMOVING_INIT:
|
||||
currentCleanState = OnOffType.ON;
|
||||
currentStartState = OnOffType.ON;
|
||||
currentHomeState = OnOffType.OFF;
|
||||
break;
|
||||
case HBSTATE_HOMING:
|
||||
case HBSTATE_DOCKING:
|
||||
currentCleanState = OnOffType.OFF;
|
||||
currentStartState = OnOffType.OFF;
|
||||
currentHomeState = OnOffType.ON;
|
||||
break;
|
||||
default:
|
||||
currentCleanState = OnOffType.OFF;
|
||||
currentStartState = OnOffType.OFF;
|
||||
currentHomeState = OnOffType.OFF;
|
||||
break;
|
||||
}
|
||||
updateState(CHANNEL_CLEAN, currentCleanState);
|
||||
updateState(CHANNEL_START, currentStartState);
|
||||
updateState(CHANNEL_HOME, currentHomeState);
|
||||
}
|
||||
break;
|
||||
case "JSON_BATTPERC":
|
||||
try {
|
||||
DecimalType battery = DecimalType.valueOf(value);
|
||||
if (!battery.equals(currentBattery)) {
|
||||
currentBattery = battery;
|
||||
updateState(CHANNEL_BATTERY, battery);
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
logger.debug("Couldn't parse Battery Percent.");
|
||||
parsingOk = false;
|
||||
}
|
||||
break;
|
||||
case "CPU_IDLE":
|
||||
if (isLinked(CHANNEL_CPU_LOAD)) {
|
||||
try {
|
||||
DecimalType cpuLoad = new DecimalType(100 - Double.valueOf(value).longValue());
|
||||
if (!cpuLoad.equals(currentCPULoad)) {
|
||||
currentCPULoad = cpuLoad;
|
||||
updateState(CHANNEL_CPU_LOAD, cpuLoad);
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
logger.debug("Couldn't parse CPU Idle.");
|
||||
parsingOk = false;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "LGSRV_MEMUSAGE":
|
||||
if (!value.equals(currentSrvMem)) {
|
||||
currentSrvMem = value;
|
||||
updateState(CHANNEL_SRV_MEM, StringType.valueOf(value));
|
||||
}
|
||||
break;
|
||||
case "JSON_TURBO":
|
||||
OnOffType turbo = OnOffType.from("true".equalsIgnoreCase(value));
|
||||
if (!turbo.equals(currentTurbo)) {
|
||||
currentTurbo = turbo;
|
||||
updateState(CHANNEL_TURBO, turbo);
|
||||
}
|
||||
break;
|
||||
case "JSON_REPEAT":
|
||||
OnOffType repeat = OnOffType.from("true".equalsIgnoreCase(value));
|
||||
if (!repeat.equals(currentRepeat)) {
|
||||
currentRepeat = repeat;
|
||||
updateState(CHANNEL_REPEAT, repeat);
|
||||
}
|
||||
break;
|
||||
case "JSON_MODE":
|
||||
if (!value.equals(currentMode)) {
|
||||
currentMode = value;
|
||||
updateState(CHANNEL_MODE, StringType.valueOf(value));
|
||||
}
|
||||
break;
|
||||
case "JSON_NICKNAME":
|
||||
if (!value.equals(currentNickname)) {
|
||||
currentNickname = value;
|
||||
updateState(CHANNEL_NICKNAME, StringType.valueOf(value));
|
||||
}
|
||||
break;
|
||||
case "CLREC_LAST_CLEAN":
|
||||
if (value.length() < 19) {
|
||||
logger.debug("Couldn't parse Last Clean from: String length: {}", value.length());
|
||||
parsingOk = false;
|
||||
break;
|
||||
}
|
||||
final String stringDate = value.substring(0, 19);
|
||||
try {
|
||||
LocalDateTime localDateTime = LocalDateTime.parse(stringDate, formatterLG);
|
||||
ZonedDateTime date = ZonedDateTime.of(localDateTime, ZoneId.systemDefault());
|
||||
DateTimeType lastClean = new DateTimeType(date);
|
||||
if (!lastClean.equals(currentLastClean)) {
|
||||
currentLastClean = lastClean;
|
||||
updateState(CHANNEL_LAST_CLEAN, lastClean);
|
||||
}
|
||||
} catch (DateTimeException e) {
|
||||
logger.debug("Couldn't parse Last Clean from: {}", stringDate);
|
||||
parsingOk = false;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!parsingOk) {
|
||||
logger.debug("Couldn't parse status response;\n {}", status);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void fetchSchedule() {
|
||||
String status = sendCommand("/.../usr/data/htdocs/timer.txt");
|
||||
|
||||
if (status != null && !status.isEmpty()) {
|
||||
String monday = "";
|
||||
String tuesday = "";
|
||||
String wednesday = "";
|
||||
String thursday = "";
|
||||
String friday = "";
|
||||
String saturday = "";
|
||||
String sunday = "";
|
||||
String[] rows = status.split("\\r?\\n");
|
||||
for (String row : rows) {
|
||||
int idx = row.indexOf('=');
|
||||
String name = row.substring(0, idx);
|
||||
String state = row.substring(idx + 1);
|
||||
switch (name) {
|
||||
case "MONDAY":
|
||||
monday = state;
|
||||
break;
|
||||
case "TUESDAY":
|
||||
tuesday = state;
|
||||
break;
|
||||
case "WEDNESDAY":
|
||||
wednesday = state;
|
||||
break;
|
||||
case "THURSDAY":
|
||||
thursday = state;
|
||||
break;
|
||||
case "FRIDAY":
|
||||
friday = state;
|
||||
break;
|
||||
case "SATURDAY":
|
||||
saturday = state;
|
||||
break;
|
||||
case "SUNDAY":
|
||||
sunday = state;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
if (!currentMonday.equals(monday)) {
|
||||
currentMonday = monday;
|
||||
updateState(CHANNEL_MONDAY, StringType.valueOf(monday));
|
||||
}
|
||||
if (!currentTuesday.equals(tuesday)) {
|
||||
currentTuesday = tuesday;
|
||||
updateState(CHANNEL_TUESDAY, StringType.valueOf(tuesday));
|
||||
}
|
||||
if (!currentWednesday.equals(wednesday)) {
|
||||
currentWednesday = wednesday;
|
||||
updateState(CHANNEL_WEDNESDAY, StringType.valueOf(wednesday));
|
||||
}
|
||||
if (!currentThursday.equals(thursday)) {
|
||||
currentThursday = thursday;
|
||||
updateState(CHANNEL_THURSDAY, StringType.valueOf(thursday));
|
||||
}
|
||||
if (!currentFriday.equals(friday)) {
|
||||
currentFriday = friday;
|
||||
updateState(CHANNEL_FRIDAY, StringType.valueOf(friday));
|
||||
}
|
||||
if (!currentSaturday.equals(saturday)) {
|
||||
currentSaturday = saturday;
|
||||
updateState(CHANNEL_SATURDAY, StringType.valueOf(saturday));
|
||||
}
|
||||
if (!currentSunday.equals(sunday)) {
|
||||
currentSunday = sunday;
|
||||
updateState(CHANNEL_SUNDAY, StringType.valueOf(sunday));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void parseImage() {
|
||||
if (!isLinked(CHANNEL_CAMERA)) {
|
||||
return;
|
||||
}
|
||||
final int width = 320;
|
||||
final int height = 240;
|
||||
final int size = width * height;
|
||||
String url = buildHttpAddress("/images/snapshot.yuv");
|
||||
RawType rawData = HttpUtil.downloadData(url, null, false, size * 2);
|
||||
if (rawData != null) {
|
||||
byte[] yuvData = rawData.getBytes();
|
||||
currentImage = CameraUtil.parseImageFromBytes(yuvData, width, height);
|
||||
} else {
|
||||
logger.info("No camera image returned from HomBot.");
|
||||
}
|
||||
}
|
||||
|
||||
/** Parse the maps.html file to find the black-box filename. */
|
||||
private String findBlackBoxFile() {
|
||||
String url = buildHttpAddress("/sites/maps.html");
|
||||
try {
|
||||
String htmlString = HttpUtil.executeUrl("GET", url, 1000);
|
||||
int idx = htmlString.indexOf("blkfiles");
|
||||
return "/.../usr/data/blackbox/" + htmlString.substring(idx + 13, idx + 50);
|
||||
} catch (IOException e1) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e1.getMessage());
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
private void parseMap() {
|
||||
if (!isLinked(CHANNEL_MAP)) {
|
||||
return;
|
||||
}
|
||||
final int tileSize = 10;
|
||||
final int tileArea = tileSize * tileSize;
|
||||
final int rowLength = 100;
|
||||
final int scale = 1;
|
||||
|
||||
String blackBox = findBlackBoxFile();
|
||||
String url = buildHttpAddress(blackBox);
|
||||
RawType dlData = HttpUtil.downloadData(url, null, false, -1);
|
||||
if (dlData == null) {
|
||||
return;
|
||||
}
|
||||
byte[] mapData = dlData.getBytes();
|
||||
|
||||
final int tileCount = mapData[32];
|
||||
int maxX = 0;
|
||||
int maxY = 0;
|
||||
int minX = 0x10000;
|
||||
int minY = 0x10000;
|
||||
int pixPos;
|
||||
|
||||
for (int i = 0; i < tileCount; i++) {
|
||||
pixPos = (mapData[52 + i * 16] & 0xFF) + (mapData[52 + 1 + i * 16] << 8);
|
||||
int xPos = (pixPos % rowLength) * tileSize;
|
||||
int yPos = (pixPos / rowLength) * tileSize;
|
||||
if (xPos < minX) {
|
||||
minX = xPos;
|
||||
}
|
||||
if (xPos > maxX) {
|
||||
maxX = xPos;
|
||||
}
|
||||
if (yPos > maxY) {
|
||||
maxY = yPos;
|
||||
}
|
||||
if (yPos < minY) {
|
||||
minY = yPos;
|
||||
}
|
||||
}
|
||||
|
||||
final int width = (tileSize + maxX - minX) * scale;
|
||||
final int height = (tileSize + maxY - minY) * scale;
|
||||
|
||||
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
|
||||
for (int i = 0; i < height; i++) {
|
||||
for (int j = 0; j < width; j++) {
|
||||
image.setRGB(j, i, 0xFFFFFF);
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < tileCount; i++) {
|
||||
pixPos = (mapData[52 + i * 16] & 0xFF) + (mapData[52 + 1 + i * 16] << 8);
|
||||
int xPos = ((pixPos % rowLength) * tileSize - minX) * scale;
|
||||
int yPos = (maxY - (pixPos / rowLength) * tileSize) * scale;
|
||||
int indexTab = 16044 + i * tileArea;
|
||||
for (int j = 0; j < tileSize; j++) {
|
||||
for (int k = 0; k < tileSize; k++) {
|
||||
int p = 0xFFFFFF;
|
||||
if ((mapData[indexTab] & 0xF0) != 0) {
|
||||
p = 0xFF0000;
|
||||
} else if (mapData[indexTab] != 0) {
|
||||
p = 0xBFBFBF;
|
||||
}
|
||||
image.setRGB(xPos + k * scale, yPos + (9 - j) * scale, p);
|
||||
indexTab++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
try {
|
||||
if (!ImageIO.write(image, "png", baos)) {
|
||||
logger.debug("Couldn't find PNG writer.");
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.info("IOException creating PNG image.", e);
|
||||
}
|
||||
byte[] byteArray = baos.toByteArray();
|
||||
if (byteArray != null && byteArray.length > 0) {
|
||||
currentMap = new RawType(byteArray, "image/png");
|
||||
} else {
|
||||
currentMap = UnDefType.UNDEF;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.lghombot.internal;
|
||||
|
||||
import static org.openhab.binding.lghombot.internal.LGHomBotBindingConstants.THING_TYPE_LGHOMBOT;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerFactory;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
|
||||
/**
|
||||
* The {@link LGHomBotHandlerFactory} is responsible for creating things and thing
|
||||
* handlers.
|
||||
*
|
||||
* @author Fredrik Ahlström - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(configurationPid = "binding.lghombot", service = ThingHandlerFactory.class)
|
||||
public class LGHomBotHandlerFactory extends BaseThingHandlerFactory {
|
||||
|
||||
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections.singleton(THING_TYPE_LGHOMBOT);
|
||||
|
||||
@Override
|
||||
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
|
||||
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable ThingHandler createHandler(Thing thing) {
|
||||
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
|
||||
|
||||
if (THING_TYPE_LGHOMBOT.equals(thingTypeUID)) {
|
||||
return new LGHomBotHandler(thing);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,288 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.lghombot.internal.discovery;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.Inet4Address;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.lghombot.internal.LGHomBotBindingConstants;
|
||||
import org.openhab.binding.lghombot.internal.LGHomBotConfiguration;
|
||||
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.io.net.http.HttpUtil;
|
||||
import org.openhab.core.net.CidrAddress;
|
||||
import org.openhab.core.net.NetUtil;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Discovery class for the LG HomBot line. Right now we try to do http requests to all IPs on port 6260.
|
||||
* If we get a connection and correct answer we set the IP as result.
|
||||
*
|
||||
* @author Fredrik Ahlström - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(service = { DiscoveryService.class, LGHomBotDiscovery.class }, configurationPid = "discovery.lghombot")
|
||||
public class LGHomBotDiscovery extends AbstractDiscoveryService {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(LGHomBotDiscovery.class);
|
||||
|
||||
/**
|
||||
* HTTP read timeout (in milliseconds) - allows us to shutdown the listening every TIMEOUT
|
||||
*/
|
||||
private static final int TIMEOUT_MS = 500;
|
||||
|
||||
/**
|
||||
* Timeout in seconds of the complete scan
|
||||
*/
|
||||
private static final int FULL_SCAN_TIMEOUT_SECONDS = 30;
|
||||
|
||||
/**
|
||||
* Total number of concurrent threads during scanning.
|
||||
*/
|
||||
private static final int SCAN_THREADS = 10;
|
||||
|
||||
/**
|
||||
* Whether we are currently scanning or not
|
||||
*/
|
||||
private boolean scanning;
|
||||
|
||||
private int octet;
|
||||
private int ipMask;
|
||||
private int addressCount;
|
||||
private @Nullable CidrAddress baseIp;
|
||||
|
||||
/**
|
||||
* The {@link ExecutorService} to run the listening threads on.
|
||||
*/
|
||||
private @Nullable ExecutorService executorService;
|
||||
|
||||
/**
|
||||
* Constructs the discovery class using the thing IDs that we can discover.
|
||||
*/
|
||||
public LGHomBotDiscovery() {
|
||||
super(LGHomBotBindingConstants.SUPPORTED_THING_TYPES_UIDS, FULL_SCAN_TIMEOUT_SECONDS, false);
|
||||
}
|
||||
|
||||
private void setupBaseIp(CidrAddress adr) {
|
||||
byte[] octets = adr.getAddress().getAddress();
|
||||
addressCount = (1 << (32 - adr.getPrefix())) - 2;
|
||||
ipMask = 0xFFFFFFFF << (32 - adr.getPrefix());
|
||||
octets[0] &= ipMask >> 24;
|
||||
octets[1] &= ipMask >> 16;
|
||||
octets[2] &= ipMask >> 8;
|
||||
octets[3] &= ipMask;
|
||||
try {
|
||||
InetAddress iAdr = InetAddress.getByAddress(octets);
|
||||
baseIp = new CidrAddress(iAdr, (short) adr.getPrefix());
|
||||
} catch (UnknownHostException e) {
|
||||
logger.debug("Could not build net ip address.", e);
|
||||
}
|
||||
octet = 0;
|
||||
}
|
||||
|
||||
private synchronized String getNextIPAddress(CidrAddress adr) {
|
||||
octet++;
|
||||
octet &= ~ipMask;
|
||||
byte[] octets = adr.getAddress().getAddress();
|
||||
octets[2] += (octet >> 8);
|
||||
octets[3] += octet;
|
||||
String address = "";
|
||||
try {
|
||||
InetAddress iAdr = null;
|
||||
iAdr = InetAddress.getByAddress(octets);
|
||||
address = iAdr.getHostAddress();
|
||||
} catch (UnknownHostException e) {
|
||||
logger.debug("Could not find next ip address.", e);
|
||||
}
|
||||
return address;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* Starts the scan. This discovery will:
|
||||
* <ul>
|
||||
* <li>Request this hosts first IPV4 address.</li>
|
||||
* <li>Send a HTTP request on port 6260 to all IPs on the subnet.</li>
|
||||
* <li>The response is then investigated to see if is an answer from a HomBot lg.srv</li>
|
||||
* </ul>
|
||||
* The process will continue until all addresses are checked, timeout or {@link #stopScan()} is called.
|
||||
*/
|
||||
@Override
|
||||
protected void startScan() {
|
||||
if (executorService != null) {
|
||||
stopScan();
|
||||
}
|
||||
|
||||
CidrAddress localAdr = getLocalIP4Address();
|
||||
if (localAdr == null) {
|
||||
stopScan();
|
||||
return;
|
||||
}
|
||||
setupBaseIp(localAdr);
|
||||
CidrAddress baseAdr = baseIp;
|
||||
scanning = true;
|
||||
ExecutorService localExecutorService = Executors.newFixedThreadPool(SCAN_THREADS);
|
||||
executorService = localExecutorService;
|
||||
for (int i = 0; i < addressCount; i++) {
|
||||
|
||||
localExecutorService.execute(() -> {
|
||||
if (scanning && baseAdr != null) {
|
||||
String ipAdd = getNextIPAddress(baseAdr);
|
||||
String url = "http://" + ipAdd + ":" + LGHomBotBindingConstants.DEFAULT_HOMBOT_PORT + "/status.txt";
|
||||
|
||||
try {
|
||||
String message = HttpUtil.executeUrl("GET", url, TIMEOUT_MS);
|
||||
if (message != null && !message.isEmpty()) {
|
||||
messageReceive(message, ipAdd);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// Ignore, this is the expected behavior.
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to find valid IP4 address.
|
||||
*
|
||||
* @return An IP4 address or null if none is found.
|
||||
*/
|
||||
private @Nullable CidrAddress getLocalIP4Address() {
|
||||
List<CidrAddress> adrList = NetUtil.getAllInterfaceAddresses().stream()
|
||||
.filter(a -> a.getAddress() instanceof Inet4Address).collect(Collectors.toList());
|
||||
|
||||
for (CidrAddress adr : adrList) {
|
||||
// Don't return a "fake" DHCP lease.
|
||||
if (!adr.toString().startsWith("169.254.")) {
|
||||
return adr;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* lgsrv message has the following format
|
||||
*
|
||||
* <pre>
|
||||
* JSON_ROBOT_STATE="CHARGING"
|
||||
* JSON_BATTPERC="100"
|
||||
* LGSRV_VERSION="lg.srv, V2.51 compiled 18.11.2016, by fx2"
|
||||
* LGSRV_SUMCMD="0"
|
||||
* LGSRV_SUMCMDSEC="0.000000"
|
||||
* LGSRV_NUMHTTP="929"
|
||||
* LGSRV_MEMUSAGE="0.387 MB"
|
||||
* CPU_IDLE="67.92"
|
||||
* CPU_USER="19.49"
|
||||
* CPU_SYS="12.57"
|
||||
* CPU_NICE="0.00"
|
||||
* JSON_TURBO="false"
|
||||
* JSON_REPEAT="false"
|
||||
* JSON_MODE="ZZ"
|
||||
* JSON_VERSION="16552"
|
||||
* JSON_NICKNAME="HOMBOT"
|
||||
* CLREC_CURRENTBUMPING="29441"
|
||||
* CLREC_LAST_CLEAN="2018/08/30/11/00/00.826531"
|
||||
* </pre>
|
||||
*
|
||||
* First parse the first string to see that it's a HomBot, then parse nickname, server version & firmware version.
|
||||
* We then create our thing from it.
|
||||
*
|
||||
* @param message a response from a lgsrv to be parsed
|
||||
* @param ipAddress current probed ip address
|
||||
*/
|
||||
private void messageReceive(String message, String ipAddress) {
|
||||
if (!message.startsWith("JSON_ROBOT_STATE=")) {
|
||||
return;
|
||||
}
|
||||
|
||||
String model = "HomBot";
|
||||
String nickName = "";
|
||||
String srvVersion = "0";
|
||||
String fwVersion = "0";
|
||||
|
||||
for (String msg : message.split("\\r?\\n")) {
|
||||
int idx = msg.indexOf('=');
|
||||
if (idx > 0) {
|
||||
String name = msg.substring(0, idx);
|
||||
|
||||
if (name.equalsIgnoreCase("JSON_NICKNAME")) {
|
||||
nickName = msg.substring(idx + 1).trim().replaceAll("\"", "");
|
||||
} else if (name.equalsIgnoreCase("JSON_VERSION")) {
|
||||
fwVersion = msg.substring(idx + 1).trim().replaceAll("\"", "");
|
||||
} else if (name.equalsIgnoreCase("LGSRV_VERSION")) {
|
||||
srvVersion = msg.substring(idx + 1).trim().replaceAll("\"", "");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (!ipAddress.isEmpty()) {
|
||||
if (nickName.isEmpty()) {
|
||||
nickName = "HOMBOT1";
|
||||
}
|
||||
ThingTypeUID typeId = LGHomBotBindingConstants.THING_TYPE_LGHOMBOT;
|
||||
ThingUID uid = new ThingUID(typeId, nickName);
|
||||
|
||||
Map<String, Object> properties = new HashMap<>(3);
|
||||
properties.put(LGHomBotConfiguration.IP_ADDRESS, ipAddress);
|
||||
properties.put(Thing.PROPERTY_FIRMWARE_VERSION, fwVersion);
|
||||
properties.put("server", srvVersion);
|
||||
DiscoveryResult result = DiscoveryResultBuilder.create(uid).withProperties(properties)
|
||||
.withLabel(model + " (" + nickName + ")").build();
|
||||
thingDiscovered(result);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* Stops the discovery scan. We set {@link #scanning} to false (allowing the listening threads to end naturally
|
||||
* within {@link #TIMEOUT_MS) * {@link #SCAN_THREADS} time then shutdown the {@link #executorService}
|
||||
*/
|
||||
@Override
|
||||
protected synchronized void stopScan() {
|
||||
super.stopScan();
|
||||
ExecutorService localExecutorService = executorService;
|
||||
if (localExecutorService != null) {
|
||||
scanning = false;
|
||||
try {
|
||||
localExecutorService.awaitTermination(TIMEOUT_MS * SCAN_THREADS, TimeUnit.MILLISECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
logger.debug("Stop scan interrupted.", e);
|
||||
}
|
||||
localExecutorService.shutdown();
|
||||
executorService = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<binding:binding id="lghombot" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
|
||||
|
||||
<name>LG HomBot Binding</name>
|
||||
<description>The LG HomBot binding allows control and supervision of your HomBot.</description>
|
||||
<author>Fredrik Ahlström</author>
|
||||
|
||||
</binding:binding>
|
||||
@@ -0,0 +1,85 @@
|
||||
# binding
|
||||
binding.lghombot.name = LG HomBot Binding
|
||||
binding.lghombot.description = The LG HomBot binding allows control and supervision of your HomBot.
|
||||
|
||||
# thing types
|
||||
thing-type.lghombot.LGHomBot.label = LG HomBot
|
||||
thing-type.lghombot.LGHomBot.description = HomBot vacuum robot
|
||||
|
||||
# thing type configuration
|
||||
thing-type.config.lghombot.LGHomBot.ipAddress.label = Network Address
|
||||
thing-type.config.lghombot.LGHomBot.ipAddress.description = The IP or host name of the HomBot.
|
||||
thing-type.config.lghombot.LGHomBot.port.label = Port
|
||||
thing-type.config.lghombot.LGHomBot.port.description = Port of the HomBot to control.
|
||||
thing-type.config.lghombot.LGHomBot.pollingPeriod.label = Polling Period
|
||||
thing-type.config.lghombot.LGHomBot.pollingPeriod.description = Time between polls in seconds.
|
||||
|
||||
# channel types
|
||||
channel-type.lghombot.stateType.label = State
|
||||
channel-type.lghombot.stateType.description = Current state.
|
||||
channel-type.lghombot.stateType.state.option.UNKNOWN = Unknown
|
||||
channel-type.lghombot.stateType.state.option.WORKING = Cleaning
|
||||
channel-type.lghombot.stateType.state.option.BACKMOVING = Backmoving
|
||||
channel-type.lghombot.stateType.state.option.BACKMOVING_INIT = Backmoving init
|
||||
channel-type.lghombot.stateType.state.option.BACKMOVING_JOY = Backmoving joy
|
||||
channel-type.lghombot.stateType.state.option.PAUSE = Paused
|
||||
channel-type.lghombot.stateType.state.option.STANDBY = Standby
|
||||
channel-type.lghombot.stateType.state.option.HOMING = Homing
|
||||
channel-type.lghombot.stateType.state.option.DOCKING = Docking
|
||||
channel-type.lghombot.stateType.state.option.CHARGING = Charging
|
||||
channel-type.lghombot.stateType.state.option.DIAGNOSIS = Running diagnosis
|
||||
channel-type.lghombot.stateType.state.option.RESERVATION = Changing settings
|
||||
channel-type.lghombot.stateType.state.option.ERROR = Error
|
||||
|
||||
channel-type.lghombot.batteryType.label = Battery
|
||||
channel-type.lghombot.batteryType.description = Current battery charge
|
||||
|
||||
channel-type.lghombot.cpuLoadType.label = CPU Load
|
||||
channel-type.lghombot.cpuLoadType.description = Current CPU load
|
||||
channel-type.lghombot.srvMemType.label = Used Memory
|
||||
channel-type.lghombot.srvMemType.description = Memory used by webserver on HomBot.
|
||||
channel-type.lghombot.cleanType.label = Clean
|
||||
channel-type.lghombot.cleanType.description = Start cleaning / return home.
|
||||
channel-type.lghombot.startType.label = Start
|
||||
channel-type.lghombot.startType.description = Start cleaning.
|
||||
channel-type.lghombot.homeType.label = Home
|
||||
channel-type.lghombot.homeType.description = Return home.
|
||||
channel-type.lghombot.stopType.label = Pause
|
||||
channel-type.lghombot.stopType.description = Pause the HomBot.
|
||||
channel-type.lghombot.turboType.label = Turbo
|
||||
channel-type.lghombot.turboType.description = Turbo mode ON, OFF.
|
||||
channel-type.lghombot.repeatType.label = Repeat
|
||||
channel-type.lghombot.repeatType.description = Repeat cleaning ON, OFF.
|
||||
|
||||
channel-type.lghombot.modeType.label = Mode
|
||||
channel-type.lghombot.modeType.description = Cleaning mode.
|
||||
channel-type.lghombot.modeType.state.option.ZZ = Zigzag mode
|
||||
channel-type.lghombot.modeType.state.option.SB = Cell by cell mode
|
||||
channel-type.lghombot.modeType.state.option.SPOT = Spiral spot mode
|
||||
channel-type.lghombot.modeType.state.option.MACRO_SECTOR = My space mode
|
||||
|
||||
channel-type.lghombot.nicknameType.label = Nickname
|
||||
channel-type.lghombot.nicknameType.description = Nickname of the HomBot.
|
||||
channel-type.lghombot.moveType.label = Move
|
||||
channel-type.lghombot.moveType.description = Move direction.
|
||||
channel-type.lghombot.cameraType.label = Camera
|
||||
channel-type.lghombot.cameraType.description = Image feed from the top camera.
|
||||
channel-type.lghombot.lastCleanType.label = Last Clean
|
||||
channel-type.lghombot.lastCleanType.description = Last time the HomBot cleaned.
|
||||
channel-type.lghombot.mapType.label = Map
|
||||
channel-type.lghombot.mapType.description = Map of last cleaned area.
|
||||
|
||||
channel-type.lghombot.mondayType.label = Monday
|
||||
channel-type.lghombot.mondayType.description = Scheduled start time on Monday.
|
||||
channel-type.lghombot.tuesdayType.label = Tuesday
|
||||
channel-type.lghombot.tuesdayType.description = Scheduled start time on Tuesday.
|
||||
channel-type.lghombot.wednesdayType.label = Wednesday
|
||||
channel-type.lghombot.wednesdayType.description = Scheduled start time on Wednesday.
|
||||
channel-type.lghombot.thursdayType.label = Thursday
|
||||
channel-type.lghombot.thursdayType.description = Scheduled start time on Thursday.
|
||||
channel-type.lghombot.fridayType.label = Friday
|
||||
channel-type.lghombot.fridayType.description = Scheduled start time on Friday.
|
||||
channel-type.lghombot.saturdayType.label = Saturday
|
||||
channel-type.lghombot.saturdayType.description = Scheduled start time on Saturday.
|
||||
channel-type.lghombot.sundayType.label = Sunday
|
||||
channel-type.lghombot.sundayType.description = Scheduled start time on Sunday.
|
||||
@@ -0,0 +1,86 @@
|
||||
|
||||
# binding
|
||||
binding.lghombot.name = LG HomBot Binding
|
||||
binding.lghombot.description = LG HomBot binding tillåter kontrol och övervakning av din HomBot.
|
||||
|
||||
# thing types
|
||||
thing-type.lghombot.LGHomBot.label = LG HomBot
|
||||
thing-type.lghombot.LGHomBot.description = HomBot Robotdamsugare
|
||||
|
||||
# thing type configuration
|
||||
thing-type.config.lghombot.LGHomBot.ipAddress.label = Nätverksdress
|
||||
thing-type.config.lghombot.LGHomBot.ipAddress.description = IP-adress eller namn på HomBot.
|
||||
thing-type.config.lghombot.LGHomBot.port.label = Port
|
||||
thing-type.config.lghombot.LGHomBot.port.description = Nätverksport på HomBot.
|
||||
thing-type.config.lghombot.LGHomBot.pollingPeriod.label = Pollningsperiod
|
||||
thing-type.config.lghombot.LGHomBot.pollingPeriod.description = Tid mellan pollningar i sekunder.
|
||||
|
||||
# channel types
|
||||
channel-type.lghombot.stateType.label = Status
|
||||
channel-type.lghombot.stateType.description = Nuvarande status.
|
||||
channel-type.lghombot.stateType.state.option.UNKNOWN = Okänd
|
||||
channel-type.lghombot.stateType.state.option.WORKING = Städar
|
||||
channel-type.lghombot.stateType.state.option.BACKMOVING = Backar
|
||||
channel-type.lghombot.stateType.state.option.BACKMOVING_INIT = Startar backning
|
||||
channel-type.lghombot.stateType.state.option.BACKMOVING_JOY = Backar ur
|
||||
channel-type.lghombot.stateType.state.option.PAUSE = Pausad
|
||||
channel-type.lghombot.stateType.state.option.STANDBY = Väntar
|
||||
channel-type.lghombot.stateType.state.option.HOMING = Åker hemåt
|
||||
channel-type.lghombot.stateType.state.option.DOCKING = Dockar
|
||||
channel-type.lghombot.stateType.state.option.CHARGING = Laddar
|
||||
channel-type.lghombot.stateType.state.option.DIAGNOSIS = Kör diagnos
|
||||
channel-type.lghombot.stateType.state.option.RESERVATION = Gör inställning
|
||||
channel-type.lghombot.stateType.state.option.ERROR = Fel
|
||||
|
||||
channel-type.lghombot.batteryType.label = Batteri
|
||||
channel-type.lghombot.batteryType.description = Nuvarande batteriladdning.
|
||||
|
||||
channel-type.lghombot.cpuLoadType.label = CPU belastning
|
||||
channel-type.lghombot.cpuLoadType.description = Nuvarande CPU belastning.
|
||||
channel-type.lghombot.srvMemType.label = Använt minne
|
||||
channel-type.lghombot.srvMemType.description = Minne använt av webserver på HomBot.
|
||||
channel-type.lghombot.cleanType.label = Städa
|
||||
channel-type.lghombot.cleanType.description = Börja städa / återvänd hem.
|
||||
channel-type.lghombot.startType.label = Starta
|
||||
channel-type.lghombot.startType.description = Börja städa.
|
||||
channel-type.lghombot.homeType.label = Hemåt
|
||||
channel-type.lghombot.homeType.description = Återvänd hem.
|
||||
channel-type.lghombot.stopType.label = Paus
|
||||
channel-type.lghombot.stopType.description = Pausa HomBot.
|
||||
channel-type.lghombot.turboType.label = Turbo
|
||||
channel-type.lghombot.turboType.description = Turbo läge AV, PÅ.
|
||||
channel-type.lghombot.repeatType.label = Återupprepa
|
||||
channel-type.lghombot.repeatType.description = Återupprepande städning AV, PÅ.
|
||||
|
||||
channel-type.lghombot.modeType.label = Städläge
|
||||
channel-type.lghombot.modeType.description = Städläge.
|
||||
channel-type.lghombot.modeType.state.option.ZZ = Sicksack läge
|
||||
channel-type.lghombot.modeType.state.option.SB = Cell vid cell läge
|
||||
channel-type.lghombot.modeType.state.option.SPOT = Punktspiral läge
|
||||
channel-type.lghombot.modeType.state.option.MACRO_SECTOR = Mitt utrymme läge
|
||||
|
||||
channel-type.lghombot.nicknameType.label = Smeknamn
|
||||
channel-type.lghombot.nicknameType.description = Smeknamn på HomBot.
|
||||
channel-type.lghombot.moveType.label = Styr
|
||||
channel-type.lghombot.moveType.description = Styr riktning.
|
||||
channel-type.lghombot.cameraType.label = Kamera
|
||||
channel-type.lghombot.cameraType.description = Bild från toppkameran.
|
||||
channel-type.lghombot.lastCleanType.label = Senaste städning
|
||||
channel-type.lghombot.lastCleanType.description = Senaste tillfälle som HomBot städade.
|
||||
channel-type.lghombot.mapType.label = Karta
|
||||
channel-type.lghombot.mapType.description = Karta över senaste städade arean.
|
||||
|
||||
channel-type.lghombot.mondayType.label = Måndag
|
||||
channel-type.lghombot.mondayType.description = Schemalagd tid på Måndag.
|
||||
channel-type.lghombot.tuesdayType.label = Tisdag
|
||||
channel-type.lghombot.tuesdayType.description = Schemalagd tid på Tisdag.
|
||||
channel-type.lghombot.wednesdayType.label = Onsdag
|
||||
channel-type.lghombot.wednesdayType.description = Schemalagd tid på Onsdag.
|
||||
channel-type.lghombot.thursdayType.label = Torsdag
|
||||
channel-type.lghombot.thursdayType.description = Schemalagd tid på Torsdag.
|
||||
channel-type.lghombot.fridayType.label = Fredag
|
||||
channel-type.lghombot.fridayType.description = Schemalagd tid på Fredag.
|
||||
channel-type.lghombot.saturdayType.label = Lördag
|
||||
channel-type.lghombot.saturdayType.description = Schemalagd tid på Lördag.
|
||||
channel-type.lghombot.sundayType.label = Söndag
|
||||
channel-type.lghombot.sundayType.description = Schemalagd tid på Söndag.
|
||||
@@ -0,0 +1,239 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="lghombot"
|
||||
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="LGHomBot">
|
||||
<label>LG HomBot</label>
|
||||
<description>HomBot vacuum robot.</description>
|
||||
<category>CleaningRobot</category>
|
||||
|
||||
<channels>
|
||||
<channel id="state" typeId="stateType"/>
|
||||
<channel id="battery" typeId="batteryType"/>
|
||||
<channel id="cpuLoad" typeId="cpuLoadType"/>
|
||||
<channel id="srvMem" typeId="srvMemType"/>
|
||||
<channel id="clean" typeId="cleanType"/>
|
||||
<channel id="start" typeId="startType"/>
|
||||
<channel id="home" typeId="homeType"/>
|
||||
<channel id="pause" typeId="pauseType"/>
|
||||
<channel id="turbo" typeId="turboType"/>
|
||||
<channel id="repeat" typeId="repeatType"/>
|
||||
<channel id="mode" typeId="modeType"/>
|
||||
<channel id="nickname" typeId="nicknameType"/>
|
||||
<channel id="move" typeId="moveType"/>
|
||||
<channel id="camera" typeId="cameraType"/>
|
||||
<channel id="lastClean" typeId="lastCleanType"/>
|
||||
<channel id="map" typeId="mapType"/>
|
||||
<channel id="monday" typeId="mondayType"/>
|
||||
<channel id="tuesday" typeId="tuesdayType"/>
|
||||
<channel id="wednesday" typeId="wednesdayType"/>
|
||||
<channel id="thursday" typeId="thursdayType"/>
|
||||
<channel id="friday" typeId="fridayType"/>
|
||||
<channel id="saturday" typeId="saturdayType"/>
|
||||
<channel id="sunday" typeId="sundayType"/>
|
||||
</channels>
|
||||
|
||||
<properties>
|
||||
<property name="vendor">LG</property>
|
||||
<property name="modelId">HomBot</property>
|
||||
</properties>
|
||||
<representation-property>deviceId</representation-property>
|
||||
|
||||
<config-description>
|
||||
<parameter name="ipAddress" type="text" required="true">
|
||||
<label>Network Address</label>
|
||||
<description>The IP or host name of the HomBot.</description>
|
||||
<context>network-address</context>
|
||||
</parameter>
|
||||
<parameter name="port" type="integer" min="1000" max="65535">
|
||||
<label>Port</label>
|
||||
<description>Port of the HomBot to control.</description>
|
||||
<default>6260</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="pollingPeriod" type="integer" min="1" max="60" unit="s">
|
||||
<label>Polling Period</label>
|
||||
<description>Time between polls in seconds.</description>
|
||||
<default>3</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
</config-description>
|
||||
|
||||
</thing-type>
|
||||
|
||||
<channel-type id="stateType">
|
||||
<item-type>String</item-type>
|
||||
<label>State</label>
|
||||
<description>Current state.</description>
|
||||
<state pattern="%s" readOnly="true">
|
||||
<options>
|
||||
<option value="UNKNOWN">Unknown</option>
|
||||
<option value="WORKING">Cleaning</option>
|
||||
<option value="BACKMOVING">Backmoving</option>
|
||||
<option value="BACKMOVING_INIT">Backmoving init</option>
|
||||
<option value="BACKMOVING_JOY">Backmoving joy</option>
|
||||
<option value="PAUSE">Pause</option>
|
||||
<option value="STANDBY">Standby</option>
|
||||
<option value="HOMING">Homing</option>
|
||||
<option value="DOCKING">Docking</option>
|
||||
<option value="CHARGING">Charging</option>
|
||||
<option value="DIAGNOSIS">Running diagnosis</option>
|
||||
<option value="RESERVATION">Changing settings</option>
|
||||
<option value="ERROR">Error</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="batteryType">
|
||||
<item-type>Number</item-type>
|
||||
<label>Battery</label>
|
||||
<description>Current battery charge.</description>
|
||||
<category>BatteryLevel</category>
|
||||
<state pattern="%d%%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="cpuLoadType" advanced="true">
|
||||
<item-type>Number</item-type>
|
||||
<label>CPU Load</label>
|
||||
<description>Current CPU load.</description>
|
||||
<state pattern="%d%%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="srvMemType" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>Used Memory</label>
|
||||
<description>Memory used by webserver on HomBot.</description>
|
||||
<state pattern="%s" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="cleanType">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Clean</label>
|
||||
<description>Start cleaning / return home.</description>
|
||||
<tags>
|
||||
<tag>Switchable</tag>
|
||||
</tags>
|
||||
</channel-type>
|
||||
<channel-type id="startType">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Start</label>
|
||||
<description>Start cleaning.</description>
|
||||
</channel-type>
|
||||
<channel-type id="homeType">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Home</label>
|
||||
<description>Return home.</description>
|
||||
</channel-type>
|
||||
<channel-type id="pauseType" advanced="true">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Pause</label>
|
||||
<description>Pause the HomBot.</description>
|
||||
</channel-type>
|
||||
<channel-type id="turboType">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Turbo</label>
|
||||
<description>Turbo mode ON, OFF.</description>
|
||||
</channel-type>
|
||||
<channel-type id="repeatType">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Repeat</label>
|
||||
<description>Repeat cleaning ON, OFF.</description>
|
||||
</channel-type>
|
||||
<channel-type id="modeType">
|
||||
<item-type>String</item-type>
|
||||
<label>Mode</label>
|
||||
<description>Cleaning mode.</description>
|
||||
<state>
|
||||
<options>
|
||||
<option value="ZZ">Zigzag mode</option>
|
||||
<option value="SB">Cell by cell mode</option>
|
||||
<option value="SPOT">Spiral spot mode</option>
|
||||
<option value="MACRO_SECTOR">My space mode</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="nicknameType">
|
||||
<item-type>String</item-type>
|
||||
<label>Nickname</label>
|
||||
<description>Nickname of the HomBot.</description>
|
||||
<state pattern="%s" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="moveType" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>Move</label>
|
||||
<description>Move direction.</description>
|
||||
<category>MoveControl</category>
|
||||
<state>
|
||||
<options>
|
||||
<option value="FORWARD">Forward</option>
|
||||
<option value="FORWARD_LEFT">Forward left</option>
|
||||
<option value="FORWARD_RIGHT">Forward right</option>
|
||||
<option value="LEFT">Left</option>
|
||||
<option value="RIGHT">Right</option>
|
||||
<option value="BACKWARD">Backward</option>
|
||||
<option value="BACKWARD_LEFT">Backward left</option>
|
||||
<option value="BACKWARD_RIGHT">Backward right</option>
|
||||
<option value="RELEASE">Release</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="cameraType">
|
||||
<item-type>Image</item-type>
|
||||
<label>Camera</label>
|
||||
<description>Image feed from the top camera.</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="lastCleanType">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Last Clean</label>
|
||||
<description>Last time the HomBot cleaned.</description>
|
||||
<state pattern="%1$tF %1$tR" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="mapType" advanced="true">
|
||||
<item-type>Image</item-type>
|
||||
<label>Cleaning map</label>
|
||||
<description>Map of last cleaned area.</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="mondayType" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>Monday</label>
|
||||
<description>Scheduled start time on Monday.</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="tuesdayType" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>Tuesday</label>
|
||||
<description>Scheduled start time on Tuesday.</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="wednesdayType" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>Wednesday</label>
|
||||
<description>Scheduled start time on Wednesday.</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="thursdayType" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>Thursday</label>
|
||||
<description>Scheduled start time on Thursday.</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="fridayType" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>Friday</label>
|
||||
<description>Scheduled start time on Friday.</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="saturdayType" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>Saturday</label>
|
||||
<description>Scheduled start time on Saturday.</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="sundayType" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>Sunday</label>
|
||||
<description>Scheduled start time on Sunday.</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
</thing:thing-descriptions>
|
||||
Reference in New Issue
Block a user