mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-10 15:11:59 +01:00
Working version of split connection manager
Working (sort of) version of split connection manager. Problem with the connection manager being null when the binding is stop/start. Need reset to clear with clean-cache too. Signed-off-by: Bob Eckhoff <katmandodo@yahoo.com>
This commit is contained in:
parent
925fc2860e
commit
a2159bed2a
@ -64,7 +64,6 @@ Following channels are available:
|
||||
| off-timer | String | Sets the future time to turn off the AC. | | Yes |
|
||||
| screen-display | Switch | If device supports across LAN, turns off the LED display. | | Yes |
|
||||
| humidity | Number | If device supports, the indoor humidity. | Yes | Yes |
|
||||
| dropped-commands | Number | Quality of WiFi connections - For debugging only. | Yes | Yes |
|
||||
| appliance-error | Switch | If device supports, appliance error | Yes | Yes |
|
||||
| auxiliary-heat | Switch | If device supports, auxiliary heat | Yes | Yes |
|
||||
| alternate-target-temperature | Number:Temperature | Alternate Target Temperature - not currently used | Yes | Yes |
|
||||
|
@ -25,7 +25,7 @@ public class MideaACConfiguration {
|
||||
|
||||
public String ipAddress = "";
|
||||
|
||||
public String ipPort = "6444";
|
||||
public int ipPort = 6444;
|
||||
|
||||
public String deviceId = "";
|
||||
|
||||
@ -45,7 +45,7 @@ public class MideaACConfiguration {
|
||||
|
||||
public boolean promptTone;
|
||||
|
||||
public String version = "";
|
||||
public int version = 0;
|
||||
|
||||
/**
|
||||
* Check during initialization that the params are valid
|
||||
@ -53,7 +53,7 @@ public class MideaACConfiguration {
|
||||
* @return true(valid), false (not valid)
|
||||
*/
|
||||
public boolean isValid() {
|
||||
return !("0".equalsIgnoreCase(deviceId) || deviceId.isBlank() || ipPort.isBlank() || ipAddress.isBlank());
|
||||
return !("0".equalsIgnoreCase(deviceId) || deviceId.isBlank() || ipPort <= 0 || ipAddress.isBlank());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -62,7 +62,26 @@ public class MideaACConfiguration {
|
||||
* @return true(discovery needed), false (not needed)
|
||||
*/
|
||||
public boolean isDiscoveryNeeded() {
|
||||
return ("0".equalsIgnoreCase(deviceId) || deviceId.isBlank() || ipPort.isBlank() || ipAddress.isBlank()
|
||||
return ("0".equalsIgnoreCase(deviceId) || deviceId.isBlank() || ipPort <= 0 || ipAddress.isBlank()
|
||||
|| !Utils.validateIP(ipAddress));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check during initialization if key and token can be obtained
|
||||
* from the cloud.
|
||||
*
|
||||
* @return true (yes they can), false (they cannot)
|
||||
*/
|
||||
public boolean isTokenKeyObtainable() {
|
||||
return (!email.isBlank() && !password.isBlank() && !"".equals(cloud));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check during initialization if cloud, key and token are true for v3
|
||||
*
|
||||
* @return true (Valid, all items are present) false (key, token and/or provider missing)
|
||||
*/
|
||||
public boolean isV3ConfigValid() {
|
||||
return (!key.isBlank() && !token.isBlank() && !"".equals(cloud));
|
||||
}
|
||||
}
|
||||
|
@ -39,9 +39,9 @@ import org.osgi.service.component.annotations.Reference;
|
||||
@Component(configurationPid = "binding.mideaac", service = ThingHandlerFactory.class)
|
||||
public class MideaACHandlerFactory extends BaseThingHandlerFactory {
|
||||
|
||||
private UnitProvider unitProvider;
|
||||
private final HttpClientFactory httpClientFactory;
|
||||
private final CloudsDTO clouds;
|
||||
private final UnitProvider unitProvider;
|
||||
|
||||
@Override
|
||||
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
|
||||
@ -56,8 +56,8 @@ public class MideaACHandlerFactory extends BaseThingHandlerFactory {
|
||||
*/
|
||||
@Activate
|
||||
public MideaACHandlerFactory(@Reference UnitProvider unitProvider, @Reference HttpClientFactory httpClientFactory) {
|
||||
this.unitProvider = unitProvider;
|
||||
this.httpClientFactory = httpClientFactory;
|
||||
this.unitProvider = unitProvider;
|
||||
clouds = new CloudsDTO();
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,438 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2024 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.mideaac.internal.connection;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.mideaac.internal.handler.CommandBase.FanSpeed;
|
||||
import org.openhab.binding.mideaac.internal.handler.CommandBase.OperationalMode;
|
||||
import org.openhab.binding.mideaac.internal.handler.CommandBase.SwingMode;
|
||||
import org.openhab.binding.mideaac.internal.handler.CommandSet;
|
||||
import org.openhab.binding.mideaac.internal.handler.Response;
|
||||
import org.openhab.binding.mideaac.internal.handler.Timer;
|
||||
import org.openhab.binding.mideaac.internal.handler.Timer.TimeParser;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.QuantityType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.library.unit.ImperialUnits;
|
||||
import org.openhab.core.library.unit.SIUnits;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link CommandHelper} is a static class that is able to translate {@link Command} to {@link CommandSet}
|
||||
*
|
||||
* @author Leo Siepel - Initial contribution
|
||||
*/
|
||||
|
||||
@NonNullByDefault
|
||||
public class CommandHelper {
|
||||
private static Logger logger = LoggerFactory.getLogger(CommandHelper.class);
|
||||
|
||||
private static final StringType OPERATIONAL_MODE_OFF = new StringType("OFF");
|
||||
private static final StringType OPERATIONAL_MODE_AUTO = new StringType("AUTO");
|
||||
private static final StringType OPERATIONAL_MODE_COOL = new StringType("COOL");
|
||||
private static final StringType OPERATIONAL_MODE_DRY = new StringType("DRY");
|
||||
private static final StringType OPERATIONAL_MODE_HEAT = new StringType("HEAT");
|
||||
private static final StringType OPERATIONAL_MODE_FAN_ONLY = new StringType("FAN_ONLY");
|
||||
|
||||
private static final StringType FAN_SPEED_OFF = new StringType("OFF");
|
||||
private static final StringType FAN_SPEED_SILENT = new StringType("SILENT");
|
||||
private static final StringType FAN_SPEED_LOW = new StringType("LOW");
|
||||
private static final StringType FAN_SPEED_MEDIUM = new StringType("MEDIUM");
|
||||
private static final StringType FAN_SPEED_HIGH = new StringType("HIGH");
|
||||
private static final StringType FAN_SPEED_FULL = new StringType("FULL");
|
||||
private static final StringType FAN_SPEED_AUTO = new StringType("AUTO");
|
||||
|
||||
private static final StringType SWING_MODE_OFF = new StringType("OFF");
|
||||
private static final StringType SWING_MODE_VERTICAL = new StringType("VERTICAL");
|
||||
private static final StringType SWING_MODE_HORIZONTAL = new StringType("HORIZONTAL");
|
||||
private static final StringType SWING_MODE_BOTH = new StringType("BOTH");
|
||||
|
||||
/**
|
||||
* Device Power ON OFF
|
||||
*
|
||||
* @param command On or Off
|
||||
*/
|
||||
public static CommandSet handlePower(Command command, Response lastResponse) throws UnsupportedOperationException {
|
||||
CommandSet commandSet = CommandSet.fromResponse(lastResponse);
|
||||
|
||||
if (command.equals(OnOffType.OFF)) {
|
||||
commandSet.setPowerState(false);
|
||||
} else if (command.equals(OnOffType.ON)) {
|
||||
commandSet.setPowerState(true);
|
||||
} else {
|
||||
throw new UnsupportedOperationException(String.format("Unknown power command: {}", command));
|
||||
}
|
||||
return commandSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Supported AC - Heat Pump modes
|
||||
*
|
||||
* @param command Operational Mode Cool, Heat, etc.
|
||||
*/
|
||||
public static CommandSet handleOperationalMode(Command command, Response lastResponse)
|
||||
throws UnsupportedOperationException {
|
||||
CommandSet commandSet = CommandSet.fromResponse(lastResponse);
|
||||
|
||||
if (command instanceof StringType) {
|
||||
if (command.equals(OPERATIONAL_MODE_OFF)) {
|
||||
commandSet.setPowerState(false);
|
||||
} else if (command.equals(OPERATIONAL_MODE_AUTO)) {
|
||||
commandSet.setOperationalMode(OperationalMode.AUTO);
|
||||
} else if (command.equals(OPERATIONAL_MODE_COOL)) {
|
||||
commandSet.setOperationalMode(OperationalMode.COOL);
|
||||
} else if (command.equals(OPERATIONAL_MODE_DRY)) {
|
||||
commandSet.setOperationalMode(OperationalMode.DRY);
|
||||
} else if (command.equals(OPERATIONAL_MODE_HEAT)) {
|
||||
commandSet.setOperationalMode(OperationalMode.HEAT);
|
||||
} else if (command.equals(OPERATIONAL_MODE_FAN_ONLY)) {
|
||||
commandSet.setOperationalMode(OperationalMode.FAN_ONLY);
|
||||
} else {
|
||||
throw new UnsupportedOperationException(String.format("Unknown operational mode command: {}", command));
|
||||
}
|
||||
}
|
||||
return commandSet;
|
||||
}
|
||||
|
||||
private static float limitTargetTemperatureToRange(float temperatureInCelsius) {
|
||||
if (temperatureInCelsius < 17.0f) {
|
||||
return 17.0f;
|
||||
}
|
||||
if (temperatureInCelsius > 30.0f) {
|
||||
return 30.0f;
|
||||
}
|
||||
|
||||
return temperatureInCelsius;
|
||||
}
|
||||
|
||||
/**
|
||||
* Device only uses Celsius in 0.5 degree increments
|
||||
* Fahrenheit is rounded to fit (example
|
||||
* setting to 64 F is 18 C but will result in 64.4 F display in OH)
|
||||
* The evaporator only displays 2 digits, so will show 64.
|
||||
*
|
||||
* @param command Target Temperature
|
||||
*/
|
||||
public static CommandSet handleTargetTemperature(Command command, Response lastResponse)
|
||||
throws UnsupportedOperationException {
|
||||
CommandSet commandSet = CommandSet.fromResponse(lastResponse);
|
||||
|
||||
if (command instanceof DecimalType decimalCommand) {
|
||||
logger.debug("Handle Target Temperature as DecimalType in degrees C");
|
||||
commandSet.setTargetTemperature(limitTargetTemperatureToRange(decimalCommand.floatValue()));
|
||||
} else if (command instanceof QuantityType quantityCommand) {
|
||||
if (quantityCommand.getUnit().equals(ImperialUnits.FAHRENHEIT)) {
|
||||
quantityCommand = Objects.requireNonNull(quantityCommand.toUnit(SIUnits.CELSIUS));
|
||||
}
|
||||
commandSet.setTargetTemperature(limitTargetTemperatureToRange(quantityCommand.floatValue()));
|
||||
} else {
|
||||
throw new UnsupportedOperationException(String.format("Unknown target temperature command: {}", command));
|
||||
}
|
||||
return commandSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fan Speeds vary by V2 or V3 and device. This command also turns the power ON
|
||||
*
|
||||
* @param command Fan Speed Auto, Low, High, etc.
|
||||
*/
|
||||
public static CommandSet handleFanSpeed(Command command, Response lastResponse, int version)
|
||||
throws UnsupportedOperationException {
|
||||
CommandSet commandSet = CommandSet.fromResponse(lastResponse);
|
||||
|
||||
if (command instanceof StringType) {
|
||||
commandSet.setPowerState(true);
|
||||
if (command.equals(FAN_SPEED_OFF)) {
|
||||
commandSet.setPowerState(false);
|
||||
} else if (command.equals(FAN_SPEED_SILENT)) {
|
||||
if (version == 2) {
|
||||
commandSet.setFanSpeed(FanSpeed.SILENT2);
|
||||
} else if (version == 3) {
|
||||
commandSet.setFanSpeed(FanSpeed.SILENT3);
|
||||
}
|
||||
} else if (command.equals(FAN_SPEED_LOW)) {
|
||||
if (version == 2) {
|
||||
commandSet.setFanSpeed(FanSpeed.LOW2);
|
||||
} else if (version == 3) {
|
||||
commandSet.setFanSpeed(FanSpeed.LOW3);
|
||||
}
|
||||
} else if (command.equals(FAN_SPEED_MEDIUM)) {
|
||||
if (version == 2) {
|
||||
commandSet.setFanSpeed(FanSpeed.MEDIUM2);
|
||||
} else if (version == 3) {
|
||||
commandSet.setFanSpeed(FanSpeed.MEDIUM3);
|
||||
}
|
||||
} else if (command.equals(FAN_SPEED_HIGH)) {
|
||||
if (version == 2) {
|
||||
commandSet.setFanSpeed(FanSpeed.HIGH2);
|
||||
} else if (version == 3) {
|
||||
commandSet.setFanSpeed(FanSpeed.HIGH3);
|
||||
}
|
||||
} else if (command.equals(FAN_SPEED_FULL)) {
|
||||
if (version == 2) {
|
||||
commandSet.setFanSpeed(FanSpeed.FULL2);
|
||||
} else if (version == 3) {
|
||||
commandSet.setFanSpeed(FanSpeed.FULL3);
|
||||
}
|
||||
} else if (command.equals(FAN_SPEED_AUTO)) {
|
||||
if (version == 2) {
|
||||
commandSet.setFanSpeed(FanSpeed.AUTO2);
|
||||
} else if (version == 3) {
|
||||
commandSet.setFanSpeed(FanSpeed.AUTO3);
|
||||
}
|
||||
} else {
|
||||
throw new UnsupportedOperationException(String.format("Unknown fan speed command: {}", command));
|
||||
}
|
||||
}
|
||||
return commandSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Must be set in Cool mode. Fan will switch to Auto
|
||||
* and temp will be 24 C or 75 F on unit (75.2 F in OH)
|
||||
*
|
||||
* @param command Eco Mode
|
||||
*/
|
||||
public static CommandSet handleEcoMode(Command command, Response lastResponse)
|
||||
throws UnsupportedOperationException {
|
||||
CommandSet commandSet = CommandSet.fromResponse(lastResponse);
|
||||
|
||||
if (command.equals(OnOffType.OFF)) {
|
||||
commandSet.setEcoMode(false);
|
||||
} else if (command.equals(OnOffType.ON)) {
|
||||
commandSet.setEcoMode(true);
|
||||
} else {
|
||||
throw new UnsupportedOperationException(String.format("Unknown eco mode command: {}", command));
|
||||
}
|
||||
|
||||
return commandSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Modes supported depends on the device
|
||||
* Power is turned on when swing mode is changed
|
||||
*
|
||||
* @param command Swing Mode
|
||||
*/
|
||||
public static CommandSet handleSwingMode(Command command, Response lastResponse, int version)
|
||||
throws UnsupportedOperationException {
|
||||
CommandSet commandSet = CommandSet.fromResponse(lastResponse);
|
||||
|
||||
commandSet.setPowerState(true);
|
||||
|
||||
if (command instanceof StringType) {
|
||||
if (command.equals(SWING_MODE_OFF)) {
|
||||
if (version == 2) {
|
||||
commandSet.setSwingMode(SwingMode.OFF2);
|
||||
} else if (version == 3) {
|
||||
commandSet.setSwingMode(SwingMode.OFF3);
|
||||
}
|
||||
} else if (command.equals(SWING_MODE_VERTICAL)) {
|
||||
if (version == 2) {
|
||||
commandSet.setSwingMode(SwingMode.VERTICAL2);
|
||||
} else if (version == 3) {
|
||||
commandSet.setSwingMode(SwingMode.VERTICAL3);
|
||||
}
|
||||
} else if (command.equals(SWING_MODE_HORIZONTAL)) {
|
||||
if (version == 2) {
|
||||
commandSet.setSwingMode(SwingMode.HORIZONTAL2);
|
||||
} else if (version == 3) {
|
||||
commandSet.setSwingMode(SwingMode.HORIZONTAL3);
|
||||
}
|
||||
} else if (command.equals(SWING_MODE_BOTH)) {
|
||||
if (version == 2) {
|
||||
commandSet.setSwingMode(SwingMode.BOTH2);
|
||||
} else if (version == 3) {
|
||||
commandSet.setSwingMode(SwingMode.BOTH3);
|
||||
}
|
||||
} else {
|
||||
throw new UnsupportedOperationException(String.format("Unknown swing mode command: {}", command));
|
||||
}
|
||||
}
|
||||
|
||||
return commandSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Turbo mode is only with Heat or Cool to quickly change
|
||||
* Room temperature. Power is turned on.
|
||||
*
|
||||
* @param command Turbo mode - Fast cooling or Heating
|
||||
*/
|
||||
public static CommandSet handleTurboMode(Command command, Response lastResponse)
|
||||
throws UnsupportedOperationException {
|
||||
CommandSet commandSet = CommandSet.fromResponse(lastResponse);
|
||||
|
||||
commandSet.setPowerState(true);
|
||||
|
||||
if (command.equals(OnOffType.OFF)) {
|
||||
commandSet.setTurboMode(false);
|
||||
} else if (command.equals(OnOffType.ON)) {
|
||||
commandSet.setTurboMode(true);
|
||||
} else {
|
||||
throw new UnsupportedOperationException(String.format("Unknown turbo mode command: {}", command));
|
||||
}
|
||||
|
||||
return commandSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* May not be supported via LAN in all models - IR only
|
||||
*
|
||||
* @param command Screen Display Toggle to ON or Off - One command
|
||||
*/
|
||||
public static CommandSet handleScreenDisplay(Command command, Response lastResponse)
|
||||
throws UnsupportedOperationException {
|
||||
CommandSet commandSet = CommandSet.fromResponse(lastResponse);
|
||||
|
||||
if (command.equals(OnOffType.OFF)) {
|
||||
commandSet.setScreenDisplay(true);
|
||||
} else if (command.equals(OnOffType.ON)) {
|
||||
commandSet.setScreenDisplay(true);
|
||||
} else {
|
||||
throw new UnsupportedOperationException(String.format("Unknown screen display command: {}", command));
|
||||
}
|
||||
|
||||
return commandSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is only for the AC LED device display units, calcs always in Celsius
|
||||
*
|
||||
* @param command Temp unit on the indoor evaporator
|
||||
*/
|
||||
public static CommandSet handleTempUnit(Command command, Response lastResponse)
|
||||
throws UnsupportedOperationException {
|
||||
CommandSet commandSet = CommandSet.fromResponse(lastResponse);
|
||||
|
||||
if (command.equals(OnOffType.OFF)) {
|
||||
commandSet.setFahrenheit(false);
|
||||
} else if (command.equals(OnOffType.ON)) {
|
||||
commandSet.setFahrenheit(true);
|
||||
} else {
|
||||
throw new UnsupportedOperationException(String.format("Unknown temperature unit command: {}", command));
|
||||
}
|
||||
|
||||
return commandSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Power turned on with Sleep Mode Change
|
||||
* Sleep mode increases temp slightly in first 2 hours of sleep
|
||||
*
|
||||
* @param command Sleep function
|
||||
*/
|
||||
public static CommandSet handleSleepFunction(Command command, Response lastResponse)
|
||||
throws UnsupportedOperationException {
|
||||
CommandSet commandSet = CommandSet.fromResponse(lastResponse);
|
||||
|
||||
commandSet.setPowerState(true);
|
||||
|
||||
if (command.equals(OnOffType.OFF)) {
|
||||
commandSet.setSleepMode(false);
|
||||
} else if (command.equals(OnOffType.ON)) {
|
||||
commandSet.setSleepMode(true);
|
||||
} else {
|
||||
throw new UnsupportedOperationException(String.format("Unknown sleep mode command: {}", command));
|
||||
}
|
||||
|
||||
return commandSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the time (from now) that the device will turn on at it's current settings
|
||||
*
|
||||
* @param command Sets On Timer
|
||||
*/
|
||||
public static CommandSet handleOnTimer(Command command, Response lastResponse) {
|
||||
CommandSet commandSet = CommandSet.fromResponse(lastResponse);
|
||||
int hours = 0;
|
||||
int minutes = 0;
|
||||
Timer timer = new Timer(true, hours, minutes);
|
||||
TimeParser timeParser = timer.new TimeParser();
|
||||
if (command instanceof StringType) {
|
||||
String timeString = ((StringType) command).toString();
|
||||
if (!timeString.matches("\\d{2}:\\d{2}")) {
|
||||
logger.debug("Invalid time format. Expected HH:MM.");
|
||||
commandSet.setOnTimer(false, hours, minutes);
|
||||
} else {
|
||||
int[] timeParts = timeParser.parseTime(timeString);
|
||||
boolean on = true;
|
||||
hours = timeParts[0];
|
||||
minutes = timeParts[1];
|
||||
// Validate minutes and hours
|
||||
if (minutes < 0 || minutes > 59 || hours > 24 || hours < 0) {
|
||||
logger.debug("Invalid hours (24 max) and or minutes (59 max)");
|
||||
hours = 0;
|
||||
minutes = 0;
|
||||
}
|
||||
if (hours == 0 && minutes == 0) {
|
||||
commandSet.setOnTimer(false, hours, minutes);
|
||||
} else {
|
||||
commandSet.setOnTimer(on, hours, minutes);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logger.debug("Command must be of type StringType: {}", command);
|
||||
commandSet.setOnTimer(false, hours, minutes);
|
||||
}
|
||||
|
||||
return commandSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the time (from now) that the device will turn off
|
||||
*
|
||||
* @param command Sets Off Timer
|
||||
*/
|
||||
public static CommandSet handleOffTimer(Command command, Response lastResponse) {
|
||||
CommandSet commandSet = CommandSet.fromResponse(lastResponse);
|
||||
int hours = 0;
|
||||
int minutes = 0;
|
||||
Timer timer = new Timer(true, hours, minutes);
|
||||
TimeParser timeParser = timer.new TimeParser();
|
||||
if (command instanceof StringType) {
|
||||
String timeString = ((StringType) command).toString();
|
||||
if (!timeString.matches("\\d{2}:\\d{2}")) {
|
||||
logger.debug("Invalid time format. Expected HH:MM.");
|
||||
commandSet.setOffTimer(false, hours, minutes);
|
||||
} else {
|
||||
int[] timeParts = timeParser.parseTime(timeString);
|
||||
boolean on = true;
|
||||
hours = timeParts[0];
|
||||
minutes = timeParts[1];
|
||||
// Validate minutes and hours
|
||||
if (minutes < 0 || minutes > 59 || hours > 24 || hours < 0) {
|
||||
logger.debug("Invalid hours (24 max) and or minutes (59 max)");
|
||||
hours = 0;
|
||||
minutes = 0;
|
||||
}
|
||||
if (hours == 0 && minutes == 0) {
|
||||
commandSet.setOffTimer(false, hours, minutes);
|
||||
} else {
|
||||
commandSet.setOffTimer(on, hours, minutes);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logger.debug("Command must be of type StringType: {}", command);
|
||||
commandSet.setOffTimer(false, hours, minutes);
|
||||
}
|
||||
|
||||
return commandSet;
|
||||
}
|
||||
}
|
@ -0,0 +1,555 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2024 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.mideaac.internal.connection;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketException;
|
||||
import java.util.Arrays;
|
||||
import java.util.HexFormat;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.mideaac.internal.Utils;
|
||||
import org.openhab.binding.mideaac.internal.connection.exception.MideaAuthenticationException;
|
||||
import org.openhab.binding.mideaac.internal.connection.exception.MideaConnectionException;
|
||||
import org.openhab.binding.mideaac.internal.connection.exception.MideaException;
|
||||
import org.openhab.binding.mideaac.internal.dto.CloudProviderDTO;
|
||||
import org.openhab.binding.mideaac.internal.handler.Callback;
|
||||
import org.openhab.binding.mideaac.internal.handler.CommandBase;
|
||||
import org.openhab.binding.mideaac.internal.handler.CommandSet;
|
||||
import org.openhab.binding.mideaac.internal.handler.Packet;
|
||||
import org.openhab.binding.mideaac.internal.handler.Response;
|
||||
import org.openhab.binding.mideaac.internal.security.Decryption8370Result;
|
||||
import org.openhab.binding.mideaac.internal.security.Security;
|
||||
import org.openhab.binding.mideaac.internal.security.Security.MsgType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link ConnectionManager} class is responsible for managing the state of the TCP connection to the
|
||||
* indoor AC unit evaporator.
|
||||
*
|
||||
* @author Jacek Dobrowolski - Initial Contribution
|
||||
* @author Bob Eckhoff - Revised logic to reconnect with security before each poll or command
|
||||
*
|
||||
* This gets around the issue that any command needs to be within 30 seconds of the authorization
|
||||
* in testing this only adds 50 ms, but allows polls at longer intervals
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ConnectionManager {
|
||||
private Logger logger = LoggerFactory.getLogger(ConnectionManager.class);
|
||||
|
||||
private final String ipAddress;
|
||||
private final int ipPort;
|
||||
private final int timeout;
|
||||
private String key;
|
||||
private String token;
|
||||
private final String cloud;
|
||||
private final String deviceId;
|
||||
private Response lastResponse;
|
||||
private CloudProviderDTO cloudProvider;
|
||||
private Security security;
|
||||
private final int version;
|
||||
private final boolean promptTone;
|
||||
|
||||
/**
|
||||
* True allows one short retry after connection problem
|
||||
*/
|
||||
private boolean retry = true;
|
||||
|
||||
/**
|
||||
* Suppresses the connection message if was online before
|
||||
*/
|
||||
private boolean connectionMessage = true;
|
||||
|
||||
public ConnectionManager(String ipAddress, int ipPort, int timeout, String key, String token, String cloud,
|
||||
String email, String password, String deviceId, int version, boolean promptTone) {
|
||||
this.deviceIsConnected = false;
|
||||
this.ipAddress = ipAddress;
|
||||
this.ipPort = ipPort;
|
||||
this.timeout = timeout;
|
||||
this.key = key;
|
||||
this.token = token;
|
||||
this.cloud = cloud;
|
||||
this.deviceId = deviceId;
|
||||
this.version = version;
|
||||
this.promptTone = promptTone;
|
||||
this.lastResponse = new Response(HexFormat.of().parseHex("C00042667F7F003C0000046066000000000000000000F9ECDB"),
|
||||
version, "query", (byte) 0xc0);
|
||||
this.cloudProvider = CloudProviderDTO.getCloudProvider(cloud);
|
||||
this.security = new Security(cloudProvider);
|
||||
}
|
||||
|
||||
private boolean deviceIsConnected;
|
||||
private int droppedCommands = 0;
|
||||
|
||||
private Socket socket = new Socket();
|
||||
private InputStream inputStream = new ByteArrayInputStream(new byte[0]);
|
||||
private DataOutputStream writer = new DataOutputStream(System.out);
|
||||
|
||||
/**
|
||||
* Gets last response
|
||||
*
|
||||
* @return byte array of last response
|
||||
*/
|
||||
public Response getLastResponse() {
|
||||
return this.lastResponse;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate if String is blank
|
||||
*
|
||||
* @param str string to be evaluated
|
||||
* @return boolean true or false
|
||||
*/
|
||||
public static boolean isBlank(String str) {
|
||||
return str.trim().isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset dropped commands from initialization in MideaACHandler
|
||||
* Channel created for easy observation
|
||||
* Dropped commands when no bytes to read after two tries or other
|
||||
* byte reading problem. Device not responding.
|
||||
*/
|
||||
public void resetDroppedCommands() {
|
||||
droppedCommands = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets Dropped command
|
||||
*
|
||||
* @return dropped commands
|
||||
*/
|
||||
public int getDroppedCommands() {
|
||||
return droppedCommands = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* After checking if the key and token need to be updated (Default = 0 Never)
|
||||
* The socket is established with the writer and inputStream (for reading responses)
|
||||
* The device is considered connected. V2 devices will proceed to send the poll or the
|
||||
* set command. V3 devices will proceed to authenticate
|
||||
*/
|
||||
public synchronized void connect() throws MideaConnectionException, MideaAuthenticationException {
|
||||
logger.trace("Connecting to {}:{}", ipAddress, ipPort);
|
||||
|
||||
// Open socket
|
||||
try {
|
||||
socket = new Socket();
|
||||
socket.setSoTimeout(timeout * 1000);
|
||||
socket.connect(new InetSocketAddress(ipAddress, ipPort), timeout * 1000);
|
||||
} catch (IOException e) {
|
||||
logger.debug("IOException connecting to {}: {}", ipAddress, e.getMessage());
|
||||
deviceIsConnected = false;
|
||||
if (retry) {
|
||||
try {
|
||||
Thread.sleep(5000);
|
||||
} catch (InterruptedException ex) {
|
||||
logger.debug("An interupted error (pause) has occured {}", ex.getMessage());
|
||||
}
|
||||
connect();
|
||||
}
|
||||
throw new MideaConnectionException(e);
|
||||
}
|
||||
|
||||
// Create streams
|
||||
try {
|
||||
writer = new DataOutputStream(socket.getOutputStream());
|
||||
inputStream = socket.getInputStream();
|
||||
} catch (IOException e) {
|
||||
logger.debug("IOException getting streams for {}: {}", ipAddress, e.getMessage(), e);
|
||||
deviceIsConnected = false;
|
||||
throw new MideaConnectionException(e);
|
||||
}
|
||||
if (!deviceIsConnected || !connectionMessage) {
|
||||
logger.info("Connected to IP {}", ipAddress);
|
||||
resetConnectionMessage();
|
||||
}
|
||||
logger.debug("Connected to IP {}", ipAddress);
|
||||
deviceIsConnected = true;
|
||||
resetRetry();
|
||||
|
||||
if (version == 3) {
|
||||
logger.debug("Device {} require authentication, going to authenticate", ipAddress);
|
||||
try {
|
||||
authenticate();
|
||||
} catch (MideaAuthenticationException | MideaConnectionException e) {
|
||||
deviceIsConnected = false;
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
// requestStatus(getDoPoll());
|
||||
deviceIsConnected = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* For V3 devices only. This method checks for the Cloud Provider
|
||||
* key and token (and goes offline if any are missing). It will retrieve the
|
||||
* missing key and/or token if the account email and password are provided.
|
||||
*
|
||||
* @throws MideaAuthenticationException
|
||||
* @throws MideaConnectionException
|
||||
*/
|
||||
public void authenticate() throws MideaConnectionException, MideaAuthenticationException {
|
||||
logger.trace("Key: {}", key);
|
||||
logger.trace("Token: {}", token);
|
||||
logger.trace("Cloud {}", cloud);
|
||||
|
||||
if (!isBlank(token) && !isBlank(key) && !"".equals(cloud)) {
|
||||
logger.debug("Device {} authenticating", ipAddress);
|
||||
doV3Handshake();
|
||||
} else {
|
||||
throw new MideaAuthenticationException("Token, Key and / or cloud provider missing");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the Handshake Request to the V3 device. Generally quick response
|
||||
* Without the 1000 ms sleep delay there are problems in sending the Poll/Command
|
||||
* Suspect that the socket write and read streams need a moment to clear
|
||||
* as they will be reused in the SendCommand method
|
||||
*/
|
||||
private void doV3Handshake() throws MideaConnectionException, MideaAuthenticationException {
|
||||
byte[] request = security.encode8370(Utils.hexStringToByteArray(token), MsgType.MSGTYPE_HANDSHAKE_REQUEST);
|
||||
try {
|
||||
logger.trace("Device {} writing handshake_request: {}", ipAddress, Utils.bytesToHex(request));
|
||||
|
||||
write(request);
|
||||
byte[] response = read();
|
||||
|
||||
if (response != null && response.length > 0) {
|
||||
logger.trace("Device {} response for handshake_request length: {}", ipAddress, response.length);
|
||||
if (response.length == 72) {
|
||||
boolean success = security.tcpKey(Arrays.copyOfRange(response, 8, 72),
|
||||
Utils.hexStringToByteArray(key));
|
||||
if (success) {
|
||||
logger.debug("Authentication successful");
|
||||
// Altering the sleep caused or can cause write errors problems. Use caution.
|
||||
// At 500 ms the first write usually fails. Works, but no backup
|
||||
try {
|
||||
Thread.sleep(1000);
|
||||
} catch (InterruptedException e) {
|
||||
logger.debug("An interupted error (success) has occured {}", e.getMessage());
|
||||
}
|
||||
// requestStatus(getDoPoll()); need to handle
|
||||
} else {
|
||||
throw new MideaAuthenticationException("Invalid Key. Correct Key in configuration.");
|
||||
}
|
||||
} else if (Arrays.equals(new String("ERROR").getBytes(), response)) {
|
||||
throw new MideaAuthenticationException("Authentication failed!");
|
||||
} else {
|
||||
logger.warn("Authentication reponse unexpected data length ({} instead of 72)!", response.length);
|
||||
throw new MideaAuthenticationException("Invalid Key. Correct Key in configuration.");
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new MideaConnectionException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the routine polling command from the DoPoll
|
||||
* in the MideaACHandler
|
||||
*
|
||||
* @param callback
|
||||
* @throws MideaConnectionException
|
||||
* @throws MideaAuthenticationException
|
||||
* @throws MideaException
|
||||
*/
|
||||
public void getStatus(Callback callback)
|
||||
throws MideaConnectionException, MideaAuthenticationException, MideaException {
|
||||
CommandBase requestStatusCommand = new CommandBase();
|
||||
sendCommand(requestStatusCommand, callback);
|
||||
}
|
||||
|
||||
private void ensureConnected() throws MideaConnectionException, MideaAuthenticationException {
|
||||
disconnect();
|
||||
connect();
|
||||
}
|
||||
|
||||
/**
|
||||
* Pulls the packet byte array together. There is a check to
|
||||
* make sure to make sure the input stream is empty before sending
|
||||
* the new command and another check if input stream is empty after 1.5 seconds.
|
||||
* Normal device response in 0.75 - 1 second range
|
||||
* If still empty, send the bytes again. If there are bytes, the read method is called.
|
||||
* If the socket times out with no response the command is dropped. There will be another poll
|
||||
* in the time set by the user (30 seconds min) or the set command can be retried
|
||||
*
|
||||
* @param command either the set or polling command
|
||||
* @throws MideaAuthenticationException
|
||||
* @throws MideaConnectionException
|
||||
*/
|
||||
public synchronized void sendCommand(CommandBase command, @Nullable Callback callback)
|
||||
throws MideaConnectionException, MideaAuthenticationException {
|
||||
ensureConnected();
|
||||
|
||||
if (command instanceof CommandSet) {
|
||||
((CommandSet) command).setPromptTone(promptTone);
|
||||
}
|
||||
Packet packet = new Packet(command, deviceId, security);
|
||||
packet.compose();
|
||||
|
||||
try {
|
||||
byte[] bytes = packet.getBytes();
|
||||
logger.debug("Writing to {} bytes.length: {}", ipAddress, bytes.length);
|
||||
|
||||
if (version == 3) {
|
||||
bytes = security.encode8370(bytes, MsgType.MSGTYPE_ENCRYPTED_REQUEST);
|
||||
}
|
||||
|
||||
// Ensure input stream is empty before writing packet
|
||||
if (inputStream.available() == 0) {
|
||||
logger.debug("Input stream empty sending write {}", command);
|
||||
write(bytes);
|
||||
}
|
||||
|
||||
try {
|
||||
Thread.sleep(1500);
|
||||
} catch (InterruptedException e) {
|
||||
logger.debug("An interupted error (retrycommand2) has occured {}", e.getMessage());
|
||||
Thread.currentThread().interrupt();
|
||||
// Note, but continue anyway. Command will be dropped
|
||||
}
|
||||
|
||||
if (inputStream.available() == 0) {
|
||||
logger.debug("Input stream empty sending second write {}", command);
|
||||
write(bytes);
|
||||
}
|
||||
|
||||
// Socket timeout (UI parameter) 2 seconds minimum up to 10 seconds.
|
||||
byte[] responseBytes = read();
|
||||
|
||||
if (responseBytes != null) {
|
||||
if (version == 3) {
|
||||
Decryption8370Result result = security.decode8370(responseBytes);
|
||||
for (byte[] response : result.getResponses()) {
|
||||
logger.debug("Response length:{} IP address:{} ", response.length, ipAddress);
|
||||
if (response.length > 40 + 16) {
|
||||
byte[] data = security.aesDecrypt(Arrays.copyOfRange(response, 40, response.length - 16));
|
||||
|
||||
logger.trace("Bytes in HEX, decoded and with header: length: {}, data: {}", data.length,
|
||||
Utils.bytesToHex(data));
|
||||
byte bodyType2 = data[0xa];
|
||||
|
||||
// data[3]: Device Type - 0xAC = AC
|
||||
// https://github.com/georgezhao2010/midea_ac_lan/blob/06fc4b582a012bbbfd6bd5942c92034270eca0eb/custom_components/midea_ac_lan/midea_devices.py#L96
|
||||
|
||||
// data[9]: MessageType - set, query, notify1, notify2, exception, querySN, exception2,
|
||||
// querySubtype
|
||||
// https://github.com/georgezhao2010/midea_ac_lan/blob/30d0ff5ff14f150da10b883e97b2f280767aa89a/custom_components/midea_ac_lan/midea/core/message.py#L22-L29
|
||||
String responseType = "";
|
||||
switch (data[0x9]) {
|
||||
case 0x02:
|
||||
responseType = "set";
|
||||
break;
|
||||
case 0x03:
|
||||
responseType = "query";
|
||||
break;
|
||||
case 0x04:
|
||||
responseType = "notify1";
|
||||
break;
|
||||
case 0x05:
|
||||
responseType = "notify2";
|
||||
break;
|
||||
case 0x06:
|
||||
responseType = "exception";
|
||||
break;
|
||||
case 0x07:
|
||||
responseType = "querySN";
|
||||
break;
|
||||
case 0x0A:
|
||||
responseType = "exception2";
|
||||
break;
|
||||
case 0x09: // Helyesen: 0xA0
|
||||
responseType = "querySubtype";
|
||||
break;
|
||||
default:
|
||||
logger.debug("Invalid response type: {}", data[0x9]);
|
||||
}
|
||||
logger.trace("Response Type: {} and bodyType:{}", responseType, bodyType2);
|
||||
|
||||
// The response data from the appliance includes a packet header which we don't want
|
||||
data = Arrays.copyOfRange(data, 10, data.length);
|
||||
byte bodyType = data[0x0];
|
||||
logger.trace("Response Type expected: {} and bodyType:{}", responseType, bodyType);
|
||||
logger.trace("Bytes in HEX, decoded and stripped without header: length: {}, data: {}",
|
||||
data.length, Utils.bytesToHex(data));
|
||||
logger.debug("Bytes in BINARY, decoded and stripped without header: length: {}, data: {}",
|
||||
data.length, Utils.bytesToBinary(data));
|
||||
|
||||
if (data.length > 0) {
|
||||
if (data.length < 21) {
|
||||
logger.warn("Response data is {} long, minimum is 21!", data.length);
|
||||
return;
|
||||
}
|
||||
if (bodyType != -64) {
|
||||
if (bodyType == 30) {
|
||||
logger.warn("Error response 0x1E received {} from IP Address {}", bodyType,
|
||||
ipAddress);
|
||||
return;
|
||||
}
|
||||
logger.warn("Unexpected response bodyType {}", bodyType);
|
||||
return;
|
||||
}
|
||||
lastResponse = new Response(data, version, responseType, bodyType);
|
||||
try {
|
||||
logger.trace("data length is {} version is {} IP address is {}", data.length,
|
||||
version, ipAddress);
|
||||
if (callback != null) {
|
||||
callback.updateChannels(lastResponse);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
logger.warn("Processing response exception: {}", ex.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
byte[] data = security.aesDecrypt(Arrays.copyOfRange(responseBytes, 40, responseBytes.length - 16));
|
||||
// The response data from the appliance includes a packet header which we don't want
|
||||
logger.trace("V2 Bytes decoded with header: length: {}, data: {}", data.length,
|
||||
Utils.bytesToHex(data));
|
||||
if (data.length > 0) {
|
||||
data = Arrays.copyOfRange(data, 10, data.length);
|
||||
logger.trace("V2 Bytes decoded and stripped without header: length: {}, data: {}", data.length,
|
||||
Utils.bytesToHex(data));
|
||||
|
||||
lastResponse = new Response(data, version, "", (byte) 0x00);
|
||||
logger.debug("V2 data length is {} version is {} Ip Address is {}", data.length, version,
|
||||
ipAddress);
|
||||
if (callback != null) {
|
||||
callback.updateChannels(lastResponse);
|
||||
}
|
||||
} else {
|
||||
droppedCommands = droppedCommands + 1;
|
||||
logger.debug("Problem with reading V2 response, skipping command {} dropped count{}", command,
|
||||
droppedCommands);
|
||||
}
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
droppedCommands = droppedCommands + 1;
|
||||
logger.debug("Problem with reading response, skipping command {} dropped count{}", command,
|
||||
droppedCommands);
|
||||
return;
|
||||
}
|
||||
} catch (SocketException e) {
|
||||
logger.debug("SocketException writing to {}: {}", ipAddress, e.getMessage());
|
||||
droppedCommands = droppedCommands + 1;
|
||||
logger.debug("Socket exception, skipping command {} dropped count{}", command, droppedCommands);
|
||||
throw new MideaConnectionException(e);
|
||||
} catch (IOException e) {
|
||||
logger.debug(" Send IOException writing to {}: {}", ipAddress, e.getMessage());
|
||||
droppedCommands = droppedCommands + 1;
|
||||
logger.debug("Socket exception, skipping command {} dropped count{}", command, droppedCommands);
|
||||
throw new MideaConnectionException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes all elements of the connection before starting a new one
|
||||
*/
|
||||
public synchronized void disconnect() {
|
||||
// Make sure writer, inputStream and socket are closed before each command is started
|
||||
logger.debug("Disconnecting from device at {}", ipAddress);
|
||||
|
||||
InputStream inputStream = this.inputStream;
|
||||
DataOutputStream writer = this.writer;
|
||||
Socket socket = this.socket;
|
||||
try {
|
||||
writer.close();
|
||||
inputStream.close();
|
||||
socket.close();
|
||||
|
||||
} catch (IOException e) {
|
||||
logger.warn("IOException closing connection to device at {}: {}", ipAddress, e.getMessage(), e);
|
||||
}
|
||||
socket = null;
|
||||
inputStream = null;
|
||||
writer = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the inputStream byte array
|
||||
*
|
||||
* @return byte array
|
||||
*/
|
||||
public synchronized byte @Nullable [] read() {
|
||||
byte[] bytes = new byte[512];
|
||||
InputStream inputStream = this.inputStream;
|
||||
|
||||
try {
|
||||
int len = inputStream.read(bytes);
|
||||
if (len > 0) {
|
||||
logger.debug("Response received length: {} Device IP {}", len, ipAddress);
|
||||
bytes = Arrays.copyOfRange(bytes, 0, len);
|
||||
return bytes;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
String message = e.getMessage();
|
||||
logger.debug(" Byte read exception {}", message);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the packet that will be sent to the device
|
||||
*
|
||||
* @param buffer socket writer
|
||||
* @throws IOException writer could be null
|
||||
*/
|
||||
public synchronized void write(byte[] buffer) throws IOException {
|
||||
DataOutputStream writer = this.writer;
|
||||
|
||||
try {
|
||||
writer.write(buffer, 0, buffer.length);
|
||||
} catch (IOException e) {
|
||||
String message = e.getMessage();
|
||||
logger.debug("Write error {}", message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset Retry controls the short 5 second delay
|
||||
* Before starting 30 second delays. (More severe Wifi issue)
|
||||
* It is reset after a successful connection
|
||||
*/
|
||||
private void resetRetry() {
|
||||
retry = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Limit logging of INFO connection messages to
|
||||
* only when the device was Offline in its prior
|
||||
* state
|
||||
*/
|
||||
private void resetConnectionMessage() {
|
||||
connectionMessage = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnects from the device
|
||||
*
|
||||
* @param force
|
||||
*/
|
||||
public void dispose(boolean force) {
|
||||
disconnect();
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2024 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.mideaac.internal.connection.exception;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link MideaAuthenticationException} represents a binding specific {@link Exception}.
|
||||
*
|
||||
* @author Leo Siepel - Initial contribution
|
||||
*/
|
||||
|
||||
@NonNullByDefault
|
||||
public class MideaAuthenticationException extends Exception {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public MideaAuthenticationException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public MideaAuthenticationException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public MideaAuthenticationException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2024 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.mideaac.internal.connection.exception;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link MideaConnectionException} represents a binding specific {@link Exception}.
|
||||
*
|
||||
* @author Leo Siepel - Initial contribution
|
||||
*/
|
||||
|
||||
@NonNullByDefault
|
||||
public class MideaConnectionException extends Exception {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public MideaConnectionException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public MideaConnectionException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public MideaConnectionException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2024 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.mideaac.internal.connection.exception;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link MideaException} represents a binding specific {@link Exception}.
|
||||
*
|
||||
* @author Leo Siepel - Initial contribution
|
||||
*/
|
||||
|
||||
@NonNullByDefault
|
||||
public class MideaException extends Exception {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public MideaException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public MideaException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public MideaException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2024 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.mideaac.internal.handler;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link Response} performs the byte data stream decoding
|
||||
*
|
||||
* @author Leo Siepel - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface Callback {
|
||||
|
||||
void updateChannels(Response response);
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -17,6 +17,7 @@ import java.time.LocalDateTime;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.mideaac.internal.Utils;
|
||||
import org.openhab.binding.mideaac.internal.security.Security;
|
||||
|
||||
/**
|
||||
* The {@link Packet} class for Midea AC creates the
|
||||
@ -28,18 +29,18 @@ import org.openhab.binding.mideaac.internal.Utils;
|
||||
public class Packet {
|
||||
private CommandBase command;
|
||||
private byte[] packet;
|
||||
private MideaACHandler mideaACHandler;
|
||||
private Security security;
|
||||
|
||||
/**
|
||||
* The Packet class parameters
|
||||
*
|
||||
* @param command command from Command Base
|
||||
* @param deviceId the device ID
|
||||
* @param mideaACHandler the MideaACHandler class
|
||||
* @param security the Security class
|
||||
*/
|
||||
public Packet(CommandBase command, String deviceId, MideaACHandler mideaACHandler) {
|
||||
public Packet(CommandBase command, String deviceId, Security security) {
|
||||
this.command = command;
|
||||
this.mideaACHandler = mideaACHandler;
|
||||
this.security = security;
|
||||
|
||||
packet = new byte[] {
|
||||
// 2 bytes - StaticHeader
|
||||
@ -78,7 +79,7 @@ public class Packet {
|
||||
command.compose();
|
||||
|
||||
// Append the command data(48 bytes) to the packet
|
||||
byte[] cmdEncrypted = mideaACHandler.getSecurity().aesEncrypt(command.getBytes());
|
||||
byte[] cmdEncrypted = security.aesEncrypt(command.getBytes());
|
||||
|
||||
// Ensure 48 bytes
|
||||
if (cmdEncrypted.length < 48) {
|
||||
@ -97,7 +98,7 @@ public class Packet {
|
||||
System.arraycopy(lenBytes, 0, packet, 4, 2);
|
||||
|
||||
// calculate checksum data
|
||||
byte[] checksumData = mideaACHandler.getSecurity().encode32Data(packet);
|
||||
byte[] checksumData = security.encode32Data(packet);
|
||||
|
||||
// Append a basic checksum data(16 bytes) to the packet
|
||||
byte[] newPacketTwo = new byte[packet.length + checksumData.length];
|
||||
|
@ -31,7 +31,6 @@
|
||||
<channel id="humidity" typeId="humidity"/>
|
||||
<channel id="screen-display" typeId="screen-display"/>
|
||||
<channel id="alternate-target-temperature" typeId="alternate-target-temperature"/>
|
||||
<channel id="dropped-commands" typeId="dropped-commands"/>
|
||||
</channels>
|
||||
|
||||
<representation-property>ipAddress</representation-property>
|
||||
@ -256,11 +255,4 @@
|
||||
<category>Temperature</category>
|
||||
<state readOnly="true" pattern="%.1f %unit%"/>
|
||||
</channel-type>
|
||||
<channel-type id="dropped-commands" advanced="true">
|
||||
<item-type>Number</item-type>
|
||||
<label>Dropped Command Monitor</label>
|
||||
<description>Commands dropped due to TCP read() issues.</description>
|
||||
<category>Number</category>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
</thing:thing-descriptions>
|
||||
|
@ -36,7 +36,7 @@ public class MideaACConfigurationTest {
|
||||
@Test
|
||||
public void testValidConfigs() {
|
||||
config.ipAddress = "192.168.0.1";
|
||||
config.ipPort = "6444";
|
||||
config.ipPort = 6444;
|
||||
config.deviceId = "1234567890";
|
||||
assertTrue(config.isValid());
|
||||
assertFalse(config.isDiscoveryNeeded());
|
||||
@ -48,7 +48,7 @@ public class MideaACConfigurationTest {
|
||||
@Test
|
||||
public void testnonValidConfigs() {
|
||||
config.ipAddress = "192.168.0.1";
|
||||
config.ipPort = "";
|
||||
config.ipPort = 0;
|
||||
config.deviceId = "1234567890";
|
||||
assertFalse(config.isValid());
|
||||
assertTrue(config.isDiscoveryNeeded());
|
||||
@ -60,7 +60,7 @@ public class MideaACConfigurationTest {
|
||||
@Test
|
||||
public void testBadIpConfigs() {
|
||||
config.ipAddress = "192.1680.1";
|
||||
config.ipPort = "6444";
|
||||
config.ipPort = 6444;
|
||||
config.deviceId = "1234567890";
|
||||
assertTrue(config.isValid());
|
||||
assertTrue(config.isDiscoveryNeeded());
|
||||
|
Loading…
Reference in New Issue
Block a user