added migrated 2.x add-ons

Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
Kai Kreuzer
2020-09-21 01:58:32 +02:00
parent bbf1a7fd29
commit 6df6783b60
11662 changed files with 1302875 additions and 11 deletions

View File

@@ -0,0 +1,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>

View File

@@ -0,0 +1,83 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.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;
}
}
}

View File

@@ -0,0 +1,85 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.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.
}
}

View File

@@ -0,0 +1,46 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.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;
}

View File

@@ -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;
}
}
}

View File

@@ -0,0 +1,56 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.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;
}
}

View File

@@ -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;
}
}
}

View File

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

View File

@@ -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.

View File

@@ -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.

View File

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