diff --git a/README.md b/README.md index 993b1b3e..3aa53697 100644 --- a/README.md +++ b/README.md @@ -73,6 +73,7 @@ to `$OH_CONFDIR/services/runtime.cfg` . | `logPrefix` | `text` | Log prefix to use for this device. | deviceId | no | yes | | `deviceLogLevel` | `text` | ESPHome device log level to stream from the device. | NONE | no | yes | | `enableBluetoothProxy` | `boolean` | Allow this device to proxy Bluetooth traffic. Requires ESPHome device to be configured with `bluetooth_proxy` | false | no | yes | +| `configFileFullPath` | `string` | Fully qualified path to esphome yaml for this device. Used for firmware upgrades. | false | no | yes | ## Channels @@ -90,15 +91,21 @@ be linked to an Item. Instead, they fire an event on the openHAB event bus when You can use these in rules like this: ```js -configuration: {} +configuration: { +} triggers: - - id: "1" - label: My Event Channel triggered - pluginId: core.ChannelEventTrigger - type: core.ChannelEventTrigger - configuration: - event: dag - channelUID: esphome:device:mydevice:scene_dag + -id +: +"1" +label: My +Event +Channel +triggered +pluginId: core.ChannelEventTrigger +type: core.ChannelEventTrigger +configuration: + event: dag +channelUID: esphome:device:mydevice:scene_dag ``` Or in Rules DSL: @@ -386,6 +393,32 @@ For JRuby, use the [ For Python Scripting, use [ `GenericEventTrigger`](https://www.openhab.org/addons/automation/pythonscripting/#module-openhab-triggers). +## Firmware upgrade support + +The binding can now trigger firmware updates via a `Thing` action. + +It finds the latest firmware version by checking https://github.com/esphome/esphome/releases/latest at startup +and every 24 hours. + +2 channels are automatically added to each thing: + +- `latestFirmwareVersion` (`String`) which contains the latest firmware version. +- `firmwareUpdateAvailable` (`Contact`) where `OPEN` indicates a newer esphome version is available + +The following configuration must be in place for firmware **upgrade** to work: + +1. Set the `Thing` configuration parameter `configFileFullPath` to the fully qualified path of the esphome yaml file. +2. In the addon/binding configuration, add the fully qualified path to `esphome` binary. Parameter is + `esphomeExecutable` + if not done via MainUI +3. Similarly, add the command to run to upgrade your local installation of esphome to the latest version. For Homebrew ( + MacOS) this could be `brew upgrade esphome`. Parameter is `esphomeUpgradeExecutable` if not done via MainUI. + +> Note: Point 2 and 3 really depend on what OS your machine is running and whether Docker is involved or not. If you get +> it working, feel free to add an example section below for your OS and Docker setup. + +> Note2: The upgrade process runs even if the device is running the latest esphome version available. + ## Limitations Most entity types and functions are now supported. However, there are some limitations: diff --git a/src/main/java/no/seime/openhab/binding/esphome/internal/ESPHomeConfiguration.java b/src/main/java/no/seime/openhab/binding/esphome/internal/ESPHomeConfiguration.java index 378cb212..a55aa0ab 100644 --- a/src/main/java/no/seime/openhab/binding/esphome/internal/ESPHomeConfiguration.java +++ b/src/main/java/no/seime/openhab/binding/esphome/internal/ESPHomeConfiguration.java @@ -48,4 +48,6 @@ public class ESPHomeConfiguration { public LogLevel deviceLogLevel = LogLevel.NONE; public boolean enableBluetoothProxy = false; + + public String configFileFullPath; } diff --git a/src/main/java/no/seime/openhab/binding/esphome/internal/FirmwareUpgradeService.java b/src/main/java/no/seime/openhab/binding/esphome/internal/FirmwareUpgradeService.java new file mode 100644 index 00000000..08150953 --- /dev/null +++ b/src/main/java/no/seime/openhab/binding/esphome/internal/FirmwareUpgradeService.java @@ -0,0 +1,114 @@ +package no.seime.openhab.binding.esphome.internal; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; + +import org.eclipse.jdt.annotation.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class FirmwareUpgradeService { + + private final Logger logger = LoggerFactory.getLogger(FirmwareUpgradeService.class); + + private String bindingPropertyEspHomeExecutable; + private String bindingPropertyEspHomeUpgradeExecutable; + + public @Nullable String getInstalledVersion() { + if (bindingPropertyEspHomeExecutable == null) { + return null; + } + + try { + Process process = new ProcessBuilder(bindingPropertyEspHomeExecutable, "version").redirectErrorStream(true) + .start(); + try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) { + String line; + while ((line = reader.readLine()) != null) { + if (line.startsWith("Version: ")) { + return line.substring("Version: ".length()).trim(); + } + } + } + process.waitFor(); + } catch (IOException e) { + logger.warn("Failed to run esphome version command", e); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + return null; + } + + public synchronized boolean upgradeLocalInstallation() { + if (bindingPropertyEspHomeUpgradeExecutable == null) { + logger.warn("No esphome upgrade executable configured"); + return false; + } + try { + Process process = new ProcessBuilder(bindingPropertyEspHomeUpgradeExecutable).redirectErrorStream(true) + .start(); + try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) { + String line; + while ((line = reader.readLine()) != null) { + logger.debug("esphome upgrade: {}", line); + } + } + int exitCode = process.waitFor(); + if (exitCode == 0) { + return true; + } else { + logger.warn("esphome upgrade command exited with code {}", exitCode); + return false; + } + } catch (IOException e) { + logger.warn("Failed to run esphome upgrade command", e); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + return false; + } + + public boolean upgradeDevice(String configFilePath) { + if (bindingPropertyEspHomeExecutable == null) { + logger.warn("No esphome executable configured"); + return false; + } + try { + Process process = new ProcessBuilder(bindingPropertyEspHomeExecutable, "run", configFilePath, "--no-logs") + .redirectErrorStream(true).start(); + try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) { + String line; + while ((line = reader.readLine()) != null) { + logger.debug("esphome run: {}", line); + } + } + boolean finished = process.waitFor(5, java.util.concurrent.TimeUnit.MINUTES); + if (!finished) { + process.destroyForcibly(); + logger.warn("esphome run command timed out after 5 minutes for config: {}", configFilePath); + return false; + } + int exitCode = process.exitValue(); + if (exitCode == 0) { + return true; + } else { + logger.warn("esphome run command exited with code {} for config: {}", exitCode, configFilePath); + return false; + } + } catch (IOException e) { + logger.warn("Failed to run esphome run command for config: {}", configFilePath, e); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + return false; + } + + public void setBindingPropertyEspHomeExecutable(@Nullable String bindingPropertyEspHomeExecutable) { + this.bindingPropertyEspHomeExecutable = bindingPropertyEspHomeExecutable; + } + + public void setBindingPropertyEspHomeUpgradeExecutable(@Nullable String bindingPropertyEspHomeUpgradeExecutable) { + this.bindingPropertyEspHomeUpgradeExecutable = bindingPropertyEspHomeUpgradeExecutable; + } +} diff --git a/src/main/java/no/seime/openhab/binding/esphome/internal/handler/ESPHomeHandler.java b/src/main/java/no/seime/openhab/binding/esphome/internal/handler/ESPHomeHandler.java index 90bd8e3c..85a4a817 100644 --- a/src/main/java/no/seime/openhab/binding/esphome/internal/handler/ESPHomeHandler.java +++ b/src/main/java/no/seime/openhab/binding/esphome/internal/handler/ESPHomeHandler.java @@ -47,11 +47,11 @@ import io.esphome.api.*; import no.seime.openhab.binding.esphome.events.ESPHomeEventFactory; import no.seime.openhab.binding.esphome.internal.*; -import no.seime.openhab.binding.esphome.internal.LogLevel; import no.seime.openhab.binding.esphome.internal.bluetooth.ESPHomeBluetoothProxyHandler; import no.seime.openhab.binding.esphome.internal.comm.*; import no.seime.openhab.binding.esphome.internal.handler.action.AbstractESPHomeThingAction; import no.seime.openhab.binding.esphome.internal.handler.action.DynamicThingActionsGenerator; +import no.seime.openhab.binding.esphome.internal.handler.action.FirmwareUpgradeAction; import no.seime.openhab.binding.esphome.internal.message.*; import no.seime.openhab.binding.esphome.internal.message.statesubscription.ESPHomeEventSubscriber; import no.seime.openhab.binding.esphome.internal.message.statesubscription.EventSubscription; @@ -85,9 +85,10 @@ public class ESPHomeHandler extends BaseThingHandler implements CommunicationLis private final KeySequentialExecutor packetProcessor; private final EventPublisher eventPublisher; @Nullable - private final String defaultEncryptionKey; + private final String bindingPropertyDefaultEncryptionKey; private final BundleContext bundleContext; private final ESPHomeVersionService versionService; + private final FirmwareUpgradeService firmwareUpgradeService; private @Nullable ESPHomeConfiguration config; private @Nullable EncryptedFrameHelper frameHelper; @Nullable @@ -119,8 +120,9 @@ public class ESPHomeHandler extends BaseThingHandler implements CommunicationLis public ESPHomeHandler(Thing thing, ConnectionSelector connectionSelector, ESPChannelTypeProvider dynamicChannelTypeProvider, ESPStateDescriptionProvider stateDescriptionProvider, ESPHomeEventSubscriber eventSubscriber, MonitoredScheduledThreadPoolExecutor executorService, - KeySequentialExecutor packetProcessor, EventPublisher eventPublisher, @Nullable String defaultEncryptionKey, - BundleContext bundleContext, ESPHomeVersionService versionService) { + KeySequentialExecutor packetProcessor, EventPublisher eventPublisher, + @Nullable String bindingPropertyDefaultEncryptionKey, BundleContext bundleContext, + ESPHomeVersionService versionService, FirmwareUpgradeService firmwareUpgradeService) { super(thing); this.connectionSelector = connectionSelector; this.dynamicChannelTypeProvider = dynamicChannelTypeProvider; @@ -130,9 +132,10 @@ public ESPHomeHandler(Thing thing, ConnectionSelector connectionSelector, this.executorService = executorService; this.packetProcessor = packetProcessor; this.eventPublisher = eventPublisher; - this.defaultEncryptionKey = defaultEncryptionKey; + this.bindingPropertyDefaultEncryptionKey = bindingPropertyDefaultEncryptionKey; this.bundleContext = bundleContext; this.versionService = versionService; + this.firmwareUpgradeService = firmwareUpgradeService; // Register message handlers for each type of message pairs registerMessageHandler(EntityTypes.SELECT, new SelectMessageHandler(this), ListEntitiesSelectResponse.class, @@ -274,8 +277,8 @@ private void connect() { // Default to using the default encryption key from the binding if not set in device configuration String encryptionKey = config.encryptionKey; if (encryptionKey == null || encryptionKey.isEmpty()) { - if (defaultEncryptionKey != null) { - encryptionKey = defaultEncryptionKey; + if (bindingPropertyDefaultEncryptionKey != null) { + encryptionKey = bindingPropertyDefaultEncryptionKey; logger.info("[{}] Using binding default encryption key", logPrefix); } else { logger.warn("[{}] No encryption key configured on neither binding nor thing. Cannot continue", @@ -502,6 +505,9 @@ private void handleConnected(GeneratedMessage message) throws ProtocolAPIError { addFirmwareChannels(); + thingActionServiceRegistrations.add(bundleContext.registerService(ThingActions.class, + new FirmwareUpgradeAction(this), new Hashtable<>())); + updateThing(editThing().withChannels(dynamicChannels).build()); logger.debug("[{}] Device interrogation complete, done updating thing channels", logPrefix); interrogated = true; @@ -691,7 +697,7 @@ private void handleHelloResponse(GeneratedMessage message) throws ProtocolAPIErr logger.debug("[{}] Requesting device to send actions and events", logPrefix); frameHelper.send(SubscribeHomeassistantServicesRequest.getDefaultInstance()); } - if (config.deviceLogLevel != LogLevel.NONE) { + if (config.deviceLogLevel != no.seime.openhab.binding.esphome.internal.LogLevel.NONE) { logger.info("[{}] Starting to stream logs to logger " + DEVICE_LOGGER_NAME, logPrefix); frameHelper.send(SubscribeLogsRequest.newBuilder() @@ -969,4 +975,12 @@ private enum ConnectionState { CONNECTED } + + public FirmwareUpgradeService getFirmwareUpgradeService() { + return firmwareUpgradeService; + } + + public ESPHomeVersionService getVersionService() { + return versionService; + } } diff --git a/src/main/java/no/seime/openhab/binding/esphome/internal/handler/ESPHomeHandlerFactory.java b/src/main/java/no/seime/openhab/binding/esphome/internal/handler/ESPHomeHandlerFactory.java index d9b9992a..cffa6efc 100644 --- a/src/main/java/no/seime/openhab/binding/esphome/internal/handler/ESPHomeHandlerFactory.java +++ b/src/main/java/no/seime/openhab/binding/esphome/internal/handler/ESPHomeHandlerFactory.java @@ -38,6 +38,7 @@ import no.seime.openhab.binding.esphome.internal.BindingConstants; import no.seime.openhab.binding.esphome.internal.ESPHomeVersionService; +import no.seime.openhab.binding.esphome.internal.FirmwareUpgradeService; import no.seime.openhab.binding.esphome.internal.bluetooth.ESPHomeBluetoothProxyHandler; import no.seime.openhab.binding.esphome.internal.comm.ConnectionSelector; import no.seime.openhab.binding.esphome.internal.message.statesubscription.ESPHomeEventSubscriber; @@ -56,8 +57,9 @@ public class ESPHomeHandlerFactory extends BaseThingHandlerFactory { private static final Set SUPPORTED_THING_TYPES_UIDS = Set.of(BindingConstants.THING_TYPE_DEVICE, BindingConstants.THING_TYPE_BLE_PROXY); + public FirmwareUpgradeService firmwareUpgradeService; - private @Nullable String defaultEncryptionKey; + private @Nullable String bindingPropertyDefaultEncryptionKey; private final AtomicLong threadCounter = new AtomicLong(0); @@ -104,6 +106,8 @@ public ESPHomeHandlerFactory(@Reference ESPChannelTypeProvider dynamicChannelTyp connectionSelector = new ConnectionSelector(); versionService = new ESPHomeVersionService(scheduler); + + firmwareUpgradeService = new FirmwareUpgradeService(); } @Override @@ -113,7 +117,7 @@ public ESPHomeHandlerFactory(@Reference ESPChannelTypeProvider dynamicChannelTyp if (BindingConstants.THING_TYPE_DEVICE.equals(thingTypeUID)) { ESPHomeHandler handler = new ESPHomeHandler(thing, connectionSelector, dynamicChannelTypeProvider, stateDescriptionProvider, eventSubscriber, scheduler, packetExecutor, eventPublisher, - defaultEncryptionKey, getBundleContext(), versionService); + bindingPropertyDefaultEncryptionKey, getBundleContext(), versionService, firmwareUpgradeService); esphomeHandlers.put(thing.getUID(), handler); return handler; } else if (BindingConstants.THING_TYPE_BLE_PROXY.equals(thingTypeUID)) { @@ -132,11 +136,18 @@ protected void activate(ComponentContext componentContext) { connectionSelector.start(); versionService.start(); Dictionary properties = componentContext.getProperties(); - defaultEncryptionKey = StringUtils.trimToNull((String) properties.get("defaultEncryptionKey")); - if (defaultEncryptionKey != null) { + bindingPropertyDefaultEncryptionKey = StringUtils.trimToNull((String) properties.get("defaultEncryptionKey")); + if (bindingPropertyDefaultEncryptionKey != null) { logger.info( "Found binding default encryption key for ESPHome devices, will use if not configured on thing"); } + + String bindingPropertyEspHomeExecutable = StringUtils.trimToNull((String) properties.get("esphomeExecutable")); + String bindingPropertyEspHomeUpgradeExecutable = StringUtils + .trimToNull((String) properties.get("esphomeUpgradeExecutable")); + + firmwareUpgradeService.setBindingPropertyEspHomeExecutable(bindingPropertyEspHomeExecutable); + firmwareUpgradeService.setBindingPropertyEspHomeUpgradeExecutable(bindingPropertyEspHomeUpgradeExecutable); } @Override diff --git a/src/main/java/no/seime/openhab/binding/esphome/internal/handler/action/AbstractESPHomeThingAction.java b/src/main/java/no/seime/openhab/binding/esphome/internal/handler/action/AbstractESPHomeThingAction.java index c99af977..846a052f 100644 --- a/src/main/java/no/seime/openhab/binding/esphome/internal/handler/action/AbstractESPHomeThingAction.java +++ b/src/main/java/no/seime/openhab/binding/esphome/internal/handler/action/AbstractESPHomeThingAction.java @@ -20,8 +20,8 @@ public abstract class AbstractESPHomeThingAction implements ThingActions { @Override public void setThingHandler(@Nullable ThingHandler handler) { - if (handler instanceof ESPHomeHandler bridgeHandler) { - this.handler = bridgeHandler; + if (handler instanceof ESPHomeHandler espHomeHandler) { + this.handler = espHomeHandler; } } diff --git a/src/main/java/no/seime/openhab/binding/esphome/internal/handler/action/FirmwareUpgradeAction.java b/src/main/java/no/seime/openhab/binding/esphome/internal/handler/action/FirmwareUpgradeAction.java new file mode 100644 index 00000000..70aa7fa3 --- /dev/null +++ b/src/main/java/no/seime/openhab/binding/esphome/internal/handler/action/FirmwareUpgradeAction.java @@ -0,0 +1,100 @@ +package no.seime.openhab.binding.esphome.internal.handler.action; + +import java.util.Map; + +import org.apache.commons.lang3.StringUtils; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.automation.annotation.ActionOutput; +import org.openhab.core.automation.annotation.ActionOutputs; +import org.openhab.core.automation.annotation.RuleAction; +import org.openhab.core.thing.binding.ThingActions; +import org.openhab.core.thing.binding.ThingActionsScope; +import org.openhab.core.thing.binding.ThingHandler; + +import no.seime.openhab.binding.esphome.internal.ESPHomeConfiguration; +import no.seime.openhab.binding.esphome.internal.ESPHomeVersionService; +import no.seime.openhab.binding.esphome.internal.FirmwareUpgradeService; +import no.seime.openhab.binding.esphome.internal.handler.ESPHomeHandler; + +@ThingActionsScope(name = "esphome") +@NonNullByDefault +public class FirmwareUpgradeAction implements ThingActions { + + @Nullable + private ESPHomeHandler handler; + + public FirmwareUpgradeAction(ESPHomeHandler espHomeHandler) { + handler = espHomeHandler; + } + + @RuleAction(label = "Upgrade device firmware", description = "Recompiles and flashes the latest firmware to the device. This includes upgrading the local installation of ESPHome first if necessary. See binding configuration for required parameters") + public @ActionOutputs({ + @ActionOutput(name = "result", label = "Upgrade result", type = "java.lang.String") }) Map upgradeFirmware() { + + ESPHomeHandler handler = this.handler; + if (handler == null) { + return Map.of("result", + "Failed: Action thing handler is null. This should not happen, check your installation and logs for errors."); + } + + ESPHomeConfiguration deviceConfig = handler.getThing().getConfiguration().as(ESPHomeConfiguration.class); + if (StringUtils.trimToNull(deviceConfig.configFileFullPath) == null) { + return Map.of("result", + "Failed: Device configuration parameter configFileFullPath is empty. It should be a fully qualified path to the esphome yaml for your device."); + } + + FirmwareUpgradeService firmwareUpgradeService = handler.getFirmwareUpgradeService(); + @Nullable + String installedVersion = firmwareUpgradeService.getInstalledVersion(); + if (installedVersion == null) { + return Map.of("result", + "Failed: Unable to detect installed esphome version (on your computer). Check that binding parameter bindingPropertyEspHomeExecutable is set to the fully qualified path or your esphome installation binary"); + } + + ESPHomeVersionService versionService = handler.getVersionService(); + @Nullable + String latestVersion = versionService.getLatestVersion(); + if (latestVersion == null) { + return Map.of("result", "Failed: Unable to detect latest esphome version (from github)."); + } + + if (ESPHomeVersionService.isVersionNewer(latestVersion, installedVersion, handler.getLogPrefix())) { + // Upgrade locally installed version + boolean upgradeResult = firmwareUpgradeService.upgradeLocalInstallation(); + if (!upgradeResult) { + return Map.of("result", + String.format("Failed: Unable to upgrade local esphome installation from %s to %s.", + installedVersion, latestVersion)); + } + installedVersion = firmwareUpgradeService.getInstalledVersion(); + if (!installedVersion.equals(latestVersion)) { + return Map.of("result", String.format( + "Failed: Upgrading to latest esphome version (%s) locally apparently went fine, but esphome still reports version ", + latestVersion, installedVersion)); + + } + } + + boolean upgradeResult = firmwareUpgradeService.upgradeDevice(deviceConfig.configFileFullPath); + if (upgradeResult) { + return Map.of("result", String.format("Success: Firmware upgrade completed successfully for device %s", + deviceConfig.deviceId)); + } else { + return Map.of("result", + String.format("Failed: Firmware upgrade failed for device %s", deviceConfig.deviceId)); + } + } + + @Override + public void setThingHandler(ThingHandler handler) { + if (handler instanceof ESPHomeHandler espHomeHandler) { + this.handler = espHomeHandler; + } + } + + @Override + public @Nullable ThingHandler getThingHandler() { + return handler; + } +} diff --git a/src/main/resources/OH-INF/addon/addon.xml b/src/main/resources/OH-INF/addon/addon.xml index a34edbd5..955b2839 100644 --- a/src/main/resources/OH-INF/addon/addon.xml +++ b/src/main/resources/OH-INF/addon/addon.xml @@ -14,6 +14,16 @@ https://esphome.io/components/api#configuration-variables. Will be used as default if not configured on the device thing. + + + Used for firmware upgrades. Make sure you have given OH the necessary permissions to execute this + command. + + + + This depends on your package manager. If using homebrew, the correct command would be 'brew upgrade + esphome'. Make sure you have given OH the necessary permissions to execute this command. + diff --git a/src/main/resources/OH-INF/thing/thing-esphome.xml b/src/main/resources/OH-INF/thing/thing-esphome.xml index 13d734d6..2beba3f3 100644 --- a/src/main/resources/OH-INF/thing/thing-esphome.xml +++ b/src/main/resources/OH-INF/thing/thing-esphome.xml @@ -26,6 +26,9 @@ + + + @@ -131,6 +134,10 @@ false false + + + Full path to the ESPHome configuration file, ie '/var/lib/esphome/configfolder/mydevice.yaml' + diff --git a/src/test/java/no/seime/openhab/binding/esphome/devicetest/AbstractESPHomeDeviceTest.java b/src/test/java/no/seime/openhab/binding/esphome/devicetest/AbstractESPHomeDeviceTest.java index 7f29d8d3..fd628ea6 100644 --- a/src/test/java/no/seime/openhab/binding/esphome/devicetest/AbstractESPHomeDeviceTest.java +++ b/src/test/java/no/seime/openhab/binding/esphome/devicetest/AbstractESPHomeDeviceTest.java @@ -31,10 +31,7 @@ import com.jano7.executor.KeySequentialExecutor; import no.seime.openhab.binding.esphome.deviceutil.ESPHomeDeviceRunner; -import no.seime.openhab.binding.esphome.internal.BindingConstants; -import no.seime.openhab.binding.esphome.internal.ESPHomeConfiguration; -import no.seime.openhab.binding.esphome.internal.ESPHomeVersionService; -import no.seime.openhab.binding.esphome.internal.LogLevel; +import no.seime.openhab.binding.esphome.internal.*; import no.seime.openhab.binding.esphome.internal.comm.ConnectionSelector; import no.seime.openhab.binding.esphome.internal.handler.ESPChannelTypeProvider; import no.seime.openhab.binding.esphome.internal.handler.ESPHomeHandler; @@ -105,9 +102,11 @@ public void setUp() throws Exception { .thenAnswer(invocation -> new ESPHomeVersionService(executor) .createFirmwareUpdateAvailableChannel(invocation.getArgument(0), invocation.getArgument(1))); + FirmwareUpgradeService firmwareUpgradeService = Mockito.mock(FirmwareUpgradeService.class); + thingHandler = new ESPHomeHandler(thing, selector, channelTypeProvider, stateDescriptionProvider, eventSubscriber, executor, new KeySequentialExecutor(executor), eventPublisher, null, bundleContext, - versionService); + versionService, firmwareUpgradeService); thingHandlerCallback = Mockito.mock(ThingHandlerCallback.class); thingHandler.setCallback(thingHandlerCallback); diff --git a/src/test/java/no/seime/openhab/binding/esphome/internal/handler/ESPHomeHandlerLastKnownIpAddressTest.java b/src/test/java/no/seime/openhab/binding/esphome/internal/handler/ESPHomeHandlerLastKnownIpAddressTest.java index 66ba46d1..18e3cda4 100644 --- a/src/test/java/no/seime/openhab/binding/esphome/internal/handler/ESPHomeHandlerLastKnownIpAddressTest.java +++ b/src/test/java/no/seime/openhab/binding/esphome/internal/handler/ESPHomeHandlerLastKnownIpAddressTest.java @@ -15,6 +15,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; import org.openhab.core.events.EventPublisher; import org.openhab.core.thing.binding.ThingHandlerCallback; @@ -26,6 +27,7 @@ import io.esphome.api.DeviceInfoResponse; import no.seime.openhab.binding.esphome.internal.BindingConstants; import no.seime.openhab.binding.esphome.internal.ESPHomeVersionService; +import no.seime.openhab.binding.esphome.internal.FirmwareUpgradeService; import no.seime.openhab.binding.esphome.internal.comm.ConnectionSelector; import no.seime.openhab.binding.esphome.internal.comm.ProtocolAPIError; import no.seime.openhab.binding.esphome.internal.message.statesubscription.ESPHomeEventSubscriber; @@ -56,9 +58,12 @@ void setUp() throws Exception { thing = new ThingImpl(BindingConstants.THING_TYPE_DEVICE, "device"); executor = new MonitoredScheduledThreadPoolExecutor(1, Executors.defaultThreadFactory(), 1000); packetProcessorExecutor = Executors.newSingleThreadExecutor(); + + FirmwareUpgradeService firmwareUpgradeService = Mockito.mock(FirmwareUpgradeService.class); + handler = new ESPHomeHandler(thing, new ConnectionSelector(), channelTypeProvider, stateDescriptionProvider, eventSubscriber, executor, new KeySequentialExecutor(packetProcessorExecutor), eventPublisher, null, - bundleContext, mock(ESPHomeVersionService.class)); + bundleContext, mock(ESPHomeVersionService.class), firmwareUpgradeService); handler.setCallback(callback); }